File Coverage

blib/lib/Games/Maze.pm
Criterion Covered Total %
statement 452 502 90.0
branch 177 226 78.3
condition 57 100 57.0
subroutine 39 42 92.8
pod 9 10 90.0
total 734 880 83.4


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