File Coverage

lib/Games/Maze/FirstPerson.pm
Criterion Covered Total %
statement 118 132 89.3
branch 27 32 84.3
condition 6 9 66.6
subroutine 31 32 96.8
pod 20 20 100.0
total 202 225 89.7


line stmt bran cond sub pod time code
1             package Games::Maze::FirstPerson;
2              
3 2     2   51209 use warnings;
  2         5  
  2         68  
4 2     2   10 use strict;
  2         3  
  2         69  
5 2     2   2122 use Games::Maze;
  2         19722  
  2         63  
6              
7 2     2   16 use constant MOVE_NORTH => -2;
  2         2  
  2         117  
8 2     2   8 use constant MOVE_SOUTH => 2;
  2         4  
  2         65  
9 2     2   7 use constant MOVE_WEST => -2;
  2         4  
  2         64  
10 2     2   8 use constant MOVE_EAST => 2;
  2         2  
  2         66  
11              
12 2     2   8 use constant NORTH_WALL => -1;
  2         3  
  2         65  
13 2     2   9 use constant SOUTH_WALL => 1;
  2         3  
  2         64  
14 2     2   16 use constant WEST_WALL => -1;
  2         4  
  2         61  
15 2     2   9 use constant EAST_WALL => 1;
  2         3  
  2         2659  
16              
17             =head1 NAME
18              
19             Games::Maze::FirstPerson - First person viewpoint of Games::Maze
20              
21             =head1 VERSION
22              
23             Version 0.03
24              
25             =cut
26              
27             our $VERSION = '0.03';
28              
29             =head1 SYNOPSIS
30              
31             use Games::Maze::FirstPerson;
32              
33             my $maze = Games::Maze::FirstPerson->new();
34             if ( $maze->south ) {
35             $maze->go_south;
36             }
37             print $maze->to_ascii if $maze->has_won;
38              
39             =head1 DESCRIPTION
40              
41             This module is merely a wrapper around C. I needed a simple maze
42             module which would represent a maze from a first-person viewpoint but nothing
43             on the CPAN did that, hence this code.
44              
45             Patches welcome.
46              
47             =head1 EXPORT
48              
49             None.
50              
51             =head1 METHODS
52              
53             =head2 new
54              
55             my $maze = Games::Maze::FirstPerson->new(@arguments);
56              
57             This constructor takes the same arguments as C. Currently we only
58             support 2D rectangular mazes.
59              
60             =cut
61              
62             sub new {
63 4     4 1 3961 my $class = shift;
64              
65 4         21 my %attr_for = @_;
66 4 100 66     35 if ( exists $attr_for{cell} && 'Quad' ne $attr_for{cell} ) {
67 1         17 die "'cell' attribute must be 'Quad'";
68             }
69 3 50       22 if ( defined( my $dimensions = $attr_for{dimensions} ) ) {
70 3 100       35 die "dimensions must be an array ref" unless 'ARRAY' eq ref $dimensions;
71 2 100 66     42 die "multi-level mazes not (yet) supported"
72             if @$dimensions > 2 && $dimensions->[2] > 1;
73             }
74              
75 1         14 my $maze = Games::Maze->new(@_);
76 1         581 $maze->make;
77              
78             # these gymnastics make maneuvering through the maze really,
79             # really easy.
80              
81 11         359 my @grid =
82             map {
83 1         1421 s/\s+$//;
84 11         42 s/ /0/g;
85 11         127 [ _tighten( split '', $_ ) ]
86             }
87             split "\n", $maze->to_ascii;
88              
89 1         6 my $east_west;
90              
91             # find the opening and close it
92 1         33 foreach my $i ( 0 .. $#{ $grid[0] } ) {
  1         40  
93 2 100       9 if ( $grid[0][$i] ) {
94 1         3 $grid[0][$i] = 0;
95 1         2 $east_west = $i;
96 1         2 last;
97             }
98             }
99              
100             bless {
101 1         27 maze => $maze,
102             grid => \@grid,
103             has_won => 0,
104             facing => 'south',
105              
106             east_west => $east_west, # X coordinates
107             north_south => 1, # Y coordinates
108              
109             cols => ( @grid - 1 ) / 2,
110 1         6 rows => ( @{ $grid[0] } - 1 ) / 2,
111             } => $class;
112             }
113              
114             sub _tighten {
115 11     11   85 my @list = @_;
116 11         13 my @new_list;
117 11         35 for ( my $i = 0 ; $i < @list ; $i += 3 ) {
118 44 100       6842 push @new_list, map { $_ ? 0 : 1 } @list[ $i, $i + 1 ];
  88         283  
119             }
120 11         16 pop @new_list; # get rid of the undef at the end
121 11         2303 @new_list;
122             }
123              
124             ##############################################################################
125              
126             =head2 to_ascii
127              
128             print $maze->to_ascii;
129              
130             This method returns an ascii representation of the maze constructed with
131             periods and spaces. It is not the same as the C representation.
132              
133             =cut
134              
135             my $WALLS = qr/[-:|]/;
136              
137             sub to_ascii {
138 0     0 1 0 my $self = shift;
139 0         0 my $maze = $self->{maze};
140 0         0 my ( @ascii, $ascii );
141 0 0       0 if (wantarray) {
142 0         0 @ascii = $maze->to_ascii;
143 0         0 return map { s/$WALLS/./g; $_ } @ascii;
  0         0  
  0         0  
144             }
145             else {
146 0         0 $ascii = $maze->to_ascii;
147 0         0 $ascii =~ s/$WALLS/./g;
148 0         0 return $ascii;
149             }
150             }
151              
152             ##############################################################################
153              
154             =head2 location
155              
156             $maze->location($x, $y);
157              
158             Set the C and C location in the maze.
159              
160             =cut
161              
162             sub location {
163 5     5 1 100 my ( $self, $x, $y ) = @_;
164 5 100 66     8 if ( grep { !defined || !/^\d+/ } ( $x, $y ) ) {
  10         65  
165 2         17 die "Arguments to location must be positive integers";
166             }
167 3 100       9 if ( $x > $self->{cols} ) {
168 1         8 die "x value out of range";
169             }
170 2 100       7 if ( $y > $self->{rows} ) {
171 1         7 die "y value out of range";
172             }
173 1         6 $_ = ( $_ * 2 ) + 1 foreach $x, $y;
174 1         3 $self->{east_west} = $x;
175 1         65 $self->{north_south} = $y;
176 1         7 return $self;
177             }
178              
179             ##############################################################################
180              
181             =head2 x
182              
183             my $x = $maze->x;
184              
185             Returns the current C location in the maze.
186              
187             =cut
188              
189 1     1 1 7 sub x { ( $_[0]{east_west} - 1 ) / 2 }
190              
191             ##############################################################################
192              
193             =head2 y
194              
195             my $y = $maze->y;
196              
197             Returns the current C location in the maze.
198              
199             =cut
200              
201 1     1 1 7 sub y { ( $_[0]{north_south} - 1 ) / 2 }
202              
203             ##############################################################################
204              
205             =head2 rows
206              
207             my $rows = $maze->rows;
208              
209             Returns the number of rows of the maze.
210              
211             =cut
212              
213 1     1 1 7 sub rows { $_[0]{rows} }
214              
215             ##############################################################################
216              
217             =head2 cols
218              
219             my $columns = $maze->cols;
220              
221             Returns the number of columns of the maze.
222              
223             =cut
224              
225 1     1 1 6 sub cols { $_[0]{cols} }
226              
227             ##############################################################################
228              
229             =head2 columns
230              
231             Same as C<< $maze->cols >>.
232              
233             =cut
234              
235 1     1 1 6 sub columns { $_[0]{cols} }
236              
237             ##############################################################################
238              
239             =head2 north
240              
241             if ( $maze->north ) { ... }
242              
243             Returns true if there is an opening to the north of the current position.
244              
245             =cut
246              
247             sub north {
248 4     4 1 9 my $self = shift;
249 4         34 return $self->{grid}[ $self->{north_south} + NORTH_WALL ]
250             [ $self->{east_west} ];
251             }
252              
253             ##############################################################################
254              
255             =head2 go_north
256              
257             $maze->go_north;
258              
259             Moves one space to the north. Returns false if you cannot go that way.
260              
261             =cut
262              
263             sub go_north {
264 1     1 1 3 my $self = shift;
265 1 50       5 return unless $self->north;
266 0         0 $self->{facing} = 'north';
267 0         0 $self->{north_south} += MOVE_NORTH;
268 0         0 return $self;
269             }
270              
271             ##############################################################################
272              
273             =head2 south
274              
275             if ( $maze->south ) { ... }
276              
277             Returns true if there is an opening to the south of the current position.
278              
279             =cut
280              
281             sub south {
282 8     8 1 12 my $self = shift;
283 8         33 return $self->{grid}[ $self->{north_south} + SOUTH_WALL ]
284             [ $self->{east_west} ];
285             }
286              
287             ##############################################################################
288              
289             =head2 go_south
290              
291             $maze->go_south;
292              
293             Moves one space to the south. Returns false if you cannot go that way.
294              
295             =cut
296              
297             sub go_south {
298 6     6 1 10 my $self = shift;
299 6 100       10 return unless $self->south;
300 5         57 $self->{facing} = 'south';
301 5         9 $self->{north_south} += MOVE_SOUTH;
302 5 100       8 $self->{has_won} = 1 if $self->{north_south} >= @{ $self->{grid} };
  5         12  
303 5         13 return $self;
304             }
305              
306             ##############################################################################
307              
308             =head2 west
309              
310             if ( $maze->west ) { ... }
311              
312             Returns true if there is an opening to the west of the current position.
313              
314             =cut
315              
316             sub west {
317 6     6 1 10 my $self = shift;
318 6         36 return $self->{grid}[ $self->{north_south} ]
319             [ $self->{east_west} + WEST_WALL ];
320             }
321              
322             ##############################################################################
323              
324             =head2 go_west
325              
326             $maze->go_west;
327              
328             Moves one space to the west. Returns false if you cannot go that way.
329              
330             =cut
331              
332             sub go_west {
333 4     4 1 6 my $self = shift;
334 4 100       9 return unless $self->west;
335 3         6 $self->{facing} = 'west';
336 3         7 $self->{east_west} += MOVE_WEST;
337 3         6 return $self;
338             }
339              
340             ##############################################################################
341              
342             =head2 east
343              
344             if ( $maze->east ) { ... }
345              
346             Returns true if there is an opening to the east of the current position.
347              
348             =cut
349              
350             sub east {
351 5     5 1 7 my $self = shift;
352 5         27 return $self->{grid}[ $self->{north_south} ]
353             [ $self->{east_west} + EAST_WALL ];
354             }
355              
356             ##############################################################################
357              
358             =head2 go_east
359              
360             $maze->go_east;
361              
362             Moves one space to the east. Returns false if you cannot go that way.
363              
364             =cut
365              
366             sub go_east {
367 3     3 1 7 my $self = shift;
368 3 50       7 return unless $self->east;
369 3         5 $self->{facing} = 'east';
370 3         5 $self->{east_west} += MOVE_EAST;
371 3         8 return $self;
372             }
373              
374             ##############################################################################
375              
376             =head2 surroundings
377              
378             print $maze->surroundings;
379              
380             Prints an ascii representation of the immediate surroundings. For example, if
381             there are exits to the north and east, it will look like this:
382              
383             . .
384             .
385             ...
386              
387             =cut
388              
389             sub surroundings {
390 3     3 1 6 my $self = shift;
391 3         5 my $surroundings = '';
392 3         7 for my $y ( -1 .. 1 ) {
393 9         17 for my $x ( -1 .. 1 ) {
394 27 100       77 $surroundings .= $self->{grid}[ $self->{north_south} + $y ]
395             [ $self->{east_west} + $x ]
396             ? ' '
397             : '.';
398             }
399 9         15 $surroundings .= "\n";
400             }
401 3         12 return $surroundings;
402             }
403              
404             ##############################################################################
405              
406             =head2 directions
407              
408             my @directions = $maze->directions;
409              
410             Returns a list of directions in which you can currently move. Directions are
411             in lower-case and in the order "north", "south", "east" and "west".
412              
413             =cut
414              
415             sub directions {
416 1     1 1 2 my $self = shift;
417 1         2 return grep { $self->$_ } qw/north south east west/;
  4         13  
418             }
419              
420             ##############################################################################
421              
422             =head2 has_won
423              
424             if ($maze->has_won) { ... }
425            
426             Returns true if you have reached the exit.
427              
428             =cut
429              
430 2     2 1 9 sub has_won { $_[0]{has_won} }
431              
432             ##############################################################################
433              
434             =head2 facing
435              
436             my $facing = $maze->facing;
437             print "You are currently facing $facing\n";
438              
439             This method returns the direction you are currently facing as determined by
440             the last direction you have moved. When a maze if first created, you are
441             facing south.
442              
443             =cut
444              
445 3     3 1 14 sub facing { $_[0]{facing} }
446              
447             =head1 EXAMPLE
448              
449             The following simple program will print out the surroundings of the location
450             the person is currently at and allow them to move through the maze until they
451             reach the end. It is also included in the C directory of
452             this distribution.
453              
454             #!/usr/bin/perl
455            
456             use strict;
457             use warnings;
458             use Term::ReadKey;
459             use Games::Maze::FirstPerson;
460            
461             my $rows = 5;
462             my $columns = 8;
463             my $maze = Games::Maze::FirstPerson->new(
464             dimensions => [$rows,$columns]
465             );
466            
467             print <<"END_CONTROLS";
468             q = quit
469            
470             w = move north
471             a = move west
472             s = move south
473             d = move east
474            
475             END_CONTROLS
476            
477             ReadMode 'cbreak';
478            
479             my %move_for = (
480             w => 'go_north',
481             a => 'go_west',
482             s => 'go_south',
483             d => 'go_east'
484             );
485            
486             while ( ! $maze->has_won ) {
487             print $maze->surroundings;
488             my $key = lc ReadKey(0);
489             if ( 'q' eq $key ) {
490             print "OK. Quitting\n";
491             exit;
492             }
493             if ( my $action = $move_for{$key} ) {
494             unless ( $maze->$action ) {
495             print "You can't go that direction\n\n";
496             }
497             else {
498             print "\n";
499             }
500             }
501             else {
502             print "I don't understand\n\n";
503             }
504             }
505            
506             print "Congratulations! You found the exit!\n";
507             print $maze->to_ascii;
508              
509             =head1 AUTHOR
510              
511             Curtis "Ovid" Poe, C<< >>
512              
513             =head1 BUGS
514              
515             Please report any bugs or feature requests to
516             C, or through the web interface at
517             L.
518             I will be notified, and then you'll automatically be notified of progress on
519             your bug as I make changes.
520              
521             =head1 ACKNOWLEDGEMENTS
522              
523             See John Gamble's L.
524              
525             =head1 COPYRIGHT & LICENSE
526              
527             Copyright 2005 Curtis "Ovid" Poe, all rights reserved.
528              
529             This program is free software; you can redistribute it and/or modify it
530             under the same terms as Perl itself.
531              
532             =cut
533              
534             1;