File Coverage

blib/lib/Games/Wumpus/Cave.pm
Criterion Covered Total %
statement 88 148 59.4
branch 8 34 23.5
condition n/a
subroutine 21 27 77.7
pod 15 15 100.0
total 132 224 58.9


line stmt bran cond sub pod time code
1             package Games::Wumpus::Cave;
2              
3 4     4   55619 use 5.010;
  4         15  
  4         171  
4              
5 4     4   23 use strict;
  4         9  
  4         140  
6 4     4   26 use warnings;
  4         8  
  4         167  
7 4     4   20 no warnings 'syntax';
  4         7  
  4         320  
8              
9             our $VERSION = '2009112401';
10              
11             #
12             # Cave for the wumpus game.
13             #
14             # Cave will contain rooms, and connections to various rooms.
15             # Rooms may contain hazards: wumpus, bats, pits.
16             #
17              
18             #
19             # Default layout is the one of a dodecahedron: vertices are rooms,
20             # edges are tunnels.
21             #
22              
23 4     4   1080 use Games::Wumpus::Constants;
  4         10  
  4         727  
24 4     4   1860 use Games::Wumpus::Room;
  4         11  
  4         128  
25 4     4   116 use Hash::Util::FieldHash qw [fieldhash];
  4         23  
  4         207  
26 4     4   21 use List::Util qw [shuffle];
  4         7  
  4         7915  
27              
28             fieldhash my %rooms; # List of rooms.
29             fieldhash my %wumpus; # Location of the wumpus.
30             fieldhash my %start; # Start location.
31             fieldhash my %location; # Location of the player.
32              
33             #
34             # Accessors
35             #
36 2     2 1 822 sub rooms {@{$rooms {$_ [0]}}}
  2         12  
37 20     20 1 13190 sub room { $rooms {$_ [0]} [$_ [1] - 1]}
38 0     0 1 0 sub random_room { $rooms {$_ [0]} [rand @{$rooms {$_ [0]}}]}
  0         0  
39              
40 6     6 1 34 sub location { $location {$_ [0]}}
41 1     1 1 5 sub set_location { $location {$_ [0]} = $_ [1]; $_ [0]}
  1         3  
42              
43 0     0 1 0 sub wumpus { $wumpus {$_ [0]}}
44 2     2 1 9 sub set_wumpus { $wumpus {$_ [0]} = $_ [1]; $_ [0]}
  2         4  
45              
46 16     16 1 1135 sub start { $start {$_ [0]}}
47              
48             #
49             # Construction
50             #
51 2     2 1 1241 sub new {bless \do {my $var} => shift}
  2         14  
52              
53             sub init {
54 2     2 1 6 my $self = shift;
55 2         5 my %args = @_;
56              
57             #
58             # Classical layout.
59             #
60 2         12 $self -> _create_rooms (scalar @CLASSICAL_LAYOUT);
61 2         14 $self -> _classical_layout (%args);
62              
63 2         9 $self -> _name_rooms (%args);
64 2         9 $self -> _create_hazards (%args);
65              
66 2 50       7 if ($::DEBUG) {
67 0         0 my %h;
68 0         0 foreach my $room (@{$rooms {$self}}) {
  0         0  
69 0 0       0 if ($room -> has_hazard ($WUMPUS)) {
70 0         0 push @{$h {Wumpus}} => $room -> name;
  0         0  
71             }
72 0 0       0 if ($room -> has_hazard ($BAT)) {
73 0         0 push @{$h {Bat}} => $room -> name;
  0         0  
74             }
75 0 0       0 if ($room -> has_hazard ($PIT)) {
76 0         0 push @{$h {Pit}} => $room -> name;
  0         0  
77             }
78             }
79 0         0 local $, = " ";
80 0         0 say STDERR "Wumpus in", @{$h {Wumpus}};
  0         0  
81 0         0 say STDERR "Bats in", @{$h {Bat}};
  0         0  
82 0         0 say STDERR "Pits in", @{$h {Pit}};
  0         0  
83             }
84              
85 2         7 $self;
86             }
87              
88             #
89             # Create the given number of rooms.
90             # Note that the rooms aren't named here, nor are either exits or hazards set.
91             #
92             sub _create_rooms {
93 2     2   5 my $self = shift;
94 2         3 my $rooms = shift;
95              
96 2         8 $rooms {$self} = [map {Games::Wumpus::Room -> new -> init} 1 .. $rooms];
  40         183  
97              
98 2         6 $self;
99             }
100              
101             #
102             # Create the classical layout
103             #
104             sub _classical_layout {
105 2     2   4 my $self = shift;
106              
107 2         10 for (my $i = 0; $i < @CLASSICAL_LAYOUT; $i ++) {
108 40         34 foreach my $exit (@{$CLASSICAL_LAYOUT [$i]}) {
  40         554  
109 120         395 $rooms {$self} [$i] -> add_exit ($rooms {$self} [$exit]);
110             }
111             }
112              
113 2         5 $self;
114             }
115              
116              
117             #
118             # Randomly name the rooms; then store them in order.
119             #
120             sub _name_rooms {
121 2     2   6 my $self = shift;
122 2         5 my %args = @_;
123              
124 2         3 my $rooms = @{$rooms {$self}};
  2         5  
125 2         22 my @names = 1 .. $rooms;
126 2 50       10 @names = shuffle @names if $args {shuffle_names};
127              
128 2         13 for (my $i = 0; $i < @names; $i ++) {
129 40         120 $rooms {$self} [$i] -> set_name ($names [$i]);
130             }
131              
132 2         4 $rooms {$self} = [sort {$a -> name <=> $b -> name} @{$rooms {$self}}];
  38         83  
  2         14  
133              
134 2         9 $self;
135             }
136              
137             #
138             # Assign hazards to rooms. Initially, no room will have more than one hazard.
139             # This method also assigns the start location (hazard free).
140             #
141             sub _create_hazards {
142 2     2   5 my $self = shift;
143              
144 2         4 my @rooms = shuffle @{$rooms {$self}};
  2         103  
145              
146 2         4 my $wumpus_room = pop @rooms;
147 2         10 $wumpus_room -> set_hazard ($WUMPUS);
148              
149 2         9 $self -> set_wumpus ($wumpus_room);
150              
151 2         12 (pop @rooms) -> set_hazard ($PIT) for 1 .. $NR_OF_PITS;
152 2         10 (pop @rooms) -> set_hazard ($BAT) for 1 .. $NR_OF_BATS;
153              
154 2         8 $start {$self} = pop @rooms;
155              
156 2         6 $self;
157             }
158              
159              
160             #
161             # Describe the room the player is currently in.
162             #
163             sub describe {
164 4     4 1 23 my $self = shift;
165              
166 4         7 my $text;
167              
168 4         13 my $room = $self -> location;
169              
170 4         17 $text = "You are in room " . $room -> name . ".\n";
171 4 100       17 $text .= "I smell a Wumpus!\n" if $room -> near_hazard ($WUMPUS);
172 4 100       14 $text .= "I feel a draft.\n" if $room -> near_hazard ($PIT);
173 4 100       13 $text .= "Bats nearby!\n" if $room -> near_hazard ($BAT);
174              
175 12         33 $text .= "Tunnels lead to " . join " ", sort {$a <=> $b}
  12         25  
176 4         15 map {$_ -> name} $room -> exits;
177 4         8 $text .= ".\n";
178              
179 4         11 $text;
180             }
181              
182              
183             #
184             # Return whether player can move from current destination to new location.
185             #
186             # If the current location has an exit with the given name, then yes.
187             #
188             sub can_move_to {
189 0     0 1   my $self = shift;
190 0           my $new = shift;
191              
192 0 0         $self -> location -> exit_by_name ($new) ? 1 : 0;
193             }
194              
195              
196             #
197             # Move the player to a new location. Return the hazards encountered.
198             # Since bats may move the player, encountering a new hazard, more
199             # than one hazard may be encountered.
200             #
201             sub move {
202 0     0 1   my $self = shift;
203 0           my $new = shift;
204              
205 0           my @hazards;
206              
207 0           $self -> set_location ($self -> room ($new));
208              
209 0 0         if ($self -> location -> has_hazard ($WUMPUS)) {
210             # Death.
211 0           return $WUMPUS;
212             }
213 0 0         if ($self -> location -> has_hazard ($PIT)) {
214             # Death.
215 0           return $PIT;
216             }
217 0 0         if ($self -> location -> has_hazard ($BAT)) {
218             # Moved.
219 0           return $BAT, $self -> move ($self -> random_room -> name);
220             }
221              
222             # Nothing special.
223 0           return;
224             }
225              
226              
227             #
228             # Shoot an arrow. Return the first thing hit (ends shot).
229             # If a tunnel doesn't exist, pick something at random.
230             #
231             sub shoot {
232 0     0 1   my $self = shift;
233 0           my @path = @_;
234              
235 0           my $cur = $self -> location;
236              
237 0           foreach my $p (@path) {
238             #
239             # Is $p a valid exit of $cur?
240             #
241 0           my $e = $cur -> exit_by_name ($p);
242 0 0         unless ($e) {
243             #
244             # Not a valid exit. Pick one at random.
245             #
246 0           my @e = $cur -> exits;
247 0           $e = $e [rand @e];
248             }
249 0           $cur = $e;
250              
251 0 0         if ($cur -> has_hazard ($WUMPUS)) {return $WUMPUS}
  0            
252 0 0         if ($cur == $self -> location) {return $PLAYER}
  0            
253             }
254             }
255              
256              
257              
258             #
259             # Stir the Wumpus. It *may* move.
260             #
261             # Return true if it moves, false otherwise.
262             #
263             sub stir_wumpus {
264 0     0 1   my $self = shift;
265              
266 0 0         if (rand (1) < $WUMPUS_MOVES) {
267             #
268             # He moves.
269             #
270 0           my @exits = $self -> wumpus -> exits;
271 0           my $new = $exits [rand @exits];
272              
273 0 0         if ($::DEBUG) {
274 0           say STDERR "Wumpus moves to ", $new -> name;
275             }
276              
277 0           $self -> wumpus -> clear_hazard ($WUMPUS);
278 0           $new -> set_hazard ($WUMPUS);
279 0           $self -> set_wumpus ($new);
280 0           return 1;
281             }
282              
283 0           return 0;
284             }
285              
286              
287             __END__