File Coverage

blib/lib/Game/Life/Infinite/Board.pm
Criterion Covered Total %
statement 415 522 79.5
branch 185 270 68.5
condition 49 78 62.8
subroutine 24 28 85.7
pod 17 22 77.2
total 690 920 75.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Game::Life::Infinite::Board;
4            
5 9     9   221987 use strict;
  9         23  
  9         383  
6 9     9   48 use warnings;
  9         18  
  9         290  
7 9     9   9324 use Time::HiRes;
  9         18697  
  9         53  
8             require 5.10.1;
9              
10             BEGIN {
11 9     9   1289 use Exporter ();
  9         21  
  9         278  
12 9     9   46 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
  9         15  
  9         1298  
13 9     9   88 $VERSION = sprintf( "%d.%02d", q($Revision: 0.07 $) =~ /\s(\d+)\.(\d+)/ );
14 9         125 @ISA = qw(Exporter);
15 9         24 @EXPORT_OK = qw(format_is);
16 9         85410 %EXPORT_TAGS = ( );
17             }
18              
19             sub new {
20 11     11 1 716 my ( $class, $rulesRef, @args ) = @_;
21 11         27 my $self = {};
22 11         52 $self->{'maxx'} = $self->{'maxy'} = $self->{'minx'} = $self->{'miny'} = 0; # Board boundaries.
23 11         39 $self->{'gen'} = $self->{'liveCells'} = $self->{'usedCells'} = 0;
24 11         33 $self->{'delta'} = -1; # Number of cells that changed state since previous epoch (newborns+dead).
25 11         25 $self->{'factor2'} = 1;
26 11         23 $self->{'oscilator'} = 0; # If oscilator is detected, contains the period.
27 11         23 $self->{'cells'} = {};
28 11         27 $self->{'currentFn'} = 'Untitled.cells';
29 11         18 $self->{'name'} = 'Untitled';
30 11         21 $self->{'totalTime'} = 0;
31 11         22 $self->{'osccheck'} = 0;
32 11         32 $self->{'color'} = 0;
33             # Check for rules:
34 11         617 &setRules($self, $rulesRef);
35 11         45 &updateCell($self, 0, 0, 0); # Create first cell in 0,0 coordinates.
36 11         30 bless $self, $class;
37 11         32 return $self;
38             };
39              
40             sub setRules {
41 42     42 1 60 my ( $self, $rulesRef) = @_;
42 42 100       356 my ($breedRef, $liveRef) = (ref($rulesRef) eq "ARRAY") ? ($rulesRef->[0], $rulesRef->[1]) : ([3], [2,3]);
43 42 100       317 if ($self->{'color'} > 0) {
44             # Force standard rules in colourised board.
45 30         70 ($breedRef, $liveRef) = ([3], [2,3]);
46             };
47 42 50       341 $self->{'breedRules'} = (ref($breedRef) eq "ARRAY") ? $breedRef : [];
48 42 50       110 $self->{'liveRules'} = (ref($liveRef) eq "ARRAY") ? $liveRef : [];
49 42         242 return;
50             };
51              
52             sub setColor {
53 30     30 1 38 my ($self, $color) = @_;
54 30 50       223 $color = (defined $color) ? $color : '';
55 30 50       76 if ($self->{'gen'} > 0) {return;};
  0         0  
56 30 100       82 if (lc($color) eq 'immigration') {
    50          
    0          
57 13         17 $self->{'color'} = 1;
58             # Reset rules to standard:
59 13         28 &setRules($self, undef);
60 13         35 return 1;
61             } elsif (lc($color) eq 'quadlife') {
62 17         24 $self->{'color'} = 2;
63             # Reset rules to standard:
64 17         30 &setRules($self, undef);
65 17         47 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 32737 my ( $self, $xpos, $ypos, $state ) = @_;
82 19509 100       57271 defined ($self->{'cells'}->{$xpos, $ypos}) or &createCell($self, $xpos, $ypos);
83 19509 100 66     79698 if (($self->{'cells'}->{$xpos, $ypos}->{'state'}) and (not $state)) {
84 9042         18665 my $oldstate = $self->{'cells'}->{$xpos, $ypos}->{'state'};
85 9042         10981 --$self->{'liveCells'};
86             # Update neighbours counts:
87 9042         18274 foreach my $xx ($xpos-1 .. $xpos+1) {
88 27126         45211 foreach my $yy ($ypos-1 .. $ypos+1) {
89 81378 100 100     212660 if (($xx == $xpos) and ($yy == $ypos)) {
90 9042         11063 next;
91             };
92 72336 50       192689 if (defined ($self->{'cells'}->{$xx, $yy})) {
93 72336         154895 --$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$oldstate};
94 72336         194364 --$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
95             } else {
96             #&createCell($self, $xx, $yy);
97             };
98             };
99             };
100             };
101 19509 100 100     88224 if ((not $self->{'cells'}->{$xpos, $ypos}->{'state'}) and ($state)) {
102             # Check validity:
103 10456 50 66     87875 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         14514 ++$self->{'liveCells'};
113             # Update neighbours counts:
114 10456         26662 foreach my $xx ($xpos-1 .. $xpos+1) {
115 31368         54615 foreach my $yy ($ypos-1 .. $ypos+1) {
116 94104 100 100     254018 if (($xx == $xpos) and ($yy == $ypos)) {
117 10456         15828 next;
118             };
119 83648 100       238421 if (defined ($self->{'cells'}->{$xx, $yy})) {
120 73211         166534 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
121 73211         196885 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
122             } else {
123 10437         19054 &createCell($self, $xx, $yy);
124 10437         23207 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
125 10437         34189 ++$self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'};
126             };
127             };
128             };
129             };
130 19509         46496 $self->{'cells'}->{$xpos, $ypos}->{'state'} = $state;
131 19509         42596 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 17676 my ($self, $xpos, $ypos, @rest) = @_;
147 10693         39619 $self->{'cells'}->{$xpos, $ypos}->{'state'} = 0;
148 10693         32848 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{1} = 0;
149 10693         23181 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{2} = 0;
150 10693         22002 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{3} = 0;
151 10693         22069 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{4} = 0;
152 10693         20419 $self->{'cells'}->{$xpos, $ypos}->{'neighbours'}->{'total'} = 0;
153             # Update boundaries:
154 10693 100       25356 $self->{'maxx'} < $xpos and $self->{'maxx'} = $xpos;
155 10693 100       23285 $self->{'minx'} > $xpos and $self->{'minx'} = $xpos;
156 10693 100       21356 $self->{'maxy'} < $ypos and $self->{'maxy'} = $ypos;
157 10693 100       20705 $self->{'miny'} > $ypos and $self->{'miny'} = $ypos;
158 10693         12266 ++$self->{'usedCells'};
159 10693         16019 return;
160             };
161              
162             sub loadInit {
163             # Load an initial grid from a file.
164 11     11 1 8213 my ($self, $fn, @rest) = @_;
165 11         25 my $untitled = 'Untitled.cells';
166 11 50       38 if (not defined $fn) { return $untitled; };
  0         0  
167 11         15 my @array;
168 11 50       1839 open(my $fh,"<:crlf", $fn) or die "Failed to open $fn: $!";
169 11         331 while (<$fh>) { push @array, $_; };
  706         2147  
170 11         184 close $fh;
171 11         53 my $ftype = &format_is(\@array);
172 11         21 my $loadok;
173 11 50       35 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     78 if ($ftype->{'format'} eq 'CELLS') {
    100 33        
    100          
    50          
178 8         36 $loadok = $self->loadCells(\@array);
179 8         7123 print "DEBUG: Loaded CELLS\n";
180             } elsif ($ftype->{'format'} eq 'RLE') {
181 1         6 $loadok = $self->loadRLE(\@array);
182 1         380 print "DEBUG: Loaded RLE\n";
183             } elsif (($ftype->{'format'} eq 'Life') and ($ftype->{'version'} eq '1.05')) {
184 1         8 $loadok = $self->loadL105(\@array);
185 1         364 print "DEBUG: Loaded Life 1.05\n";
186             } elsif (($ftype->{'format'} eq 'Life') and ($ftype->{'version'} eq '1.06')) {
187 1         5 $loadok = $self->loadL106(\@array);
188 1         301 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       51 if (not $loadok) {
194 0         0 print "DEBUG: Load failed!\n";
195 0         0 return $untitled;
196             } else {
197 11         227 $self->{'currentFn'} = $fn;
198 11         259 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 8975 my ($self, $oscCheck) = @_;
261 1048         3372 my $t0 = [Time::HiRes::gettimeofday()];
262 1048         2498 $oscCheck = &setOscCheck($self, $oscCheck);
263              
264 1048         2118 my $resref = &tickMainLoop($self);
265 1048         1552 my @newCells = @{$resref->{'newCells'}};
  1048         3822  
266 1048         1260 my @dieCells = @{$resref->{'dieCells'}};
  1048         2864  
267 1048         1456 my @delCells = @{$resref->{'delCells'}};
  1048         2677  
268             #use Data::Dumper;
269 1048         2170 $self->{'delta'} = scalar(@newCells) + scalar(@dieCells);
270             # Apply changes on board:
271 1048         1962 foreach my $rec (@newCells) {
272             # TODO: Do something in case of error?
273 9040         22718 my $error = &updateCell($self, $rec->[0], $rec->[1], $rec->[2]);
274 9040 50       19950 $error = (defined $error) ? $error : 0;
275             };
276 1048         1901 foreach my $rec (@dieCells) {
277 9042         21492 my $error = &updateCell($self, $rec->[0], $rec->[1], 0);
278             };
279 1048         2099 foreach my $rec (@delCells) {
280             # Verify that these cells are still without neighbours:
281 9310 100       35399 if ($self->{'cells'}->{$rec->[0], $rec->[1]}->{'neighbours'}->{'total'} == 0) {
282 5361         18557 delete $self->{'cells'}->{$rec->[0], $rec->[1]};
283 5361         8696 --$self->{'usedCells'};
284             };
285             };
286 1048         2026 $self->{'gen'} = $self->{'gen'} + 1;
287 1048 50 33     6017 $self->{'factor2'} = ((defined $self->{'usedCells'}) and ($self->{'usedCells'} > 0)) ? $self->{'liveCells'} / $self->{'usedCells'} : 1;
288 1048 100       2052 if ($oscCheck > 1) { &oscCheck($self, $oscCheck); };
  207         714  
289 1048         4029 my $t1 = [Time::HiRes::gettimeofday];
290 1048         3845 my $t0_t1 = Time::HiRes::tv_interval( $t0, $t1 );
291 1048         11087 $self->{'lastTI'} = $t0_t1; # Time spend to calculate last epoch.
292 1048         1513 $self->{'totalTime'} += $t0_t1; # Total Time spend calculating this board.
293 1048         21822 return;
294             };
295              
296             sub tickMainLoop {
297             # TODO: Return new cell's color if board is immigration or quadlife.
298 1048     1048 0 1198 my ($self) = @_;
299 1048         1055 my @newCells;
300             my @dieCells;
301 0         0 my @delCells;
302 1048         1103 foreach my $key (keys %{ $self->{'cells'} }) {
  1048         13812  
303 88902         231207 my ($xx, $yy) = split(/$;/, $key, 2);
304 88902         181850 my $rec = [$xx, $yy];
305 88902 100 100     1046495 if (
    100 100        
    100 100        
306             ($self->{'cells'}->{$xx, $yy}->{'state'} > 0) and
307             (not $self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'} ~~ $self->{'liveRules'})
308             ) {
309             # Die.
310 9042         19013 push @dieCells, $rec;
311             } elsif (
312             ($self->{'cells'}->{$xx, $yy}->{'state'} == 0) and
313             ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'} ~~ $self->{'breedRules'})
314             ) {
315             # New.
316 9040 100       17131 if ($self->{'color'} == 0) {
317             # Standard
318 8792         15206 $rec->[2] = 1;
319 8792         18308 push @newCells, $rec;
320             } else {
321             # Colorized
322             #use Data::Dumper;
323 248         341 my @colorcounts = ();
324 248         302 my $ccnt = 0;
325 248         256 foreach my $state (keys %{ $self->{'cells'}->{$xx, $yy}->{'neighbours'} }) {
  248         925  
326 1240 100       2240 if ($state ne 'total') {
327 992 100       2864 if ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state} > 0) {
328 309         807 $colorcounts[$state] = $self->{'cells'}->{$xx, $yy}->{'neighbours'}->{$state};
329 309         504 $ccnt++;
330             };
331             };
332             };
333 248 100       823 if ($ccnt == 2) {
    100          
    50          
334             # Immigration, or first rule of Quadlife (Identical).
335             # Select maximum.
336 57         64 my $max = -1;
337 57         79 for my $i (1..4) {
338 228 100       500 if (defined $colorcounts[$i]) {
339 114 100       206 if ($colorcounts[$i] > $max) {
340 103         99 $max = $colorcounts[$i];
341 103         174 $rec->[2] = $i
342             };
343             };
344             };
345             } elsif ($ccnt == 3) {
346             # Second rule of Quadlife
347 2         5 for my $i (1..4) {
348 6 100       14 if (not defined $colorcounts[$i]) {
349 2         4 $rec->[2] = $i;
350 2         4 last;
351             };
352             };
353             } elsif ($ccnt == 1) {
354             # Same color as parents:
355 189         275 for my $i (1..4) {
356 471 100       939 if (defined $colorcounts[$i]) {
357 189         322 $rec->[2] = $i;
358 189         275 last;
359             };
360             };
361             } else {
362             # Unsupported or standard. Return first color.
363 0         0 $rec->[2] = 1;
364             };
365 248         624 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         18632 push @delCells, $rec;
373             };
374             };
375 1048         13496 return {'newCells' => \@newCells, 'dieCells' => \@dieCells, 'delCells' => \@delCells};
376             };
377              
378             sub setOscCheck {
379 1048     1048 0 1467 my ($self, $oscCheck) = @_;
380              
381 1048 50       2022 $oscCheck = (defined $oscCheck) ? $oscCheck : 0;
382 1048 100       2541 if ($oscCheck != $self->{'osccheck'}) {
383             # Change, delete all previous snapshots:
384 3         7 delete $self->{'snapshots'};
385             };
386 1048         1542 $self->{'osccheck'} = $oscCheck;
387 1048         1722 return $oscCheck;
388             };
389              
390             sub oscCheck {
391 207     207 0 358 my ($self, $oscCheck) = @_;
392 207         567 my $lgen = $self->{'gen'};
393 207         1037 my $lgenString = sprintf("s%d", $lgen);
394 207         297 my $ogen;
395             my $ogenString;
396 207         607 $self->{'snapshots'}->{$lgenString} = &snapshot($self); # Smile!
397 207         888 for (my $i = 2; $i <= $oscCheck; $i++) {
398 262         419 $ogen = $lgen - $i;
399 262         962 $ogenString = sprintf("s%d", $ogen);
400 262 100       954 if (defined ($self->{'snapshots'}->{$ogenString})) {
401             # Snapshot of grandma!
402 219 100 66     1734 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         21 $self->{'oscilator'} = $i;
411 10         24 last;
412             } else {
413 209         756 $self->{'oscilator'} = 0;
414             };
415             };
416             };
417             # Delete oldest snapshot.
418 207         1103 delete $self->{'snapshots'}->{$ogenString};
419 207         571 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 6775 my $self = shift;
426 221         447 my $snapshot = '';
427 221         1196 for (my $yy = $self->{'miny'}; $yy <= $self->{'maxy'}; $yy++) {
428 6790         12867 foreach my $xx ($self->{'minx'} .. $self->{'maxx'}) {
429 263028 100       542913 if (defined ($self->{'cells'}->{$xx, $yy})) {
430 66796 100       221512 if ($self->{'cells'}->{$xx, $yy}->{'state'} == 1) {
    100          
431 17392         23423 $snapshot .= 'O';
432             } elsif ($self->{'cells'}->{$xx, $yy}->{'state'} > 1) {
433 51         103 $snapshot .= $self->{'cells'}->{$xx, $yy}->{'state'};
434             } else {
435 49353         66560 $snapshot .= '.';
436             };
437             } else {
438 196232         224162 $snapshot .= '.';
439             };
440             };
441 6790         19578 $snapshot .= "\n";
442             };
443             return {
444 221         4592 'snapshot' => $snapshot,
445             'minx' => $self->{'minx'},
446             'maxx' => $self->{'maxx'},
447             'miny' => $self->{'miny'},
448             '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 59 my $self = shift;
455 7         34 $self->{'minx'} = $self->{'maxx'} = $self->{'miny'} = $self->{'maxy'} = 0;
456 7         16 my $ok = 0;
457 7         16 foreach my $key (keys %{ $self->{'cells'} }) {
  7         84  
458 524         1271 my ($xx, $yy) = split(/$;/, $key, 2);
459 524 100 100     2603 if (($self->{'cells'}->{$xx, $yy}->{'state'} == 0) and ($self->{'cells'}->{$xx, $yy}->{'neighbours'}->{'total'} == 0)) {
460 68         166 delete $self->{'cells'}->{$key};
461 68         100 --$self->{'usedCells'};
462             } else {
463 456 100       665 if ($ok) {
464 449 100       875 if ($xx > $self->{'maxx'}) { $self->{'maxx'} = $xx; };
  15         48  
465 449 100       787 if ($xx < $self->{'minx'}) { $self->{'minx'} = $xx; };
  14         27  
466 449 100       822 if ($yy > $self->{'maxy'}) { $self->{'maxy'} = $yy; };
  12         21  
467 449 100       983 if ($yy < $self->{'miny'}) { $self->{'miny'} = $yy; };
  14         27  
468             } else {
469 7         19 $self->{'minx'} = $xx;
470 7         16 $self->{'maxx'} = $xx;
471 7         19 $self->{'miny'} = $yy;
472 7         14 $self->{'maxy'} = $yy;
473 7         22 $ok = 1;
474             };
475             };
476             };
477 7         85 return;
478             };
479              
480             sub statistics {
481 14     14 1 62 my $self = shift;
482             return {
483 14         274 '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             'lastTI' => $self->{'lastTI'},
496             };
497             };
498              
499             sub format_is {
500 11     11 1 24 my ($faref) = @_; # Array ref containing file.
501 11         51 my %result = (
502             format => undef,
503             version => undef
504             );
505 11         23 foreach my $line (@{ $faref }) {
  11         28  
506 15         30 chomp($line);
507 15 100 100     241 if (($line =~ /^#Life 1.05/) or ($line =~ /^#Life 1.06/) or ($line =~ /^#MCell/)) {
    100 66        
    100          
    50          
508 2         10 ($result{format}, $result{version}) = split / /, $line, 2;
509 2         10 $result{format} =~ s/#//;
510 2         7 return \%result;
511             } elsif ($line =~ /^(b|o|[[:digit:]])/) {
512 1         4 ($result{format}, $result{version}) = ('RLE', 'N/A');
513 1         5 return \%result;
514             } elsif ($line =~ /^(\.|O|!)/) {
515 8         32 ($result{format}, $result{version}) = ('CELLS', 'N/A');
516 8         30 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       3 if (not defined $array) { return undef; };
  0         0  
530 1         9 foreach my $input (@{ $array }) {
  1         2  
531 325         339 chomp($input);
532 325         1406 $input =~ s/^\s*(.*?)\s*$/$1/;
533 325 50       579 if ($input eq '') { next; };
  0         0  
534 325 100       496 if ($input =~ /^#Life 1.06/) { next; };
  1         3  
535             # Not much checks here. Assume that data are numbers.
536 324         679 my ($xx, $yy) = split / /, $input, 2;
537 324         394 $xx += 0;
538 324         288 $yy += 0;
539 324         603 $self->updateCell($xx, $yy, 1);
540             };
541 1         4 $self->{'name'} = '#Life 1.06 file'; # :-)
542 1         10 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 3 my ($self, $array, @rest) = @_;
550 1 50       5 if (not defined $array) { return undef; };
  0         0  
551 1         3 my $posState = 0;
552 1         2 my $name = 'Untitled';
553 1         2 my $rules = '';
554 1         1 my $dataline = '';
555 1         2 my @descrArr = ();
556 1         3 my $ulcx = my $ulcy = 0; # Upper left corner
557 1         2 my $dlinecnt = 0;
558 1         1 foreach my $input (@{ $array }) {
  1         3  
559 203         257 chomp($input);
560 203 100       331 if ($input eq '') { next; };
  1         4  
561 202 100 33     1781 if ($input =~ /^#Life 1.05/) {
    100          
    100          
    100          
    50          
    100          
    50          
562 1         2 next;
563             } elsif ($input =~ /^#D Name: /) {
564             # Out of spec, but it seems that it's used:
565 1         5 (undef, undef, $name) = split /(^#D Name: )/, $input, 3;
566 1         2 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         21 $dline =~ s/^\s*(.*?)\s*$/$1/;
571 2 50       5 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         4 push @descrArr, $dline;
576             };
577 2         4 $dlinecnt++;
578             } elsif ($input =~ /^#N/) {
579 1         2 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         10 (undef, $ulcx, $ulcy) = split / /, $input, 3;
608 2         4 $ulcx += 0;
609 2         4 $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         234 my $xx = $ulcx;
618 195         195 my $yy = $ulcy;
619 195         342 while (length($input) > 0) {
620 4086         4889 my $char = substr($input, 0, 1);
621 4086         4876 $input = substr($input, 1);
622 4086 100       7985 if ($char eq '*') {
    50          
623             #print "\tDEBUG: LIVE ($xx,$yy)\n";
624 324         736 $self->updateCell($xx, $yy, 1);
625 324         868 $xx++;
626             } elsif ($char eq '.') {
627 3762         6882 $xx++;
628             } else {
629             # Fail.
630 0         0 print "DEBUG ERROR: unknown char\n";
631 0         0 return undef;
632             };
633             };
634 195         376 $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         4 $self->{'description'} = \@descrArr;
642 1         14 $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         3 my $posState = 0;
653 1         4 my $name = 'Untitled';
654 1         2 my $headRules = my $rules = '';
655 1         3 my $dataline = '';
656 1         2 my $ulcx = my $ulcy = 0; # Upper left corner
657 1         2 foreach my $input (@{ $array }) {
  1         2  
658 20         25 chomp($input);
659 20 100 33     128 if ($input =~ /^#N /) {
    50          
    50          
    100          
    100          
660 1         6 (undef, undef, $name) = split /(^#N )/, $input, 3;
661 1         3 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         6 next;
672             } elsif ($input =~ /^x/) {
673             # Header line.
674 1         6 $input =~ s/ //g;
675 1         2 my ($xc, $yc);
676 1         5 ($xc, $yc, $rules) = split /,/ , $input;
677 1 50 33     9 if ((not ($xc =~ /x=/)) or (not ($yc =~ /y=/))) {
678             # Fail.
679 0         0 return undef;
680             };
681 1         3 $posState = 1;
682 1         2 next;
683             } else {
684             # Normal line:
685 16 50       30 if (not $posState) {
686             # No header?
687 0         0 return undef;
688             };
689             # Join in one big string:
690 16         20 $dataline .= $input;
691 16         20 next;
692             };
693             };
694             # Now parse actual data:
695 1         35 my @dataArr = split /\$/, $dataline;
696 1         5 my %extraStates = ();
697 1         2 my $extraStatesCnt = 0;
698 1         2 my $xx = $ulcx;
699 1         2 my $yy = $ulcy;
700             #print "DEBUG: " . scalar(@dataArr) . " lines\n";
701 1         2 REC: foreach my $rec (@dataArr) {
702             #print "DEBUG: RLE REC: \n\t$rec\n";
703             #print "\tLINE: $yy\n";
704 81         84 my $cntstr = '0';
705 81         147 CHAR: while (length($rec) > 0) {
706 994         1360 my $char = substr($rec, 0, 1);
707 994         1229 $rec = substr($rec, 1);
708 994 100       2803 if ($char =~ /\d/) {
    100          
    100          
    50          
709 518         566 $cntstr .= $char;
710 518         1129 next CHAR;
711             } elsif ($char eq 'o') {
712 200 100       332 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
713 200         205 $cntstr = '0'; # Reset.
714 200         421 for (my $i = 0; $i < $cnt; $i++) {
715 324         791 $self->updateCell($xx++, $yy, 1);
716             };
717             } elsif ($char eq 'b') {
718 275 100       580 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
719 275         323 $cntstr = '0'; # Reset.
720 275         595 $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       135 my $cnt = ($cntstr eq '0') ? 1 : $cntstr + 0;
754 80         86 $cntstr = '0'; # Reset.
755 80         88 $yy += $cnt; # Next line or skip lines.
756 80         129 $xx = $ulcx; # Reset
757             };
758 1         11 $self->{'name'} = $name;
759 1 50       6 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         7 (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     16 if (($rules ne '') or ($headRules ne '')) {
776 1         4 my $barr = [];
777 1         2 my $sarr = [];
778 1         5 for (my $i = 0; $i < length($bstr); $i++) {
779 2 100       16 if (substr($bstr, $i, 1) =~ /\d/) { push @{ $barr }, substr($bstr, $i, 1); };
  1         48  
  1         7  
780             };
781 1         4 for (my $i = 0; $i < length($sstr); $i++) {
782 3 100       14 if (substr($sstr, $i, 1) =~ /\d/) { push @{ $sarr }, substr($sstr, $i, 1); };
  2         3  
  2         7  
783             };
784 1         6 $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 19 my ($self, $array, @rest) = @_;
793 8 50       24 if (not defined $array) { return undef; };
  0         0  
794 8         13 my $name = 'Untitled';
795 8         14 my @descrArr = ();
796 8         17 my $xx = my $yy = my $cnt = 0;
797 8         13 my $immigration = my $quadlife = 0;
798             #my $input;
799             #open($input, "<", $fn) or die "cannot open $input: $!\n";
800             #while (<$input>) {
801 8         10 foreach my $input (@{ $array }) {
  8         20  
802 158         244 chomp($input);
803 158 100       550 if ($input =~ /^!Name: /) {
    100          
804 1         33 (undef, undef, $name) = split /(^!Name: )/, $input, 3;
805 1         3 next;
806             } elsif ($input =~ /^!/) {
807 2         3 push @descrArr, $input;
808 2         2 next;
809             };
810 155         345 for ($yy = 0; $yy <= length($input); $yy++) {
811 5835 100       26455 if (substr($input, $yy, 1) eq 'O') {
    100          
    100          
    100          
812 417         1042 $self->updateCell($yy, $xx, 1);
813 417         1022 $cnt++;
814             } elsif (substr($input, $yy, 1) eq '2') {
815 12 50       33 $self->setColor('immigration') or die "Failed to set color on load!\n";
816 12         29 $self->updateCell($yy, $xx, 2);
817 12         15 $immigration = 1;
818 12         32 $cnt++;
819             } elsif (substr($input, $yy, 1) eq '3') {
820 9 50       21 $self->setColor('quadlife') or die "Failed to set color on load!\n";
821 9         22 $self->updateCell($yy, $xx, 3);
822 9         12 $quadlife = 1;
823 9         21 $cnt++;
824             } elsif (substr($input, $yy, 1) eq '4') {
825 6 50       25 $self->setColor('quadlife') or die "Failed to set color on load!\n";
826 6         24 $self->updateCell($yy, $xx, 4);
827 6         7 $quadlife = 1;
828 6         17 $cnt++;
829             };
830             };
831 155         207 $xx++;
832             };
833 8         34 $self->{'name'} = $name;
834 8         39 $self->{'description'} = \@descrArr;
835             # Finalize board colour:
836 8 100       41 if ($quadlife) {
    100          
837 2 50       7 $self->setColor('quadlife') or die "Failed to set color on load!\n";
838             } elsif ($immigration) {
839 1 50       3 $self->setColor('immigration') or die "Failed to set color on load!\n";
840             };
841 8         31 return 1;
842             };
843              
844             42;
845              
846             __END__