File Coverage

blib/lib/Game/Life/Infinite/Board.pm
Criterion Covered Total %
statement 418 524 79.7
branch 185 270 68.5
condition 48 77 62.3
subroutine 24 28 85.7
pod 17 22 77.2
total 692 921 75.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Game::Life::Infinite::Board;
4            
5 9     9   1338771 use strict;
  9         23  
  9         525  
6 9     9   59 use warnings;
  9         69  
  9         879  
7 9     9   65 use Time::HiRes;
  9         18  
  9         120  
8             require 5.010_001;
9              
10             BEGIN {
11 9     9   1260 use Exporter ();
  9         74  
  9         402  
12 9     9   68 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
  9         19  
  9         2957  
13 9     9   183 $VERSION = sprintf( "%d.%02d", q($Revision: 0.08 $) =~ /\s(\d+)\.(\d+)/ );
14 9         246 @ISA = qw(Exporter);
15 9         33 @EXPORT_OK = qw(format_is);
16 9         95917 %EXPORT_TAGS = ( );
17             }
18              
19             sub new {
20 11     11 1 1837190 my ( $class, $rulesRef, @args ) = @_;
21 11         38 my $self = {};
22 11         78 $self->{'maxx'} = $self->{'maxy'} = $self->{'minx'} = $self->{'miny'} = 0; # Board boundaries.
23 11         63 $self->{'gen'} = $self->{'liveCells'} = $self->{'usedCells'} = 0;
24 11         31 $self->{'delta'} = -1; # Number of cells that changed state since previous epoch (newborns+dead).
25 11         38 $self->{'factor2'} = 1;
26 11         33 $self->{'oscilator'} = 0; # If oscilator is detected, contains the period.
27 11         39 $self->{'cells'} = {};
28 11         35 $self->{'currentFn'} = 'Untitled.cells';
29 11         35 $self->{'name'} = 'Untitled';
30 11         33 $self->{'totalTime'} = 0;
31 11         31 $self->{'osccheck'} = 0;
32 11         30 $self->{'color'} = 0;
33             # Check for rules:
34 11         55 &setRules($self, $rulesRef);
35 11         56 &updateCell($self, 0, 0, 0); # Create first cell in 0,0 coordinates.
36 11         31 bless $self, $class;
37 11         49 return $self;
38             };
39              
40             sub setRules {
41 42     42 1 122 my ( $self, $rulesRef) = @_;
42 42 100       202 my ($breedRef, $liveRef) = (ref($rulesRef) eq "ARRAY") ? ($rulesRef->[0], $rulesRef->[1]) : ([3], [2,3]);
43 42 100       163 if ($self->{'color'} > 0) {
44             # Force standard rules in colourised board.
45 30         120 ($breedRef, $liveRef) = ([3], [2,3]);
46             };
47 42 50       171 $self->{'breedRules'} = (ref($breedRef) eq "ARRAY") ? $breedRef : [];
48 42 50       157 $self->{'liveRules'} = (ref($liveRef) eq "ARRAY") ? $liveRef : [];
49 42         99 return;
50             };
51              
52             sub setColor {
53 30     30 1 76 my ($self, $color) = @_;
54 30 50       72 $color = (defined $color) ? $color : '';
55 30 50       108 if ($self->{'gen'} > 0) {return;};
  0         0  
56 30 100       91 if (lc($color) eq 'immigration') {
    50          
    0          
57 13         27 $self->{'color'} = 1;
58             # Reset rules to standard:
59 13         43 &setRules($self, undef);
60 13         44 return 1;
61             } elsif (lc($color) eq 'quadlife') {
62 17         33 $self->{'color'} = 2;
63             # Reset rules to standard:
64 17         63 &setRules($self, undef);
65 17         57 return 2;
66             } elsif (lc($color) eq 'normal') {
67 0         0 $self->{'color'} = 0;
68 0         0 return 0;
69             } else {
70 0         0 return;
71             };
72             };
73              
74             sub getColor {
75 0     0 1 0 my $self = shift;
76 0         0 return $self->{'color'};
77             };
78              
79             sub updateCell {
80             # Update the state of a cell. If non-existing, create it.
81 19509     19509 1 56389 my ( $self, $xpos, $ypos, $state ) = @_;
82 19509 100       78954 defined ($self->{'cells'}->{$xpos, $ypos}) or &createCell($self, $xpos, $ypos);
83 19509 100 66     88196 if (($self->{'cells'}->{$xpos, $ypos}->{'state'}) and (not $state)) {
84 9042         23084 my $oldstate = $self->{'cells'}->{$xpos, $ypos}->{'state'};
85 9042         15641 --$self->{'liveCells'};
86             # Update neighbours counts:
87 9042         24503 foreach my $xx ($xpos-1 .. $xpos+1) {
88 27126         61740 foreach my $yy ($ypos-1 .. $ypos+1) {
89 81378 100 100     223134 if (($xx == $xpos) and ($yy == $ypos)) {
90 9042         16971 next;
91             };
92 72336 50       210847 if (defined ($self->{'cells'}->{$xx, $yy})) {
93 72336         192519 --$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$oldstate};
94 72336         207241 --$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
95             } else {
96             #&createCell($self, $xx, $yy);
97             };
98             };
99             };
100             };
101 19509 100 100     91615 if ((not $self->{'cells'}->{$xpos, $ypos}->{'state'}) and ($state)) {
102             # Check validity:
103 10456 50 66     95408 if (
      66        
      33        
      33        
      33        
104             (($state > 1) and ($self->{'color'} < 1)) or
105             (($state > 2) and ($self->{'color'} < 2)) or
106             ($state > 4) or
107             ($state < 0)
108             ) {
109             # Invalid state for current color.
110 0         0 return -1;
111             };
112 10456         21111 ++$self->{'liveCells'};
113             # Update neighbours counts:
114 10456         33510 foreach my $xx ($xpos-1 .. $xpos+1) {
115 31368         75270 foreach my $yy ($ypos-1 .. $ypos+1) {
116 94104 100 100     277622 if (($xx == $xpos) and ($yy == $ypos)) {
117 10456         20451 next;
118             };
119 83648 100       258200 if (defined ($self->{'cells'}->{$xx, $yy})) {
120 73211         200845 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
121 73211         224915 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
122             } else {
123 10437         29640 &createCell($self, $xx, $yy);
124 10437         32365 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
125 10437         34050 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
126             };
127             };
128             };
129             };
130 19509         55014 $self->{'cells'}->{$xpos, $ypos}->{'state'} = $state;
131 19509         62176 return;
132             };
133              
134             sub queryCell {
135             # Return the state of a cell:
136 0     0 1 0 my ( $self, $xpos, $ypos ) = @_;
137 0 0       0 if (defined $self->{'cells'}->{$xpos, $ypos}) {
138 0         0 return $self->{'cells'}->{$xpos, $ypos}->{'state'};
139             } else {
140 0         0 return;
141             };
142             };
143              
144             sub createCell {
145             # Create an empty cell.
146 10693     10693 0 25917 my ($self, $xpos, $ypos, @rest) = @_;
147 10693         53398 $self->{'cells'}->{$xpos, $ypos}->{'state'} = 0;
148 10693         37543 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{1} = 0;
149 10693         29226 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{2} = 0;
150 10693         31744 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{3} = 0;
151 10693         28948 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{4} = 0;
152 10693         28156 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{'total'} = 0;
153             # Update boundaries:
154 10693 100       27044 $self->{'maxx'} < $xpos and $self->{'maxx'} = $xpos;
155 10693 100       25850 $self->{'minx'} > $xpos and $self->{'minx'} = $xpos;
156 10693 100       25433 $self->{'maxy'} < $ypos and $self->{'maxy'} = $ypos;
157 10693 100       36364 $self->{'miny'} > $ypos and $self->{'miny'} = $ypos;
158 10693         18666 ++$self->{'usedCells'};
159 10693         21835 return;
160             };
161              
162             sub loadInit {
163             # Load an initial grid from a file.
164 11     11 1 8395 my ($self, $fn, @rest) = @_;
165 11         30 my $untitled = 'Untitled.cells';
166 11 50       54 if (not defined $fn) { return $untitled; };
  0         0  
167 11         24 my @array;
168 11 50       938 open(my $fh,"<:crlf", $fn) or die "Failed to open $fn: $!";
169 11         697 while (<$fh>) { push @array, $_; };
  706         2833  
170 11         159 close $fh;
171 11         84 my $ftype = &format_is(\@array);
172 11         27 my $loadok;
173 11 50       42 if (not defined $ftype) {
174 0         0 print "DEBUG: File format could not be detected.\n";
175 0         0 return $untitled;
176             };
177 11 100 66     70 if ($ftype->{'format'} eq 'CELLS') {
    100 33        
    100          
    50          
178 8         59 $loadok = $self->loadCells(\@array);
179 8         2036 print "DEBUG: Loaded CELLS\n";
180             } elsif ($ftype->{'format'} eq 'RLE') {
181 1         7 $loadok = $self->loadRLE(\@array);
182 1         28 print "DEBUG: Loaded RLE\n";
183             } elsif (($ftype->{'format'} eq 'Life') and ($ftype->{'version'} eq '1.05')) {
184 1         6 $loadok = $self->loadL105(\@array);
185 1         28 print "DEBUG: Loaded Life 1.05\n";
186             } elsif (($ftype->{'format'} eq 'Life') and ($ftype->{'version'} eq '1.06')) {
187 1         6 $loadok = $self->loadL106(\@array);
188 1         20 print "DEBUG: Loaded Life 1.06\n";
189             } else {
190 0         0 print "DEBUG: " . $ftype->{'format'} . " V. " . $ftype->{'version'} . " not supported yet.\n";
191 0         0 return $untitled;
192             };
193 11 50       64 if (not $loadok) {
194 0         0 print "DEBUG: Load failed!\n";
195 0         0 return $untitled;
196             } else {
197 11         40 $self->{'currentFn'} = $fn;
198 11         319 return $fn;
199             };
200             };
201              
202             sub saveGridTxt {
203             # Save a grid to a txt file.
204 0     0 1 0 my ($self, $fn, @rest) = @_;
205 0 0       0 if (not defined $fn) { return; };
  0         0  
206 0         0 my $output;
207 0 0       0 open($output, ">", $fn) or die "cannot open $fn: $!\n";
208 0         0 for (my $yy = $self->{'miny'}; $yy <= $self->{'maxy'}; $yy++) {
209 0         0 foreach my $xx ($self->{'minx'} .. $self->{'maxx'}) {
210 0 0       0 if (defined ($self->{'cells'}->{$xx, $yy})) {
211 0 0       0 if ($self->{'cells'}->{$xx, $yy}->{'state'} == 1) {
    0          
212 0         0 print $output 'O';
213             } elsif ($self->{'cells'}->{$xx, $yy}->{'state'} > 1) {
214 0         0 print $output $self->{'cells'}->{$xx, $yy}->{'state'};
215             } else {
216 0         0 print $output '.';
217             };
218             } else {
219 0         0 print $output '.';
220             };
221             };
222 0         0 print $output "\n";
223             };
224 0         0 close $output;
225 0         0 return $fn;
226             };
227              
228              
229             sub crudePrintBoard {
230             # Basic board print.
231 0     0 1 0 my $self = shift;
232 0         0 for (1 .. 80) {
233 0         0 print "-";
234             };
235 0         0 print "\n";
236 0         0 for (my $yy = $self->{'miny'}; $yy <= $self->{'maxy'}; $yy++) {
237 0         0 foreach my $xx ($self->{'minx'} .. $self->{'maxx'}) {
238 0 0       0 if (defined ($self->{'cells'}->{$xx, $yy})) {
239 0 0       0 if ($self->{'cells'}->{$xx, $yy}->{'state'} == 1) {
    0          
240 0         0 print 'O';
241             } elsif ($self->{'cells'}->{$xx, $yy}->{'state'} > 1) {
242 0         0 print $self->{'cells'}->{$xx, $yy}->{'state'};
243             } else {
244 0         0 print '_';
245             };
246             } else {
247 0         0 print '.';
248             };
249             };
250 0         0 print "\n";
251             };
252 0         0 my $stats = &statistics($self);
253 0         0 print "---\tGeneration: " . $stats->{'generation'} . " x: " . $stats->{'minx'} . ".." . $stats->{'maxx'} . " y: " . $stats->{'miny'} . ".." . $stats->{'maxy'} . " Size: $stats->{'size'} LiveCells: " . $stats->{'liveCells'} . "\n";
254 0         0 print "\tDelta: " . $stats->{'delta'} . "\n";
255 0         0 return;
256             };
257              
258             sub tick {
259             # Calculate next epoch.
260 1048     1048 1 13971 my ($self, $oscCheck) = @_;
261 1048         3454 my $t0 = [Time::HiRes::gettimeofday()];
262 1048         3239 $oscCheck = &setOscCheck($self, $oscCheck);
263              
264 1048         3816 my $resref = &tickMainLoop($self);
265 1048         2077 my @newCells = @{$resref->{'newCells'}};
  1048         3922  
266 1048         2216 my @dieCells = @{$resref->{'dieCells'}};
  1048         2946  
267 1048         1878 my @delCells = @{$resref->{'delCells'}};
  1048         2903  
268             #use Data::Dumper;
269 1048         3177 $self->{'delta'} = scalar(@newCells) + scalar(@dieCells);
270             # Apply changes on board:
271 1048         2533 foreach my $rec (@newCells) {
272             # TODO: Do something in case of error?
273 9040         25009 my $error = &updateCell($self, $rec->[0], $rec->[1], $rec->[2]);
274 9040 50       26205 $error = (defined $error) ? $error : 0;
275             };
276 1048         2600 foreach my $rec (@dieCells) {
277 9042         23972 my $error = &updateCell($self, $rec->[0], $rec->[1], 0);
278             };
279 1048         2411 foreach my $rec (@delCells) {
280             # Verify that these cells are still without neighbours:
281 9310 100       37137 if ($self->{'cells'}->{$rec->[0], $rec->[1]}->{'neighbours'}->{'total'} == 0) {
282 5361         25401 delete $self->{'cells'}->{$rec->[0], $rec->[1]};
283 5361         10718 --$self->{'usedCells'};
284             };
285             };
286 1048         5716 $self->{'gen'} = $self->{'gen'} + 1;
287 1048 50 33     14246 $self->{'factor2'} = ((defined $self->{'usedCells'}) and ($self->{'usedCells'} > 0)) ? $self->{'liveCells'} / $self->{'usedCells'} : 1;
288 1048 100       2847 if ($oscCheck > 1) { &oscCheck($self, $oscCheck); };
  207         970  
289 1048         8102 my $t1 = [Time::HiRes::gettimeofday];
290 1048         5250 my $t0_t1 = Time::HiRes::tv_interval( $t0, $t1 );
291 1048         18122 $self->{'lastTI'} = $t0_t1; # Time spend to calculate last epoch.
292 1048         2389 $self->{'totalTime'} += $t0_t1; # Total Time spend calculating this board.
293 1048         25121 return;
294             };
295              
296             sub tickMainLoop {
297             # TODO: Return new cell's color if board is immigration or quadlife.
298 1048     1048 0 2331 my ($self) = @_;
299 1048         2983 my @newCells;
300             my @dieCells;
301 1048         0 my @delCells;
302 1048         1841 foreach my $key (keys %{ $self->{'cells'} }) {
  1048         21555  
303 88902         350374 my ($xx, $yy) = split(/$;/, $key, 2);
304 88902         230449 my $rec = [$xx, $yy];
305 88902 100 100     455600 if (
    100 100        
    100 100        
306             ($self->{'cells'}->{$xx, $yy}->{'state'} > 0) and
307 21801         379195 (not (scalar(grep(/$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'}/, @{ $self->{'liveRules'} }))))
308             ) {
309             # Die.
310 9042         25220 push @dieCells, $rec;
311             } elsif (
312             ($self->{'cells'}->{$xx, $yy}->{'state'} == 0) and
313 67101         966760 (scalar(grep(/$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'}/, @{ $self->{'breedRules'} })))
314             ) {
315             # New.
316 9040 100       22882 if ($self->{'color'} == 0) {
317             # Standard
318 8792         19479 $rec->[2] = 1;
319 8792         22553 push @newCells, $rec;
320             } else {
321             # Colorized
322             #use Data::Dumper;
323 248         489 my @colorcounts = ();
324 248         478 my $ccnt = 0;
325 248         421 foreach my $state (keys %{ $self->{'cells'}->{$xx, $yy}->{'neighbours'} }) {
  248         1214  
326 1240 100       2697 if ($state ne 'total') {
327 992 100       3159 if ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state} > 0) {
328 309         1038 $colorcounts[$state] = $self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
329 309         604 $ccnt++;
330             };
331             };
332             };
333 248 100       860 if ($ccnt == 2) {
    100          
    50          
334             # Immigration, or first rule of Quadlife (Identical).
335             # Select maximum.
336 57         91 my $max = -1;
337 57         137 for my $i (1..4) {
338 228 100       556 if (defined $colorcounts[$i]) {
339 114 100       296 if ($colorcounts[$i] > $max) {
340 103         167 $max = $colorcounts[$i];
341 103         222 $rec->[2] = $i
342             };
343             };
344             };
345             } elsif ($ccnt == 3) {
346             # Second rule of Quadlife
347 2         24 for my $i (1..4) {
348 6 100       17 if (not defined $colorcounts[$i]) {
349 2         5 $rec->[2] = $i;
350 2         5 last;
351             };
352             };
353             } elsif ($ccnt == 1) {
354             # Same color as parents:
355 189         525 for my $i (1..4) {
356 471 100       1106 if (defined $colorcounts[$i]) {
357 189         534 $rec->[2] = $i;
358 189         414 last;
359             };
360             };
361             } else {
362             # Unsupported or standard. Return first color.
363 0         0 $rec->[2] = 1;
364             };
365 248         843 push @newCells, $rec;
366             };
367             } elsif (
368             ($self->{'cells'}->{$xx, $yy}->{'state'} == 0) and
369             ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'} == 0)
370             ) {
371             # Isolated empty cell. GC.
372 9310         26276 push @delCells, $rec;
373             };
374             };
375 1048         25212 return {'newCells' => \@newCells, 'dieCells' => \@dieCells, 'delCells' => \@delCells};
376             };
377              
378             sub setOscCheck {
379 1048     1048 0 2400 my ($self, $oscCheck) = @_;
380              
381 1048 50       2649 $oscCheck = (defined $oscCheck) ? $oscCheck : 0;
382 1048 100       3302 if ($oscCheck != $self->{'osccheck'}) {
383             # Change, delete all previous snapshots:
384 3         8 delete $self->{'snapshots'};
385             };
386 1048         2082 $self->{'osccheck'} = $oscCheck;
387 1048         2362 return $oscCheck;
388             };
389              
390             sub oscCheck {
391 207     207 0 673 my ($self, $oscCheck) = @_;
392 207         578 my $lgen = $self->{'gen'};
393 207         911 my $lgenString = sprintf("s%d", $lgen);
394 207         518 my $ogen;
395             my $ogenString;
396 207         761 $self->{'snapshots'}->{$lgenString} = &snapshot($self); # Smile!
397 207         990 for (my $i = 2; $i <= $oscCheck; $i++) {
398 262         538 $ogen = $lgen - $i;
399 262         956 $ogenString = sprintf("s%d", $ogen);
400 262 100       992 if (defined ($self->{'snapshots'}->{$ogenString})) {
401             # Snapshot of grandma!
402 219 100 66     1644 if (
      66        
      33        
      33        
403             ($self->{'snapshots'}->{$ogenString}->{'snapshot'} eq $self->{'snapshots'}->{$lgenString}->{'snapshot'}) and
404             ($self->{'snapshots'}->{$ogenString}->{'minx'} == $self->{'snapshots'}->{$lgenString}->{'minx'}) and
405             ($self->{'snapshots'}->{$ogenString}->{'maxx'} == $self->{'snapshots'}->{$lgenString}->{'maxx'}) and
406             ($self->{'snapshots'}->{$ogenString}->{'miny'} == $self->{'snapshots'}->{$lgenString}->{'miny'}) and
407             ($self->{'snapshots'}->{$ogenString}->{'maxy'} == $self->{'snapshots'}->{$lgenString}->{'maxy'})
408             ) {
409             # Grandma and grandson are identical!
410 10         35 $self->{'oscilator'} = $i;
411 10         24 last;
412             } else {
413 209         834 $self->{'oscilator'} = 0;
414             };
415             };
416             };
417             # Delete oldest snapshot.
418 207         1120 delete $self->{'snapshots'}->{$ogenString};
419 207         674 return;
420             };
421              
422             sub snapshot {
423             # Take a snapshot of the board, in a way that it can be easily stored and compared
424             # to another snapshot.
425 221     221 0 8065 my $self = shift;
426 221         569 my $snapshot = '';
427 221         1349 for (my $yy = $self->{'miny'}; $yy <= $self->{'maxy'}; $yy++) {
428 6790         18021 foreach my $xx ($self->{'minx'} .. $self->{'maxx'}) {
429 263028 100       723521 if (defined ($self->{'cells'}->{$xx, $yy})) {
430 66796 100       227574 if ($self->{'cells'}->{$xx, $yy}->{'state'} == 1) {
    100          
431 17392         32385 $snapshot .= 'O';
432             } elsif ($self->{'cells'}->{$xx, $yy}->{'state'} > 1) {
433 51         139 $snapshot .= $self->{'cells'}->{$xx, $yy}->{'state'};
434             } else {
435 49353         97333 $snapshot .= '.';
436             };
437             } else {
438 196232         366225 $snapshot .= '.';
439             };
440             };
441 6790         22467 $snapshot .= "\n";
442             };
443             return {
444             'snapshot' => $snapshot,
445             'minx' => $self->{'minx'},
446             'maxx' => $self->{'maxx'},
447             'miny' => $self->{'miny'},
448 221         5226 'maxy' => $self->{'maxy'},
449             };
450             };
451              
452             sub shrinkBoard {
453             # Shrink board: Now mostly used to keep boundaries track, for printing and snapshot.
454 7     7 1 63 my $self = shift;
455 7         37 $self->{'minx'} = $self->{'maxx'} = $self->{'miny'} = $self->{'maxy'} = 0;
456 7         20 my $ok = 0;
457 7         20 foreach my $key (keys %{ $self->{'cells'} }) {
  7         114  
458 524         1892 my ($xx, $yy) = split(/$;/, $key, 2);
459 524 100 100     2577 if (($self->{'cells'}->{$xx, $yy}->{'state'} == 0) and ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'} == 0)) {
460 68         256 delete $self->{'cells'}->{$key};
461 68         148 --$self->{'usedCells'};
462             } else {
463 456 100       868 if ($ok) {
464 449 100       1124 if ($xx > $self->{'maxx'}) { $self->{'maxx'} = $xx; };
  14         35  
465 449 100       1095 if ($xx < $self->{'minx'}) { $self->{'minx'} = $xx; };
  12         31  
466 449 100       1029 if ($yy > $self->{'maxy'}) { $self->{'maxy'} = $yy; };
  9         21  
467 449 100       1131 if ($yy < $self->{'miny'}) { $self->{'miny'} = $yy; };
  17         48  
468             } else {
469 7         19 $self->{'minx'} = $xx;
470 7         42 $self->{'maxx'} = $xx;
471 7         20 $self->{'miny'} = $yy;
472 7         20 $self->{'maxy'} = $yy;
473 7         22 $ok = 1;
474             };
475             };
476             };
477 7         144 return;
478             };
479              
480             sub statistics {
481 14     14 1 79 my $self = shift;
482             return {
483             'size' => (($self->{'maxx'} - $self->{'minx'}) * ($self->{'maxy'} - $self->{'miny'})),
484             'generation' => $self->{'gen'},
485             'minx' => $self->{'minx'},
486             'maxx' => $self->{'maxx'},
487             'miny' => $self->{'miny'},
488             'maxy' => $self->{'maxy'},
489             'liveCells' => $self->{'liveCells'},
490             'delta' => $self->{'delta'},
491             'oscilator' => $self->{'oscilator'},
492             'totalTime' => $self->{'totalTime'},
493             'usedCells' => $self->{'usedCells'},
494             'factor2' => $self->{'factor2'},
495 14         326 'lastTI' => $self->{'lastTI'},
496             };
497             };
498              
499             sub format_is {
500 11     11 1 36 my ($faref) = @_; # Array ref containing file.
501 11         69 my %result = (
502             format => undef,
503             version => undef
504             );
505 11         26 foreach my $line (@{ $faref }) {
  11         42  
506 15         38 chomp($line);
507 15 100 100     249 if (($line =~ /^#Life 1.05/) or ($line =~ /^#Life 1.06/) or ($line =~ /^#MCell/)) {
    100 66        
    100          
    50          
508 2         15 ($result{format}, $result{version}) = split / /, $line, 2;
509 2         13 $result{format} =~ s/#//;
510 2         8 return \%result;
511             } elsif ($line =~ /^(b|o|[[:digit:]])/) {
512 1         4 ($result{format}, $result{version}) = ('RLE', 'N/A');
513 1         4 return \%result;
514             } elsif ($line =~ /^(\.|O|!)/) {
515 8         39 ($result{format}, $result{version}) = ('CELLS', 'N/A');
516 8         35 return \%result;
517             } elsif ($line =~ /^(\.|O|2|3|4|!)/) {
518 0         0 ($result{format}, $result{version}) = ('CELLS', 'N/A');
519 0         0 return \%result;
520             };
521             };
522 0         0 return undef;
523             };
524              
525             sub loadL106 {
526             # Load an initial grid from an array containing
527             # a file in Life 1.06 format.
528 1     1 1 3 my ($self, $array, @rest) = @_;
529 1 50       4 if (not defined $array) { return undef; };
  0         0  
530 1         2 foreach my $input (@{ $array }) {
  1         3  
531 325         671 chomp($input);
532 325         2449 $input =~ s/^\s*(.*?)\s*$/$1/;
533 325 50       920 if ($input eq '') { next; };
  0         0  
534 325 100       868 if ($input =~ /^#Life 1.06/) { next; };
  1         3  
535             # Not much checks here. Assume that data are numbers.
536 324         1109 my ($xx, $yy) = split / /, $input, 2;
537 324         791 $xx += 0;
538 324         602 $yy += 0;
539 324         941 $self->updateCell($xx, $yy, 1);
540             };
541 1         3 $self->{'name'} = '#Life 1.06 file'; # :-)
542 1         2 return 1;
543             };
544              
545              
546             sub loadL105 {
547             # Load an initial grid from an array containing
548             # a file in Life 1.05 format.
549 1     1 1 4 my ($self, $array, @rest) = @_;
550 1 50       4 if (not defined $array) { return undef; };
  0         0  
551 1         2 my $posState = 0;
552 1         3 my $name = 'Untitled';
553 1         2 my $rules = '';
554 1         3 my $dataline = '';
555 1         2 my @descrArr = ();
556 1         4 my $ulcx = my $ulcy = 0; # Upper left corner
557 1         2 my $dlinecnt = 0;
558 1         2 foreach my $input (@{ $array }) {
  1         3  
559 203         394 chomp($input);
560 203 100       457 if ($input eq '') { next; };
  1         5  
561 202 100 33     1822 if ($input =~ /^#Life 1.05/) {
    100          
    100          
    100          
    50          
    100          
    50          
562 1         3 next;
563             } elsif ($input =~ /^#D Name: /) {
564             # Out of spec, but it seems that it's used:
565 1         7 (undef, undef, $name) = split /(^#D Name: )/, $input, 3;
566 1         3 next;
567             } elsif ($input =~ /^#D /) {
568 2 50       5 if ($dlinecnt >= 22) { return undef }; # Specs.
  0         0  
569 2         8 my (undef, undef, $dline) = split /(^#D )/, $input, 3;
570 2         25 $dline =~ s/^\s*(.*?)\s*$/$1/;
571 2 50       6 if (length($dline) > 78) {
572 0         0 print "DEBUG ERROR: D-line too long\n";
573 0         0 return undef; # Specs.
574             } else {
575 2         5 push @descrArr, $dline;
576             };
577 2         5 $dlinecnt++;
578             } elsif ($input =~ /^#N/) {
579 1         3 next;
580             } elsif ($input =~ /^#R /) {
581             # Rules.
582 0         0 (undef, undef, $rules) = split /(^#R )/, $input, 3;
583 0         0 my ($sstr, $bstr) = split /\//, $rules, 2;
584              
585 0         0 my $barr = [];
586 0         0 my $sarr = [];
587 0         0 for (my $i = 0; $i < length($bstr); $i++) {
588 0 0       0 if (substr($bstr, $i, 1) =~ /[0-8]/) {
589 0         0 push @{ $barr }, substr($bstr, $i, 1);
  0         0  
590             } else {
591             # Fail. Specs.
592 0         0 print "DEBUG ERROR: wrong rules\n";
593 0         0 return undef;
594             };
595             };
596 0         0 for (my $i = 0; $i < length($sstr); $i++) {
597 0 0       0 if (substr($sstr, $i, 1) =~ /[0-8]/) {
598 0         0 push @{ $sarr }, substr($sstr, $i, 1);
  0         0  
599             } else {
600             # Fail. Specs.
601 0         0 print "DEBUG ERROR: wrong rules\n";
602 0         0 return undef;
603             };
604             };
605 0         0 $self->setRules([$barr, $sarr]);
606             } elsif ($input =~ /^#P /) {
607 2         12 (undef, $ulcx, $ulcy) = split / /, $input, 3;
608 2         6 $ulcx += 0;
609 2         7 $ulcy += 0;
610 2         5 $posState = 1;
611             } elsif (
612             ($input =~/^(\.|\*)/) and
613             ($posState == 1)
614             ) {
615             # Data line:
616             #print "DEBUG: DL: $input\n";
617 195         398 my $xx = $ulcx;
618 195         320 my $yy = $ulcy;
619 195         447 while (length($input) > 0) {
620 4086         7245 my $char = substr($input, 0, 1);
621 4086         7304 $input = substr($input, 1);
622 4086 100       9444 if ($char eq '*') {
    50          
623             #print "\tDEBUG: LIVE ($xx,$yy)\n";
624 324         1254 $self->updateCell($xx, $yy, 1);
625 324         943 $xx++;
626             } elsif ($char eq '.') {
627 3762         8583 $xx++;
628             } else {
629             # Fail.
630 0         0 print "DEBUG ERROR: unknown char\n";
631 0         0 return undef;
632             };
633             };
634 195         426 $ulcy++;
635             } else {
636             # Unrecognized input:
637 0         0 print "DEBUG ERROR: unknown input:\n\t|$input|\n";
638 0         0 return undef;
639             };
640             };
641 1         6 $self->{'description'} = \@descrArr;
642 1         4 $self->{'name'} = $name;
643 1         5 return 1;
644             };
645              
646              
647             sub loadRLE {
648             # Load an initial grid from an array containing
649             # a file in RLE format.
650 1     1 1 3 my ($self, $array, @rest) = @_;
651 1 50       4 if (not defined $array) { return undef; };
  0         0  
652 1         4 my $posState = 0;
653 1         2 my $name = 'Untitled';
654 1         4 my $headRules = my $rules = '';
655 1         45 my $dataline = '';
656 1         4 my $ulcx = my $ulcy = 0; # Upper left corner
657 1         3 foreach my $input (@{ $array }) {
  1         3  
658 20         61 chomp($input);
659 20 100 33     116 if ($input =~ /^#N /) {
    50          
    50          
    100          
    100          
660 1         8 (undef, undef, $name) = split /(^#N )/, $input, 3;
661 1         4 next;
662             } elsif ($input =~ /^#r /) {
663             # Rules:
664 0         0 (undef, undef, $headRules) = split /(^#r )/, $input, 3;
665 0         0 next;
666             } elsif (($input =~ /^#P /) or ($input =~ /^#R /)) {
667 0         0 (undef, $ulcx, $ulcy) = split / /, $input, 3;
668 0         0 next;
669             } elsif ($input =~ /^#/) {
670             # Ignore all other # lines:
671 2         4 next;
672             } elsif ($input =~ /^x/) {
673             # Header line.
674 1         7 $input =~ s/ //g;
675 1         3 my ($xc, $yc);
676 1         5 ($xc, $yc, $rules) = split /,/ , $input;
677 1 50 33     11 if ((not ($xc =~ /x=/)) or (not ($yc =~ /y=/))) {
678             # Fail.
679 0         0 return undef;
680             };
681 1         2 $posState = 1;
682 1         3 next;
683             } else {
684             # Normal line:
685 16 50       37 if (not $posState) {
686             # No header?
687 0         0 return undef;
688             };
689             # Join in one big string:
690 16         31 $dataline .= $input;
691 16         34 next;
692             };
693             };
694             # Now parse actual data:
695 1         33 my @dataArr = split /\$/, $dataline;
696 1         4 my %extraStates = ();
697 1         3 my $extraStatesCnt = 0;
698 1         2 my $xx = $ulcx;
699 1         4 my $yy = $ulcy;
700             #print "DEBUG: " . scalar(@dataArr) . " lines\n";
701 1         3 REC: foreach my $rec (@dataArr) {
702             #print "DEBUG: RLE REC: \n\t$rec\n";
703             #print "\tLINE: $yy\n";
704 81         146 my $cntstr = '0';
705 81         175 CHAR: while (length($rec) > 0) {
706 994         1867 my $char = substr($rec, 0, 1);
707 994         1853 $rec = substr($rec, 1);
708 994 100       3135 if ($char =~ /\d/) {
    100          
    100          
    50          
709 518         887 $cntstr .= $char;
710 518         1336 next CHAR;
711             } elsif ($char eq 'o') {
712 200 100       432 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
713 200         343 $cntstr = '0'; # Reset.
714 200         531 for (my $i = 0; $i < $cnt; $i++) {
715 324         1009 $self->updateCell($xx++, $yy, 1);
716             };
717             } elsif ($char eq 'b') {
718 275 100       791 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
719 275         486 $cntstr = '0'; # Reset.
720 275         687 $xx += $cnt;
721             } elsif ($char eq '!') {
722             # No more!
723 1         4 last REC;
724             } else {
725             # Handle unknown characters as extra states. Good luck with that.
726             # We will handle a max of 3 extra states (Immigration/Quadlife).
727 0 0       0 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
728 0 0       0 if (defined $extraStates{$char}) {
729 0         0 for (my $i = 0; $i < $cnt; $i++) {
730 0         0 $self->updateCell($xx++, $yy, $extraStates{$char});
731             };
732             } else {
733             # New state
734 0         0 $extraStatesCnt++;
735 0 0       0 if ($extraStatesCnt > 3) {
736             # Fail.
737 0         0 return undef;
738             } else {
739 0         0 $extraStates{$char} = $extraStatesCnt+1;
740             };
741 0 0       0 if ($extraStatesCnt == 1) {
    0          
742 0 0       0 $self->setColor('immigration') or die "Failed to set color on load!\n";
743             } elsif ($extraStatesCnt > 1) {
744 0 0       0 $self->setColor('quadlife') or die "Failed to set color on load!\n";
745             };
746 0         0 for (my $i = 0; $i < $cnt; $i++) {
747 0         0 $self->updateCell($xx++, $yy, $extraStates{$char});
748             };
749             };
750             };
751             };
752             # If there are digits left, they are empty lines.
753 80 100       205 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
754 80         143 $cntstr = '0'; # Reset.
755 80         126 $yy += $cnt; # Next line or skip lines.
756 80         172 $xx = $ulcx; # Reset
757             };
758 1         11 $self->{'name'} = $name;
759 1 50       7 if ($extraStatesCnt > 1) {
    50          
760 0 0       0 $self->setColor('quadlife') or die "Failed to set color on load!\n";
761             #print "DEBUG: RLE: Quadlife\n";
762             } elsif ($extraStatesCnt == 1) {
763 0 0       0 $self->setColor('immigration') or die "Failed to set color on load!\n";
764             #print "DEBUG: RLE: Immigration\n";
765             };
766             # Handle rules:
767 1         3 my $bstr = my $sstr = '';
768 1 50       3 if ($rules ne '') {
    0          
769             # Rules in header line have priority over comment line rules:
770 1         6 (undef, $rules) = split /=/, $rules, 2;
771 1         5 ($bstr, $sstr) = split /\//, $rules, 2;
772             } elsif ($headRules ne '') {
773 0         0 my ($sstr, $bstr) = split /\//, $headRules, 2;
774             };
775 1 50 33     13 if (($rules ne '') or ($headRules ne '')) {
776 1         3 my $barr = [];
777 1         3 my $sarr = [];
778 1         5 for (my $i = 0; $i < length($bstr); $i++) {
779 2 100       11 if (substr($bstr, $i, 1) =~ /\d/) { push @{ $barr }, substr($bstr, $i, 1); };
  1         2  
  1         5  
780             };
781 1         5 for (my $i = 0; $i < length($sstr); $i++) {
782 3 100       49 if (substr($sstr, $i, 1) =~ /\d/) { push @{ $sarr }, substr($sstr, $i, 1); };
  2         5  
  2         8  
783             };
784 1         10 $self->setRules([$barr, $sarr]);
785             };
786 1         13 return 1;
787             };
788              
789             sub loadCells {
790             # Load an initial grid from an array containing
791             # a file in cells (ASCII) format.
792 8     8 1 26 my ($self, $array, @rest) = @_;
793 8 50       62 if (not defined $array) { return undef; };
  0         0  
794 8         65 my $name = 'Untitled';
795 8         21 my @descrArr = ();
796 8         22 my $xx = my $yy = my $cnt = 0;
797 8         16 my $immigration = my $quadlife = 0;
798             #my $input;
799             #open($input, "<", $fn) or die "cannot open $input: $!\n";
800             #while (<$input>) {
801 8         17 foreach my $input (@{ $array }) {
  8         25  
802 158         347 chomp($input);
803 158 100       536 if ($input =~ /^!Name: /) {
    100          
804 1         7 (undef, undef, $name) = split /(^!Name: )/, $input, 3;
805 1         3 next;
806             } elsif ($input =~ /^!/) {
807 2         5 push @descrArr, $input;
808 2         5 next;
809             };
810 155         377 for ($yy = 0; $yy <= length($input); $yy++) {
811 5835 100       22161 if (substr($input, $yy, 1) eq 'O') {
    100          
    100          
    100          
812 417         1350 $self->updateCell($yy, $xx, 1);
813 417         1108 $cnt++;
814             } elsif (substr($input, $yy, 1) eq '2') {
815 12 50       40 $self->setColor('immigration') or die "Failed to set color on load!\n";
816 12         78 $self->updateCell($yy, $xx, 2);
817 12         25 $immigration = 1;
818 12         36 $cnt++;
819             } elsif (substr($input, $yy, 1) eq '3') {
820 9 50       27 $self->setColor('quadlife') or die "Failed to set color on load!\n";
821 9         32 $self->updateCell($yy, $xx, 3);
822 9         17 $quadlife = 1;
823 9         27 $cnt++;
824             } elsif (substr($input, $yy, 1) eq '4') {
825 6 50       33 $self->setColor('quadlife') or die "Failed to set color on load!\n";
826 6         24 $self->updateCell($yy, $xx, 4);
827 6         11 $quadlife = 1;
828 6         21 $cnt++;
829             };
830             };
831 155         289 $xx++;
832             };
833 8         51 $self->{'name'} = $name;
834 8         77 $self->{'description'} = \@descrArr;
835             # Finalize board colour:
836 8 100       80 if ($quadlife) {
    100          
837 2 50       8 $self->setColor('quadlife') or die "Failed to set color on load!\n";
838             } elsif ($immigration) {
839 1 50       5 $self->setColor('immigration') or die "Failed to set color on load!\n";
840             };
841 8         34 return 1;
842             };
843              
844             42;
845              
846             __END__