File Coverage

blib/lib/Game/LevelMap.pm
Criterion Covered Total %
statement 72 72 100.0
branch 15 16 93.7
condition 15 20 75.0
subroutine 12 12 100.0
pod 5 6 83.3
total 119 126 94.4


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # a small level map implementation that uses characters (or strings
4             # consisting of escape sequences or combining characters, or objects
5             # that ideally stringify themselves properly) in an array of arrays
6             # representing a level map as might be used in a game
7              
8             package Game::LevelMap;
9              
10 2     2   52981 use 5.24.0;
  2         12  
11 2     2   8 use warnings;
  2         4  
  2         60  
12 2     2   8 use Carp qw(croak);
  2         2  
  2         108  
13 2     2   861 use Moo;
  2         18638  
  2         8  
14 2     2   2994 use namespace::clean;
  2         19395  
  2         11  
15              
16             our $VERSION = '0.01';
17              
18             has level => (
19             is => 'rw',
20             default => sub { [ [] ] },
21             isa => sub {
22             my ($lm) = @_;
23             croak "LevelMap must be an AoA"
24             if !defined $lm
25             or ref $lm ne 'ARRAY'
26             or ref $lm->[0] ne 'ARRAY';
27             my $cols = $lm->[0]->$#*;
28             for my $row ( 1 .. $lm->$#* ) {
29             if ( $cols != $lm->[$row]->$#* ) {
30             croak 'unequal column length at row index ' . $row;
31             }
32             }
33             },
34             trigger => sub {
35             my ( $self, $lm ) = @_;
36             $self->_set_rows( $lm->$#* );
37             $self->_set_cols( $lm->[0]->$#* );
38             }
39             );
40             has rows => ( is => 'rwp' );
41             has cols => ( is => 'rwp' );
42              
43             sub BUILD {
44 7     7 0 53 my ( $self, $args ) = @_;
45             croak "level and from_string both may not be set"
46 7 50 66     21 if exists $args->{level} and exists $args->{from_string};
47 7 100       33 $self->from_string( $args->{from_string} ) if exists $args->{from_string};
48             }
49              
50             sub clone {
51 1     1 1 250 my ($self) = @_;
52 1         20 my $lm = $self->level;
53 1         6 my @map;
54 1         4 for my $rown ( 0 .. $lm->$#* ) {
55 3         4 for my $coln ( 0 .. $lm->$#* ) {
56 9         16 $map[$rown][$coln] = $lm->[$rown][$coln];
57             }
58             }
59 1         17 return __PACKAGE__->new( level => \@map );
60             }
61              
62             sub from_string {
63 5     5 1 493 my ( $self, $s ) = @_;
64 5         8 my @map;
65             my $cols;
66 5         55 for my $row ( split $/, $s ) {
67 23         63 push @map, [ split '', $row ];
68 23         35 my $newcols = $map[-1]->$#*;
69 23 100       33 if ( defined $cols ) {
70 18 100       34 if ( $cols != $newcols ) {
71 1         9 croak 'unequal column length at row index ' . $#map;
72             }
73             } else {
74 5         9 $cols = $newcols;
75             }
76             }
77 4         71 $self->level( \@map );
78 4         25 return $self;
79             }
80              
81             # TODO this might buffer and only print what differs across successive
82             # calls (for less bandwidth over an SSH connection)
83             sub to_panel {
84 6     6 1 1465 my $self = shift;
85 6         27 my ( $col, $row, $width, $height, $x, $y ) = map int, @_[ 0 .. 5 ];
86 6   100 5   22 my $oobfn = $_[6] // sub { return ' ' };
  5         8  
87 6         113 my $lm = $self->level;
88 6         30 my $map_cols = $lm->$#*;
89 6         9 my $map_rows = $lm->[0]->$#*;
90 6 100 66     31 croak "x must be within the level map" if $x < 0 or $x > $map_cols;
91 5 100 66     26 croak "y must be within the level map" if $y < 0 or $y > $map_rows;
92 4         9 my $scol = $x - int( $width / 2 );
93 4         6 my $srow = $y - int( $height / 2 );
94 4         5 my $s = '';
95 4         9 for my $r ( $srow .. $srow + $height - 1 ) {
96 14         31 $s .= "\e[" . $row++ . ';' . $col . 'H';
97 14         19 for my $c ( $scol .. $scol + $width - 1 ) {
98 52 100 66     205 if ( $c < 0 or $c > $map_cols or $r < 0 or $r > $map_rows ) {
      100        
      66        
99 26         35 $s .= $oobfn->( $lm, $c, $r, $map_cols, $map_rows );
100             } else {
101 26         37 $s .= $lm->[$r][$c];
102             }
103             }
104             }
105 4         9 print $s;
106 4         11 return $self;
107             }
108              
109             sub to_string {
110 3     3 1 2006 my ($self) = @_;
111 3         58 my $lm = $self->level;
112 3         18 my $s = '';
113 3         7 for my $rowref ( $lm->@* ) { $s .= join( '', $rowref->@* ) . $/ }
  7         17  
114 3         13 return $s;
115             }
116              
117             sub to_terminal {
118 2     2 1 1610 my $self = shift;
119 2         4 my ( $col, $row );
120 2 100       5 if (@_) {
121 1         5 ( $col, $row ) = map int, @_;
122             } else {
123 1         2 ( $col, $row ) = ( 1, 1 );
124             }
125 2         42 my $lm = $self->level;
126 2         11 my $s = '';
127 2         5 for my $rowref ( $lm->@* ) {
128 6         16 $s .= "\e[" . $row++ . ';' . $col . 'H' . join( '', $rowref->@* );
129             }
130 2         7 print $s;
131 2         5 return $self;
132             }
133              
134             # TODO may want an update method that only redraws a list of changed
135             # points from the level map (on the assumption something else meanwhile
136             # (player, monsters moving, etc) has altered the level map
137              
138             1;
139             __END__