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   213018 use 5.008001;
  2         11  
4              
5 2     2   9 use strict;
  2         3  
  2         44  
6 2     2   8 use warnings;
  2         4  
  2         53  
7              
8 2     2   9 use Carp;
  2         4  
  2         121  
9 2     2   12 use List::Util qw{ max min };
  2         4  
  2         148  
10              
11             our $VERSION = '0.007';
12              
13 2     2   12 use constant ARRAY_REF => ref [];
  2         4  
  2         130  
14              
15 2     2   11 use constant DEFAULT_BREED => [ 3 ];
  2         3  
  2         91  
16 2     2   10 use constant DEFAULT_LIVE => [ 2, 3 ];
  2         2  
  2         89  
17 2     2   11 use constant DEFAULT_SIZE => 100;
  2         4  
  2         121  
18              
19 2     2   18 use constant NEW_LINE_RE => qr< \n >smx;
  2         4  
  2         138  
20 2     2   11 use constant NON_NEGATIVE_INTEGER_RE => qr< \A [0-9]+ \z >smx;
  2         4  
  2         126  
21 2     2   12 use constant POSITIVE_INTEGER_RE => qr< \A [1-9][0-9]* \z >smx;
  2         4  
  2         123  
22              
23 2     2   11 use constant TOGGLE_STATE => do { bless \my $x, 'Toggle_State' };
  2         3  
  2         2  
  2         106  
24 2     2   10 use constant TOGGLE_STATE_REF => ref TOGGLE_STATE;
  2         18  
  2         2655  
25              
26             sub new {
27 3     3 1 395 my ( $class, $size, $breed, $live ) = @_;
28              
29 3         5 my $self;
30              
31 3         5 my $ref = ref $size;
32 3 100       14 if ( ARRAY_REF eq $ref ) {
    50          
33 1         3 $self->{size_x} = $size->[1];
34 1         2 $self->{size_y} = $size->[0];
35             } elsif ( ! $ref ) {
36 2         7 $self->{size_x} = $self->{size_y} = $size;
37             } else {
38 0         0 croak "Argument may not be $size";
39             }
40 3   100     14 $self->{size_x} ||= DEFAULT_SIZE;
41 3   100     8 $self->{size_y} ||= DEFAULT_SIZE;
42             $self->{size_x} =~ POSITIVE_INTEGER_RE
43 3 50 33     31 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         5 $self->{max_y} = $self->{size_y} - 1;
47              
48 3   66     15 bless $self, ref $class || $class;
49 3         11 $self->set_rules( $breed, $live );
50              
51 3         8 $self->clear();
52              
53 3         20 return $self;
54             }
55              
56             sub clear {
57 4     4 1 9 my ( $self ) = @_;
58 4         15 delete $self->{grid};
59 4         7 delete $self->{changed};
60 4         9 $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 611 my ( $self ) = @_;
68             my ( $min_x, $max_x, $min_y, $max_y ) = ( $self->{size_x}, 0,
69 2         7 $self->{size_y}, 0 );
70 2         3 foreach my $ix ( keys %{ $self->{changed} } ) {
  2         8  
71 10 100       19 $min_x = $ix if $ix < $min_x;
72 10 100       15 $max_x = $ix if $ix > $max_x;
73 10         17 foreach my $iy ( keys %{ $self->{changed}{$ix} } ) {
  10         20  
74 40 100       51 $min_y = $iy if $iy < $min_y;
75 40 100       65 $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         7 return [ $min_x, $max_x, $min_y, $max_y ];
81             }
82              
83             sub get_breeding_rules {
84 1     1 1 7266 my ( $self ) = @_;
85 1         5 return $self->get_rule( 'breed' );
86             }
87              
88             sub get_grid {
89 1     1 1 3 my ( $self, $coord ) = @_;
90 1   33     6 $coord ||= $self->get_grid_coord();
91             $self->{grid}
92 1 50       3 or return [ ( [ ( 0 ) x ( $coord->[3] - $coord->[2] + 1 ) ] ) x
93             ( $coord->[1] - $coord->[0] + 1 ) ];
94 1         16 my @rslt;
95 1         5 foreach my $x ( $coord->[0] .. $coord->[1] ) {
96 10 100       21 if ( $self->{grid}{$x} ) {
97 7         8 push @rslt, [];
98 7         10 foreach my $y ( $coord->[2] .. $coord->[3] ) {
99 70         135 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         8 push @rslt, [ ( 0 ) x ( $coord->[3] - $coord->[2] + 1 ) ];
104             }
105             }
106 1         21 return \@rslt;
107             }
108              
109             sub get_grid_coord {
110 9     9 1 13 my ( $self ) = @_;
111 9         57 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 24 my ( $self, $living, $dead, $coord ) = @_;
121 10   50     39 $living ||= 'X';
122 10   50     31 $dead ||= '.';
123 10   66     26 $coord ||= $self->get_grid_coord();
124 10         17 my @rslt;
125 10 100       19 if ( $self->{grid} ) {
126 7         17 foreach my $x ( $coord->[0] .. $coord->[1] ) {
127 58 100       98 if ( $self->{grid}{$x} ) {
128             push @rslt, join '', map {
129 37 100 100     55 ( $self->{grid}{$x}{$_} && $self->{grid}{$x}{$_}[0]) ?
  324         892  
130             $living : $dead
131             } $coord->[2] .. $coord->[3];
132             } else {
133 21         39 push @rslt, $dead x ( $coord->[3] - $coord->[2] + 1 );
134             }
135             }
136             } else {
137 3         11 @rslt = ( $dead x ( $coord->[3] - $coord->[2] + 1 ) ) x (
138             $coord->[1] - $coord->[0] + 1 );
139             }
140 10 50       22 return wantarray ? @rslt : join '', map { "$_\n" } @rslt;
  83         151  
141             }
142              
143             sub get_active_text_grid {
144 1     1 1 1148 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 869 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 5 my ( $self ) = @_;
162 2         3 my $min_x = $self->{size_x};
163 2         8 for ( $min_x = 0; $min_x < $self->{size_x}; $min_x++ ) {
164 8 100       19 $self->{living_x}[$min_x]
165             or next;
166 2         4 my ( $max_x, $min_y, $max_y );
167 2         5 for ( $max_x = $self->{size_x}; $max_x >= $min_x; ) {
168 10 100       22 $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         5 for ( $max_y = $self->{size_y}; $max_y >= $min_y; ) {
176 12 100       32 $self->{living_y}[--$max_y]
177             and last;
178             }
179 2         8 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 356 my ( $self, $x, $y, $array ) = @_;
186 1         2 my $ix = $x;
187 1         2 foreach my $row ( @{ $array } ) {
  1         3  
188 1         2 my $iy = $y;
189 1         2 foreach my $state ( @{ $row } ) {
  1         1  
190 3         7 $self->set_point_state( $ix, $iy, $state );
191 3         5 $iy++;
192             }
193 1         18 $ix++;
194             }
195 1         4 return;
196             }
197              
198             sub place_text_points {
199 3     3 1 14 my ( $self, $x, $y, $living, @array ) = @_;
200 3         5 my $ix = $x;
201 3 100 66     25 1 == @array
202             and $array[0] =~ NEW_LINE_RE
203 2         43 and @array = split qr< @{[ NEW_LINE_RE ]} >smx, $array[0];
204 3         11 foreach my $line ( @array ) {
205 7         11 my $iy = $y;
206 7         35 foreach my $state ( map { $living eq $_ } split qr<>, $line ) {
  17         31  
207 17         34 $self->set_point_state( $ix, $iy, $state );
208 17         20 $iy++;
209             }
210 7         17 $ix++;
211             }
212 3         9 return;
213             }
214              
215             sub process {
216 4     4 1 10 my ( $self, $steps ) = @_;
217 4   100     15 $steps ||= 1;
218              
219 4         9 foreach ( 1 .. $steps ) {
220              
221 13         21 my $changed = delete $self->{changed};
222 13         19 $self->{change_count} = 0;
223              
224 13         15 foreach my $x ( keys %{ $changed } ) {
  13         30  
225 60         62 foreach my $y ( keys %{ $changed->{$x} } ) {
  60         120  
226 253         307 my $cell = $self->{grid}{$x}{$y};
227 2     2   14 no warnings qw{ uninitialized };
  2         4  
  2         1508  
228 253 100       316 if ( $cell->[0] ) {
229 66 100       127 $self->{live}[ $changed->{$x}{$y} ]
230             or $self->unset_point( $x, $y );
231             } else {
232 187 100       388 $self->{breed}[ $changed->{$x}{$y} ]
233             and $self->set_point( $x, $y );
234             }
235             }
236             }
237              
238             $self->{change_count}
239 13 100       45 or last;
240             }
241 4         15 return $self->{change_count};
242             }
243              
244             sub set_point {
245 26     26 1 43 my ( $self, $x, $y ) = @_;
246 26         42 return $self->set_point_state( $x, $y, 1 );
247             }
248              
249             sub set_point_state {
250 74     74 1 112 my ( $self, $x, $y, $state ) = @_;
251              
252 74 50 33     483 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       135 defined $state
258             or return $state;
259              
260 74 50 33     308 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     171 my $prev_state = $self->{grid}{$x}{$y}[0] || 0;
267 74 100       129 $state = TOGGLE_STATE_REF eq ref $state ? 1 - $prev_state :
    100          
268             $state ? 1 : 0;
269              
270 74         105 $self->{grid}{$x}{$y}[0] = $state;
271 74   100     127 $self->{grid}{$x}{$y}[1] ||= 0;
272 74 100       121 my $delta = $state - $prev_state
273             or return $state;
274 68         90 $self->{living_x}[$x] += $delta;
275 68         81 $self->{living_y}[$y] += $delta;
276              
277 68         74 $self->{change_count}++;
278              
279 68         199 foreach my $ix ( max( 0, $x - 1 ) .. min( $self->{max_x}, $x + 1 ) ) {
280 197         393 foreach my $iy ( max( 0, $y - 1 ) .. min( $self->{max_y}, $y + 1 )
281             ) {
282             $self->{changed}{$ix}{$iy} =
283 563         1155 $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         114 $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         120 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 4 my ( $self, $kind ) = @_;
308 2 50       9 $dflt{$kind}
309             or croak "'$kind' is not a valid rule kind";
310 2         4 return( grep { $self->{$kind}[$_] } 0 .. $#{ $self->{$kind} } );
  8         21  
  2         5  
311             }
312              
313             sub set_rule {
314 6     6 1 10 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       13 ARRAY_REF eq ref $rule
319             or croak "\u$kind rule must be an array reference";
320 6         15 $self->{$kind} = [];
321 6         11 foreach ( @{ $rule } ) {
  6         12  
322 9 50       27 $_ =~ 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         10 return;
327             }
328             }
329              
330             sub set_rules {
331 3     3 1 7 my ( $self, $breed, $live ) = @_;
332 3         10 $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 7 my ( $self, $x, $y ) = @_;
339 2         6 return $self->set_point_state( $x, $y, TOGGLE_STATE );
340             }
341              
342             sub unset_point {
343 26     26 1 38 my ( $self, $x, $y ) = @_;
344 26         36 return $self->set_point_state( $x, $y, 0 );
345             }
346              
347             1;
348              
349             __END__