I'm writing a texbased dungeoncrawler style game for my C++ class and I'm encountering a runtime error stating that boost assert has triggered a breakpoint. I've made a very similar project in C# using the same logic, so I think it's something to do with a specific of C++ that's causing my issue.
Here's my code:
ArtoriaClasses.h
#pragma once
//Jake Farley
//CSC275
//Artoria (Final)
//11/25/17
#include <iostream>
#include <sstream>
#include <memory>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/algorithm/string.hpp>
#include <string>
#include <ctime>
#include <vector>
#include <fstream>
#include "boost/multi_array.hpp"
#include <cassert>
using namespace std;
//Class representing one tile of the map
class MapTile
{
private:
int X;
int Y;
string message;
int eventID;
public:
//Default Constructor
MapTile()
{
}
//Constructor with minimum arguments
MapTile(int x, int y)
{
X = x;
Y = y;
message = "You are unable to go that way.";
eventID = -1;
}
//Constructor with all arguments
MapTile(int x, int y, string message, int eventID)
{
X = x;
Y = y;
this->message = message;
this->eventID = eventID;
}
//function to return coords as a vector with 2 elements
vector<int> getCoords()
{
vector<int> coords;
coords.push_back(X);
coords.push_back(Y);
return coords;
}
//function to get the message;
string getMessage()
{
return message;
}
//function to get the eventID
int getEventID()
{
return eventID;
}
};
//Class that is the game object itself, is implemented in the main "runner"
class Artoria
{
private:
int tempCoordinates[2];
int coordinates[2];
Player player;
Enemy currentEnemy;
bool activeEnemy;
//This is what I tried originally but it throws a bad_alloc at runtime
//when I first try to assign it a value in populateMap()
//MapTile map[10][10];
//Trying something for map
boost::multi_array<MapTile, 2> map2 { boost::extents[10][10] };
vector<Item> looseItems;
vector<Weapon> weapons;
vector<Armor> armors;
Random randomGen;
bool playerDied;
public:
//Default Constructor
Artoria()
{
populateItems();
populateMap();
activeEnemy = false;
startGame();
}
//Function that creates the entire map by populating the multidimensional array of MapTile objects
void populateMap()
{
map2[0][0] = MapTile(0, 0, "You cannot go this way.", -1);
//Omitted additional assignments to save character space
map2[9][9] = MapTile(9, 9, "You are in the Alchemists' Lab!", 3);
}
//Function to evaluate user's input
string evaluateInput(string userInput)
{
boost::algorithm::to_lower(userInput);
if (userInput == "north")
{
if (!activeEnemy)
{
//moving south so add one to Y coordinate
coordinates[1] = (coordinates[1] + 1);
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "south")
{
if (!activeEnemy)
{
//moving south so subract one from Y coordinate
coordinates[1] = (coordinates[1] - 1);
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "east")
{
if (!activeEnemy)
{
//moving east so add 1 to X coordinate
coordinates[0] = (coordinates[0] + 1);
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "west")
{
if (!activeEnemy)
{
//moving west so subract 1 from X coordinate
coordinates[0] = (coordinates[0] - 1);
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "attack")
{
if (activeEnemy)
{
battleEnemy();
}
else
{
cout << "There is no enemy attacking you, move around to find enemies." << endl << "Type 'help_me' for detailed instructions." << endl;
}
}
else if (userInput == "stopplayingartoria")
{
string ret = quitGame();
return ret;
}
else if (userInput == "help_me")
{
stringstream ret;
ret << "You can move around the map typing the following commands: 'north', 'south', 'east', and 'west'" << endl << "If you encounter an enemy, attack them by typing 'attack' If you wish to quit the game, type 'StopPlayingArtoria'" << endl;
}
else
{
return "false";
}
return "true";
}
//Function to generate the message output to the console.
string genMessage()
{
if (!activeEnemy)
{
//get the current map tile from the map container
MapTile currentTile = map2[coordinates[0]][coordinates[1]];
string message = currentTile.getMessage();
int eventID = currentTile.getEventID();
//string variable to fill with return value;
string retStr;
//3 different areas each with event id(1-3), event ids are added for additional custom events
switch (eventID)
{
case 1:
//Dungeon Hallway
retStr = genDungeonHallway();
cout << message << endl << retStr;
break;
case 2:
//Crypt
retStr = genCrypt();
cout << message << endl << retStr;
break;
case 3:
//Alchemist's Lab
retStr = genAlchemistsLab();
cout << message << endl << retStr;
break;
case 4:
retStr = "This appears to be a safe place, nothing can surprise you here, but you will also find nothing here.";
cout << message << retStr << endl;
break;
case -1:
//player cannot go here so reset his coordinates
coordinates[0] = tempCoordinates[0];
coordinates[1] = tempCoordinates[1];
return message;
default:
// Case 0 is just print message(currently unused)
retStr = message;
break;
}
//set tempCoords to keep track of last tile.
tempCoordinates[0] = coordinates[0];
tempCoordinates[1] = coordinates[1];
return retStr;
}
}
};
Runner.cpp
//Jake Farley
//CSC275
//Artoria (Final)
//11/25/17
#include <iostream>
#include <string>
#include <memory>
#include <boost/algorithm/string.hpp>
#include "ArtoriaClasses.h";
using namespace std;
int main()
{
string userInput;
cout << "Welcome to the game 'Artoria'. You can start a new game by typing 'new' " << endl << " load an existing save by typing 'load' or view instructions on how to play the game by typing 'help_me'" << endl;
getline(cin, userInput);
boost::algorithm::to_lower(userInput);
if (userInput == "new")
{
Artoria artoriaGame = Artoria();
while (userInput != "QUIT")
{
getline(cin, userInput);
string ret = artoriaGame.evaluateInput(userInput);
if (ret == "true")
{
cout << artoriaGame.genMessage();
}
else if (ret == "false")
{
cout << "You have entered an invalid command. You can type 'help_me' for instructions.";
}
else if (ret == "QUIT")
{
}
}
}
else if (userInput == "load")
{
cout << "What is the name you used?" << endl;
getline(cin, userInput);
Artoria artoriaGame = Artoria(userInput);
while (userInput != "QUIT")
{
getline(cin, userInput);
string ret = artoriaGame.evaluateInput(userInput);
if (ret == "true")
{
cout << artoriaGame.genMessage();
}
else if (ret == "false")
{
cout << "You have entered an invalid command. You can type 'help_me' for instructions.";
}
else if (ret == "QUIT")
{
}
}
}
else if (userInput == "help_me")
{
cout << "Artoria is a text-based dungeon crawler created by Jake Farley. The goal of the game is to get the highest score." << endl;
cout << "You score points by defeating monsters and picking up items, as you walk around the map you will encounter various enemies and find various items." << endl;
cout << "As you fight enemies, you will take damage; be warned, there is no way to regain your health. Once your health reaches zero, the game will end." << endl;
cout << "You can move around the map by typing the four compass directions 'north', 'south', 'east', and 'west'" << endl;
cout << "If you encounter an enemy, attack them by typing 'attack' you cannot run away from an enemy encounter." << endl;
cout << "If you wish to quit the game, type 'StopPlayingArtoria'" << endl;
}
else
{
cout << "Error: Invalid response, closing program." << endl;
}
system("pause");
}
What are the "omitted assignments"? If they address illegal indices, that would do it.
I don't see you initialize the coordinates anywhere, either. You probably think you get that for free, like in C#?
int tempCoordinates[2] = {0,0};
int coordinates[2] = {0,0};
It doesn't look like you do any range validation on the movements (west, east etc.), also look at the copy-paste comments ... :)
//moving south so add one to Y coordinate
coordinates[1] = (coordinates[1] + 1);
//moving south so subract one from Y coordinate
coordinates[1] = (coordinates[1] - 1);
//moving east so add 1 to X coordinate
coordinates[0] = (coordinates[0] + 1);
//moving west so subract 1 from X coordinate
coordinates[0] = (coordinates[0] - 1);
Suggestion
It would be better to abstract a little bit:
enum { Rows = 10, Columns = 10 };
struct Coords {
int x = 0, y = 0;
bool west() { if (x>0) { --x; return true; } return false; }
bool south() { if (y>0) { --y; return true; } return false; }
bool east() { if (x+1<Columns) { ++x; return true; } return false; }
bool north() { if (y+1<Rows) { ++y; return true; } return false; }
};
Coords lastCoordinates;
Coords coordinates;
Now you can use those constants:
boost::multi_array<MapTile, 2> map2 { boost::extents[Columns][Rows] };
And use them safe actions:
if (userInput == "north")
{
if (!activeEnemy)
{
coordinates.north();
}
// ...
No more need for comments, let alone wrong comments :)
Using it:
MapTile currentTile = map2[coordinates.x][coordinates.y];
Or:
case -1:
//player cannot go here so reset his coordinates
coordinates = lastCoordinates;
return message;
And:
//set lastCoordinates to keep track of last tile.
lastCoordinates = coordinates;
Live Demo
Managed to make navigation "playable". It reaches Alchemist Lab, just fine:
Live On Coliru
//#pragma once
//Jake Farley
//CSC275
//Artoria (Final)
//11/25/17
struct Player {
};
struct Enemy {
};
struct Item {
};
struct Weapon {
};
struct Armor {
};
#include <iostream>
#include <sstream>
#include <memory>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/algorithm/string.hpp>
#include <string>
#include <ctime>
#include <vector>
#include <fstream>
#include "boost/multi_array.hpp"
#include <cassert>
using Random = boost::random::mt19937;
using namespace std;
//Class representing one tile of the map
class MapTile
{
private:
int X;
int Y;
string message;
int eventID;
public:
//Default Constructor
MapTile()
{
}
//Constructor with minimum arguments
MapTile(int x, int y)
{
X = x;
Y = y;
message = "You are unable to go that way.";
eventID = -1;
}
//Constructor with all arguments
MapTile(int x, int y, string message, int eventID)
{
X = x;
Y = y;
this->message = message;
this->eventID = eventID;
}
//function to return coords as a vector with 2 elements
vector<int> getCoords()
{
vector<int> coords;
coords.push_back(X);
coords.push_back(Y);
return coords;
}
//function to get the message;
string getMessage()
{
return message;
}
//function to get the eventID
int getEventID()
{
return eventID;
}
};
//Class that is the game object itself, is implemented in the main "runner"
class Artoria
{
private:
enum { Rows = 10, Columns = 10 };
struct Coords {
int x = 0, y = 0;
bool west() { if (x>0) { --x; return true; } return false; }
bool south() { if (y>0) { --y; return true; } return false; }
bool east() { if (x+1<Columns) { ++x; return true; } return false; }
bool north() { if (y+1<Rows) { ++y; return true; } return false; }
};
Coords lastCoordinates;
Coords coordinates;
Player player;
Enemy currentEnemy;
bool activeEnemy;
boost::multi_array<MapTile, 2> map2 { boost::extents[Columns][Rows] };
vector<Item> looseItems;
vector<Weapon> weapons;
vector<Armor> armors;
Random randomGen;
bool playerDied;
public:
//Default Constructor
Artoria(std::string const& /*s*/ = {})
{
//populateItems();
populateMap();
activeEnemy = false;
//startGame();
}
//Function that creates the entire map by populating the multidimensional array of MapTile objects
void populateMap()
{
map2[0][0] = MapTile(0, 0, "You cannot go this way.", -1);
//Omitted additional assignments to save character space
map2[9][9] = MapTile(9, 9, "You are in the Alchemists' Lab!", 3);
}
//Function to evaluate user's input
string evaluateInput(string userInput)
{
boost::algorithm::to_lower(userInput);
if (userInput == "north")
{
if (!activeEnemy)
{
coordinates.north();
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "south")
{
if (!activeEnemy)
{
coordinates.south();
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "east")
{
if (!activeEnemy)
{
coordinates.east();
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "west")
{
if (!activeEnemy)
{
coordinates.west();
}
else
{
cout << "You cannot move when there is an enemy attacking you!" << endl << "Type 'Attack' to attack the enemy. Type 'help_me' for detailed instructions." << endl;
return "false";
}
}
else if (userInput == "attack")
{
if (activeEnemy)
{
//battleEnemy();
}
else
{
cout << "There is no enemy attacking you, move around to find enemies." << endl << "Type 'help_me' for detailed instructions." << endl;
}
}
else if (userInput == "stopplayingartoria")
{
//string ret = quitGame();
return "quit";
}
else if (userInput == "help_me")
{
stringstream ret;
ret << "You can move around the map typing the following commands: 'north', 'south', 'east', and 'west'" << endl << "If you encounter an enemy, attack them by typing 'attack' If you wish to quit the game, type 'StopPlayingArtoria'" << endl;
}
else
{
return "false";
}
return "true";
}
//Function to generate the message output to the console.
string genMessage()
{
if (!activeEnemy)
{
//get the current map tile from the map container
MapTile currentTile = map2[coordinates.x][coordinates.y];
string message = currentTile.getMessage();
int eventID = currentTile.getEventID();
//string variable to fill with return value;
string retStr;
//3 different areas each with event id(1-3), event ids are added for additional custom events
switch (eventID)
{
case 1:
//Dungeon Hallway
retStr = "genDungeonHallway()";
cout << message << endl << retStr;
break;
case 2:
//Crypt
retStr = "genCrypt()";
cout << message << endl << retStr;
break;
case 3:
//Alchemist's Lab
retStr = "genAlchemistsLab()";
cout << message << endl << retStr;
break;
case 4:
retStr = "This appears to be a safe place, nothing can surprise you here, but you will also find nothing here.";
cout << message << retStr << endl;
break;
case -1:
//player cannot go here so reset his coordinates
coordinates = lastCoordinates;
return message;
default:
// Case 0 is just print message(currently unused)
retStr = message;
break;
}
//set lastCoordinates to keep track of last tile.
lastCoordinates = coordinates;
return retStr;
}
}
};
//Jake Farley
//CSC275
//Artoria (Final)
//11/25/17
#include <iostream>
#include <string>
#include <memory>
#include <boost/algorithm/string.hpp>
//#include "ArtoriaClasses.h";
using namespace std;
int main()
{
string userInput;
cout << "Welcome to the game 'Artoria'. You can start a new game by typing 'new' " << endl << " load an existing save by typing 'load' or view instructions on how to play the game by typing 'help_me'" << endl;
getline(cin, userInput);
boost::algorithm::to_lower(userInput);
if (userInput == "new")
{
Artoria artoriaGame = Artoria();
while (userInput != "QUIT")
{
getline(cin, userInput);
string ret = artoriaGame.evaluateInput(userInput);
if (ret == "true")
{
cout << artoriaGame.genMessage();
}
else if (ret == "false")
{
cout << "You have entered an invalid command. You can type 'help_me' for instructions.";
}
else if (ret == "QUIT")
{
}
}
}
else if (userInput == "load")
{
cout << "What is the name you used?" << endl;
getline(cin, userInput);
Artoria artoriaGame = Artoria(userInput);
while (userInput != "QUIT")
{
getline(cin, userInput);
string ret = artoriaGame.evaluateInput(userInput);
if (ret == "true")
{
cout << artoriaGame.genMessage();
}
else if (ret == "false")
{
cout << "You have entered an invalid command. You can type 'help_me' for instructions.";
}
else if (ret == "QUIT")
{
}
}
}
else if (userInput == "help_me")
{
cout << "Artoria is a text-based dungeon crawler created by Jake Farley. The goal of the game is to get the highest score." << endl;
cout << "You score points by defeating monsters and picking up items, as you walk around the map you will encounter various enemies and find various items." << endl;
cout << "As you fight enemies, you will take damage; be warned, there is no way to regain your health. Once your health reaches zero, the game will end." << endl;
cout << "You can move around the map by typing the four compass directions 'north', 'south', 'east', and 'west'" << endl;
cout << "If you encounter an enemy, attack them by typing 'attack' you cannot run away from an enemy encounter." << endl;
cout << "If you wish to quit the game, type 'StopPlayingArtoria'" << endl;
}
else
{
cout << "Error: Invalid response, closing program." << endl;
}
}
Prints
Welcome to the game 'Artoria'. You can start a new game by typing 'new'
load an existing save by typing 'load' or view instructions on how to play the game by typing 'help_me'
You cannot go this way.
You are in the Alchemists' Lab!
genAlchemistsLab()
genAlchemistsLab()
You have entered an invalid command.
You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
You have entered an invalid command. You can type 'help_me' for instructions.
...
As you can see you need to work on the input loop and detecting end-of-file :)
Related
i was creating a tictactoe game and for the player vs player feature of the game I cant seem to wrap my head around getting the player symbols the correct way around.
I'm not really able to provide the minimum amount of code, as it would be my entire program, so I have grabbed the function. I belive it is something to do with the conditional operator but cant figure out what.
but some guy provided a compilable example for me :)
#include <iostream>
#include <string>
/* MOCK */
void print() {};
bool win() { return false; };
bool tie() { return false; };
void player_move() {};
void player_move2() {};
char player, player2;
std::string playerName, player2Name;
/********/
void pvp() {
while (true) {
std::cout << "Player 1 Name: ";
std::cin >> playerName;
std::cout << "Player 2 Name: ";
std::cin >> player2Name;
std::cout << "Which symbol (Player 1: X or player 2: O) goes first? ";
std::cin >> player;
if (player == 'X' || player == 'O') {
break;
}
}
player2 = player == 'O' ? 'X' : 'O';
if (player == 'O') {
player_move2();
}
print();
while (true) {
std::cout << playerName << "'s turn to make a move\n";
player_move();
print();
if (win()) {
std::cout << playerName << " wins!\n";
return;
} else if (tie()) {
std::cout << "Tie!\n";
return;
}
std::cout << player2Name << "'s turn to make a move\n";
player_move2();
print();
if (win()) {
std::cout << player2Name << " wins!\n";
return;
} else if (tie()) {
std::cout << "Tie!\n";
return;
}
}
}
int main() {
pvp();
}
EXPLANATION
There are some patterns in your code, e.g. "making a move":
std::cout << playerName << "'s turn to make a move\n";
player_move();
print();
if (win()) {
std::cout << playerName << " wins!\n";
return;
} else if (tie()) {
std::cout << "Tie!\n";
return;
}
Which is the same for player 1 and player 2, except that you call player2_move(), and display the name of player 2.
Your second loop can then be simplified as
while(true) {
// do_player1_move
// do_player2_move
}
You now come about the problem that the loop will always begin with player 1. To solve this, you're trying to do_player2_move before the loop, if player 2 shall start.
if (player == 'O') {
// do_player2_move
}
while(true) {
// do_player1_move
// do_player2_move
}
However, in your code you only call player2_move(), instead of the entire pattern "making a move" for player 2.
SOLUTION
Therefore, adjusting
if (player == 'O') {
player_move2();
}
to
if (player == 'O') {
std::cout << player2Name << "'s turn to make a move\n";
player_move2();
print();
if (win()) {
std::cout << player2Name << " wins!\n";
return;
} else if (tie()) {
std::cout << "Tie!\n";
return;
}
}
should solve your problem
I am trying to build a simple dungeon crawl and am stuck at the battle. The damage taking is working but I cannot return the new health value so that it decreases past the initialized value. Every time the l;oop repeats it returns to the initial value. The same with treasure. What gives? How can I return a value from a member function to main?
#include <iostream>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
class monster
{
public:
int fight()
{
}
};
class chest
{
int loot()
{
}
};
class movement
{
public:
char wasd()
{
char mv;
char up = 'w';
char left = 'a';
char right = 'd';
char down = 's';
cout << "\nMove using w for (up), a for (left), d for (right), and s for (down).\n\n";
mv = _getch();
if (mv == up)
{
cout << "You have moved up 1 space.\n";
}
else if (mv == left)
{
cout << "You have moved left 1 space.\n";
}
else if (mv == right)
{
cout << "You have moved right 1 space.\n";
}
else if (mv == down)
{
cout << "You have moved down 1 space.\n";
}
else
{
cout << "it didn't work.";
}
return 0;
}
};
class random_enc
{ public:
int treasure;
int health = 12;
public:
int encounter(int)
{
int randnumber = rand() % 5 + 1;
treasure = 0;
if (randnumber == 1)
{
cout << "You have been attacked!!! You lose 1 hitpoint." << endl;
health = --health;
cout << "Hitpoints remaining: " << health << endl;
return health;
}
else if (randnumber == 2)
{
cout << "You found treasure!" << endl;
treasure = ++treasure;
cout << "Treasure collected: " << treasure << endl;;
return random_enc::treasure;
}
else if (randnumber == 3)
{
return health;
}
else if (randnumber == 4)
{
cout << "You step on a trap and take damage!! You lose 1 hit point.\n" << "Good bye." << endl;
health = --health;
cout << "Hitpoints remaining: " << health << endl;
}
return health;
}
};
int main()
{
string name;
cout << "Welcome to the dungeon, enter your name:\n";
cin >> name;
cout << "\nGood luck in the dungeon " << name << "\n";
int health = 12;
while (health != 0)
{
movement mvmt;
random_enc random;
mvmt.wasd();
random.encounter(health);
}
cout << "You have been killed. Goodbye.\n";
return 0;
}
I replaced the argument health on the encounter function in random_enc. I replaced it with a pointer: void encounter (int& health)
This passes the reference rather than the value. Then health is defined in the member function.
#include <iostream>
using namespace std;
void encounter(int& health) // note the ampersand
{
--health; // this will change health in main
}
int main()
{
int health = 12;
encounter(health);
cout << health << '\n'; // prints 11
}
So I am making a textbased RPG and I wanted to have multiple enemy encounter at once. So I modified my function that determines whether an object of the class Monster, to fill in the Monster(s) into an array of the class monster and set the objects bool to true, as you can see here:
Monster * Map::checkRandomEncounter()
{
Monster* monster = new Monster[3];
for (int i = 0; i < 3; i++)
{
int roll = Random(0, 20);
if (roll <= 5)
{
//No encounter
return 0;
}
else if (roll > 6 && roll < 10)
{
monster[i] = Monster();
monster[i].giveID("Orc", 10, 8, 200, 100, 1, "Short Sword", 2, 7);
monster[i].isFilled();
std::cout << "You encounter an Orc!" << std::endl;
std::cout << "Prepare for battle!" << std::endl;
std::cout << std::endl;
}
else if (roll >= 11 && roll <= 15)
{
monster[i] = Monster();
monster[i].giveID("Goblin", 6, 6, 100, 75, 0, "Dagger", 1, 5);
monster[i].isFilled();
std::cout << "You encounter a Goblin!" << std::endl;
std::cout << "Prepare for battle!" << std::endl;
std::cout << std::endl;
}
else if (roll >= 16 && roll <= 19)
{
monster[i] = Monster();
monster[i].giveID("Ogre", 20, 12, 500, 200, 2, "Club", 3, 8);
monster[i].isFilled();
std::cout << "You encounter an Ogre!" << std::endl;
std::cout << "Prepare for battle!" << std::endl;
std::cout << std::endl;
}
else if (roll == 20)
{
monster[i] = Monster();
monster[i].giveID("Orc Lord",
25,
15,
2000,
1000,
5,
"Two Handed Sword",
5,
20);
monster[i].isFilled();
std::cout << "You encounter an Orc Lord!" << std::endl;
std::cout << "Prepare for battle!" << std::endl;
std::cout << std::endl;
}
}
return monster;
}
The function above will be called in my main function, which looks like this:
int main()
{
srand(time(0));
Map gameMap;
Player mainPlayer;
mainPlayer.createClass();
//Beginn adventure
bool done = false;
while (done == false)
{
// Each Loop Cycle outputs player pos and selection menu
gameMap.printPlayerPos();
int selection = 1;
std::cout << "1) Move 2) Rest 3) View Stats 4) Quit: ";
std::cin >> selection;
Monster* monster = 0;
switch (selection)
{
case 1: // Move the player
gameMap.movePlayer();
if (gameMap.getPlayerXPos() == 2
&& gameMap.getPlayerYPos() == 3)
{
std::cout << "You see a store nearby !" << std::endl;
}
if (gameMap.getPlayerXPos() == 2
&& gameMap.getPlayerYPos() == 4)
{
Store store;
store.enter();
store.showInventory(mainPlayer);
}
//Check for a random encounter
//returns a null pointer if no encounter happened
monster = gameMap.checkRandomEncounter();
//'monster' not null, start battle script
if (monster != 0)
{
//Loop until a break statement
for (int i = 0; i < 3; i++)
{
//Display Hitpoints
mainPlayer.displayHitPoints();
monster[i].displayHitPoints();
std::cout << std::endl;
//Players turn to attack first
**bool runAway = mainPlayer.attack(monster, mainPlayer);** // Crash happening in this area
if (runAway) // Player flees
{
break;
}
if (monster[i].isDead())
{
mainPlayer.victory(monster->getXPReward(),
monster->getGoldReward());
mainPlayer.levelUp(mainPlayer);
}
break;
//Monster attacks
monster[i].attack(mainPlayer);
if (mainPlayer.isDead())
{
mainPlayer.gameOver();
done = true;
break;
}
}
//Pointer to Monster must destroy created instance of Monster
//to make sure that there is no Memory leak
}
delete monster;
monster = 0;
break;
case 2: // resting
mainPlayer.rest();
monster = gameMap.checkRandomEncounter();
//'monster' not null, start battle script
monster = gameMap.checkRandomEncounter();
//'monster' not null, start battle script
if (monster != 0)
{
//Loop until a break statement
for (int i = 0; i < 3; i++)
{
//Display Hitpoints
mainPlayer.displayHitPoints();
monster[i].displayHitPoints();
std::cout << std::endl;
//Players turn to attack first
bool runAway = mainPlayer.attack(monster, mainPlayer);
if (runAway) // Player flees
{
break;
}
if (monster[i].isDead())
{
mainPlayer.victory(monster->getXPReward(),
monster->getGoldReward());
mainPlayer.levelUp(mainPlayer);
}
break;
//Monster attacks
monster[i].attack(mainPlayer);
if (mainPlayer.isDead())
{
mainPlayer.gameOver();
done = true;
break;
}
}
//Pointer to Monster must destroy created instance of Monster
//to make sure that there is no Memory leak
}
delete monster;
monster = 0;
break;
case 3: // viewing stats
mainPlayer.viewStats();
break;
case 4: // quitting
done = true;
break;
}
}
return 0;
}
and finally the last puzzle piece, the function where the player attacks the Monster(s):
bool Player::attack(Monster Monster[], Player& Player)
{
int ArmorBefore = 0;
int Roll = 0;
int selection = 1;
int i;
if (Monster[0].isFilled() == true)
{
i = 0;
}
else if (Monster[1].isFilled() == true)
{
i = 1;
}
else if (Monster[2].isFilled() == true)
{
i = 2;
}
if (Monster[i].isFilled() == true)
{
std::cout << "1) Attack 2) Run 3) Cast Spell 4) Use Item: ";
std::cin >> selection;
std::cout << std::endl;
switch (selection)
{
case 1: // Player fights
std::cout << " You attack an " << Monster[i].getName()
<< " with a " << mWeapon.mName << std::endl;
if (Random(0, 20) < mAccuracy) // Player hits Monster
{
int damage = Random(mWeapon.mDamageRange);
int totalDamage = damage - Monster[i].getArmor();
if (totalDamage <= 0) // Armor is equal or higher than player atk
{
std::cout << "Your attack failed to penetrate "
<< Monster[i].getName() << "'s armor !"
<< std::endl;
return false;
}
else // Attack is higher than Monsters armor
{
std::cout << "You hit " << Monster[i].getName()
<< " for " << totalDamage << " damage !"
<< std::endl;
// Subtract dmg from Monsters hp
Monster[i].takeDamage(totalDamage);
return false;
}
}
else // Player Misses
{
std::cout << "You miss !" << std::endl;
}
std::cout << std::endl;
return false;
break;
case 2: // Player runs with a 25% chance
Roll = Random(1, 4);
if (Roll == 1) // Success
{
std::cout << "You run away !" << std::endl;
return true; // <- Return out of the function
}
else
{
std::cout << "You failed to escape !" << std::endl;
return false;
}
case 3: // Casting Spell
{
int SpellSelect;
// Spells for the Fighter
if (Player.mClassName == "Fighter")
{
std::cout << std::endl;
std::cout << "1) Shield 2) Mighty Blow: ";
std::cin >> SpellSelect;
if (SpellSelect == 1)
{
if (Player.mMagicPoints >= 10) // checks for player mana
{
std::cout << "You cast a mighty shield!"
<< std::endl;
ArmorBefore = Player.mArmor;
Player.shield(Player);
Player.mMagicPoints -= 10;
}
else
{
std::cout << "Not enough Mana" << std::endl;
break;
}
}
else
{
if (Player.mMagicPoints >= 5) // casting Mighty Blow
{
int damage = Random(mMightyBlow.mDamageRange);
std::cout
<< "You strike with all your might ! and Deal "
<< damage << " damage !" << std::endl;
Monster[i].takeDamage(damage);
Player.mMagicPoints -= 5;
return false;
}
else
{
std::cout << "Not enough Mana" << std::endl;
return false;
}
}
}
//Spells for the Wizard
else if (Player.mClassName == "Wizard")
{
std::cout << "1) Fireball";
std::cin >> SpellSelect;
if (Player.mMagicPoints >= 45)
{
int damage = Random(mFireball.mDamageRange);
std::cout << "You cast a Fireball and deal " << damage
<< " damage !" << std::endl;
Monster[i].takeDamage(damage);
Player.mMagicPoints -= 45;
return false;
}
else
{
std::cout << "Not enough Mana" << std::endl;
return false;
}
}
// Spells for the Cleric
else if (Player.mClassName == "Cleric")
{
std::cout << "1) Magic Missile";
std::cin >> SpellSelect;
if (Player.mMagicPoints >= 35)
{
int damage = Random(mMagicMissile.mDamageRange);
std::cout << "You cast a Magic Missile and deal "
<< damage << " damage !" << std::endl;
Monster[i].takeDamage(damage);
Player.mMagicPoints -= 35;
}
else
{
std::cout << "Not enough Mana" << std::endl;
return false;
}
}
}
case 4: // using Item
int invSlot;
std::cout << "1) HP Potion: ";
std::cin >> invSlot;
if (invSlot == 1) // Potion slot
{
if (mHPot.mAmount.size() > 0)
{
std::cout << "You used a Potion and healed for 5 HP !"
<< std::endl;
int currentSize = mHPot.mAmount.size();
mHPot.mAmount.resize(currentSize - 1);
Player.mHitPoints += 5;
if (Player.mHitPoints > Player.mMaxHitPoints)
{
Player.mHitPoints = Player.mMaxHitPoints;
}
return false;
}
else
{
std::cout << "You have no Potions!" << std::endl;
return false;
}
}
else // wrong slot
{
std::cout << "No Item found!" << std::endl;
return false;
}
}
// Clearing stat boosts
if (Player.shield(Player) == true)
{
Player.mArmor = ArmorBefore;
}
}
return false;
}
When I run the game, I sometimes have the problem, that after filling in a Monster in a slot of the array, no battle will be triggered. And if a battle will be triggered, I get a crash with an error report every time, which says:
_CrtlValidHeadPointer(block)
I guess that something with my pointer is not functioning well.... but since I am a beginner I am pretty much stuck. I would be very grateful for some enlightenment :)
This place can potentially call undefined behavior and crash:
int i;
if (Monster[0].isFilled() == true)
{
i = 0;
}
else if (Monster[1].isFilled() == true)
{
i = 1;
}
else if (Monster[2].isFilled() == true)
{
i = 2;
}
/*else // one of solutions
break;*/
//"i" can be unset! and can have any value from INT_MIN to INT_MAX!
if (Monster[i].isFilled() == true) //potentially index over array
{
Also there are memory leaks and undefined behavior with memory management:
Monster* monster = new Monster[3];
...
delete monster
must be delete [] monster
but it is recommended to use smart pointers, vector, array, etc, for memory management
I'm creating a board game (stratego) in c++ and was wondering if it considered a poor practice to return an integer from a class method in order to determine which case in a switch statement to show to the user.
Example:
In stratego, you can't attack board pieces that are part of your own army so I have a message "You cannot attack your own army" when the user tries to do so.
Same thing if a movement is performed that would result in the player jumping off the board, moving too many spaces, etc.
Each of these invalid movements has it's own unique message, but to avoid printing them from the Class.cpp file, which is where the player's moves are validated, I have the Class.cpp file returning an integer to a switch statement in the main() that it was called from.
What is the most recommended way to handle how the messages get called?
class Test
{
public:
Test()
{
}
int Validate_Move(int valid)
{
if (valid > 0 && valid < 5)
{
return 1;
}
else if (valid > 5)
{
return 2;
}
}
};
int main()
{
int entry;
std::cout << "Enter move: ";
std::cin >> entry;
Test obj;
switch (obj.Validate_Move(entry))
{
case 1:
std::cout << "Move is valid" << std::endl;
case 2:
std::cout << "Move is invalid" << std::endl;
default:
std::cout << "Error occured" << std::endl;
}
return 0;
}
There's nothing wrong with that technique. If you want to be more explicit, you could always make an enum
class Test
{
public:
Test() = default;
enum EValidity {eError, eValid, eInvalid};
EValidity Validate_Move(int valid)
{
if (valid > 0 && valid < 5)
{
return eValid;
}
else if (valid > 5)
{
return eInvalid;
}
else
{
return eError;
}
}
};
int main()
{
int entry;
std::cout << "Enter move: ";
std::cin >> entry;
Test obj;
switch (obj.Validate_Move(entry))
{
case Test::eValid:
std::cout << "Move is valid" << std::endl;
break;
case Test::eInvalid:
std::cout << "Move is invalid" << std::endl;
break;
case Test::eError:
std::cout << "Error occured" << std::endl;
break;
default:
assert(false);
}
return 0;
}
I'm following a tutorial for making a MUD (text-based RPG), and I am having issues with my main function. If you'll look at the code, you'll see that when the player moves it will check for a random encounter, and if monster != 0, it will go into the combat loop. When I execute this in the command prompt, it will allow me to attack the monster, but it never makes it to the monster->attack(mainPlayer) function. It just goes back to the screen that states whether I want to move, rest, view stats, or quit. Any help with this would be greatly appreciated!
#include "stdafx.h"
#include "Map.h"
#include "Player.h"
#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;
int main()
{
srand( time(0) );
Map gameMap;
Player mainPlayer;
mainPlayer.createClass();
// Begin adventure
bool done = false;
while( !done )
{
// Each loop cycle we output the player position and
// a selection menu.
gameMap.printPlayerPos();
int selection = 1;
cout << "1) Move 2) Rest 3) View Stats 4) Quit: ";
cin >> selection;
Monster* monster = 0;
switch( selection )
{
case 1:
// Move the player
gameMap.movePlayer();
// Check for a random encounter. This function
// returns a null pointer if no monsters are
// encountered.
monster = gameMap.checkRandomEncounter();
// 'monster' not null, run combat simulation.
if( monster != 0)
{
// Loop until 'break' statement.
while( true )
{
// Display hitpoints
mainPlayer.displayHitPoints();
monster->displayHitPoints();
cout << endl;
// Player's turn to attack first.
bool runAway = mainPlayer.attack(*monster);
if( runAway )
{
break;
}
if( monster->isDead() )
{
mainPlayer.victory(monster->getXPReward());
mainPlayer.levelUp();
break;
}
monster->attack(mainPlayer);
if( mainPlayer.isDead() )
{
mainPlayer.gameover();
done = true;
break;
}
}
// The pointer to a monster returned from
// checkRandomEncounter was allocated with
// 'new', so we must delete it to avoid
// memeory leaks.
delete monster;
monster = 0;
}
break;
case 2:
mainPlayer.rest();
break;
case 3:
mainPlayer.viewStats();
break;
case 4:
done = true;
break;
} // End switch statement
} // End While statement
} // End main function
Here is the Player::attack function:
bool Player::attack(Monster& monster)
{
int selection = 1;
std::cout << "1) Attack 2) Run: ";
std::cin >> selection;
std::cout << std::endl;
switch( selection )
{
case 1:
std::cout << "You attack the " << monster.getName()
<< " with a " << mWeapon.mName << std::endl;
if( Random(0, 20) < mAccuracy )
{
int damage = Random(mWeapon.mDamageRange);
int totalDamage = damage - monster.getArmor();
if( totalDamage <= 0)
{
std::cout << "Your attack failed to penetrate the "
<< monster.getName() << "'s armor." << std::endl;
}
else
{
std::cout << "You attack for " << totalDamage
<< " damage!" << std::endl;
// Subtract from monster's hitpoints.
monster.takeDamage(totalDamage);
}
}
else
{
std::cout << "You miss!" << std::endl;
}
std::cout << std::endl;
break;
case 2:
// 25% chance of being able to run.
int roll = Random(1, 4);
if( roll == 1 )
{
std::cout << "You run away!" << std::endl;
return true; //<-- Return out of the function.
}
else
{
std::cout << "You could not escape!" << std::endl;
break;
}
}
}
And here is the Monster::attack function:
void Monster::attack(Player& player)
{
cout << "A " <<mName << " attacks you "
<< "with a " << mWeapon.mName << std::endl;
if( Random(0,20) < mAccuracy )
{
int damage = Random(mWeapon.mDamageRange);
int totalDamage = damage - player.getArmor();
if( totalDamage <= 0 )
{
cout << "The " << mName << "'s attack failed to "
<< "penetrate your armor." << endl;
}
else
{
cout << "You are hit for " << totalDamage
<< " damage!" << endl;
player.takeDamage(totalDamage);
}
}
else
{
cout << "The " << mName << " missed!" << endl;
}
cout << endl;
}
Your Player::attack() method has only one return-statement: return true;. You forgot to add the final line return false; to your method.
This could have easily been prevented if you enable warnings (and pay attention to them!)
Your Player::attack doesn't return in all cases (specifically when it needs to return false). When the calling function tries to access the return value of Player::Attack it will get junk and so you enter the if(ranAway) block and break out of your while loop