File Coverage

blib/lib/Games/Maze.pm
Criterion Covered Total %
statement 487 547 89.0
branch 182 236 77.1
condition 61 104 58.6
subroutine 45 48 93.7
pod 8 8 100.0
total 783 943 83.0


line stmt bran cond sub pod time code
1             package Games::Maze;
2 7     7   140675 use 5.008003;
  7         25  
  7         387  
3              
4 7     7   7154 use integer;
  7         70  
  7         37  
5 7     7   262 use strict;
  7         19  
  7         260  
6 7     7   38 use warnings;
  7         15  
  7         213  
7 7     7   35 use Carp;
  7         14  
  7         23098  
8              
9             our $VERSION = '1.08';
10              
11              
12             our $North = 0x0001; # 0;
13             our $NorthWest = 0x0002; # 1;
14             our $West = 0x0004; # 2;
15             our $SouthWest = 0x0008; # 3;
16             our $Ceiling = 0x0010; # 4;
17             our $South = 0x0020; # 5;
18             our $SouthEast = 0x0040; # 6;
19             our $East = 0x0080; # 7;
20             our $NorthEast = 0x0100; # 8;
21             our $Floor = 0x0200; # 9;
22             our $Path_Mark = 0x8000; # 15;
23              
24             #
25             # So, in bytes, cells are the bit sum of:
26             #
27             # 1 0 | 3 2 1 0 | 3 2 1 0
28             # ------+-------------+-----------
29             # F NE | E SE S C | SW W NW N
30             #
31             #
32             # 200 (down)
33             #
34             # 002 001 100
35             # \ | /
36             # \ | /
37             # \ | /
38             # \|/
39             # 004 --------+-------- 080
40             # /|\
41             # / | \
42             # / | \
43             # / | \
44             # 008 020 040
45             #
46             # 010 (up)
47             #
48             # Path_Mark = 8000
49             #
50             # The legal directions (in hexadecimal) for square cells.
51             #
52             #
53             # North
54             # (1)
55             # :------------: (200) Down
56             # | |
57             # | |
58             # West | . | East
59             # (4) | | (80)
60             # | |
61             # :------------:
62             # South
63             # Up (10) (20)
64             #
65             #
66             #
67             # The legal directions (in hexadecimal) for hexagon cells.
68             #
69             # North
70             # (1)
71             # ________ (200) Down
72             # / \
73             # NorthWest / \ NorthEast
74             # (2) / . \ (100)
75             # \ /
76             # SouthWest \ / SouthEast
77             # (8) \________/ (40)
78             # South
79             # Up (10) (20)
80             #
81             #
82             # The maze is represented as a matrix, sized 0..lvls+1, 0..cols+1, 0..rows+1.
83             # To avoid special "are we at the edge" checks, the outer border
84             # cells of the matrix are pre-marked, which leaves the cells in the
85             # area of 1..lvls, 1..cols, 1..rows to generate the maze.
86             #
87             # The top level upper left hand cell is the 0,0,0 corner of the maze, be
88             # it a cube or a honeycomb. This is why they are called "levels" instead
89             # of "storeys".
90             #
91              
92             my($Debug_make_ascii, $Debug_make_vx) = (0, 0);
93             my($Debug_solve_ascii, $Debug_solve_vx) = (0, 0);
94             my($Debug_internal) = 0;
95              
96              
97             #
98             # Valid options to new().
99             #
100              
101             my %valid = (
102             dimensions => 'array',
103             form => 'scalar',
104             cell => 'scalar',
105             upcolumn_even => 'scalar',
106             generate => 'scalar',
107             connect => 'scalar',
108             fn_choosedir => 'scalar',
109             entry => 'array',
110             exit => 'array',
111             start => 'array',
112             );
113              
114             #
115             # new
116             #
117             # Creates the object with its attributes. Valid attributes
118             # are listed in the %valid hash.
119             #
120             sub new
121             {
122 10     10 1 892 my $class = shift;
123 10         24 my $self = {};
124              
125             #
126             # We are copying from an existing maze object?
127             #
128 10 100       41 if (ref $class)
129             {
130 2 50       50 if ($class->isa("Games::Maze"))
131             {
132 2         16 $class->_copy($self);
133 2         47 return bless($self, ref $class);
134             }
135              
136 0         0 warn "Attempts to create a Maze object from a '",
137             ref $class, "' object fail.\n";
138 0         0 return undef;
139             }
140              
141             #
142             # Starting from scratch.
143             #
144 8         55 my(%params) = @_;
145              
146 8         53 while ( my($key, $keyval) = each %params)
147             {
148 42         80 $key = lc $key;
149 42         100 my $ref_type = $valid{$key};
150              
151 42 50       102 unless (defined $ref_type)
152             {
153 0         0 carp "Ignoring unknown parameter '$key'\n";
154 0         0 next;
155             }
156              
157 42 100       107 $self->{$key} = $keyval if ($ref_type eq 'scalar');
158 42 100       129 push(@{ $self->{$key} }, @{ $keyval }) if ($ref_type eq 'array');
  24         49  
  24         104  
159             }
160              
161              
162             #
163             # Put in defaults for any unnamed but required parameters.
164             #
165 8   100     46 $self->{dimensions} ||= [3, 3, 1];
166 8 50       36 push @{ $self->{dimensions} }, 3 if (@{ $self->{dimensions} } < 1);
  0         0  
  8         37  
167 8 50       15 push @{ $self->{dimensions} }, 3 if (@{ $self->{dimensions} } < 2);
  0         0  
  8         34  
168 8 100       16 push @{ $self->{dimensions} }, 1 if (@{ $self->{dimensions} } < 3);
  1         1  
  8         30  
169              
170 8   100     61 $self->{form} = ucfirst lc($self->{form} || 'Rectangle');
171 8   100     45 $self->{cell} = ucfirst lc($self->{cell} || 'Quad');
172              
173 8 50       74 unless ($self->{form} =~ /^(?:Rectangle|Hexagon)$/)
174             {
175 0         0 carp "Unknown form type ", $self->{form};
176 0         0 return undef;
177             }
178 8 50       47 unless ($self->{cell} =~ /^(?:Quad|Hex)$/)
179             {
180 0         0 carp "Unknown cell type ", $self->{cell};
181 0         0 return undef;
182             }
183              
184 8         40 bless($self, $class . "::" . $self->{cell});
185              
186 8         70 return $self->reset();
187             }
188              
189              
190             #
191             # describe
192             #
193             # %maze_attr = $obj->describe();
194             #
195             # Returns as a hash the attributes of the maze object.
196             #
197             # Only keys that don't begin with an underscore
198             # are allowed to be seen.
199             #
200             sub describe()
201             {
202 0     0 1 0 my $self = shift;
203              
204 0         0 return map {$_, $self->{$_}} grep(/^[a-z]/, keys %{$self});
  0         0  
  0         0  
205             }
206              
207             #
208             # internals
209             #
210             # %maze_attr = $obj->internals();
211             #
212             # Returns as a hash the hidden internal attributes of the maze object.
213             #
214             # Only keys that begin with an underscore (excepting _corn)
215             # are allowed to be seen.
216             #
217             sub internals()
218             {
219 0     0 1 0 my $self = shift;
220              
221 0         0 return map {$_, $self->{$_}} grep(/^_(?!corn)/, keys %{$self});
  0         0  
  0         0  
222             }
223              
224             #
225             # reset
226             #
227             # Resets the matrix m. You should not normally need to call this method,
228             # as the other methods will call it when needed.
229             #
230             sub reset
231             {
232 8     8 1 18 my $self = shift;
233 8         19 my($l, $c, $r);
234              
235 8         14631 $self->{_corn} = ([]);
236 8 50       49 $self->{form} = 'Rectangle' unless (exists $self->{form});
237 8         26 $self->{generate} = 'Random';
238 8         18 $self->{connect} = 'Simple';
239              
240 8 50       43 return undef unless ($self->_set_internals());
241              
242             #
243             # Now that we've got one level reset, copy it to the rest.
244             #
245 8         17 my $m = $self->{_corn};
246              
247 8         29 foreach $l (2..$self->{_lvls})
248             {
249 6         13 foreach $r (0..$self->{_rows} + 1)
250             {
251 54         97 foreach $c (0..$self->{_cols} + 1)
252             {
253 756         1652 $$m[$l][$r][$c] = $$m[1][$r][$c];
254             }
255             }
256             }
257              
258             #
259             # Top and bottom border levels. Removing the floor is good enough.
260             #
261 8         48 foreach $r (0..$self->{_rows} + 1)
262             {
263 77         138 foreach $c (0..$self->{_cols} + 1)
264             {
265 1034         2464 $$m[0][$r][$c] =
266             $$m[$self->{_lvls} + 1][$r][$c] = $Floor;
267             }
268             }
269              
270             #
271             # Now that the internals are set, do the same for
272             # the entry, exit coordinates.
273             #
274 8         40 $self->_set_entry_exit();
275              
276 8         16 $self->{_status} = 'reset';
277 8         44 return $self;
278             }
279              
280             #
281             # make
282             #
283             # $obj->make();
284             #
285             # Perform a random walk through the walls of the grid. This creates a
286             # simply-connected maze.
287             #
288             sub make
289             {
290 8     8 1 84 my $self = shift;
291 8         16 my(@queue, @dir);
292              
293 8         29 my($c, $r, $l) = $self->_get_start_point();
294 8   100     94 my $choose_dir = $self->{fn_choosedir} || \&_random_dir;
295              
296 8 50       29 $self->reset() if ($self->{_status} ne 'reset');
297              
298 8         43 for (;;)
299             {
300 2047         5019 @dir = $self->_collect_dirs($c, $r, $l);
301              
302             #
303             # There is a cell to break into.
304             #
305 2047 100       3923 if (@dir > 0)
306             {
307             #
308             # If there were multiple choices, save it
309             # for future reference.
310             #
311 1076 100       2684 push @queue, ($c, $r, $l) if (@dir > 1);
312              
313             #
314             # Choose a wall at random and break into the next cell.
315             #
316 1076         3572 ($c, $r, $l) = $self->_break_thru($choose_dir->(\@dir, [$c, $r, $l]),
317             $c, $r, $l);
318              
319 1076 50       3051 print STDERR $self->to_hex_dump() if ($Debug_make_vx);
320 1076 50       2165 print STDERR $self->to_ascii() if ($Debug_make_ascii);
321             }
322             else # No place to go, back up.
323             {
324 971 100       1817 last if (@queue == 0); # No place to back up, quit.
325 963         2339 ($c, $r, $l) = splice @queue, 0, 3;
326             }
327             }
328              
329 8         111 $self->_add_egress();
330 8         22 $self->{_status} = 'make';
331 8         32 return $self;
332             }
333              
334             #
335             # solve
336             #
337             # $obj->solve();
338             #
339             # Finds a solution to the maze by examining a path until a
340             # dead end is reached.
341             #
342             sub solve
343             {
344 6     6 1 5077 my $self = shift;
345              
346 6 50       48 $self = $self->make() if ($self->{_status} ne 'make');
347 6 50       25 return undef unless ($self);
348              
349 6         13 my $dir = $North;;
350 6         27 my($c, $r, $l, $fin_c, $fin_r, $fin_l) = $self->_get_entry_exit();
351              
352 6         101 $self->_toggle_pathmark($c, $r, $l);
353              
354 6   100     44 while ($c != $fin_c or $r != $fin_r or $l != $fin_l)
      100        
355             {
356 748         780 my($cc, $rr, $ll);
357              
358             #
359             # Look around for an open wall (bit == 1).
360             #
361 748         786 while (1)
362             {
363 2876         5622 $dir = $self->_next_direct($dir);
364 2876 100       6042 last if ($self->_wall_open($dir, $c, $r, $l));
365             }
366              
367             #
368             # Mark (or unmark) the cell we are about to enter (or leave).
369             #
370 748         1625 ($dir, $cc, $rr, $ll) = $self->_move_thru($dir, $c, $r, $l);
371              
372 748 100       2051 if ($self->_on_pathmark($cc, $rr, $ll))
373             {
374 96         180 $self->_toggle_pathmark($c, $r, $l);
375             }
376             else
377             {
378 652         1260 $self->_toggle_pathmark($cc, $rr, $ll);
379             }
380              
381 748         1155 ($c, $r, $l) = ($cc, $rr, $ll);
382              
383 748 50       1780 print $self->to_hex_dump() if ($Debug_solve_vx);
384 748 50       3681 print $self->to_ascii() if ($Debug_solve_ascii);
385             }
386              
387 6         21 $self->{_status} = 'solve';
388 6         30 return $self;
389             }
390              
391             #
392             # unsolve
393             #
394             # $obj->unsolve();
395             #
396             # Erase the path left by the solve() method.
397             #
398             sub unsolve
399             {
400 6     6 1 2862 my $self = shift;
401              
402 6 50       46 return $self if ($self->{_status} eq 'make');
403              
404 6 50       25 if ($self->{_status} eq 'solve')
405             {
406 6         16 my $m = $self->{_corn};
407 6         31 my $allwalls = $North|$NorthWest|$West|$SouthWest|$Ceiling|
408             $South|$SouthEast|$East|$NorthEast|$Floor;
409              
410 6         24 foreach my $l (1..$self->{_lvls})
411             {
412 12         32 foreach my $r (1..$self->{_rows})
413             {
414 95         141 foreach my $c (1..$self->{_cols})
415             {
416 1184         1507 $$m[$l][$r][$c] &= $allwalls;
417             }
418             }
419             }
420 6         18 $self->{_status} = 'make';
421             }
422             else
423             {
424 0         0 $self = $self->make();
425             }
426              
427 6         20 return $self;
428             }
429              
430             #
431             # to_hex_dump
432             #
433             # @xlvls = $obj->to_hex_dump();
434             # $xstr = $obj->to_hex_dump();
435             #
436             # Returns a formatted hexadecimal string all of the cell values, including
437             # the border cells, but excluding the all-border 0th and level+1 levels.
438             #
439             # If called in a list context, returns a list of strings, each one
440             # representing a level. If called in a scalar context, returns a single
441             # string, each level separated by a single newline.
442             #
443             sub to_hex_dump
444             {
445 10     10 1 4732 my $self = shift;
446 10         33 my $m = $self->{_corn};
447 10         21 my @levels;
448              
449 10         37 foreach my $l (1..$self->{_lvls})
450             {
451 16         29 my $vxstr = "";
452 16         42 foreach my $r (0..$self->{_rows} + 1)
453             {
454 143         259 foreach my $c (0..$self->{_cols} + 1)
455             {
456 1864         3445 $vxstr .= sprintf(" %04x", $$m[$l][$r][$c]);
457             }
458 143         263 $vxstr .= "\n";
459             }
460              
461 16         91 push @levels, $vxstr;
462             }
463              
464 10 50       102 return wantarray? @levels: join("\n", @levels);
465             }
466              
467             #
468             # $class->_copy($self);
469             #
470             # Duplicate the maze object.
471             #
472             sub _copy
473             {
474 2     2   3 my($other, $self) = @_;
475              
476             #
477             # Direct copy of all keys, except for '_corn', which
478             # we'll do with a deeper copy.
479             #
480 2         4 foreach my $k (grep($_ !~ /_corn/, keys %{$other}))
  2         23  
481             {
482 23         40 $self->{$k} = $other->{$k};
483             }
484              
485 2         6 $self->{_corn} = ([]);
486 2         3 my $m = $self->{_corn};
487 2         3 my $o = $other->{_corn};
488              
489 2         36 foreach my $l (0..$other->{_lvls} + 1)
490             {
491 6         11 foreach my $r (0..$other->{_rows} + 1)
492             {
493 36         56 foreach my $c (0..$other->{_cols} + 1)
494             {
495 222         418 $$m[$l][$r][$c] = $$o[$l][$r][$c];
496             }
497             }
498             }
499              
500 2         4 return $self;
501             }
502              
503             #
504             # Default mechanism to perform the random walk.
505             #
506             sub _random_dir
507             {
508 26     26   28 return ${$_[0]}[int(rand(@{$_[0]}))];
  26         92  
  26         40  
509             }
510              
511             #
512             # ($start_c, $start_r, $start_l, $fin_c, $fin_r, $fin_l) = $obj->_get_entry_exit();
513             #
514             sub _get_entry_exit
515             {
516 14     14   27 my $self = shift;
517              
518 14         42 return (@{ $self->{entry} },
  14         89  
519 14         23 @{ $self->{exit} });
520             }
521              
522             #
523             # Knock down the walls that represent the entrance and exit.
524             #
525             sub _add_egress
526             {
527 8     8   19 my $self = shift;
528 8         20 my $m = $self->{_corn};
529              
530 8         68 my @egress = $self->_get_entry_exit();
531              
532             #
533             # This is for the to_ascii() method.
534             #
535 8         32 $$m[$egress[2]][$egress[1] - 1][$egress[0]] |= $South;
536              
537 8         22 $$m[$egress[2]][$egress[1]][$egress[0]] |= $North;
538 8         24 $$m[$egress[5]][$egress[4]][$egress[3]] |= $South;
539              
540 8         19 return $self;
541             }
542              
543              
544             #
545             # $obj->_break_thru($wall, $c, $r, $l)
546             #
547             # Mark a wall as broken through. Go through that wall
548             # to the next cell. Mark the equivalent wall in that
549             # cell as broken through as well.
550             #
551             # Return the new column/row/level of the new cell.
552             #
553             sub _break_thru
554             {
555 1076     1076   7830 my $self = shift;
556 1076         1416 my($wall, $c, $r, $l) = @_;
557 1076         1488 my $m = $self->{_corn};
558              
559 1076         1717 $$m[$l][$r][$c] |= $wall;
560 1076         2134 ($wall, $c, $r, $l) = $self->_move_thru($wall, $c, $r, $l);
561 1076         1829 $$m[$l][$r][$c] |= $wall;
562              
563 1076         2332 return ($c, $r, $l);
564             }
565              
566             #
567             # if ($obj->_wall_open($dir, $c, $r, $l)) {...}
568             #
569             sub _wall_open
570             {
571 2876     2876   3840 my $self = shift;
572 2876         4807 my($dir, $c, $r, $l) = @_;
573 2876         4405 my $m = $self->{_corn};
574              
575 2876         10118 return ($$m[$l][$r][$c] & $dir) != 0;
576             }
577              
578             #
579             # $obj->_toggle_pathmark($c, $r, $l)
580             #
581             # No return value.
582             #
583             sub _toggle_pathmark
584             {
585 754     754   995 my $self = shift;
586 754         1142 my($c, $r, $l) = @_;
587 754         1091 my $m = $self->{_corn};
588              
589 754         1682 $$m[$l][$r][$c] ^= $Path_Mark;
590             }
591              
592             #
593             # if ($obj->_on_pathmark($c, $r, $l)) {...}
594             #
595             sub _on_pathmark
596             {
597 748     748   1019 my $self = shift;
598 748         1482 my($c, $r, $l) = @_;
599 748         1004 my $m = $self->{_corn};
600              
601 748         3436 return (($$m[$l][$r][$c] & $Path_Mark) == $Path_Mark);
602             }
603              
604             #
605             # Games::Maze::Quad - Create 3-D maze objects.
606             #
607             # Maze creation is done through the maze object's methods, listed below:
608             #
609             package Games::Maze::Quad;
610 7     7   7515 use parent qw(-norequire Games::Maze);
  7         2595  
  7         43  
611              
612 7     7   374 use integer;
  7         15  
  7         61  
613 7     7   152 use strict;
  7         15  
  7         190  
614 7     7   39 use warnings;
  7         12  
  7         255  
615 7     7   92 use Carp;
  7         15  
  7         16528  
616              
617             our $VERSION = '1.08';
618              
619             #
620             # to_ascii
621             #
622             # Translate the maze into a string of ascii 7-bit characters. If called in
623             # a list context, return as a list of levels. Otherwise returned as a
624             # single string, each level separated by a single newline.
625             #
626             sub to_ascii
627             {
628 3     3   20 my $self = shift;
629 3         9 my $m = $self->{_corn};
630 3         9 my @levels = ();
631 3         5 my($c, $r, $l);
632              
633 3         19 my(%horiz_walls) = (
634             (0 , ":--"),
635             ($South , ": ")
636             );
637              
638 3         81 my(%verti_walls) = (
639             (0 , "| "),
640             ($West , " "),
641             ($Path_Mark , "| *"),
642             ($West|$Path_Mark , " *"),
643             ($Floor , "|f "),
644             ($West|$Floor , " f "),
645             ($Path_Mark|$Floor , "|f*"),
646             ($West|$Path_Mark|$Floor , " f*"),
647             ($Ceiling , "|c "),
648             ($West|$Ceiling , " c "),
649             ($Path_Mark|$Ceiling , "|c*"),
650             ($West|$Path_Mark|$Ceiling , " c*"),
651             ($Floor|$Ceiling , "|b "),
652             ($West|$Floor|$Ceiling , " b "),
653             ($Path_Mark|$Floor|$Ceiling , "|b*"),
654             ($West|$Path_Mark|$Floor|$Ceiling , " b*")
655             );
656              
657 3         12 foreach $l (1..$self->{_lvls})
658             {
659 9         16 my $lvlstr = "";
660              
661             #
662             # End of all rows for this level. Print the closing South walls.
663             #
664 9         23 foreach $c (1..$self->{_cols} + 1)
665             {
666 117         190 $lvlstr .= $horiz_walls{$$m[$l][0][$c] & $South};
667             }
668              
669 9         17 $lvlstr .= "\n";
670              
671 9         19 foreach $r (1..$self->{_rows})
672             {
673 63         198 foreach $c (1..$self->{_cols} + 1)
674             {
675 819         1186 my($v) = $$m[$l][$r][$c] & ($West|$Path_Mark|$Floor|$Ceiling);
676 819         1117 $lvlstr .= $verti_walls{$v};
677             }
678              
679              
680 63         95 $lvlstr .= "\n";
681              
682 63         123 foreach $c (1..$self->{_cols} + 1)
683             {
684 819         1530 $lvlstr .= $horiz_walls{$$m[$l][$r][$c] & $South};
685             }
686              
687 63         108 $lvlstr .= "\n";
688             }
689              
690 9         376 push @levels, $lvlstr;
691             }
692              
693 3 50       173 return wantarray? @levels: join("\n", @levels);
694             }
695              
696             #
697             # _set_internals
698             #
699             # Sets the internal values of the maze, and resets the first level of the maze.
700             #
701             sub _set_internals
702             {
703 2     2   5 my $self = shift;
704 2         4 my($c, $r);
705              
706             #
707             # Check the dimensions for correctness.
708             #
709 2         4 my($cols, $rows, $lvls) = @{ $self->{dimensions} };
  2         11  
710              
711 2 50       17 if ($self->{form} eq 'Rectangle')
712             {
713 2 50 33     26 if ($cols < 2 or $rows < 2 or $lvls < 1)
      33        
714             {
715 0         0 carp "Minimum column, row, and level dimensions are 2, 2, 1";
716 0         0 return undef;
717             }
718 2         6 $self->{_rows} = $rows;
719 2         6 $self->{_cols} = $cols;
720 2         6 $self->{_lvls} = $lvls;
721             }
722             else
723             {
724 0         0 carp "Unknown form requested for ", __PACKAGE__, ".\n";
725 0         0 return undef;
726             }
727              
728             #
729             # Ensure that the starting point is set correctly.
730             #
731 2 100       8 if (defined $self->{start})
732             {
733 1         2 my @start = @{ $self->{start} };
  1         5  
734              
735 1 50 33     17 if ((not defined $start[0]) or
      33        
736             $start[0] < 1 or $start[0] > $self->{_cols})
737             {
738 0         0 $start[0] = int(rand($self->{_cols})) + 1;
739 0         0 carp "Start column $start[0] is out of range.\n";
740             }
741 1 50 33     12 if ((not defined $start[1]) or
      33        
742             $start[1] < 1 or $start[1] > $self->{_rows})
743             {
744 0         0 $start[1] = int(rand($self->{_rows})) + 1;
745 0         0 carp "Start row $start[1] is out of range.\n";
746             }
747 1 50 33     13 if ((not defined $start[2])
      33        
748             or $start[2] < 1 or $start[2] > $self->{_rows})
749             {
750 0         0 $start[2] = int(rand($self->{_lvls})) + 1;
751             }
752              
753 1         4 $self->{start} = \@start;
754             }
755              
756 2         6 my $m = $self->{_corn};
757 2         6 my $allwalls = $North | $West | $South | $East;
758              
759             #
760             # Reset the center cells to unbroken.
761             #
762 2         9 foreach $r (1..$self->{_rows})
763             {
764 10         19 foreach $c (1..$self->{_cols})
765             {
766 93         178 $$m[1][$r][$c] = 0;
767             }
768             }
769              
770             #
771             # Set the border cells.
772             #
773 2         8 foreach $r (0..$self->{_rows} + 1)
774             {
775 14         147 $$m[1][$r][$self->{_cols} + 1] = $North | $South | $East;
776 14         36 $$m[1][$r][0] = $allwalls;
777             }
778 2         8 foreach $c (0..$self->{_cols} + 1)
779             {
780 19         33 $$m[1][$self->{_rows} + 1][$c] = $allwalls;
781 19         31 $$m[1][0][$c] = $North | $West | $East;
782             }
783              
784 2         23 $$m[1][0][$self->{_cols} + 1] |= $South;
785              
786 2         14 return $self;
787             }
788              
789             #
790             # $obj->_set_entry_exit
791             #
792             # Pick the start and final points on the maze. These will become
793             # user-settable choices in the future.
794             #
795             sub _set_entry_exit
796             {
797 2     2   3 my $self = shift;
798 2         4 my $m = $self->{_corn};
799              
800 2 100       7 if (defined $self->{entry})
801             {
802 1         2 my @entry = @{ $self->{entry} };
  1         4  
803              
804 1 50 33     14 if ($entry[0] < 1 or $entry[0] > $self->{_cols})
805             {
806 0         0 $entry[0] = int(rand($self->{_cols})) + 1;
807 0         0 carp "Entry column $entry[0] is out of range.\n";
808             }
809              
810 1         3 $entry[1] = 1;
811 1         1 $entry[2] = 1;
812              
813 1         3 $self->{entry} = \@entry;
814             }
815             else
816             {
817 1         64 $self->{entry} = [int(rand($self->{_cols})) + 1, 1, 1];
818             }
819              
820 2 100       7 if (defined $self->{exit})
821             {
822 1         2 my @exit = @{ $self->{exit} };
  1         4  
823              
824 1 50 33     8 if ($exit[0] < 1 or $exit[0] > $self->{_cols})
825             {
826 0         0 $exit[0] = int(rand($self->{_cols})) + 1;
827 0         0 carp "Exit column $exit[0] is out of range.\n";
828             }
829            
830 1         2 $exit[1] = $self->{_rows};
831 1         3 $exit[2] = $self->{_lvls};
832 1         3 $self->{exit} = \@exit;
833             }
834             else
835             {
836 1         5 $self->{exit} = [int(rand($self->{_cols})) + 1,
837             $self->{_rows},
838             $self->{_lvls}];
839             }
840              
841 2         4 return $self;
842             }
843              
844             #
845             # $obj->_get_start_point
846             #
847             # Return the (or pick a) starting point in the maze.
848             #
849             sub _get_start_point
850             {
851 2     2   3 my $self = shift;
852              
853 2 100       9 return @{ $self->{start} } if (defined $self->{start});
  1         5  
854              
855             return (
856 1         5 int(rand($self->{_cols})) + 1,
857             int(rand($self->{_rows})) + 1,
858             int(rand($self->{_lvls})) + 1
859             );
860             }
861              
862             #
863             # ($dir, $c, $r, $l) = $obj->_move_thru($dir, $c, $r, $l)
864             #
865             # Move from the current cell to the next by going in the direction
866             # of $dir. The function will return your new coordinates, and the
867             # number of the wall you just came through, from the point of view
868             # of your new position.
869             #
870             sub _move_thru
871             {
872 362     362   404 my $self = shift;
873 362         459 my($dir, $c, $r, $l) = @_;
874              
875 362 50       759 print STDERR "_move_thru: [$c, $r, $l] to $dir\n" if ($Debug_internal);
876              
877 362 100 100     1540 if ($dir == $North or $dir == $South)
    100 100        
878             {
879 299 100       525 $r += ($dir == $North)? -1: 1;
880             }
881             elsif ($dir == $East or $dir == $West)
882             {
883 58 100       102 $c += ($dir == $West)? -1: 1;
884             }
885             else
886             {
887 5 100       13 $l += ($dir == $Ceiling)? -1: 1;
888             }
889              
890 362 100       712 $dir = ($dir <= $Ceiling)? ($dir << 5): ($dir >> 5);
891              
892 362 50       612 print STDERR "_move_thru: [$c, $r, $l] from $dir\n" if ($Debug_internal);
893 362         932 return ($dir, $c, $r, $l);
894             }
895              
896             #
897             # @directions = $obj->_collect_dirs($c, $r, $l);
898             #
899             # Find all of our possible directions to wander when creating the maze.
900             # You are only allowed to go into not-yet-broken cells. The directions
901             # are deliberately accumulated in a counter-clockwise fashion.
902             #
903             sub _collect_dirs
904             {
905 482     482   628 my $self = shift;
906 482         631 my $m = $self->{_corn};
907 482         507 my @dir;
908 482         600 my($c, $r, $l) = @_;
909              
910             #
911             # Search for enclosed cells.
912             #
913 482 100       1236 push(@dir, $North) if ($$m[$l][$r - 1][$c] == 0);
914 482 100       1083 push(@dir, $West) if ($$m[$l][$r][$c - 1] == 0);
915 482 100       1109 push(@dir, $South) if ($$m[$l][$r + 1][$c] == 0);
916 482 100       1104 push(@dir, $East) if ($$m[$l][$r][$c + 1] == 0);
917 482 100       1035 push(@dir, $Ceiling) if ($$m[$l - 1][$r][$c] == 0);
918 482 100       952 push(@dir, $Floor) if ($$m[$l + 1][$r][$c] == 0);
919              
920 482 50       793 print STDERR "_collect_dirs($c, $r, $l) returns (", join(", ", @dir), ")\n" if ($Debug_internal);
921 482         1289 return @dir;
922             }
923              
924             #
925             # $dir = $obj->_next_direct($dir)
926             #
927             # Returns the next direction to move to when checking walls.
928             #
929             sub _next_direct
930             {
931 303     303   333 my $self = shift;
932 303         440 my($dir) = @_;
933              
934 303 50       502 print STDERR "_next_direct: start with ", $dir, "\n" if ($Debug_internal);
935 303 100       618 if ($dir == $Floor)
    100          
936             {
937 41         50 $dir = $North;
938             }
939             elsif ($dir == $Ceiling)
940             {
941 60         83 $dir = $South;
942             }
943             else
944             {
945 202         226 $dir <<= 2;
946             }
947 303 50       495 print STDERR "_next_direct: return ", $dir, "\n" if ($Debug_internal);
948 303         490 return $dir;
949             }
950              
951             #
952             # NAME
953             #
954             # Games::Maze::Hex - Create 3-D hexagon maze objects.
955             #
956             # Maze creation is done through the maze object's methods, listed below:
957             #
958             package Games::Maze::Hex;
959 7     7   55 use parent qw(-norequire Games::Maze);
  7         16  
  7         46  
960              
961 7     7   327 use integer;
  7         27  
  7         53  
962 7     7   183 use strict;
  7         14  
  7         211  
963 7     7   52 use warnings;
  7         18  
  7         214  
964 7     7   34 use Carp;
  7         10  
  7         28827  
965              
966             our $VERSION = '1.08';
967              
968             #
969             # to_ascii
970             #
971             # Translate the maze into a string of ascii 7-bit characters. If called in
972             # a list context, return as a list of levels. Otherwise returned as a
973             # single string, each level separated by a single newline.
974             #
975             sub to_ascii
976             {
977 15     15   88 my $self = shift;
978 15         35 my $m = $self->{_corn};
979 15         21 my($c, $r, $l, @levels);
980              
981 15         390 my(%upper_west) = (
982             (0 , '/ '),
983             ($NorthWest , ' '),
984             ($Floor , '/f '),
985             ($NorthWest | $Floor , ' f '),
986             ($Ceiling , '/c '),
987             ($NorthWest | $Ceiling , ' c '),
988             ($Floor | $Ceiling , '/b '),
989             ($NorthWest | $Floor | $Ceiling , ' b '),
990             ($Path_Mark , '/ *'),
991             ($NorthWest | $Path_Mark , ' *'),
992             ($Floor | $Path_Mark , '/f*'),
993             ($NorthWest | $Floor | $Path_Mark , ' f*'),
994             ($Ceiling | $Path_Mark , '/c*'),
995             ($NorthWest | $Ceiling | $Path_Mark , ' c*'),
996             ($Floor | $Ceiling | $Path_Mark , '/b*'),
997             ($NorthWest | $Floor | $Ceiling | $Path_Mark, ' b*'),
998             );
999 15         97 my(%lower_west) = (
1000             (0 , '\__'),
1001             ($South , '\ '),
1002             ($SouthWest , ' __'),
1003             ($SouthWest | $South, ' '),
1004             );
1005              
1006 15         42 my $rlim = $self->{_rows} + 1;
1007              
1008 15         50 foreach $l (1..$self->{_lvls})
1009             {
1010             #
1011             # Print the top line of the border (the underscores on the
1012             # 'up' columns).
1013             #
1014 27         46 my $lvlstr = "";
1015              
1016 27         55 foreach $c (1..$self->{_cols})
1017             {
1018 333 100       574 if ($self->_up_column($c))
1019             {
1020 165         454 $lvlstr .= $lower_west{$$m[$l][0][$c] & ($SouthWest|$South)};
1021             }
1022             else
1023             {
1024 168         309 $lvlstr .= $lower_west{($SouthWest|$South)};
1025             }
1026             }
1027              
1028 27         42 $lvlstr .= "\n";
1029              
1030             #
1031             # Now print the rows.
1032             #
1033 27         54 foreach $r (1..$rlim)
1034             {
1035             # my($clim1, $clim2) = $self->_first_last_col($r);
1036 249         371 my($clim2) = $self->{_cols};
1037              
1038             #
1039             # It takes two lines to print out the hexagon, or parts of the
1040             # hexagon. First, the top half.
1041             #
1042 249         422 foreach $c (1..$clim2 + 1)
1043             {
1044 3378 100       5686 if ($self->_up_column($c))
1045             {
1046 1689         4008 $lvlstr .= $upper_west{$$m[$l][$r][$c] & ($NorthWest|$Floor|$Ceiling|$Path_Mark)};
1047             }
1048             else
1049             {
1050 1689         3611 $lvlstr .= $lower_west{$$m[$l][$r - 1][$c] & ($SouthWest|$South)};
1051             }
1052             }
1053              
1054 249         339 $lvlstr .= "\n";
1055              
1056             #
1057             # Now, the lower half.
1058             #
1059 249         376 foreach $c (1..$clim2 + 1)
1060             {
1061 3378 100       5616 if ($self->_up_column($c))
1062             {
1063 1689         3673 $lvlstr .= $lower_west{$$m[$l][$r][$c] & ($SouthWest|$South)};
1064             }
1065             else
1066             {
1067 1689         4437 $lvlstr .= $upper_west{$$m[$l][$r][$c] & ($NorthWest|$Floor|$Ceiling|$Path_Mark)};
1068             }
1069             }
1070              
1071 249         436 $lvlstr .= "\n";
1072             }
1073              
1074 27         139 push @levels, $lvlstr;
1075             }
1076              
1077 15 50       256 return wantarray? @levels: join("\n", @levels);
1078             }
1079              
1080             #
1081             # _set_internals
1082             #
1083             # Sets the internal values of the maze, and resets the first level of the maze.
1084             #
1085             sub _set_internals
1086             {
1087 6     6   14 my $self = shift;
1088 6         10 my($c, $r);
1089              
1090             #
1091             # Check the dimensions for correctness.
1092             #
1093 6         9 my($cols, $rows, $lvls) = @{ $self->{dimensions} };
  6         22  
1094              
1095 6 100       36 if ($self->{form} eq 'Rectangle')
    50          
1096             {
1097 2 50 33     22 if ($cols < 2 or $rows < 2 or $lvls < 1)
      33        
1098             {
1099 0         0 carp "Minimum column, row, and level dimensions are 2, 2, 1";
1100 0         0 return undef;
1101             }
1102              
1103 2 50       9 $self->{upcolumn_even} = 0 unless (defined $self->{upcolumn_even});
1104 2         4 $self->{_rows} = $rows;
1105 2         6 $self->{_cols} = $cols;
1106 2         4 $self->{_lvls} = $lvls;
1107             }
1108             elsif ($self->{form} eq 'Hexagon')
1109             {
1110 4 50 33     58 if ($cols < 2 or $rows < 1 or $lvls < 1)
      33        
1111             {
1112 0         0 carp "Minimum column, row, and level dimensions are 1, 2, 1";
1113 0         0 return undef;
1114             }
1115              
1116 4         19 $self->{upcolumn_even} = 1 - ($cols & 1);
1117 4         13 $self->{_rows} = $rows + $cols - 1;
1118 4         9 $self->{_cols} = $cols * 2 - 1;
1119 4         9 $self->{_lvls} = $lvls;
1120             }
1121             else
1122             {
1123 0         0 carp "Unknown form requested for ", __PACKAGE__, ".\n";
1124 0         0 return undef;
1125             }
1126              
1127             #
1128             # Ensure that the starting point is set correctly.
1129             #
1130 6 100       33 if (defined $self->{start})
1131             {
1132 5         8 my @start = @{ $self->{start} };
  5         20  
1133              
1134 5 50 33     61 if ((not defined $start[0]) or
      33        
1135             $start[0] < 1 or $start[0] > $self->{_cols})
1136             {
1137 0         0 $start[0] = int(rand($self->{_cols})) + 1;
1138 0         0 carp "Start column $start[0] is out of range.\n";
1139             }
1140 5 50 33     59 if ((not defined $start[1]) or
      33        
1141             $start[1] < 1 or $start[1] > $self->{_rows})
1142             {
1143 0         0 my($row_start, $row_end) = $self->_first_last_row($start[0]);
1144 0         0 $start[1] = int(rand($row_end - $row_start + 1)) + $row_start;
1145 0         0 carp "Start row $start[1] is out of range.\n";
1146             }
1147              
1148 5 50 66     45 if ((not defined $start[2])
      66        
1149             or $start[2] < 1 or $start[2] > $self->{_rows})
1150             {
1151 1         42 $start[2] = int(rand($self->{_lvls})) + 1;
1152             }
1153              
1154 5         13 $self->{start} = \@start;
1155             }
1156              
1157 6         17 my $m = $self->{_corn};
1158              
1159             #
1160             # Reset the center cells to unbroken.
1161             #
1162 6         23 foreach $r (1..$self->{_rows})
1163             {
1164 51         99 foreach $c (1..$self->{_cols})
1165             {
1166 621         931 $$m[1][$r][$c] = 0;
1167             }
1168             }
1169              
1170             #
1171             # Set the border cells.
1172             #
1173 6 100       35 if ($self->{form} eq 'Rectangle')
    50          
1174             {
1175             #
1176             # North and South boundry.
1177             #
1178 2         8 foreach $c (1..$self->{_cols})
1179             {
1180 24         54 $$m[1][0][$c] = $NorthWest;
1181 24         51 $$m[1][$self->{_rows} + 1][$c] = $SouthWest;
1182              
1183 24 100       51 if ($self->_up_column($c))
1184             {
1185 12         20 $$m[1][0][$c] |= $SouthWest;
1186 12         28 $$m[1][$self->{_rows} + 1][$c] |= $South;
1187             }
1188             else
1189             {
1190 12         133 $$m[1][$self->{_rows} + 1][$c] |= $NorthWest;
1191             }
1192             }
1193              
1194             #
1195             # East and West boundry.
1196             #
1197 2         7 foreach $r (0..$self->{_rows} + 1)
1198             {
1199 18         32 $$m[1][$r][0] = $South | $SouthWest;
1200 18         35 $$m[1][$r][$self->{_cols} + 1] = $South;
1201             }
1202              
1203             #
1204             # We use some of the boundry cells to print the top and bottom walls.
1205             # Make sure that some of those walls don't print.
1206             #
1207 2 100       8 if ($self->_up_column(1))
1208             {
1209 1         4 $$m[1][$self->{_rows} + 1][1] |= $NorthWest;
1210             }
1211             else
1212             {
1213 1         4 $$m[1][0][1] |= $SouthWest;
1214             }
1215              
1216             #
1217             # Eliminate some corner-border walls.
1218             #
1219 2 100       12 if ($self->_up_column($self->{_cols} + 1))
1220             {
1221 1         3 $$m[1][1][$self->{_cols} + 1] |= $NorthWest;
1222 1         4 $$m[1][$self->{_rows} + 1][$self->{_cols} + 1] |= $SouthWest;
1223             }
1224             else
1225             {
1226 1         5 $$m[1][$self->{_rows}][$self->{_cols} + 1] |= $SouthWest;
1227 1         3 $$m[1][$self->{_rows} + 1][$self->{_cols} + 1] |= $NorthWest;
1228             }
1229             }
1230             elsif ($self->{form} eq 'Hexagon')
1231             {
1232 4         14 my $allwalls = $North|$NorthWest|$SouthWest|$South|$SouthEast|$NorthEast;
1233              
1234             #
1235             # Set up the East-West boundries.
1236             #
1237 4         11 foreach $r (0..$self->{_rows} + 1)
1238             {
1239 45         94 $$m[1][$r][0] = $$m[1][$r][$self->{_cols} + 1] = $allwalls;
1240             }
1241              
1242 4 100       22 if ($self->_up_column($self->{_cols} + 1))
1243             {
1244 2         9 my($rlim1, $rlim2) = $self->_first_last_row($self->{_cols});
1245 2         7 for ($r = $rlim1; $r <= $rlim2; $r++)
1246             {
1247 12         17 $$m[1][$r + 1][1 + $self->{_cols}] ^= $NorthWest;
1248 12         28 $$m[1][$r][1 + $self->{_cols}] ^= $SouthWest;
1249             }
1250             }
1251             else
1252             {
1253 2         9 my($rlim1, $rlim2) = $self->_first_last_row($self->{_cols});
1254 2         10 for ($r = $rlim1; $r <= $rlim2; $r++)
1255             {
1256 5         11 $$m[1][$r][1 + $self->{_cols}] ^= $NorthWest;
1257 5         14 $$m[1][$r - 1][1 + $self->{_cols}] ^= $SouthWest;
1258             }
1259             }
1260              
1261             #
1262             # Extend the North and South boundries inward to create
1263             # the hexagonal form.
1264             #
1265             # In the Hexagon form, the columns dimension is the
1266             # midpoint of '_cols'.
1267             #
1268 4         16 for ($c = 1; $c <= $cols; $c++)
1269             {
1270 24         54 my($rlim1, $rlim2) = $self->_first_last_row($c);
1271              
1272 24         60 for ($r = 0; $r < $rlim1; $r++)
1273             {
1274 52         121 $$m[1][$r][$c] = $allwalls;
1275             }
1276              
1277 24         61 for ($r = $self->{_rows} + 1; $r > $rlim2; $r--)
1278             {
1279 63         146 $$m[1][$r][$c] = $allwalls;
1280             }
1281              
1282 24         68 $$m[1][$rlim1 - 1][$c] ^= $South;
1283             }
1284              
1285 4         19 for ($c = 1 + $cols; $c <= $self->{_cols}; $c++)
1286             {
1287 20         37 my($rlim1, $rlim2) = $self->_first_last_row($c);
1288              
1289 20         55 for ($r = 0; $r < $rlim1; $r++)
1290             {
1291 48         119 $$m[1][$r][$c] = $allwalls;
1292             }
1293              
1294 20         54 for ($r = $self->{_rows} + 1; $r > $rlim2; $r--)
1295             {
1296 59         125 $$m[1][$r][$c] = $allwalls;
1297             }
1298              
1299 20         37 $$m[1][$rlim1 - 1][$c] ^= $SouthWest|$South;
1300 20         68 $$m[1][$rlim2 + 1][$c] ^= $NorthWest;
1301             }
1302             }
1303              
1304 6         26 return $self;
1305             }
1306              
1307             #
1308             # $obj->_set_entry_exit
1309             #
1310             # Pick the start and final points on the maze. This will become a
1311             # user-settable choice in the future.
1312             #
1313             sub _set_entry_exit
1314             {
1315 6     6   15 my $self = shift;
1316 6         13 my $m = $self->{_corn};
1317              
1318 6 100       21 if (defined $self->{entry})
1319             {
1320 5         9 my @entry = @{ $self->{entry} };
  5         17  
1321              
1322 5 50 33     57 if ($entry[0] < 1 or $entry[0] > $self->{_cols})
1323             {
1324 0         0 $entry[0] = int(rand($self->{_cols})) + 1;
1325 0         0 carp "Entry column $entry[0] is out of range.\n";
1326             }
1327              
1328 5         27 ($entry[1], undef) = $self->_first_last_row($entry[0]);
1329 5         17 $entry[2] = 1;
1330              
1331 5         15 $self->{entry} = \@entry;
1332             }
1333             else
1334             {
1335 1         4 my @entry = (int(rand($self->{_cols})) + 1);
1336              
1337 1         3 ($entry[1], undef) = $self->_first_last_row($entry[0]);
1338 1         2 $entry[2] = 1;
1339              
1340 1         2 $self->{entry} = \@entry;
1341             }
1342              
1343 6 100       34 if (defined $self->{exit})
1344             {
1345 5         8 my @exit = @{ $self->{exit} };
  5         17  
1346              
1347 5 50 33     41 if ($exit[0] < 1 or $exit[0] > $self->{_cols})
1348             {
1349 0         0 $exit[0] = int(rand($self->{_cols})) + 1;
1350 0         0 carp "Exit column $exit[0] is out of range.\n";
1351             }
1352              
1353 5         18 (undef, $exit[1]) = $self->_first_last_row($exit[0]);
1354 5         15 $exit[2] = $self->{_lvls};
1355              
1356 5         12 $self->{exit} = \@exit;
1357             }
1358             else
1359             {
1360 1         3 my @exit = (int(rand($self->{_cols})) + 1);
1361              
1362 1         2 (undef, $exit[1]) = $self->_first_last_row($exit[0]);
1363 1         2 $exit[2] = $self->{_lvls};
1364              
1365 1         2 $self->{exit} = \@exit;
1366             }
1367              
1368 6         15 return $self;
1369             }
1370              
1371             #
1372             # $obj->_get_start_point
1373             #
1374             # Return the (or pick a) starting point in the maze.
1375             #
1376             sub _get_start_point
1377             {
1378 6     6   11 my $self = shift;
1379              
1380 6 100       31 return @{ $self->{start} } if (defined $self->{start});
  5         23  
1381              
1382 1         3 my $c = int(rand($self->{_cols})) + 1;
1383 1         2 my($row_start, $row_end) = $self->_first_last_row($c);
1384              
1385             return (
1386 1         4 $c,
1387             int(rand($row_end - $row_start + 1)) + $row_start,
1388             int(rand($self->{_lvls})) + 1
1389             );
1390             }
1391              
1392             #
1393             # ($dir, $c, $r, $l) = $obj->_move_thru($dir, $c, $r, $l)
1394             #
1395             # Move from the current cell to the next by going in the direction
1396             # of $dir. The function will return your new coordinates, and the
1397             # number of the wall you just came through, from the point of view
1398             # of your new position.
1399             #
1400             sub _move_thru
1401             {
1402 1462     1462   1717 my $self = shift;
1403 1462         1816 my($dir, $c, $r, $l) = @_;
1404              
1405 1462 50       2824 print STDERR "_move_thru: [$c, $r, $l] to $dir\n" if ($Debug_internal);
1406 1462 100 100     6923 if ($dir == $North or $dir == $South)
    100 100        
1407             {
1408 820 100       1534 $r += ($dir == $North)? -1: 1;
1409             }
1410             elsif ($dir == $Ceiling or $dir == $Floor)
1411             {
1412 29 100       61 $l += ($dir == $Ceiling)? -1: 1;
1413             }
1414             else
1415             {
1416 613 100       1285 if ($self->_up_column($c))
1417             {
1418 305 100 100     1232 $r -= 1 if ($dir == $NorthWest or $dir == $NorthEast);
1419             }
1420             else
1421             {
1422 308 100 100     1322 $r += 1 if ($dir == $SouthWest or $dir == $SouthEast);
1423             }
1424              
1425 613 100 100     3458 if ($dir == $NorthWest or $dir == $SouthWest)
    50 66        
1426             {
1427 256         324 $c -= 1;
1428             }
1429             elsif ($dir == $NorthEast or $dir == $SouthEast)
1430             {
1431 357         531 $c += 1;
1432             }
1433             }
1434              
1435 1462 100       2660 $dir = ($dir <= $Ceiling)? ($dir << 5): ($dir >> 5);
1436              
1437 1462 50       2647 print STDERR "_move_thru: [$c, $r, $l] from $dir\n" if ($Debug_internal);
1438 1462         4026 return ($dir, $c, $r, $l);
1439             }
1440              
1441             #
1442             # @directions = $obj->_collect_dirs($c, $r, $l);
1443             #
1444             # Find all of our possible directions to wander when creating the maze.
1445             # You are only allowed to go into not-yet-broken cells. The directions
1446             # are deliberately accumulated in a counter-clockwise fashion.
1447             #
1448             sub _collect_dirs
1449             {
1450 1565     1565   4458 my $self = shift;
1451 1565         2151 my($c, $r, $l) = @_;
1452 1565         1983 my $m = $self->{_corn};
1453 1565         1662 my @dir;
1454              
1455             #
1456             # Search for enclosed cells.
1457             #
1458 1565 100       3771 push(@dir, $North) if ($$m[$l][$r - 1][$c] == 0);
1459              
1460 1565 100       9246 if ($self->_up_column($c))
1461             {
1462 774 100       1814 push(@dir, $NorthWest) if ($$m[$l][$r - 1][$c - 1] == 0);
1463 774 100       1591 push(@dir, $SouthWest) if ($$m[$l][$r][$c - 1] == 0);
1464              
1465 774 100       1715 push(@dir, $South) if ($$m[$l][$r + 1][$c] == 0);
1466              
1467 774 100       1616 push(@dir, $SouthEast) if ($$m[$l][$r][$c + 1] == 0);
1468 774 100       1708 push(@dir, $NorthEast) if ($$m[$l][$r - 1][$c + 1] == 0);
1469             }
1470             else
1471             {
1472 791 100       1705 push(@dir, $NorthWest) if ($$m[$l][$r][$c - 1] == 0);
1473 791 100       1760 push(@dir, $SouthWest) if ($$m[$l][$r + 1][$c - 1] == 0);
1474              
1475 791 100       1661 push(@dir, $South) if ($$m[$l][$r + 1][$c] == 0);
1476              
1477 791 100       1801 push(@dir, $SouthEast) if ($$m[$l][$r + 1][$c + 1] == 0);
1478 791 100       2030 push(@dir, $NorthEast) if ($$m[$l][$r][$c + 1] == 0);
1479             }
1480              
1481 1565 100       3808 push(@dir, $Ceiling) if ($$m[$l - 1][$r][$c] == 0);
1482 1565 100       3928 push(@dir, $Floor) if ($$m[$l + 1][$r][$c] == 0);
1483              
1484 1565 50       2530 print STDERR "_collect_dirs($c, $r, $l) returns (", join(", ", @dir), ")\n" if ($Debug_internal);
1485 1565         3897 return @dir;
1486             }
1487              
1488             #
1489             # $dir = $obj->_next_direct($dir)
1490             #
1491             # Returns the next direction to move to when checking walls.
1492             #
1493             sub _next_direct
1494             {
1495 2573     2573   2834 my $self = shift;
1496 2573         2960 my($dir) = @_;
1497              
1498 2573 50       5790 print STDERR "_next_direct: start with ", $dir, "\n" if ($Debug_internal);
1499 2573 100       6143 if ($dir == $Floor)
    100          
    100          
1500             {
1501 319         2904 $dir = $North;
1502             }
1503             elsif ($dir == $NorthWest)
1504             {
1505 367         581 $dir = $SouthWest;
1506             }
1507             elsif ($dir == $SouthEast)
1508             {
1509 276         331 $dir = $NorthEast;
1510             }
1511             else
1512             {
1513 1611         2011 $dir <<= 1;
1514             }
1515 2573 50       4999 print STDERR "_next_direct: return ", $dir, "\n" if ($Debug_internal);
1516 2573         4286 return $dir;
1517             }
1518              
1519             #
1520             # if ($obj->_up_column($c)) {...}
1521             #
1522             # Which columns are high due to hexagonal drift?
1523             #
1524             sub _up_column
1525             {
1526 9299     9299   10592 my $self = shift;
1527 9299         10435 my($c) = @_;
1528 9299         20530 return 1 & ($c ^ $self->{upcolumn_even});
1529             }
1530              
1531             #
1532             # ($first_col, $last_col) = $obj->_first_last_col($r)
1533             #
1534             # Given a row, what columns have the first and last non-border cells
1535             # in the hexagon-formed grid?
1536             #
1537             sub _first_last_col
1538             {
1539 0     0   0 my $self = shift;
1540 0         0 my($r) = @_;
1541              
1542 0 0       0 if ($self->{form} eq 'Hexagon')
1543             {
1544 0         0 my $mid_c = ($self->{_cols} + 1)/2;
1545 0         0 my $ante_r = $self->{_cols}/4;
1546 0         0 my $post_r = $self->{_rows} - ($self->{_cols} + 1)/4;
1547              
1548 0 0       0 if ($r <= $ante_r)
    0          
1549             {
1550 0         0 my $offset = (2 * $r - 1);
1551 0         0 return ($mid_c - $offset,
1552             $mid_c + $offset);
1553             }
1554             elsif ($r > $post_r)
1555             {
1556 0         0 my $offset = (2 * ($self->{_rows} - $r));
1557 0         0 return ($mid_c - $offset,
1558             $mid_c + $offset);
1559             }
1560             else
1561             {
1562 0         0 return (1,
1563             $self->{_cols});
1564             }
1565             }
1566             else
1567             {
1568 0         0 return (1,
1569             $self->{_cols});
1570             }
1571             }
1572              
1573             #
1574             # ($first_row, $last_row) = $obj->_first_last_row($c)
1575             #
1576             # Given a column, what rows have the first and last non-border cells
1577             # in the hexagon-formed grid?
1578             #
1579             sub _first_last_row
1580             {
1581 61     61   77 my $self = shift;
1582 61         79 my $c = $_[0];
1583              
1584 61 100       139 if ($self->{form} eq 'Hexagon')
1585             {
1586             #
1587             # Find how far off $c is from the midpoint (in the
1588             # Hexagon form, the columns dimension is the midpoint of
1589             # '_cols').
1590             #
1591 57         56 my $offset_c = abs(${ $self->{dimensions} }[0] - $c);
  57         104  
1592              
1593 57         155 return ($offset_c/2 + 1,
1594             $self->{_rows} - ($offset_c + 1)/2);
1595             }
1596             else
1597             {
1598 4         14 return (1,
1599             $self->{_rows});
1600             }
1601             }
1602             1;
1603             __END__