File Coverage

lib/Games/PMM/Arena.pm
Criterion Covered Total %
statement 112 112 100.0
branch 39 40 97.5
condition 14 15 93.3
subroutine 25 25 100.0
pod 14 22 63.6
total 204 214 95.3


line stmt bran cond sub pod time code
1             package Games::PMM::Arena;
2              
3 2     2   39173 use strict;
  2         6  
  2         1705  
4              
5             sub new
6             {
7 3     3 1 1443 bless
8             {
9             x_limit => 9,
10             y_limit => 9,
11             coordinates => [],
12             monsters => {},
13             }, shift;
14             }
15              
16             sub coordinates
17             {
18 238     238 1 835 my $self = shift;
19 238         397 return $self->{coordinates};
20             }
21              
22             sub monsters
23             {
24 196     196 1 1893 my $self = shift;
25 196         399 $self->{monsters};
26             }
27              
28             sub x_limit
29             {
30 100     100 1 1101 my $self = shift;
31 100         444 $self->{x_limit};
32             }
33              
34             sub y_limit
35             {
36 88     88 1 128 my $self = shift;
37 88         363 $self->{y_limit};
38             }
39              
40             sub add_monster
41             {
42 4     4 1 26 my ($self, $monster, %coordinates) = @_;
43              
44 4         16 $self->set_position( monster => $monster, %coordinates );
45             }
46              
47             sub set_position
48             {
49 34     34 0 121 my ($self, %args) = @_;
50              
51 34         76 my $coords = $self->coordinates();
52 34         85 my $monsters = $self->monsters();
53              
54 34 100       90 return unless $self->validate_position( %args );
55              
56 33         77 $coords->[ $args{x} ][ $args{y} ] = $args{monster};
57 33         124 $monsters->{ $args{monster}->id() } = [ $args{x}, $args{y} ];
58             }
59              
60             sub validate_position
61             {
62 82     82 1 2051 my ($self, %args) = @_;
63 82         156 my $coords = $self->coordinates();
64              
65 82 100       209 return unless $self->within_bounds( %args );
66              
67 72 100       228 return if defined $coords->[ $args{x} ][ $args{y} ];
68 68         186 return 1;
69             }
70              
71             sub within_bounds
72             {
73 87     87 1 195 my ($self, %args) = @_;
74 87         136 my $coords = $self->coordinates();
75              
76 87 100 100     195 return if $args{x} > $self->x_limit() or $args{y} > $self->y_limit();
77 80 100 100     362 return if $args{x} < 0 or $args{y} < 0;
78              
79 76         218 return 1;
80             }
81              
82             sub delete_position
83             {
84 26     26 0 64 my ($self, %args) = @_;
85 26         57 my $coords = $self->coordinates();
86 26         56 my $monsters = $self->monsters();
87 26         56 my $monster = $coords->[ $args{ x } ][ $args{ y } ];
88              
89 26         47 $coords->[ $args{ x } ][ $args{ y } ] = undef;
90 26 50       46 return unless $monster;
91 26         70 delete $monsters->{ $monster->id() };
92             }
93              
94             sub get_position
95             {
96 98     98 1 154 my ($self, $monster) = @_;
97 98         224 my $id = $monster->id();
98 98         201 my $monsters = $self->monsters();
99              
100 98 100       255 return unless exists $monsters->{ $id };
101              
102 97         102 my ($x, $y) = @{ $monsters->{ $id } };
  97         194  
103 97         429 return { x => $x, y => $y };
104             }
105              
106             sub update_position
107             {
108 32     32 1 3445 my ($self, $monster, %args) = @_;
109 32         63 my $old_pos = $self->get_position( $monster );
110              
111 32 100       96 return unless $self->validate_position( %args );
112              
113 26         86 $self->delete_position( monster => $monster, %$old_pos );
114 26         93 $self->set_position( monster => $monster, %args );
115             }
116              
117             sub get_monster
118             {
119 5     5 1 1504 my ($self, %args) = @_;
120 5         13 my $coords = $self->coordinates();
121              
122 5 100       19 return unless $self->within_bounds( %args );
123              
124 4         25 return $coords->[ $args{x} ][ $args{y} ];
125             }
126              
127             for my $method
128             (
129             { name => 'forward', modifier => +1 },
130             { name => 'reverse', modifier => -1 },
131             )
132             {
133 2     2   13 no strict 'refs';
  2         5  
  2         2476  
134              
135             *{ $method->{name} } = sub
136             {
137 10     10   1661 my ($self, $monster) = @_;
138              
139 10         27 my $pos = $self->get_position( $monster );
140 10         41 my $direction = $monster->direction( $method->{modifier} );
141 10         27 $pos->{x} += $direction->{x};
142 10         20 $pos->{y} += $direction->{y};
143              
144 10 100       46 return unless $self->validate_position( %$pos );
145              
146 8         28 $self->update_position( $monster, %$pos );
147             };
148             }
149              
150             sub is_wall
151             {
152 6     6 0 21 my ($self, %args) = @_;
153              
154 6 100 100     31 return 1 if $args{x} < 0 or $args{x} > $self->x_limit();
155 4 100 100     32 return 1 if $args{y} < 0 or $args{y} > $self->y_limit();
156 2         9 return;
157             }
158              
159             sub get_distance
160             {
161 16     16 0 90 my ($self, $pos, %to) = @_;
162              
163 16         56 my ($small_x, $big_x) = $self->minmax( $to{x}, $pos->{x} );
164 16         39 my ($small_y, $big_y) = $self->minmax( $to{y}, $pos->{y} );
165              
166 16         65 return $big_x - $small_x + $big_y - $small_y;
167             }
168              
169             sub minmax
170             {
171 32     32 0 69 my ($self, $val1, $val2) = @_;
172 32 100       96 return $val1 < $val2 ? ( $val1, $val2 ) : ( $val2, $val1 );
173             }
174              
175             sub scan
176             {
177 12     12 1 1641 my ($self, $monster) = @_;
178 12         32 my $id = $monster->id();
179 12         31 my $pos = $self->get_position( $monster );
180              
181 12         15 my @seen;
182 12         19 while ( my ($monster_id, $data) = each %{ $self->monsters() })
  36         66  
183             {
184 24 100       117 next if $monster_id == $id;
185 12         17 my ($x, $y) = @$data;
186 12         41 my $distance = $self->get_distance( $pos, x => $x, y => $y );
187              
188 12 100       38 next unless $self->can_see( $monster, $pos,
189             distance => $distance,
190             x => $x,
191             y => $y
192             );
193              
194 7         47 push @seen,
195             {
196             id => $monster_id,
197             x => $x,
198             y => $y,
199             distance => $distance,
200             };
201             }
202              
203 12         47 return @seen;
204             }
205              
206             my %facings =
207             (
208             north => sub {
209             my ($self, $pos) = @_;
210              
211             return
212             {
213             from => $pos->{x},
214             to => $self->x_limit(),
215             perp => 'y',
216             axis => 'x',
217             };
218             },
219             south => sub {
220             my ($self, $pos) = @_;
221             return
222             {
223             from => 0,
224             to => $pos->{x},
225             perp => 'y',
226             axis => 'x',
227             };
228             },
229             east => sub {
230             my ($self, $pos) = @_;
231              
232             return
233             {
234             from => $pos->{y},
235             to => $self->y_limit(),
236             perp => 'x',
237             axis => 'y',
238             }
239             },
240             west => sub {
241             my ($self, $pos) = @_;
242             return
243             {
244             from => 0,
245             to => $pos->{y},
246             perp => 'x',
247             axis => 'y',
248             }
249             },
250             );
251              
252             sub can_see
253             {
254 12     12 0 32 my ($self, $monster, $pos, %args) = @_;
255 12         31 my $look = $facings{ $monster->facing() };
256 12         34 my $limits = $self->$look( $pos );
257 12         26 my $axis = $limits->{axis};
258 12         20 my $perp_axis = $limits->{perp};
259              
260             # can see adjacent monsters, but not behind
261 12 100       47 return ! $self->behind( $monster, $pos, %args ) if $args{distance} == 1;
262              
263             # cannot see non-adjacent monsters on perpendicular axis
264 7 100       24 return if $args{$perp_axis} == $pos->{$perp_axis};
265              
266             # can see all monsters from current position to boundary along facing axis
267 6 100 66     60 return 1 if $args{$axis} >= $limits->{from} and
268             $args{$axis} <= $limits->{to};
269              
270 2         12 return;
271             }
272              
273             my %check_behind =
274             (
275             north => { axis => 'y', mod => -1 },
276             south => { axis => 'y', mod => +1 },
277             east => { axis => 'x', mod => -1 },
278             west => { axis => 'x', mod => +1 },
279             );
280              
281             sub behind
282             {
283 9     9 0 29 my ($self, $monster, $pos, %args) = @_;
284 9         25 my $check = $check_behind{ $monster->facing };
285              
286 9         74 return $pos->{$check->{axis}} + $check->{mod} == $args{$check->{axis}};
287             }
288              
289             sub move_monster
290             {
291 14     14 1 3095 my ($self, $monster, %coords) = @_;
292 14         36 my $position = $self->get_position( $monster );
293              
294 14         33 $position->{x} += $coords{x};
295 14         21 $position->{y} += $coords{y};
296              
297 14         44 $self->update_position( $monster,
298             x => $position->{x},
299             y => $position->{y},
300             );
301             }
302              
303             sub attack
304             {
305 5     5 1 12 my ($self, $monster) = @_;
306 5         13 my $victim = $self->get_victim( $monster );
307 5 100       17 return unless $victim;
308 2         9 $victim->damage();
309             }
310              
311             sub get_victim
312             {
313 5     5 0 9 my ($self, $monster) = @_;
314 5         12 my @nearby = grep { $_->{distance} == 1 } $self->scan( $monster );
  2         10  
315              
316 5 100       16 return unless @nearby;
317 2         5 return $self->get_monster( map { $_ => $nearby[0]->{$_} } qw( x y ) );
  4         18  
318             }
319              
320             1;
321             __END__