3 # This file is part of Grue Hunter.
5 # Copyright (C) 2013 Jason Self <j@jxself.org>
7 # Grue Hunter gives you software freedom; you can copy, modify, convey,
8 # and/or redistribute it under the terms of the GNU Affero General Public
9 # License as published by the Free Software Foundation; either version 3 of
10 # the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program in a file called 'AGPLv3.txt'. If not, see
19 # http://www.gnu.org/licenses/agpl-3.0-standalone.html or write to the: Free
20 # Software Foundation, Inc., 51 Franklin St, Fifth Floor Boston, MA
26 my $instructions = <<INSTRUCTIONS;
29 Grue Hunter version 1.0
30 Homepage: http://jxself.org/grue-hunter.shtml
31 Copyright (C) 2013 Jason Self <j\@jxself.org>
33 Grue Hunter gives you software freedom; you can copy, modify, convey,
34 and/or redistribute it under the terms of the GNU Affero General Public
35 License as published by the Free Software Foundation; either version 3 of
36 the License, or (at your option) any later version.
38 You should have received a copy of the GNU Affero General Public License
39 along with this program in a file called 'AGPLv3.txt'. If not, see
40 http://www.gnu.org/licenses/
44 In this game you are a brave hunter. You are going to enter an underground
45 cave system in search of the Grue. If you can capture the Grue and get out
46 alive you will win the game.
48 Once in the cave system you can move around or shoot an arrow using the
49 commands 'move' and 'shoot' and any of the four compass directions. You can
50 also shorten the commands to 'm' and 's' and the directions to 'n', 's', 'e',
51 and 'w' for brevity. You may enter 'quit' or 'q' to end the game at any time.
53 But... There are other things in this cave system. Giant bats can pick you up
54 and drop you elsewhere. There are also bottomless pits. If you fall into one
55 of these you will never get out. There is also the Grue itself. Though not an
56 aggressive creature, it will eat you if you come too close. There are also
57 earthquakes that move things around (the bats, the bottomless pits, the Grue,
62 The room here slopes downward. Old candles are scattered across the floor.
63 An old fountain sits in the middle of the room. It is covered in rubble and dirt. A toppled statue lies to the right.
64 The walls here are smooth. There is an old anvil here.
65 The walls contain claw marks and some sort of graffiti in a language you cannot read. A crack in the ceiling above the middle of the north wall allows a trickle of water to flow down to the floor. The water pools near the base of the wall, and a rivulet runs along the wall and out into the hall. The water smells fresh.
66 You push a heavy stone door open, revealing a room with pillars lining the wall. Several square holes have been cut into the walls.
67 Stalactites drip down from the ceiling in sharp points while flowstone makes strange shapes on the floor. Rusting spikes line the walls.
68 This room smells of rotting vegetation. Burning torches in iron sconces line the walls.
69 The scent of smoke fills the room. A small hole to your left attracts your attention; but it is too small to be of any concern.
70 A stream of oil flows from one side of the room to the other, in a small rut in the floor. There is a mysterious stain on the wall.
71 This is the center of a narrow passage that connects other rooms. This room is about average size; but is filled with a pungent aroma that is very nauseating.
72 There are cracks in the floor here. It is foggy, making it difficult to see.
73 Thick cobwebs fill the corners of this room and wisps of webbing hang from the ceiling, wavering in a wind you can't feel. You notice a light humming sound.
74 The floor is damp and looks quite slippery. You will need to be very careful here. A haze of smoke hangs in the air.
75 This is a very large room with a large boulder in the center of it. Part of the wall has collapsed into this room.
76 Several pieces of rotten fruit are scatted throughout the area. A hole has been dug into the wall. After looking inside you see that it doesn’t go anywhere.
77 This room is very dusty, and you see footprints set in the dust. There are spiders on the walls.
78 The passageway is very low; but if we crawl we can make it. After coming through the other side you see a stone stairway that goes up to the wall, where it abruptly ends.
79 As you enter the room part of the ceiling collapses but you jump out of the way in time to avoid getting hurt. Duck your head in here; as large rock stalactites hang from the ceiling.
80 This area is covered in rubble and dirt. There is a puddle and scraps of wood here.
81 The floor is covered with sand and pottery shards.
82 The walls have been engraved with geometric patterns. Fog gathers and swirls on the floor, concealing it.
83 There is a salty, wet smell to this area. A small hole in the ceiling lets light from outside through ... But you would never fit through it.
84 There are bones and several piece of rotting wood scattered here.
85 It is misty here. Several empty flasks are scattered throughout the area.
86 Rats run past you, in the direction you just came from. The floor here is covered in perfect hexagonal tiles.
87 There are strange looking yellow stones here. Water drips through the ceiling.
88 Your nostrils are overwhelmed by a sulphurous smell. The ceiling here is damp.
89 Grime and filth smear this room. This area is filled with stalactites and stalagmites.
90 This is a very dirty room; it was partially filled in by the last earthquake that hit. A slight breeze blows through this area.
91 A shallow pit sits in the center of the room. The walls here have been engraved with spiral patterns.
92 This cavern has a high, domed ceiling. There is a mold growing here.
93 A slight, damp breeze blows through this area. This rooms smells of mildew.
94 This area is very dusty. A dry well sits in the center of the room.
95 As you enter this room some small rocks fall from the ceiling. Several broken arrows are scatted about.
96 You are in a small passageway. The walls, ceiling and floor are composed of a smooth black stone. Illuminating your way is a silvery white glow coming from two long grooves midway up each wall that run the length of the passageway.
97 You hear an eerie sound. Jagged steel blades project from cracks in the walls.
98 Someone has scrawled the drawing of a door on the wall. A haze of dust hangs in the air.
99 The walls are covered in slime. There is broken glass on the ground.
100 There is a green slime on the floor. This room is very small; but I think we can make it through ok.
101 There is a stale smell here. Your steps echo here.
102 This large room is illuminated by a low-burning fire pit. There is an iron sconce on the wall, but the torch has been removed.
103 A rivulet of water slowly trickles from a hole in the wall. A narrow pit covered by iron bars lies in the center of the room.
104 Several empty bottles lie on the floor. Water drips from the ceiling, landing on your head.
105 You are in a low depression in the center of a medium-sized room. A flurry of bats suddenly fly past you as you enter. Their screeching fills your ears.
106 Someone has left a lighted torch on the wall and it illuminates your passage. A liquid-filled pit sits in the center of the room and is so murky that you can't see its bottom. The room smells sour.
107 This area is only a few feet high, but crawling on your stomach you make it through. You notice centipedes crawling along the wall.
108 A rattling noise abruptly stops when you enter. Black mold grows on the walls and parts of the floor.
109 You are in a small room with rocks and debris scattered everywhere. Part of the here ceiling has collapsed.
110 A huge iron cage lies on its side in this room, and its gate rests open on the floor. A broken chain lies on the floor. This room has sour odor, like something rotten or fermented.
111 An iron chandelier hangs from the ceiling. A old wooden barrel lies on its side, mostly collapsed.
120 # Update this constant to increase the number of rooms in the game.
123 # Update this constant if more room descriptions are added.
124 my $MAX_ROOM_DESCRIPTIONS = 50;
126 my $MAX_LINK_COUNT = 4;
128 my $EARTHQUAKE_CHANCE = 14;
144 my $grue_location = $CLOSED;
145 my $exit_location = $CLOSED;
146 my $bat1_location = $CLOSED;
147 my $bat2_location = $CLOSED;
148 my $pit1_location = $CLOSED;
149 my $pit2_location = $CLOSED;
150 my $player_location = $CLOSED;
156 sub randomize_rooms {
157 my @room_descriptions = split(/\n/, $space);
158 my %random_hash = ();
160 my $random_room_description = 0;
162 for (my $i = 0; $i < $MAX_ROOMS;) {
163 $random_room = int(rand($MAX_ROOMS));
164 $random_room_description = int(rand($MAX_ROOM_DESCRIPTIONS));
166 if (!$random_hash{$random_room}) {
167 $random_hash{$random_room} = 1;
169 $room{'description'} = $room_descriptions[$random_room_description];
170 $room{$NORTH} = $CLOSED;
171 $room{$SOUTH} = $CLOSED;
172 $room{$EAST} = $CLOSED;
173 $room{$WEST} = $CLOSED;
174 $room{'link_count'} = 0;
182 my $current_link_count = 0;
183 my $random_link_count = 0;
184 my $rooms_left_to_link = 0;
185 my $room_direction_state = 0;
188 for (my $i = 0; $i < $MAX_ROOMS;) {
190 # Take into consideration current link count and get random link count.
191 $current_link_count = $rooms[$i] -> {'link_count'};
192 if ($current_link_count < $MAX_LINK_COUNT) {
193 $random_link_count = int(rand($MAX_LINK_COUNT - $current_link_count)) + 1;
195 # Check that there are enough rooms left to link. If there are not
196 # then substitute the random link count with the rooms left to link.
197 $rooms_left_to_link = $MAX_ROOMS - $i;
198 if ($random_link_count >= $rooms_left_to_link) {
199 $random_link_count = $rooms_left_to_link;
203 # If the room is fully linked then set the random link count to 0.
205 $random_link_count = 0;
208 # For each room create links upto a random link count by going through
209 # the vacant compass directions.
210 for (my $j = 0; $j < $random_link_count; $j++) {
211 for (my $k = 1; $k < 5; $k++) {
212 if ($i < $MAX_ROOMS) {
213 $room_direction_state = $rooms[$i] -> {$k};
214 if ($room_direction_state == $CLOSED) {
216 if ($next_room < $MAX_ROOMS) {
217 $rooms[$i] -> {$k} = $next_room;
218 $rooms[$next_room] -> {opposite($k)} = $i;
228 sub place_directional_object {
229 my $room_number = shift;
233 # Get a random direction and set the object in a random room direction.
234 $direction = int(rand(3)) + 1;
235 $rooms[$room_number] -> {$direction} = $object;
237 # Update the room's link count
238 ($rooms[$room_number] -> {'link_count'})++;
240 if ($object == $GRUE) {
241 $grue_location = $room_number;
243 elsif ($object == $EXIT) {
244 $exit_location = $room_number;
246 elsif ($object == $PIT1) {
247 $pit1_location = $room_number;
249 elsif ($object == $PIT2) {
250 $pit2_location = $room_number;
254 sub get_random_rooms {
255 my $number_of_rooms = shift;
257 my %random_hash = ();
258 my @room_numbers = ();
260 for (my $i = 0; $i < $number_of_rooms;) {
261 $random_num = int(rand($MAX_ROOMS));
263 if (!$random_hash{$random_num}) {
264 $random_hash{$random_num} = 1;
265 $room_numbers[$i] = $random_num;
269 return \@room_numbers;
278 $number_of_rooms = 5;
281 $number_of_rooms = 6;
284 $room_numbers = get_random_rooms($number_of_rooms);
286 # Bats are non-directional objects and are placed in rooms. Since they are
287 # non-directional they do not occupy a compass direction and do not add to the
288 # room's link count. They are simply assigned to the Game State variables.
289 $bat1_location = @$room_numbers[0];
290 $bat2_location = @$room_numbers[1];
292 # The Grue, Pits and Exit are directional objects, placed in rooms. They
293 # are directional they occupy compass dirctions and are added to the room's
294 # link count. In addition they are assigned to the Game State variables. The
295 # Grue is only placed in a room again if it has not been bagged.
296 place_directional_object(@$room_numbers[2], $PIT1);
297 place_directional_object(@$room_numbers[3], $PIT2);
298 place_directional_object(@$room_numbers[4], $EXIT);
300 place_directional_object(@$room_numbers[5], $GRUE);
302 # If the game state is being set for the first time, then the player's
303 # location is set to the same location as the Exit.
305 $player_location = @$room_numbers[4];
310 my $direction = shift;
312 if ($direction == $NORTH) {
315 elsif ($direction == $SOUTH) {
318 elsif ($direction == $EAST) {
321 elsif ($direction == $WEST) {
330 my ($action, $sub_action) = @_;
331 my $action_success = 0;
333 if (defined $action) {
334 $action = lc($action);
336 # If the sub action (direction) is defined.
337 if (defined $sub_action) {
338 $sub_action = lc($sub_action);
340 # If sub action (direction) is a valid direction.
341 if (is_direction($sub_action)) {
342 if ($sub_action eq 'q' || $action eq 'quit') {
345 elsif ($action eq 'm' || $action eq 'move') {
346 move_player($sub_action);
349 elsif ($action eq 's' || $action eq 'shoot') {
355 # If the sub action (direction) is not a valid direction.
357 print "Enter 'N', 'S', 'E' OR 'W'.\n";
358 if ($action eq 'm' || $action eq 'move') {
361 elsif ($action eq 's' || $action eq 'shoot') {
366 if ($action_success) {
368 $random_num = int(rand($EARTHQUAKE_CHANCE));
370 if ($random_num == $EARTHQUAKE) {
371 print "\n<<<EARTHQUAKE>>>\n\n";
380 # If the sub action (direction) is not defined.
382 if ($action eq 'm' || $action eq 'move') {
385 elsif ($action eq 's' || $action eq 'shoot') {
388 elsif ($action eq 'q' || $action eq 'quit') {
398 for my $word (split/%/, $text) {
400 print 'Press enter when ready to continue: ';
405 print "\t\t\t\t".'***********************'."\n";
406 print "\t\t\t\t".'*** ***'."\n";
407 print "\t\t\t\t".'*** GRUE HUNTER ***'."\n";
408 print "\t\t\t\t".'*** ***'."\n";
409 print "\t\t\t\t".'***********************'."\n\n";
416 @actions = split(/\s+/, $action);
421 my $direction = shift;
422 if (defined $direction) {
425 $direction eq 'north' or
427 $direction eq 'south' or
429 $direction eq 'east' or
431 $direction eq 'west' or
440 my $direction = shift;
442 if ($direction == $NORTH) {
445 elsif ($direction == $SOUTH) {
448 elsif ($direction == $EAST) {
451 elsif ($direction == $WEST) {
460 my $sub_action = shift;
464 if (defined $sub_action) {
465 if ($sub_action eq 'n' || $sub_action eq 'north') {
468 elsif ($sub_action eq 's' || $sub_action eq 'south') {
471 elsif ($sub_action eq 'e' || $sub_action eq 'east') {
474 elsif ($sub_action eq 'w' || $sub_action eq 'west') {
478 $room_direction = $rooms[$player_location] -> {$direction};
480 # If the room that the player wants to move to is a room.
481 if ($room_direction > $CLOSED) {
483 # Check if the room has a bat.
484 if ($room_direction == $bat1_location || $room_direction == $bat2_location) {
485 print "Bats have you now.\n";
486 print "They're lifting you up.\n";
487 print "Ohhhh, where are we now?\n";
489 my $number_of_rooms = 2;
490 my $room_numbers = get_random_rooms($number_of_rooms);
492 # Change player location (to where the bat dropped the player).
493 $player_location = @$room_numbers[0];
495 # Change location of offending bat.
496 if ($room_direction == $bat1_location) {
497 $bat1_location = @$room_numbers[1];
500 $bat2_location = @$room_numbers[1];
504 # If free of bats, go ahead and move into the room.
507 $player_location = $room_direction;
510 elsif ($room_direction == $CLOSED) {
511 print "You cannot go that way.\n";
513 elsif ($room_direction == $GRUE) {
514 print "You bumped into the Grue.\n";
515 print "He ate you before you could move.\n";
518 elsif ($room_direction == $EXIT) {
520 print "You have reached the exit with your Grue.\n";
521 print "Your supper will be filling tonight for sure!\n";
524 print "You have reached the exit without any Grue.\n";
525 print "You are sure to starve.\n";
529 elsif ($room_direction < $PIT1) {
530 my $pit_chance = int(rand($PIT_CHANCE));
532 print "This room contains a bottomless pit. Part of the ground around it gave way when you entered and you almost fell in.\n";
533 print "Be more careful next time!\n";
534 $player_location = $room_direction;
537 print "You fell into a bottomless pit.\n";
538 print "You fell a loooooong way...\n";
545 print "Move which way?\n";
546 @actions = read_input();
547 action_handler('m', $actions[0]);
552 my $sub_action = shift;
556 if (defined $sub_action) {
557 if ($sub_action eq 'n' || $sub_action eq 'north') {
560 elsif ($sub_action eq 's' || $sub_action eq 'south') {
563 elsif ($sub_action eq 'e' || $sub_action eq 'east') {
566 elsif ($sub_action eq 'w' || $sub_action eq 'west') {
570 $room_direction = $rooms[$player_location] -> {$direction};
572 # If the arrow was shot in a direction without the Grue.
573 if ($room_direction == $CLOSED) {
575 print "The arrow bounced off the wall.\n";
577 # If the arrow was shot in a direction of the Grue.
578 elsif ($room_direction == $GRUE) {
579 my $shot = int(rand($EVEN_CHANCE));
581 print "You caught the Grue!\n";
582 print "Now to find a way out...\n";
585 # Remove the Grue from the Room once bagged. Set the Grue
586 # location to an undefined state (the Grue object -2).
587 $rooms[$player_location] -> {$direction} = $CLOSED;
588 ($rooms[$player_location] -> {'link_count'})--;
589 $grue_location = $GRUE;
592 print "The arrow missed the Grue.\n";
596 print "The arrow missed the Grue.\n";
601 print "Shoot which way?\n";
602 @actions = read_input();
603 action_handler('s', $actions[0]);
607 sub describe_location {
610 print $rooms[$player_location] -> {'description'} . "\n";
611 # print "Player Location: $player_location\n";
612 # print "Grue Location: $grue_location\n";
613 # print "Bat 1 Location: $bat1_location\n";
614 # print "Bat 2 Location: $bat2_location\n";
615 # print "Pit 1 Location: $pit1_location\n";
616 # print "Pit 2 Location: $pit2_location\n";
617 # print "Exit Location: $exit_location\n";
619 print 'I see passages to the ';
621 for (my $i = 1; $i < 5; $i++) {
622 $room_direction = $rooms[$player_location] -> {$i};
623 if ($room_direction != $CLOSED) {
624 push(@passages, direction($i));
628 for (my $i = 0; $i < $#passages + 1; $i++) {
632 elsif ($i < $#passages) {
633 print ', ' . $passages[$i];
635 elsif ($i == $#passages) {
636 print ' and ' . $passages[$i];
639 print ". Choose wisely.\n";
641 # If the Grue is in the player's current location.
642 if ($player_location == $grue_location) {
643 print "I smell the Grue!\n";
645 # If either pit is in the player's current location.
646 elsif ($player_location == $pit1_location || $player_location == $pit2_location) {
647 print "I feel a draft. A bottomless pit must be nearby. Be careful not to fall in.\n";
649 # If the Exit is in the player's current location, tell the player which
650 # direction the Exit is located.
651 elsif ($player_location == $exit_location) {
652 for (my $i = 1; $i < 5; $i++) {
653 $room_direction = $rooms[$player_location] -> {$i};
654 if ($room_direction == $EXIT) {
655 print 'The exit is to the ' . direction($i) . ".\n";
659 # Check the adjacent rooms for the Exit, Grue, Bats and Pits. Here
660 # check whether the adjacent rooms are equivalent to the game state
661 # variables for the Exit, Grue, Bats and Pits.
663 for (my $i = 1; $i < 5; $i++) {
664 $room_direction = $rooms[$player_location] -> {$i};
665 if ($room_direction > $CLOSED) {
666 if ($room_direction == $exit_location) {
667 print "Exit nearby...\n";
669 elsif ($room_direction == $grue_location) {
670 print "I smell the Grue!\n";
672 # If the room adjacent contains one of the two bats then warn the player.
673 elsif ($room_direction == $bat1_location || $room_direction == $bat2_location) {
674 print "I hear bats flying around in the distance. Be careful or they may grab you and carry you away.\n";
676 # If the room adjacent contains one of the two pits then warn the player.
677 elsif ($room_direction == $pit1_location || $room_direction == $pit2_location) {
678 print "I feel a draft. A bottomless pit must be nearby. Be careful not to fall in.\n";
692 display_text($instructions);
698 print "Move or shoot?\n";
699 @actions = read_input();
700 action_handler(@actions);