I've made an iteration of the text game Hunt The Wumpus. The specification came from the text Programming Principles and Practice Using C++ by Bjarne Stroustrup.
Implement a version of the game "Hunt the Wumpus". Hunt the Wumpus" (or just "Wump") is a simple (non-graphically) computer game originally invented by Gregory Yob. The basic premise is that a rather smelly monster lives in a dark cave consisting of connected rooms. Your job is to slay the wumpus using bow and arrow. In addition to the wumpus, the cave has two hazards: bottomless pits and giant bats. If you enter a room with a bat, the bat picks you up and drops you into another room. If you enter a room with a bottomless pit, its the end of the game for you. If you enter the room with the Wumpus he eats you. When you enter a room you will be told if a hazard is nearby:
"I smell the wumpus": It´s in an adjoining room. "I feel a breeze": One of the adjoining rooms is a bottomless pit. "I hear a bat": A giant bat is in an adjoining room.
For your convenience, rooms are numbered. Every room is connected by tunnels to three other rooms. When entering a room, you are told something like " You are in room 12; there are tunnels to rooms 1,13, and 4: move or shoot?" Possible answers are m13 ("Move to room 13") and s13-4-3 ("Shoot an arrow through rooms 13,4, and 3"). The range of an arrow is three rooms. At the start of the game, you have five arrows. The snag about shooting is that it wakes up the wumpus and he moves to a room adjoining the one he was in - that could be your room. Be sure to have a way to produce a debug output of the state of the cave.
New to using pointers and containers of pointers - would love feedback on my limited use of them to keep track of connections between rooms!
Looking to improve whatever I can - all feedback is welcome!
#include <iostream>
#include <vector>
#include <limits>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ GLOBAL VARIABLES ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
constexpr int max_connections = 3;
constexpr int num_rooms = 16;
constexpr int num_bats = 2;
int num_arrows = 5;
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ROOM ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
struct Room {
// holds an occupant. carries a vector of pointers ("connections") to 3 other rooms.
Room(int rn)
: rn{ rn }, occ{ EMPTY } { }
enum Occupant { EMPTY, BAT, PIT, WUMPUS };
bool needs_connections() { return (connections.size() < max_connections); }
bool is_connected_to(int);
void add_connection(Room* r) { connections.push_back(r); }
bool is_empty() { return (occ == EMPTY); }
void set_occupant(Occupant o) { occ = o; }
Occupant display();
int rn;
Occupant occ;
std::vector<Room*> connections;
};
bool Room::is_connected_to(int i)
// returns whether this room is connected to another one, specified by an int
{
for (Room* connection : connections)
if (connection->rn == i) return true;
return false;
}
bool all_rooms_conneted(std::vector<Room> rooms)
// runs through a vector of rooms and returns whether they're all connected
{
for (int i = 0; i < rooms.size(); ++i)
if (rooms[i].connections.size() < 3) return false;
return true;
}
Room::Occupant Room::display()
{
std::cout << "You are in room " << rn << std::endl;
// Current Occupants
if (occ == BAT) {
std::cout << "There's a bat in here!" << std::endl;
return BAT;
}
else if (occ == PIT) {
std::cout << "There's a pit in here!" << std::endl;
return PIT;
}
else if (occ == WUMPUS) {
std::cout << "There's a wumpus in here!" << std::endl;
return WUMPUS;
}
else if (occ == EMPTY) {
std::cout << "The room is empty." << std::endl;
std::cout << "It is connected to rooms ";
for (Room* connection : connections)
std::cout << connection->rn << " ";
std::cout << std::endl << std::endl;
// Neighboring
for (Room* r : connections)
{
if (r->occ == BAT)
std::cout << "There's a bat nearby!" << std::endl;
if (r->occ == PIT)
std::cout << "There's a pit nearby!" << std::endl;
if (r->occ == WUMPUS)
std::cout << "There's a wumpus nearby!" << std::endl;
}
std::cout << std::endl;
std::cout << "You have " << num_arrows << " arrows." << std::endl;
std::cout << std::endl;
return EMPTY;
}
}
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ CAVE ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
struct Cave {
// holds and connects a group of rooms
Cave(int);
void populate(Room::Occupant, int);
int find_empty_room();
int find_location_of(Room::Occupant);
void move_the_wumpus();
void display_debug();
std::vector<Room> rooms;
};
void Cave::populate(Room::Occupant o, int population)
{
for (int i = 0; i < population; ++i)
{
int room_num;
while (true)
{
room_num = rand() % rooms.size();
if (rooms[room_num].is_empty()) break;
}
rooms[room_num].set_occupant(o);
}
}
Cave::Cave(int number_of_rooms)
// Constructor
{
// Generate rooms
for (int i = 0; i < number_of_rooms; ++i)
{
Room r(i);
rooms.push_back(r);
}
// Connect rooms
while (!all_rooms_conneted(rooms))
{
for (int this_room = 0; this_room < rooms.size(); ++this_room) // cycle through all rooms
{
if (rooms[this_room].needs_connections()) // if this room still needs connections
{
int connection = 0;
while (true) // try until an appropriate room is found
{
connection = rand() % number_of_rooms;
if (this_room == connection) continue;
if (rooms[connection].connections.size() == max_connections) continue;
if (rooms[this_room].is_connected_to(connection)) continue;
break;
}
rooms[this_room].add_connection(&rooms[connection]);
rooms[connection].add_connection(&rooms[this_room]);
}
}
}
// Populate with entities
populate(Room::Occupant::BAT, 2);
populate(Room::Occupant::PIT, 2);
populate(Room::Occupant::WUMPUS, 1);
}
int Cave::find_empty_room()
{
int empty_room = 0;
while (true)
{
empty_room = rand() % num_rooms;
if (rooms[empty_room].occ == Room::Occupant::BAT
|| rooms[empty_room].occ == Room::Occupant::PIT
|| rooms[empty_room].occ == Room::Occupant::WUMPUS) continue;
break;
}
return empty_room;
}
int Cave::find_location_of(Room::Occupant o)
{
int result;
for (int i = 0; i < rooms.size(); ++i)
if (rooms[i].occ == o) return i;
return -1;
}
void Cave::move_the_wumpus()
{
// move the wumpus
int wumpus_location = find_location_of(Room::Occupant::WUMPUS);
int new_room = -1;
while (true)
{
new_room = find_empty_room();
if (new_room != wumpus_location) break;
}
rooms[wumpus_location].set_occupant(Room::Occupant::EMPTY);
rooms[new_room].set_occupant(Room::Occupant::WUMPUS);
}
void Cave::display_debug()
{
for (int i = 0; i < rooms.size(); ++i)
{
std::cout << "Room " << i << ": ";
if (rooms[i].occ == Room::Occupant::WUMPUS) std::cout << "WUMPUS" << std::endl;
else if (rooms[i].occ == Room::Occupant::BAT) std::cout << "BAT" << std::endl;
else if (rooms[i].occ == Room::Occupant::PIT) std::cout << "PIT" << std::endl;
else std::cout << std::endl;
}
}
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ TURN ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
std::string make_lower(std::string str)
// return a lowercase version of a string
{
std::string result;
for (int i = 0; i < str.length(); ++i)
result += tolower(str[i]);
return result;
}
bool compare(std::string input, std::initializer_list<std::string> list)
// compares a string, made lowercase, to other strings. return true if it matches any of them.
{
std::string str = make_lower(input);
bool result = false;
for (std::string s : list)
{
if (str == s) result = true;
}
return result;
}
void putback_string(std::istream& is, std::string s)
// puts a string back into an input stream
{
for (char c : s)
is.putback(c);
}
struct Turn
{
Turn() // Parse Player Input
{
std::string input;
std::getline(std::cin, input);
std::istringstream is{ input };
std::string first_word;
is >> first_word;
if (compare(first_word, { "m", "move", "g", "go", "goto", "t", "travel" })) move = 'm';
if (compare(first_word, { "f", "fire", "s", "shoot", "a", "arrow" })) move = 'f';
if (compare(first_word, { "d", "debug" })) move = 'd';
for (char c; is >> c;)
{
if (c == '-') continue;
if (isdigit(c))
{
is.putback(c);
int i;
is >> i;
targets.push_back(i);
}
}
std::cout << std::endl;
}
char move = ' ';
std::vector<int> targets;
};
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ GAME LOOP ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
int main()
{
srand(time(NULL));
while (true)
{
Cave c(num_rooms);
// pick appropriate starting room
int current_room = c.find_empty_room();
// game loop
bool gameon = true;
while (gameon)
{
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ display ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Room::Occupant current_occupant = c.rooms[current_room].display();
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ game logic ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
if (num_arrows == 0)
{
std::cout << "You ran out of arrows! Now you can't defeat the wumpus!" << std::endl;
break;
}
else if (current_occupant == Room::Occupant::EMPTY)
{
Turn player_turn;
// MOVE
if (player_turn.move == 'm')
{
if (c.rooms[current_room].is_connected_to(player_turn.targets[0]))
{
current_room = player_turn.targets[0];
std::cout << "Moving to room " << player_turn.targets[0] << std::endl;
}
else
std::cout << "This room isn't connected to that one." << std::endl;
}
// FIRE ARROW
else if (player_turn.move == 'f')
{
for (int target : player_turn.targets)
{
if (c.rooms[current_room].is_connected_to(target))
{
std::cout << "You fired an arrow into room " << target << std::endl;
if (c.rooms[target].occ == Room::Occupant::WUMPUS)
{
std::cout << "You hit the wumpus! You win!" << std::endl;
std::cout << std::endl;
gameon = false;
break;
}
else
{
std::cout << "The arrow flew off into the dark, hitting nothing." << std::endl;
std::cout << std::endl;
c.move_the_wumpus();
std::cout << "The sound of the arrow stirred the wumpus from its sleep!" << std::endl;
std::cout << "The wumpus has moved to a new room!" << std::endl;
std::cout << std::endl;
--num_arrows;
}
}
else
std::cout << "This room isn't connected to that one." << std::endl;
}
}
// DEBUG
else if (player_turn.move == 'd')
{
c.display_debug();
}
}
// EVENT
else if (current_occupant == Room::Occupant::BAT)
{
std::cout << "You were picked up by the bat!" << std::endl;
current_room = c.find_empty_room();
std::cout << "It picked you up and flew you to room " << current_room << "!" << std::endl;
}
else if (current_occupant == Room::Occupant::WUMPUS)
{
std::cout << "The wumpus ate you!! Ahh!" << std::endl;
gameon = false;
}
else if (current_occupant == Room::Occupant::PIT)
{
std::cout << "You fell into the pit!" << std::endl;
gameon = false;
}
system("PAUSE");
system("CLS");
}
std::cout << "Game Over" << std::endl;
std::cin.get();
std::cout << "Play again? (y/n): ";
char input;
std::cin >> input;
if (input == 'n') break;
system("CLS");
}
}