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   259031 use 5.008001;
  2         15  
4              
5 2     2   36 use strict;
  2         4  
  2         46  
6 2     2   10 use warnings;
  2         4  
  2         77  
7              
8 2     2   10 use Carp;
  2         4  
  2         162  
9 2     2   15 use List::Util qw{ max min };
  2         5  
  2         202  
10              
11             our $VERSION = '0.006';
12              
13 2     2   16 use constant ARRAY_REF => ref [];
  2         5  
  2         194  
14              
15 2     2   14 use constant DEFAULT_BREED => [ 3 ];
  2         3  
  2         108  
16 2     2   13 use constant DEFAULT_LIVE => [ 2, 3 ];
  2         3  
  2         108  
17 2     2   14 use constant DEFAULT_SIZE => 100;
  2         3  
  2         156  
18              
19 2     2   14 use constant NEW_LINE_RE => qr< \n >smx;
  2         12  
  2         161  
20 2     2   13 use constant NON_NEGATIVE_INTEGER_RE => qr< \A [0-9]+ \z >smx;
  2         4  
  2         152  
21 2     2   14 use constant POSITIVE_INTEGER_RE => qr< \A [1-9][0-9]* \z >smx;
  2         4  
  2         154  
22              
23 2     2   14 use constant TOGGLE_STATE => do { bless \my $x, 'Toggle_State' };
  2         4  
  2         3  
  2         148  
24 2     2   384 use constant TOGGLE_STATE_REF => ref TOGGLE_STATE;
  2         19  
  2         3479  
25              
26             sub new {
27 3     3 1 446 my ( $class, $size, $breed, $live ) = @_;
28              
29 3         5 my $self;
30              
31 3         9 my $ref = ref $size;
32 3 100       15 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         9 $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     38 and $self->{size_y} =~ POSITIVE_INTEGER_RE
44             or croak 'Sizes must be positive integers';
45 3         11 $self->{max_x} = $self->{size_x} - 1;
46 3         7 $self->{max_y} = $self->{size_y} - 1;
47              
48 3   66     16 bless $self, ref $class || $class;
49 3         17 $self->set_rules( $breed, $live );
50              
51 3         11 $self->clear();
52              
53 3         22 return $self;
54             }
55              
56             sub clear {
57 4     4 1 10 my ( $self ) = @_;
58 4         18 delete $self->{grid};
59 4         8 delete $self->{changed};
60 4         10 $self->{living_x} = [];
61 4         10 $self->{living_y} = [];
62 4         9 $self->{change_count} = 0;
63 4         7 return $self;
64             }
65              
66             sub get_active_grid_coord {
67 2     2 1 411 my ( $self ) = @_;
68             my ( $min_x, $max_x, $min_y, $max_y ) = ( $self->{size_x}, 0,
69 2         8 $self->{size_y}, 0 );
70 2         3 foreach my $ix ( keys %{ $self->{changed} } ) {
  2         10  
71 10 100       20 $min_x = $ix if $ix < $min_x;
72 10 100       21 $max_x = $ix if $ix > $max_x;
73 10         12 foreach my $iy ( keys %{ $self->{changed}{$ix} } ) {
  10         23  
74 40 100       68 $min_y = $iy if $iy < $min_y;
75 40 100       74 $max_y = $iy if $iy > $max_y;
76             }
77             }
78 2 50       8 $max_x < $min_x
79             and croak 'No active cells';
80 2         10 return [ $min_x, $max_x, $min_y, $max_y ];
81             }
82              
83             sub get_breeding_rules {
84 1     1 1 9705 my ( $self ) = @_;
85 1         5 return $self->get_rule( 'breed' );
86             }
87              
88             sub get_grid {
89 1     1 1 4 my ( $self, $coord ) = @_;
90 1   33     8 $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         22 my @rslt;
95 1         8 foreach my $x ( $coord->[0] .. $coord->[1] ) {
96 10 100       21 if ( $self->{grid}{$x} ) {
97 7         14 push @rslt, [];
98 7         13 foreach my $y ( $coord->[2] .. $coord->[3] ) {
99 70         175 push @{ $rslt[-1] }, $self->{grid}{$x}{$y} ?
100 70 100       90 $self->{grid}{$x}{$y}[0] ? 1 : 0 : 0;
    100          
101             }
102             } else {
103 3         16 push @rslt, [ ( 0 ) x ( $coord->[3] - $coord->[2] + 1 ) ];
104             }
105             }
106 1         39 return \@rslt;
107             }
108              
109             sub get_grid_coord {
110 9     9 1 16 my ( $self ) = @_;
111 9         51 return [ 0, $self->{max_x}, 0, $self->{max_y} ];
112             }
113              
114             sub get_living_rules {
115 1     1 1 4 my ( $self ) = @_;
116 1         3 return $self->get_rule( 'live' );
117             }
118              
119             sub get_text_grid {
120 10     10 1 30 my ( $self, $living, $dead, $coord ) = @_;
121 10   50     46 $living ||= 'X';
122 10   50     34 $dead ||= '.';
123 10   66     31 $coord ||= $self->get_grid_coord();
124 10         14 my @rslt;
125 10 100       23 if ( $self->{grid} ) {
126 7         19 foreach my $x ( $coord->[0] .. $coord->[1] ) {
127 58 100       108 if ( $self->{grid}{$x} ) {
128             push @rslt, join '', map {
129 37 100 100     75 ( $self->{grid}{$x}{$_} && $self->{grid}{$x}{$_}[0]) ?
  324         1035  
130             $living : $dead
131             } $coord->[2] .. $coord->[3];
132             } else {
133 21         51 push @rslt, $dead x ( $coord->[3] - $coord->[2] + 1 );
134             }
135             }
136             } else {
137 3         16 @rslt = ( $dead x ( $coord->[3] - $coord->[2] + 1 ) ) x (
138             $coord->[1] - $coord->[0] + 1 );
139             }
140 10 50       30 return wantarray ? @rslt : join '', map { "$_\n" } @rslt;
  83         193  
141             }
142              
143             sub get_active_text_grid {
144 1     1 1 986 my ( $self, $living, $dead ) = @_;
145 1         6 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 977 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         5 my $min_x = $self->{size_x};
163 2         9 for ( $min_x = 0; $min_x < $self->{size_x}; $min_x++ ) {
164 8 100       21 $self->{living_x}[$min_x]
165             or next;
166 2         4 my ( $max_x, $min_y, $max_y );
167 2         6 for ( $max_x = $self->{size_x}; $max_x >= $min_x; ) {
168 10 100       23 $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       15 $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       37 $self->{living_y}[--$max_y]
177             and last;
178             }
179 2         39 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 423 my ( $self, $x, $y, $array ) = @_;
186 1         3 my $ix = $x;
187 1         1 foreach my $row ( @{ $array } ) {
  1         4  
188 1         2 my $iy = $y;
189 1         2 foreach my $state ( @{ $row } ) {
  1         3  
190 3         9 $self->set_point_state( $ix, $iy, $state );
191 3         4 $iy++;
192             }
193 1         5 $ix++;
194             }
195 1         3 return;
196             }
197              
198             sub place_text_points {
199 3     3 1 14 my ( $self, $x, $y, $living, @array ) = @_;
200 3         7 my $ix = $x;
201 3 100 66     25 1 == @array
202             and $array[0] =~ NEW_LINE_RE
203 2         41 and @array = split qr< @{[ NEW_LINE_RE ]} >smx, $array[0];
204 3         12 foreach my $line ( @array ) {
205 7         12 my $iy = $y;
206 7         35 foreach my $state ( map { $living eq $_ } split qr<>, $line ) {
  17         40  
207 17         41 $self->set_point_state( $ix, $iy, $state );
208 17         29 $iy++;
209             }
210 7         26 $ix++;
211             }
212 3         8 return;
213             }
214              
215             sub process {
216 4     4 1 12 my ( $self, $steps ) = @_;
217 4   100     15 $steps ||= 1;
218              
219 4         12 foreach ( 1 .. $steps ) {
220              
221 13         25 my $changed = delete $self->{changed};
222 13         33 $self->{change_count} = 0;
223              
224 13         21 foreach my $x ( keys %{ $changed } ) {
  13         37  
225 60         78 foreach my $y ( keys %{ $changed->{$x} } ) {
  60         163  
226 253         409 my $cell = $self->{grid}{$x}{$y};
227 2     2   19 no warnings qw{ uninitialized };
  2         10  
  2         1977  
228 253 100       432 if ( $cell->[0] ) {
229 66 100       178 $self->{live}[ $changed->{$x}{$y} ]
230             or $self->unset_point( $x, $y );
231             } else {
232 187 100       424 $self->{breed}[ $changed->{$x}{$y} ]
233             and $self->set_point( $x, $y );
234             }
235             }
236             }
237              
238             $self->{change_count}
239 13 100       71 or last;
240             }
241 4         20 return $self->{change_count};
242             }
243              
244             sub set_point {
245 26     26 1 70 my ( $self, $x, $y ) = @_;
246 26         51 return $self->set_point_state( $x, $y, 1 );
247             }
248              
249             sub set_point_state {
250 74     74 1 124 my ( $self, $x, $y, $state ) = @_;
251              
252 74 50 33     584 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       149 defined $state
258             or return $state;
259              
260 74 50 33     385 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     234 my $prev_state = $self->{grid}{$x}{$y}[0] || 0;
267 74 100       158 $state = TOGGLE_STATE_REF eq ref $state ? 1 - $prev_state :
    100          
268             $state ? 1 : 0;
269              
270 74         115 $self->{grid}{$x}{$y}[0] = $state;
271 74   100     169 $self->{grid}{$x}{$y}[1] ||= 0;
272 74 100       153 my $delta = $state - $prev_state
273             or return $state;
274 68         92 $self->{living_x}[$x] += $delta;
275 68         98 $self->{living_y}[$y] += $delta;
276              
277 68         110 $self->{change_count}++;
278              
279 68         254 foreach my $ix ( max( 0, $x - 1 ) .. min( $self->{max_x}, $x + 1 ) ) {
280 197         456 foreach my $iy ( max( 0, $y - 1 ) .. min( $self->{max_y}, $y + 1 )
281             ) {
282             $self->{changed}{$ix}{$iy} =
283 563         1402 $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         147 $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         141 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 5 my ( $self, $kind ) = @_;
308 2 50       8 $dflt{$kind}
309             or croak "'$kind' is not a valid rule kind";
310 2         5 return( grep { $self->{$kind}[$_] } 0 .. $#{ $self->{$kind} } );
  8         23  
  2         7  
311             }
312              
313             sub set_rule {
314 6     6 1 15 my ( $self, $kind, $rule ) = @_;
315 6 50       19 $dflt{$kind}
316             or croak "'$kind' is not a valid rule name";
317 6   33     32 $rule ||= $dflt{$kind};
318 6 50       21 ARRAY_REF eq ref $rule
319             or croak "\u$kind rule must be an array reference";
320 6         22 $self->{$kind} = [];
321 6         10 foreach ( @{ $rule } ) {
  6         15  
322 9 50       38 $_ =~ NON_NEGATIVE_INTEGER_RE
323             or croak "\u$kind rule must be a reference to an array of non-negative integers";
324 9         49 $self->{$kind}[$_] = 1;
325             }
326 6         12 return;
327             }
328             }
329              
330             sub set_rules {
331 3     3 1 8 my ( $self, $breed, $live ) = @_;
332 3         11 $self->set_rule( breed => $breed );
333 3         11 $self->set_rule( live => $live );
334 3         6 return;
335             }
336              
337             sub toggle_point {
338 2     2 1 7 my ( $self, $x, $y ) = @_;
339 2         9 return $self->set_point_state( $x, $y, TOGGLE_STATE );
340             }
341              
342             sub unset_point {
343 26     26 1 51 my ( $self, $x, $y ) = @_;
344 26         51 return $self->set_point_state( $x, $y, 0 );
345             }
346              
347             1;
348              
349             __END__