
Download Release (Windows) & Source Code
Goblin Gourger is a text-based adventure game for Windows set in an endless maze of caves filled with goblins and mysteries!
Game Features Include:
- 8 Unique Weapons
- Turn-based Goblin Battles
- Endless Cave System
- A Unique Experience Each Playthrough!
The cave system is generated programmatically, so no two games will be identical. New rooms are generated “on-demand” for an endless experience.
There are a number of weapons, items, and special rooms to be discovered.
The goal of the game is to achieve the highest score possible by exploring the caves and slaying goblins!
Involvement: This is my first full game; it took me about a week to program and polish and helped me learn C++ (I had previous experience with JavaScript).
For now, the game is only for Windows but if anyone is interested, I am sure I could port it to Linux or Mac, and it would probably be a great learning experience. So, let me know if interested!
If you find bugs, please report them with as much detail as possible.
Please share any feedback or new feature requests!
No installation required; just unzip and open “goblin_gourger.exe” to play.
goblin_gourger.cpp (Main Code)
#include <iostream> // For basic input and output
#include <vector> // To use dynamic rather than static arrays
#include <cstdlib> // For random number processing
#include <ctime> // To seed random number with time
#include <algorithm> // To use 'find' on vectors
#include <windows.h> // Header for Windows-specific stuff
#include <mmsystem.h> // Multimedia system for sound (PlaySound())
#include "gg_sounds.h" // Goblin Gourger Sounds!
// Using namespace standard for this simple project
using namespace std;
string versionNumber = "2.0";
// Is the player starting a new game?
bool startNewGame = true;
int encounterRate;
// Integer used to count how many times the no dead-end loop runs
int deadEndCounter = 0;
// Adjacent room cantor values
long long int adjacentNorthRoom;
long long int adjacentEastRoom;
long long int adjacentSouthRoom;
long long int adjacentWestRoom;
// This variable holds the player's last movement direction
// This is used to ensure there is a path in the new room
// that is generated, which corresponds with the direction the
// player previously traveled.
// EXAMPLE: If the player moved North and is now in a new room,
// there should be a path South, back to the previous room
char lastMove;
int goblinsKilled = 0;
// Player Variables
int playerHealth = 100;
int playerPotions = 0;
int playerKeys = 0;
int playerBombs = 0;
string playerWeapon = "Fists";
int playerAttack = 2;
int goblinAttack = 0;
// Variable for storing random numbers
int randomNum;
// Array to hold all possible types of weapons the player can find
string weapons[] = {"Stick", "Branch", "Knife", "Dagger", "Spear", "Sword"};
// String text to be added at the beginning of the display
// I'm using this because I'm clearing the command line display after each loop
// This allows me to pass a message through cleanly to the display
// NOTE: When assigning values to 'appendMsg,' be sure to include a space
// at the end of the message.
// EXAMPLE: appendMsg = "Check out this space at the end! ";
string appendMsg = "";
string postMsg = "";
// Arrays to hold descriptions and contents of rooms
vector<string> descripText;
vector<string> roomItem;
vector<string> roomWeapon;
vector<int> roomNorthPath;
vector<int> roomEastPath;
vector<int> roomSouthPath;
vector<int> roomWestPath;
vector<int> goblinHealth;
string tempWeapon; // A variable to use when switching weapons in a room
// Array to hold possible room contents
// 0 = empty | 1 = potion | 2 = key | 3 = weapon | 4 = lockbox | 5 = goblin
string roomContent[] = {"empty", "potion", "key", "weapon", "lockbox", "goblin", "shrine", "bomb"};
int playerX = 999; // Start X and Y values high to avoid negative integers
int playerY = 999; // Cantor pairing function takes non-negatives as input
// Variable which hold the element ID of the current room
// This number will be used with roomMap to find and process
// the unique ID number of the room (the value stored inside
// the roomMap array. Whereas, the currentRoom number is
// merely the element ID for the roomMap array.
int currentRoom = 0;
// Variable to hold user input - a single character for simplicity
char userInput;
// Using a vector rather than static array for dynamic sizing
// long long int should be sufficient for large Cantor values
vector<long long int> roomMap;
// Custom function to calculate the unique Cantor value
// of two given input. Typically 'playerX' and 'playerY'
long long int cantor(int x, int y) {
return (((x + y) * (x + y + 1)) / 2) + y;
}
// This is a function be called when the player changes weapons
// It will update the 'playerAttack' value depending on the value
// of 'playerWeapon'
void updateAttack() {
if (playerWeapon == "Fists") { playerAttack = 2; }
if (playerWeapon == "Stick") { playerAttack = 3; }
if (playerWeapon == "Branch") { playerAttack = 4; }
if (playerWeapon == "Knife") { playerAttack = 5; }
if (playerWeapon == "Dagger") { playerAttack = 6; }
if (playerWeapon == "Spear") { playerAttack = 7; }
if (playerWeapon == "Sword") { playerAttack = 10; }
}
// A FUNction for displaying a cool Title at the start of the game
void displayTitle() {
cout << "============================================================\n";
cout << " Cody Ray Miller & \n";
cout << " Dude Fuel Games Presents... \n";
cout << "============================================================\n";
cout << " ____ ____ \n";
cout << " / / \n";
cout << " | ___ _ |_ | * __ | ___ _ _ _ _ _ \n";
cout << " \\____/ |_| |_|| | | | \\____/ |_||_|| |_||=\"| \n";
cout << " _| \n";
cout << " Version " << versionNumber << " \n";
cout << "============================================================\n";
}
void playRoomSound() {
if (roomItem[currentRoom] == "goblin") {
playGoblinSound();
} else if (roomItem[currentRoom] == "shrine") {
playShrineSound();
} else if (roomItem[currentRoom] == "weapon") {
//playFoundWeaponSound();
}
}
void playAttackSound() {
if (playerWeapon == "Fists") {
playFistsAttackSound();
} else if (playerWeapon == "Stick" || playerWeapon == "Branch") {
playStickAttackSound();
} else if (playerWeapon == "Knife" || playerWeapon == "Dagger") {
playKnifeAttackSound();
} else if (playerWeapon == "Spear") {
playSpearAttackSound();
} else if (playerWeapon == "Sword") {
playSwordAttackSound();
}
}
// Function to update the description text based on room contents
void updateDescripText() {
if (roomItem[currentRoom] == "empty" && currentRoom != 0) {
descripText[currentRoom] = "The room is empty.";
} else if (roomItem[currentRoom] == "potion") {
descripText[currentRoom] = "There is a \033[1;32mPotion\033[0;33m here.\033[0;0m";
} else if (roomItem[currentRoom] == "key") {
descripText[currentRoom] = "There is a \033[0;35mKey\033[0;33m here.\033[0;0m";
} else if (roomItem[currentRoom] == "weapon") {
descripText[currentRoom] = "There is a \033[0;36m" + roomWeapon[currentRoom] + "\033[0;33m here.\033[0;0m";
} else if (roomItem[currentRoom] == "lockbox") {
descripText[currentRoom] = "There is a \033[0;35mLocked Box\033[0;33m here.\033[0;0m";
} else if (roomItem[currentRoom] == "goblin") {
descripText[currentRoom] = "There is a \033[0;31mGoblin\033[0;33m here.\033[0;0m";
} else if (roomItem[currentRoom] == "shrine") {
descripText[currentRoom] = "There is a Shrine here.";
} else if (roomItem[currentRoom] == "bomb") {
descripText[currentRoom] = "There is a \033[0;31mBomb\033[33m here.\033[0;0m";
}
}
// This function runs when the player uses a bomb in a dead-end room.
// It will open a new random path in the room - N, E, S, or W.
void deadEndBomb() {
// Initialize the iterator
auto it = roomMap.end();
// Regardless of room contents, if the room is a dead-end, blow open a new random path
// If the current room is a dead-end room (only 1 path)...
if (roomNorthPath[currentRoom] + roomEastPath[currentRoom] + roomSouthPath[currentRoom] + roomWestPath[currentRoom] < 2) {
// Add this message to the end of any existing append Msg from previous if clauses
appendMsg = appendMsg + "A new path opens! ";
// Generate a random number to pick a random direction for the new path
randomNum = rand() % 4;
if (randomNum == 0) {
// NORTH
// If the north path of the current room is currently inaccessible...
if (roomNorthPath[currentRoom] == 0) {
// Open the North path of the current room
roomNorthPath[currentRoom] = 1;
// NORTH ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the North of the current room
adjacentNorthRoom = cantor(playerX, playerY + 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentNorthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomSouthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
} else {
// If the north path is already open, blow open the South path instead
roomSouthPath[currentRoom] = 1;
// SOUTH ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the North of the current room
adjacentSouthRoom = cantor(playerX, playerY - 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentSouthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomNorthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
}
if (randomNum == 1) {
// SOUTH
// If the south path of the current room is currently inaccessible...
if (roomSouthPath[currentRoom] == 0) {
// Open the South path of the current room
roomSouthPath[currentRoom] = 1;
// SOUTH ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the North of the current room
adjacentSouthRoom = cantor(playerX, playerY - 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentSouthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomNorthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
} else {
// If the south path is already open, blow open the North path instead
roomNorthPath[currentRoom] = 1;
// NORTH ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the North of the current room
adjacentNorthRoom = cantor(playerX, playerY + 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentNorthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomSouthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
}
if (randomNum == 2) {
// EAST
// If the east path of the current room is currently inaccessible...
if (roomEastPath[currentRoom] == 0) {
// Open the East path of the current room
roomEastPath[currentRoom] = 1;
// EAST ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the East of the current room
adjacentEastRoom = cantor(playerX + 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentEastRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomWestPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
} else {
// If the east path is already open, blow open the west path instead
roomWestPath[currentRoom] = 1;
// WEST ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the West of the current room
adjacentWestRoom = cantor(playerX - 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentWestRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomEastPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
}
if (randomNum == 3) {
// WEST
// If the west path of the current room is currently inaccessible...
if (roomWestPath[currentRoom] == 0) {
// Open the West path of the current room
roomWestPath[currentRoom] = 1;
// WEST ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the West of the current room
adjacentWestRoom = cantor(playerX - 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentWestRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomEastPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
} else {
// If the west path is already open, blow open the east path instead
roomEastPath[currentRoom] = 1;
// EAST ADJACENT ROOM CHECK
// Calculate the cantor value of the room to the East of the current room
adjacentEastRoom = cantor(playerX + 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentEastRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomWestPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
}
}
}
// This function will be used with a bomb to open a four-way path.
// It first opens a four-way path in the current room and then checks
// all adjacent rooms to see if they already exist or not. If they
// do, it ensures that a corresponding path is opened there also.
void fourWayBomb() {
// Open all four paths in the current room
roomNorthPath[currentRoom] = 1;
roomEastPath[currentRoom] = 1;
roomSouthPath[currentRoom] = 1;
roomWestPath[currentRoom] = 1;
// Check adjacent rooms
// If they exist, open a corresponding path
// If no adjacent room exists, there is
// nothing needing to be done
// Initialize the iterator
auto it = roomMap.end();
// NORTH
// Calculate the cantor value of the room to the North of the current room
adjacentNorthRoom = cantor(playerX, playerY + 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentNorthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomSouthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
// EAST
// Calculate the cantor value of the room to the East of the current room
adjacentEastRoom = cantor(playerX + 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentEastRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomWestPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
// SOUTH
// Calculate the cantor value of the room to the South of the current room
adjacentSouthRoom = cantor(playerX, playerY - 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentSouthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomNorthPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
// WEST
// Calculate the cantor value of the room to the West of the current room
adjacentWestRoom = cantor(playerX - 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentWestRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the adjacent room's corresponding path to be open
roomEastPath[distance(roomMap.begin(), it)] = 1;
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
void checkAdjacentNorthPath() {
auto it = roomMap.end();
// Calculate the cantor value of the room to the North of the current room
adjacentNorthRoom = cantor(playerX, playerY + 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentNorthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the current room's path value to correspond with the already explored room
roomNorthPath[currentRoom] = roomSouthPath[distance(roomMap.begin(), it)];
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
void checkAdjacentEastPath() {
auto it = roomMap.end();
// Calculate the cantor value of the room to the East of the current room
adjacentEastRoom = cantor(playerX + 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentEastRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the current room's path value to correspond with the already explored room
roomEastPath[currentRoom] = roomWestPath[distance(roomMap.begin(), it)];
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
void checkAdjacentSouthPath() {
auto it = roomMap.end();
// Calculate the cantor value of the room to the South of the current room
adjacentSouthRoom = cantor(playerX, playerY - 1);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentSouthRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the current room's path value to correspond with the already explored room
roomSouthPath[currentRoom] = roomNorthPath[distance(roomMap.begin(), it)];
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
void checkAdjacentWestPath() {
auto it = roomMap.end();
// Calculate the cantor value of the room to the West of the current room
adjacentWestRoom = cantor(playerX - 1, playerY);
// Check to see if this cantor value exists already.
it = find(roomMap.begin(), roomMap.end(), adjacentWestRoom);
// If the end is not reached, the value has been found and the room exists.
if (it != roomMap.end()) {
// Set the current room's path value to correspond with the already explored room
roomWestPath[currentRoom] = roomEastPath[distance(roomMap.begin(), it)];
} else {
// The player has never been inside the adjacent room before.
// Do nothing.
}
}
void updatePostMsg() {
// Clear post message
postMsg = "";
// If the room contains a goblin, have it attack the player!
if (roomItem[currentRoom] == "goblin" && goblinHealth[currentRoom] > 0) {
// Goblin counterattacks, dealing between 1 and 10 damage
goblinAttack = rand() % 10 + 1;
playerHealth -= goblinAttack;
postMsg = "\033[0;31m It attacks!!";
}
// Calculate whether or not the current room is a dead end. Display dead end post message if it is.
if ((roomNorthPath[currentRoom] + roomEastPath[currentRoom] + roomSouthPath[currentRoom] + roomWestPath[currentRoom]) < 2) { postMsg = "\033[0;31m It's a Dead End.\033[0;0m"; }
// If the room contains a shrine, have it heal the player
if (roomItem[currentRoom] == "shrine") {
if (playerHealth < 100) { postMsg = " \033[0;32mHealth restored!\033[0;0m"; playerHealth = 100; }
}
}
void spawnGoblin() {
roomItem.push_back("goblin");
descripText.push_back("There is a goblin here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(rand() % 10 + 1);
if (roomMap.size() > 150) { goblinHealth[currentRoom] += 4; }
}
// Function to create a new room
void makeNewRoom() {
// 'Push' a new value (0) to the 'back' (end) of the array
roomMap.push_back(0);
// Push new room path values to initialize arrays
roomNorthPath.push_back(0);
roomEastPath.push_back(0);
roomSouthPath.push_back(0);
roomWestPath.push_back(0);
// While loop to avoid dead-ends
while ((roomNorthPath[currentRoom] + roomEastPath[currentRoom] + roomSouthPath[currentRoom] + roomWestPath[currentRoom]) < 2) {
// Set random paths for the room
randomNum = rand() % 2; // Generate random number between 0 and 1
roomNorthPath[currentRoom] = randomNum;
randomNum = rand() % 2; // Generate random number between 0 and 1
roomEastPath[currentRoom] = randomNum;
randomNum = rand() % 2; // Generate random number between 0 and 1
roomSouthPath[currentRoom] = randomNum;
randomNum = rand() % 2; // Generate random number between 0 and 1
roomWestPath[currentRoom] = randomNum;
// Ensure that there is a path back to wherever the player came form
if (lastMove == 'N') { roomSouthPath[currentRoom] = 1; }
if (lastMove == 'E') { roomWestPath[currentRoom] = 1; }
if (lastMove == 'S') { roomNorthPath[currentRoom] = 1; }
if (lastMove == 'W') { roomEastPath[currentRoom] = 1; }
// Ensure that paths are not created into existing rooms without corresponding paths.
// For example, do not create a West path if the existing room to the West has no East path.
// If player came from the South, check W, N, E
if (lastMove == 'N') {
checkAdjacentWestPath();
checkAdjacentNorthPath();
checkAdjacentEastPath();
}
// If player came from the South, check N, E, S
if (lastMove == 'E') {
checkAdjacentNorthPath();
checkAdjacentEastPath();
checkAdjacentSouthPath();
}
// If player came from the South, check E, S, W
if (lastMove == 'S') {
checkAdjacentEastPath();
checkAdjacentSouthPath();
checkAdjacentWestPath();
}
// If player came from the South, check N, W, S
if (lastMove == 'W') {
checkAdjacentNorthPath();
checkAdjacentWestPath();
checkAdjacentSouthPath();
}
// OPTIONAL: This code would permit the creation of dead ends
// Break out of the loop if player has explored 100 rooms or more
// I'm still really not sure about adding dead ends. It provides an
// interesting challenge but the possibility of a closed cave system.
//if (roomMap.size() >= 100) { break; }
// This loop can get stuck because a dead-end is simply unavoidable sometimes. This counter helps to ensure
// that the loop is canceled after a certain amount of tries, so that the game can simply generate the
// dead-end and move on. Otherwise, the game will hang on a black screen.
deadEndCounter++;
if (deadEndCounter > 10) {
deadEndCounter = 0;
break;
}
}
// Use RNG to determine new room contents
// Make a certain percentage of rooms empty by default (prevents too many items, etc.)
randomNum = rand() % 2; // Pick a random number between 0 and 1
// If random number is 1, determine contents with a chance for items
if (randomNum == 1) {
randomNum = rand() % 8;
switch (randomNum) {
case 0:
roomItem.push_back("empty");
descripText.push_back("The room is empty.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
break;
case 1:
roomItem.push_back("potion");
descripText.push_back("There is a potion here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
break;
case 2:
randomNum = rand() % 2;
if (randomNum == 0) {
roomItem.push_back("key");
descripText.push_back("There is a key here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
} else {
roomItem.push_back("empty");
descripText.push_back("The room is empty.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
}
break;
case 3:
roomItem.push_back("weapon");
randomNum = rand() % 5; // Generate a random number between 0 and 5.
// cout << "Random number generated: " << randomNum << "\n\n"; // DEBUG CODE ONLY
roomWeapon.push_back(weapons[randomNum]);
descripText.push_back("There is a " + roomWeapon[currentRoom] + " here.");
goblinHealth.push_back(0);
break;
case 4:
roomItem.push_back("lockbox");
descripText.push_back("There is a locked box here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
break;
case 5:
spawnGoblin();
break;
case 6:
// Shrine room - needs to be rare, so adding RNG to prevent it from
// showing up too often...
randomNum = rand() % 4;
if (randomNum == 0) {
roomItem.push_back("shrine");
descripText.push_back("There is a shrine here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
} else {
roomItem.push_back("empty");
descripText.push_back("The room is empty.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
}
break;
case 7:
// Bomb room!
randomNum = rand() % 2; // Generate a random number between 0 and 1
if (randomNum == 0) {
roomItem.push_back("bomb");
descripText.push_back("There is a Bomb here.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
} else {
roomItem.push_back("empty");
descripText.push_back("The room is empty.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
}
break;
default:
// This is just a goofy "in case something goes wrong case"
roomItem.push_back("empty");
descripText.push_back("Something went wrong!!!!");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
break;
}
// Otherwise, if random number is 0, always make the room empty
// OR filled with a GOBLIN!
} else {
// Determine encounter rate - lower encounter rate number means MORE encounters
if (roomMap.size() >= 30) { encounterRate = 4; }
if (roomMap.size() >= 70) { encounterRate = 3; }
if (roomMap.size() >= 120) { encounterRate = 2; }
else { encounterRate = 5; }
// Pick a random number
randomNum = rand() % encounterRate;
// If the number is...
if (randomNum == 0) {
spawnGoblin();
} else {
roomItem.push_back("empty");
descripText.push_back("The room is empty.");
roomWeapon.push_back(weapons[0]);
goblinHealth.push_back(0);
}
}
updateDescripText(); // Update description text for the room
// cout << "\a"; could be used for a mostly-cross-platform "bell" sound here, if desired
// For now, I have chosen to disable it.
// Calculate the unique Cantor value for the current playerX and playerY values
// and assign that value to the array.
// Example: In the first room... roomMap[0] = 0, where '0' is the calculated
// Cantor value for the pairing, '0,0'
// This modifies the initialized value of the current roomMap element, which was
// previously set to '0' (see code in 'if' clause, above)
roomMap[currentRoom] = cantor(playerX, playerY);
}
// Main function
int main() {
// Set output to UTF-8 - for special character use
//SetConsoleOutputCP(CP_UTF8);
updateAttack();
// Seed random number generation with current time
srand(time(0));
startNewGame = true;
while (true) {
if (startNewGame) {
// Set up description, etc. for the initial room
descripText.push_back("You are in a dark cave.");
roomItem.push_back("empty");
roomWeapon.push_back(weapons[0]);
roomMap.push_back(0);
goblinHealth.push_back(0);
roomMap[currentRoom] = cantor(playerX, playerY);
roomNorthPath.push_back(1);
roomEastPath.push_back(1);
roomSouthPath.push_back(1);
roomWestPath.push_back(1);
startNewGame = false;
}
while (playerHealth > 0) {
// Clear the command window display - works on Windows!
system("CLS");
// Adjust the size of the roomMap array if needed
if (currentRoom >= roomMap.size()) {
// Function to generate a new room
makeNewRoom();
//DEBUG: appendMsg = "MAKING NEW ROOM... ";
}
updateDescripText(); // Update description text before displaying!
updatePostMsg();
// Check to see if the player has died. If so, exit the game loop.
if (playerHealth < 1) { break; }
// DEBUG DISPLAY ONLY BELOW
//cout << "Size of roomMap: " << roomMap.size() << endl;
//cout << "Size of roomItem: " << roomItem.size() << endl;
//cout << "Size of descripText: " << descripText.size() << endl;
//cout << "Size of roomWeapon: " << roomWeapon.size() << endl;
//cout << "Size of goblinHealth: " << goblinHealth.size() << endl;
//cout << "Player Attack: " << playerAttack << " | Goblin Health: " << goblinHealth[currentRoom] << " | Goblin Attack: " << goblinAttack << "\n\n";
displayTitle();
playRoomSound();
int playerScore = ((roomMap.size() * 10) + (goblinsKilled * 20)) - 10;
// Display game stats
cout << "SCORE: " << playerScore << " | Rooms Explored: " << roomMap.size() << " | Goblins Slain: " << goblinsKilled << "\n\n";
// Display Description Text for current room
cout << appendMsg << "\033[0;33m" << descripText[currentRoom] << postMsg << "\033[0;0m\n\n";
// If the room contains a goblin with more than zero hp, display Goblin HP
if (roomItem[currentRoom] == "goblin" && goblinHealth[currentRoom] > 0) { cout << "Goblin HP: " << goblinHealth[currentRoom] << "\n\n"; }
// Display the currentRoom value
//cout << "You are currently in room " << currentRoom << ".\n\n";
// Display current room's unique Cantor value
//cout << "The Cantor value of the current room is: " << roomMap[currentRoom] << "\n\n";
// Display player health - if clause changes player health color to red if low health!
if (playerHealth <= 20) { cout << "\033[0;31mHealth: " << playerHealth; }
else { cout << "\033[0;32mHealth: " << playerHealth; }
cout << "\033[0;0m - Weapon: " << playerWeapon << " (" << playerAttack << " dmg)";
cout << "\n";
// Display player inventory status
cout << "\033[0;0mPotions: " << playerPotions << " | Keys: " << playerKeys << " | Bombs: " << playerBombs << "\n\n";
// Display available paths to player
cout << "You see paths ";
if (roomNorthPath[currentRoom] == 1) { cout << "North "; }
if (roomEastPath[currentRoom] == 1) { cout << "East "; }
if (roomSouthPath[currentRoom] == 1) { cout << "South "; }
if (roomWestPath[currentRoom] == 1) { cout << "West "; }
cout << "\n\n";
// Display player current X and Y position and explain navigation
//cout << "You are in a room at " << playerX << "," << playerY << ".\n\n";
cout << "\033[0;36mAction Commands:\033[0;0m\nMovement: \033[0;36mN\033[0;0m - Go North | \033[0;36mE\033[0;0m - Go East | \033[0;36mS\033[0;0m - Go South | \033[0;36mW\033[0;0m - Go West\n";
cout << "\033[0;36mF\033[0;0m - Fight! | \033[0;36mT\033[0;0m - Take Item | \033[0;36mO\033[0;0m - Open Lock | \033[0;36mP\033[0;0m - Use Potion\n\033[0;36mB\033[0;0m - Use Bomb\033[0;0m";
cout << "\n\n>>>>";
// Get user input
cin.get(userInput);
// Convert user input to uppercase
userInput = toupper(userInput); // This avoids case sensitivity issues between input 'n' and 'N', for example
// Create a variable to hold temporary Cantor values in switch case below
long long int tempCant;
// Iterator to be used inside switch case
auto it = roomMap.end(); // Declare iterator here to avoid scope issues!
// Determine what to do based on user input
switch (userInput) {
case 'O':
// If there is a lockbox in the current room and the player has at least 1 key...
if (roomItem[currentRoom] == "lockbox" && playerKeys > 0) {
playerKeys--;
playKeyUnlockSound();
randomNum = rand() % 6; // Generate a random number between 0 and 3
// If number generated is 0, 1, or 2... (less than 3)
// If number generated is...
if (randomNum == 5) {
// The player finds a sword. Logic below handles
// what to do whether the player is barehanded
// or else already has an existing weapon
// What to do if player has no existing weapon...
if (playerWeapon == "Fists") {
playerWeapon = "Sword";
updateAttack();
playFoundSwordSound();
roomItem[currentRoom] = "empty";
appendMsg = "You found a Sword! You take it. ";
} else if (playerWeapon == "Sword") {
// Give player a potion
playerPotions ++;
playFoundPotionSound();
appendMsg = "You found a Potion! You take it. ";
roomItem[currentRoom] = "empty";
// What to do if player has an existing weapon...
} else {
// Store player's current weapon temporarily
tempWeapon = playerWeapon;
// Change player's weapon to sword
playerWeapon = "Sword";
// Update player attack value
updateAttack();
playFoundSwordSound();
// Change the room item to weapon
roomItem[currentRoom] = "weapon";
// Store the player's old weapon inside the room
roomWeapon[currentRoom] = tempWeapon;
// Let the player know they found a sword!
appendMsg = "You found a Sword! You take it. ";
}
// In every other case, do the following...
} else {
// Give player a potion
playerPotions ++;
playFoundPotionSound();
appendMsg = "You found a Potion! You take it. ";
roomItem[currentRoom] = "empty";
}
} else if (roomItem[currentRoom] == "lockbox" && playerKeys < 1) {
appendMsg = "You need a key to open the locked box. ";
} else {
appendMsg = "There is nothing to open here. ";
}
break;
case 'B':
if (playerBombs > 0) {
playerBombs--;
playBombSound();
if (roomItem[currentRoom] == "goblin") {
// Set goblinHealth to zero. Bombs kill goblins. LOL
goblinHealth[currentRoom] = 0;
// Increment goblin kill-counter variable
goblinsKilled++;
// Let the player know they've killed the goblin
appendMsg = "The bomb explodes! You defeat the goblin! ";
// Determine whether to set the room to empty or potion
randomNum = rand() % 7; // Generate random number between 0 and 6
if (randomNum == 1) { roomItem[currentRoom] = "potion"; }
else if (randomNum == 2) { roomItem[currentRoom] = "key"; }
else { roomItem[currentRoom] = "empty"; }
} else if (roomItem[currentRoom] == "shrine") {
appendMsg = "The bomb explodes! The shrine crumbles. ";
roomItem[currentRoom] = "empty";
} else if (roomItem[currentRoom] == "lockbox") {
randomNum = rand() % 6; // Generate a random number
// If number generated is...
if (randomNum == 5) {
// The player finds a sword. Logic below handles
// what to do whether the player is barehanded
// or else already has an existing weapon
// What to do if player has no existing weapon...
if (playerWeapon == "Fists") {
playerWeapon = "Sword";
updateAttack();
roomItem[currentRoom] = "empty";
appendMsg = "The bomb explodes! You found a Sword! You take it. ";
} else if (playerWeapon == "Sword") {
// Give player a potion
playerPotions ++;
appendMsg = "The bomb explodes! You found a Potion! You take it. ";
roomItem[currentRoom] = "empty";
// What to do if player has an existing weapon...
} else {
// Store player's current weapon temporarily
tempWeapon = playerWeapon;
// Change player's weapon to sword
playerWeapon = "Sword";
// Update player attack value
updateAttack();
// Change the room item to weapon
roomItem[currentRoom] = "weapon";
// Store the player's old weapon inside the room
roomWeapon[currentRoom] = tempWeapon;
// Let the player know they found a sword!
appendMsg = "The bomb explodes! You found a Sword! You take it. ";
}
// In every other case, do the following...
} else {
// Give player a potion
playerPotions ++;
appendMsg = "The bomb explodes! You found a Potion! You take it. ";
roomItem[currentRoom] = "empty";
}
} else if (roomItem[currentRoom] == "bomb") {
if (roomNorthPath[currentRoom] + roomEastPath[currentRoom] + roomSouthPath[currentRoom] + roomWestPath[currentRoom] < 4) {
appendMsg = "The bombs explode! New paths open!! ";
fourWayBomb();
roomItem[currentRoom] = "empty";
} else {
appendMsg = "The bombs explode! ";
roomItem[currentRoom] = "empty";
}
} else { appendMsg = "The bomb explodes! "; }
deadEndBomb(); // Run the function to see if the room is a dead-end and blow open a random path if it is
} else {
// Let the player know they have no bombs
appendMsg = "You don't have any bombs. ";
}
break;
case 'F':
if (roomItem[currentRoom] == "goblin") {
goblinHealth[currentRoom] -= playerAttack;
playAttackSound();
// If, after attacking, the goblin's health is 0 or lower...
if (goblinHealth[currentRoom] < 1) {
// Set goblinHealth to zero; there is really no need for this
// but it bothers me to think of negative enemy health...
goblinHealth[currentRoom] = 0;
// Increment goblin kill-counter variable
goblinsKilled++;
// Let the player know they've killed the goblin
appendMsg = "You defeat the goblin! ";
// Determine whether to set the room to empty or potion
randomNum = rand() % 7; // Generate random number between 0 and 6
if (randomNum == 1) { roomItem[currentRoom] = "potion"; }
else if (randomNum == 2) { roomItem[currentRoom] = "key"; }
else { roomItem[currentRoom] = "empty"; }
} else {
// Let the player know that they've damaged the goblin and also been damaged themselves
appendMsg = "Goblin takes " + to_string(playerAttack) + " dmg! ";
}
} else { appendMsg = "There is nothing to fight here. "; }
break;
case 'T':
if (roomItem[currentRoom] == "empty") {
appendMsg = "There is nothing to take here. ";
}
if (roomItem[currentRoom] == "potion") {
playerPotions++; // Increase number of potions held by player
playTakeItemSound();
roomItem[currentRoom] = "empty"; // Make the room empty now
appendMsg = "You take the potion. ";
}
if (roomItem[currentRoom] == "bomb") {
playerBombs++; // Increase number of potions held by player
playTakeItemSound();
roomItem[currentRoom] = "empty"; // Make the room empty now
appendMsg = "You take the bomb. ";
}
if (roomItem[currentRoom] == "key") {
playerKeys++; // Increase number of keys held by player
playTakeItemSound();
roomItem[currentRoom] = "empty"; // Make the room empty now
appendMsg = "You take the key. ";
}
if (roomItem[currentRoom] == "lockbox") {
appendMsg = "The locked box is too big to take. ";
}
if (roomItem[currentRoom] == "weapon") {
randomNum = rand() % 6; // Pick a random number between 0 and 5
if (playerWeapon == "Fists") {
playerWeapon = roomWeapon[currentRoom];
playTakeItemSound();
roomItem[currentRoom] = "empty";
updateAttack();
appendMsg = "You take the " + playerWeapon + ". ";
} else {
tempWeapon = playerWeapon; // Save the player's existing weapon in a temp variable
playerWeapon = roomWeapon[currentRoom]; // Assign the room's weapon to the player
playTakeItemSound();
roomWeapon[currentRoom] = tempWeapon; // Assign player's old weapon to the room
updateAttack();
appendMsg = "You take the " + playerWeapon + ". ";
}
}
if (roomItem[currentRoom] == "goblin") {
appendMsg = "You pick up the goblin. \033[1;31mIt attacks!! \033[0;0m"; // LOL
playerHealth -= 10; // Have the goblin damage the player
}
break;
case 'P':
if (playerPotions > 0 && playerHealth < 100) {
// Reduce player potions by 1
playerPotions--;
playUsePotionSound();
// Increase player health by 10
playerHealth += 10;
// Ensure that player health cannot exceed 100
if (playerHealth > 100) { playerHealth = 100; }
// Let the player know what happened
appendMsg = "You use a potion. \033[0;32mHealth restored! \033[0;0m";
} else if (playerPotions > 0 && playerHealth == 100) {
// Let the player know they're being silly...
appendMsg = "Your health is already full. ";
} else {
// Let the player know they have no potions
appendMsg = "You don't have any potions. ";
}
break;
case 'N':
if (roomNorthPath[currentRoom] == 1) {
lastMove = 'N';
// Increase player Y position by 1. (increment)
playerY++;
playWalkSound();
// Calculate Cantor value of new position
tempCant = cantor(playerX, playerY);
// Search to see if the unique Cantor value for the room entered already exists
it = find(roomMap.begin(), roomMap.end(), tempCant);
// If the end is not reached, the value has been found and the room
// has been previously entered by the player.
// Else, it is a room the player has never entered before.
if (it != roomMap.end()) {
// The player has been inside this room before. Set 'currentRoom' to the index
// of the roomMap array containing the found Cantor value
currentRoom = distance(roomMap.begin(), it);
} else {
// The player has never been inside this room before. Set the 'currentRoom'
// value to the next available index of the roomMap array.
currentRoom = roomMap.size();
}
appendMsg = "You go North. ";
} else { appendMsg = "\033[1;31mYou can't go that way. \033[0;0m"; }
break;
case 'E':
if (roomEastPath[currentRoom] == 1) {
lastMove = 'E';
// Increase player Y position by 1. (increment)
playerX++;
playWalkSound();
// Calculate Cantor value of new position
tempCant = cantor(playerX, playerY);
// Search to see if the unique Cantor value for the room entered already exists
it = find(roomMap.begin(), roomMap.end(), tempCant);
// If the end is not reached, the value has been found and the room
// has been previously entered by the player.
// Else, it is a room the player has never entered before.
if (it != roomMap.end()) {
// The player has been inside this room before. Set 'currentRoom' to the index
// of the roomMap array containing the found Cantor value
currentRoom = distance(roomMap.begin(), it);
} else {
// The player has never been inside this room before. Set the 'currentRoom'
// value to the next available index of the roomMap array.
currentRoom = roomMap.size();
}
appendMsg = "You go East. ";
} else { appendMsg = "\033[1;31mYou can't go that way. \033[0;0m"; }
break;
case 'S':
if (roomSouthPath[currentRoom] == 1) {
lastMove = 'S';
// Increase player Y position by 1. (increment)
playerY--;
playWalkSound();
// Check to make sure the player has not gone 'out of bounds'
// (into a negative integer player position).
if (playerY < 0) {
// If decrement goes negative, return to '0'
playerY = 0;
appendMsg = "\033[1;31mYou can't go that way; a wall blocks your path.\n\n\033[0;0m";
break; // Exit the switch, doing nothing else.
}
// Calculate Cantor value of new position
tempCant = cantor(playerX, playerY);
// Search to see if the unique Cantor value for the room entered already exists
it = find(roomMap.begin(), roomMap.end(), tempCant);
// If the end is not reached, the value has been found and the room
// has been previously entered by the player.
// Else, it is a room the player has never entered before.
if (it != roomMap.end()) {
// The player has been inside this room before. Set 'currentRoom' to the index
// of the roomMap array containing the found Cantor value
currentRoom = distance(roomMap.begin(), it);
} else {
// The player has never been inside this room before. Set the 'currentRoom'
// value to the next available index of the roomMap array.
currentRoom = roomMap.size();
}
appendMsg = "You go South. ";
} else { appendMsg = "\033[1;31mYou can't go that way. \033[0;0m"; }
break;
case 'W':
if (roomWestPath[currentRoom] == 1) {
lastMove = 'W';
// Increase player Y position by 1. (increment)
playerX--;
playWalkSound();
// Check to make sure the player has not gone 'out of bounds'
// (into a negative integer player position).
if (playerX < 0) {
// If decrement goes negative, return to '0'
playerX = 0;
appendMsg = "\033[1;31mYou can't go that way; a wall blocks your path.\n\n\033[0;0m";
break; // Exit the switch, doing nothing else.
}
// Calculate Cantor value of new position
tempCant = cantor(playerX, playerY);
// Search to see if the unique Cantor value for the room entered already exists
it = find(roomMap.begin(), roomMap.end(), tempCant);
// If the end is not reached, the value has been found and the room
// has been previously entered by the player.
// Else, it is a room the player has never entered before.
if (it != roomMap.end()) {
// The player has been inside this room before. Set 'currentRoom' to the index
// of the roomMap array containing the found Cantor value
currentRoom = distance(roomMap.begin(), it);
} else {
// The player has never been inside this room before. Set the 'currentRoom'
// value to the next available index of the roomMap array.
currentRoom = roomMap.size();
}
appendMsg = "You go West. ";
} else { appendMsg = "\033[1;31mYou can't go that way. \033[0;0m"; }
break;
case '\n':
continue;
break;
default:
appendMsg = "\033[1;31mInvalid input. Please try again.\n\n\033[0;0m";
break;
}
cin.clear(); // Clear cin state/error flags
cin.ignore(1000, '\n'); // Removes extraneous user inputs. For example, if the
// player types 'north' instead of just 'n' for North.
}
// GAME OVER code here
system("CLS");
displayTitle();
playGameOverSound();
int playerScore = ((roomMap.size() * 10) + (goblinsKilled * 20)) - 10;
// Display game stats
cout << "SCORE: " << playerScore << " | Rooms Explored: " << roomMap.size() << " | Goblins Slain: " << goblinsKilled << "\n\n";
cout << "\033[1;31mYou have died.\033[0;0m\n\nGAME OVER\n\nDo you want to play again? (Y/N)\n\n>>>>";
// Get user input
cin.get(userInput);
// Convert user input to uppercase
userInput = toupper(userInput); // This avoids case sensitivity issues between input 'n' and 'N', for example
switch (userInput) {
case 'Y':
startNewGame = true;
// Clear all arrays
descripText.clear();
roomItem.clear();
roomWeapon.clear();
goblinHealth.clear();
tempWeapon.clear();
roomMap.clear();
roomNorthPath.clear();
roomEastPath.clear();
roomSouthPath.clear();
roomWestPath.clear();
// Reset variables
playerScore = 0;
goblinsKilled = 0;
playerX = 999;
playerY = 999;
playerPotions = 0;
playerKeys = 0;
playerHealth = 100;
playerWeapon = "Fists";
playerAttack = 2;
goblinAttack = 0;
currentRoom = 0;
randomNum = 0;
appendMsg = "";
postMsg = "";
cin.clear(); // Clear cin state/error flags
cin.ignore(1000, '\n');
break;
case 'N':
exit(0);
break;
default:
// Do nothing
break;
}
}
}
gg_sounds.cpp (Sounds File)
#include "gg_sounds.h"
#include <windows.h>
#include <mmsystem.h>
// Miscellaneous Sounds
void playWalkSound() {
PlaySound(TEXT("sound/walk.wav"), NULL, SND_ASYNC);
}
void playGoblinSound() {
PlaySound(TEXT("sound/goblin.wav"), NULL, SND_ASYNC);
}
void playUsePotionSound() {
PlaySound(TEXT("sound/use_potion.wav"), NULL, SND_ASYNC);
}
void playTakeItemSound() {
PlaySound(TEXT("sound/take_item.wav"), NULL, SND_ASYNC);
}
void playBombSound() {
PlaySound(TEXT("sound/bomb.wav"), NULL, SND_ASYNC);
}
void playShrineSound() {
PlaySound(TEXT("sound/shrine.wav"), NULL, SND_ASYNC);
}
// Locked Box & Found Sounds
void playKeyUnlockSound() {
PlaySound(TEXT("sound/key_unlock.wav"), NULL, SND_SYNC);
}
void playFoundSwordSound() {
PlaySound(TEXT("sound/found_sword.wav"), NULL, SND_ASYNC);
}
void playFoundPotionSound() {
PlaySound(TEXT("sound/found_potion.wav"), NULL, SND_ASYNC);
}
void playFoundWeaponSound() {
PlaySound(TEXT("sound/found_weapon.wav"), NULL, SND_ASYNC);
}
// Weapon Attack Sounds
void playFistsAttackSound() {
PlaySound(TEXT("sound/attack_fists.wav"), NULL, SND_SYNC);
}
void playStickAttackSound() {
PlaySound(TEXT("sound/attack_stick.wav"), NULL, SND_SYNC);
}
void playKnifeAttackSound() {
PlaySound(TEXT("sound/attack_knife.wav"), NULL, SND_SYNC);
}
void playSpearAttackSound() {
PlaySound(TEXT("sound/attack_spear.wav"), NULL, SND_SYNC);
}
void playSwordAttackSound() {
PlaySound(TEXT("sound/attack_sword.wav"), NULL, SND_SYNC);
}
// Game Over Sound
void playGameOverSound() {
PlaySound(TEXT("sound/game_over.wav"), NULL, SND_ASYNC);
}
gg_sounds.h (Header File)
// gg_sounds.h
#ifndef GG_SOUNDS_H
#define GG_SOUNDS_H
// Miscellaneous Sounds
void playWalkSound();
void playGoblinSound();
void playUsePotionSound();
void playTakeItemSound();
void playBombSound();
void playShrineSound();
// Locked Box & Found Sounds
void playKeyUnlockSound();
void playFoundSwordSound();
void playFoundPotionSound();
void playFoundWeaponSound();
// Weapon Attack Sounds
void playFistsAttackSound();
void playStickAttackSound();
void playKnifeAttackSound();
void playSpearAttackSound();
void playSwordAttackSound();
// Game Over Sound
void playGameOverSound();
#endif