File Coverage

blib/lib/Game/Life/Faster.pm
Criterion Covered Total %
statement 208 213 97.6
branch 58 72 80.5
condition 30 53 56.6
subroutine 36 37 97.3
pod 22 22 100.0
total 354 397 89.1


line stmt bran cond sub pod time code
1             package Game::Life::Faster;
2              
3 2     2   206276 use 5.008001;
  2         13  
4              
5 2     2   11 use strict;
  2         3  
  2         35  
6 2     2   8 use warnings;
  2         4  
  2         61  
7              
8 2     2   10 use Carp;
  2         4  
  2         122  
9 2     2   11 use List::Util qw{ max min };
  2         4  
  2         154  
10              
11             our $VERSION = '0.006_01';
12              
13 2     2   12 use constant ARRAY_REF => ref [];
  2         4  
  2         159  
14              
15 2     2   11 use constant DEFAULT_BREED => [ 3 ];
  2         2  
  2         86  
16 2     2   9 use constant DEFAULT_LIVE => [ 2, 3 ];
  2         4  
  2         81  
17 2     2   10 use constant DEFAULT_SIZE => 100;
  2         4  
  2         125  
18              
19 2     2   12 use constant NEW_LINE_RE => qr< \n >smx;
  2         11  
  2         135  
20 2     2   12 use constant NON_NEGATIVE_INTEGER_RE => qr< \A [0-9]+ \z >smx;
  2         3  
  2         124  
21 2     2   11 use constant POSITIVE_INTEGER_RE => qr< \A [1-9][0-9]* \z >smx;
  2         6  
  2         123  
22              
23 2     2   12 use constant TOGGLE_STATE => do { bless \my $x, 'Toggle_State' };
  2         4  
  2         2  
  2         112  
24 2     2   107 use constant TOGGLE_STATE_REF => ref TOGGLE_STATE;
  2         13  
  2         2696  
25              
26             sub new {
27 3     3 1 409 my ( $class, $size, $breed, $live ) = @_;
28              
29 3         5 my $self;
30              
31 3         6 my $ref = ref $size;
32 3 100       15 if ( ARRAY_REF eq $ref ) {
    50          
33 1         3 $self->{size_x} = $size->[1];
34 1         3 $self->{size_y} = $size->[0];
35             } elsif ( ! $ref ) {
36 2         8 $self->{size_x} = $self->{size_y} = $size;
37             } else {
38 0         0 croak "Argument may not be $size";
39             }
40 3   100     15 $self->{size_x} ||= DEFAULT_SIZE;
41 3   100     11 $self->{size_y} ||= DEFAULT_SIZE;
42             $self->{size_x} =~ POSITIVE_INTEGER_RE
43 3 50 33     32 and $self->{size_y} =~ POSITIVE_INTEGER_RE
44             or croak 'Sizes must be positive integers';
45 3         9 $self->{max_x} = $self->{size_x} - 1;
46 3         6 $self->{max_y} = $self->{size_y} - 1;
47              
48 3   66     14 bless $self, ref $class || $class;
49 3         12 $self->set_rules( $breed, $live );
50              
51 3         9 $self->clear();
52              
53 3         17 return $self;
54             }
55              
56             sub clear {
57 4     4 1 8 my ( $self ) = @_;
58 4         15 delete $self->{grid};
59 4         10 delete $self->{changed};
60 4         10 $self->{living_x} = [];
61 4         9 $self->{living_y} = [];
62 4         7 $self->{change_count} = 0;
63 4         6 return $self;
64             }
65              
66             sub get_active_grid_coord {
67 2     2 1 341 my ( $self ) = @_;
68             my ( $min_x, $max_x, $min_y, $max_y ) = ( $self->{size_x}, 0,
69 2         6 $self->{size_y}, 0 );
70 2         5 foreach my $ix ( keys %{ $self->{changed} } ) {
  2         7  
71 10 100       18 $min_x = $ix if $ix < $min_x;
72 10 100       18 $max_x = $ix if $ix > $max_x;
73 10         10 foreach my $iy ( keys %{ $self->{changed}{$ix} } ) {
  10         21  
74 40 100       57 $min_y = $iy if $iy < $min_y;
75 40 100       59 $max_y = $iy if $iy > $max_y;
76             }
77             }
78 2 50       6 $max_x < $min_x
79             and croak 'No active cells';
80 2         8 return [ $min_x, $max_x, $min_y, $max_y ];
81             }
82              
83             sub get_breeding_rules {
84 1     1 1 7309 my ( $self ) = @_;
85 1         4 return $self->get_rule( 'breed' );
86             }
87              
88             sub get_grid {
89 1     1 1 4 my ( $self, $coord ) = @_;
90 1   33     7 $coord ||= $self->get_grid_coord();
91             $self->{grid}
92 1 50       4 or return [ ( [ ( 0 ) x ( $coord->[3] - $coord->[2] + 1 ) ] ) x
93             ( $coord->[1] - $coord->[0] + 1 ) ];
94 1         16 my @rslt;
95 1         7 foreach my $x ( $coord->[0] .. $coord->[1] ) {
96 10 100       17 if ( $self->{grid}{$x} ) {
97 7         13 push @rslt, [];
98 7         10 foreach my $y ( $coord->[2] .. $coord->[3] ) {
99 70         143 push @{ $rslt[-1] }, $self->{grid}{$x}{$y} ?
100 70 100       71 $self->{grid}{$x}{$y}[0] ? 1 : 0 : 0;
    100          
101             }
102             } else {
103 3         9 push @rslt, [ ( 0 ) x ( $coord->[3] - $coord->[2] + 1 ) ];
104             }
105             }
106 1         22 return \@rslt;
107             }
108              
109             sub get_grid_coord {
110 9     9 1 16 my ( $self ) = @_;
111 9         53 return [ 0, $self->{max_x}, 0, $self->{max_y} ];
112             }
113              
114             sub get_living_rules {
115 1     1 1 3 my ( $self ) = @_;
116 1         3 return $self->get_rule( 'live' );
117             }
118              
119             sub get_text_grid {
120 10     10 1 23 my ( $self, $living, $dead, $coord ) = @_;
121 10   50     35 $living ||= 'X';
122 10   50     32 $dead ||= '.';
123 10   66     24 $coord ||= $self->get_grid_coord();
124 10         16 my @rslt;
125 10 100       17 if ( $self->{grid} ) {
126 7         15 foreach my $x ( $coord->[0] .. $coord->[1] ) {
127 58 100       94 if ( $self->{grid}{$x} ) {
128             push @rslt, join '', map {
129 37 100 100     111 ( $self->{grid}{$x}{$_} && $self->{grid}{$x}{$_}[0]) ?
  324         874  
130             $living : $dead
131             } $coord->[2] .. $coord->[3];
132             } else {
133 21         38 push @rslt, $dead x ( $coord->[3] - $coord->[2] + 1 );
134             }
135             }
136             } else {
137 3         12 @rslt = ( $dead x ( $coord->[3] - $coord->[2] + 1 ) ) x (
138             $coord->[1] - $coord->[0] + 1 );
139             }
140 10 50       23 return wantarray ? @rslt : join '', map { "$_\n" } @rslt;
  83         168  
141             }
142              
143             sub get_active_text_grid {
144 1     1 1 801 my ( $self, $living, $dead ) = @_;
145 1         4 return $self->get_text_grid( $living, $dead,
146             $self->get_active_grid_coord() );
147             }
148              
149             sub get_used_text_grid {
150 1     1 1 807 my ( $self, $living, $dead ) = @_;
151 1         4 return $self->get_text_grid( $living, $dead,
152             $self->get_used_grid_coord() );
153             }
154              
155             sub get_used_grid {
156 0     0 1 0 my ( $self ) = @_;
157 0         0 return $self->get_grid( $self->get_used_grid_coord() );
158             }
159              
160             sub get_used_grid_coord {
161 2     2 1 3 my ( $self ) = @_;
162 2         4 my $min_x = $self->{size_x};
163 2         7 for ( $min_x = 0; $min_x < $self->{size_x}; $min_x++ ) {
164 8 100       18 $self->{living_x}[$min_x]
165             or next;
166 2         4 my ( $max_x, $min_y, $max_y );
167 2         7 for ( $max_x = $self->{size_x}; $max_x >= $min_x; ) {
168 10 100       20 $self->{living_x}[--$max_x]
169             and last;
170             }
171 2         6 for ( $min_y = 0; $min_y < $self->{size_y}; $min_y++ ) {
172 6 100       13 $self->{living_y}[$min_y]
173             and last;
174             }
175 2         6 for ( $max_y = $self->{size_y}; $max_y >= $min_y; ) {
176 12 100       22 $self->{living_y}[--$max_y]
177             and last;
178             }
179 2         7 return [ $min_x, $max_x, $min_y, $max_y ];
180             }
181 0         0 croak 'No occupied cells';
182             }
183              
184             sub place_points {
185 1     1 1 332 my ( $self, $x, $y, $array ) = @_;
186 1         2 my $ix = $x;
187 1         3 foreach my $row ( @{ $array } ) {
  1         3  
188 1         2 my $iy = $y;
189 1         2 foreach my $state ( @{ $row } ) {
  1         3  
190 3         7 $self->set_point_state( $ix, $iy, $state );
191 3         3 $iy++;
192             }
193 1         4 $ix++;
194             }
195 1         2 return;
196             }
197              
198             sub place_text_points {
199 3     3 1 23 my ( $self, $x, $y, $living, @array ) = @_;
200 3         7 my $ix = $x;
201 3 100 66     21 1 == @array
202             and $array[0] =~ NEW_LINE_RE
203 2         36 and @array = split qr< @{[ NEW_LINE_RE ]} >smx, $array[0];
204 3         10 foreach my $line ( @array ) {
205 7         11 my $iy = $y;
206 7         30 foreach my $state ( map { $living eq $_ } split qr<>, $line ) {
  17         31  
207 17         36 $self->set_point_state( $ix, $iy, $state );
208 17         24 $iy++;
209             }
210 7         14 $ix++;
211             }
212 3         8 return;
213             }
214              
215             sub process {
216 4     4 1 8 my ( $self, $steps ) = @_;
217 4   100     13 $steps ||= 1;
218              
219 4         11 foreach ( 1 .. $steps ) {
220              
221 13         20 my $changed = delete $self->{changed};
222 13         17 $self->{change_count} = 0;
223              
224 13         15 foreach my $x ( keys %{ $changed } ) {
  13         30  
225 60         66 foreach my $y ( keys %{ $changed->{$x} } ) {
  60         142  
226 253         305 my $cell = $self->{grid}{$x}{$y};
227 2     2   15 no warnings qw{ uninitialized };
  2         4  
  2         1519  
228 253 100       320 if ( $cell->[0] ) {
229 66 100       128 $self->{live}[ $changed->{$x}{$y} ]
230             or $self->unset_point( $x, $y );
231             } else {
232 187 100       355 $self->{breed}[ $changed->{$x}{$y} ]
233             and $self->set_point( $x, $y );
234             }
235             }
236             }
237              
238             $self->{change_count}
239 13 100       52 or last;
240             }
241 4         15 return $self->{change_count};
242             }
243              
244             sub set_point {
245 26     26 1 45 my ( $self, $x, $y ) = @_;
246 26         38 return $self->set_point_state( $x, $y, 1 );
247             }
248              
249             sub set_point_state {
250 74     74 1 110 my ( $self, $x, $y, $state ) = @_;
251              
252 74 50 33     459 defined $x
      33        
      33        
253             and defined $y
254             and $x =~ NON_NEGATIVE_INTEGER_RE
255             and $y =~ NON_NEGATIVE_INTEGER_RE
256             or croak 'Coordinates must be non-negative integers';
257 74 50       122 defined $state
258             or return $state;
259              
260 74 50 33     321 if ( $x >= 0 && $x < $self->{size_x} &&
    0 33        
      33        
261             $y >= 0 && $y < $self->{size_y}
262             ) {
263             # We're on-grid.
264              
265             # This autovivifies, but we're going to assign it anyway, so ...
266 74   100     160 my $prev_state = $self->{grid}{$x}{$y}[0] || 0;
267 74 100       135 $state = TOGGLE_STATE_REF eq ref $state ? 1 - $prev_state :
    100          
268             $state ? 1 : 0;
269              
270 74         112 $self->{grid}{$x}{$y}[0] = $state;
271 74   100     133 $self->{grid}{$x}{$y}[1] ||= 0;
272 74 100       128 my $delta = $state - $prev_state
273             or return $state;
274 68         81 $self->{living_x}[$x] += $delta;
275 68         80 $self->{living_y}[$y] += $delta;
276              
277 68         77 $self->{change_count}++;
278              
279 68         199 foreach my $ix ( max( 0, $x - 1 ) .. min( $self->{max_x}, $x + 1 ) ) {
280 197         375 foreach my $iy ( max( 0, $y - 1 ) .. min( $self->{max_y}, $y + 1 )
281             ) {
282             $self->{changed}{$ix}{$iy} =
283 563         1096 $self->{grid}{$ix}{$iy}[1] += $delta;
284             }
285             }
286              
287             # A cell is not its own neighbor, but the above nested loops
288             # assumed that it was. We fix that here, rather than skip it
289             # inside the loops.
290             $self->{changed}{$x}{$y} =
291 68         113 $self->{grid}{$x}{$y}[1] -= $delta;
292              
293             } elsif ( $state ) {
294 0         0 croak 'Attempt to place living cell outside grid';
295             }
296              
297 68         118 return $state;
298             }
299              
300             {
301             my %dflt = (
302             breed => DEFAULT_BREED,
303             live => DEFAULT_LIVE,
304             );
305              
306             sub get_rule {
307 2     2 1 6 my ( $self, $kind ) = @_;
308 2 50       7 $dflt{$kind}
309             or croak "'$kind' is not a valid rule kind";
310 2         5 return( grep { $self->{$kind}[$_] } 0 .. $#{ $self->{$kind} } );
  8         19  
  2         5  
311             }
312              
313             sub set_rule {
314 6     6 1 12 my ( $self, $kind, $rule ) = @_;
315 6 50       16 $dflt{$kind}
316             or croak "'$kind' is not a valid rule name";
317 6   33     24 $rule ||= $dflt{$kind};
318 6 50       16 ARRAY_REF eq ref $rule
319             or croak "\u$kind rule must be an array reference";
320 6         17 $self->{$kind} = [];
321 6         9 foreach ( @{ $rule } ) {
  6         12  
322 9 50       28 $_ =~ NON_NEGATIVE_INTEGER_RE
323             or croak "\u$kind rule must be a reference to an array of non-negative integers";
324 9         17 $self->{$kind}[$_] = 1;
325             }
326 6         9 return;
327             }
328             }
329              
330             sub set_rules {
331 3     3 1 9 my ( $self, $breed, $live ) = @_;
332 3         11 $self->set_rule( breed => $breed );
333 3         7 $self->set_rule( live => $live );
334 3         4 return;
335             }
336              
337             sub toggle_point {
338 2     2 1 5 my ( $self, $x, $y ) = @_;
339 2         7 return $self->set_point_state( $x, $y, TOGGLE_STATE );
340             }
341              
342             sub unset_point {
343 26     26 1 43 my ( $self, $x, $y ) = @_;
344 26         42 return $self->set_point_state( $x, $y, 0 );
345             }
346              
347             1;
348              
349             __END__