Text-based RPG game using classes

I am studying for a degree in “Bachelor of Engineering in Information and Communication Technologies.” I am currently on vacation, just after we started learning C++ at the end of the semester. I wanted to be ahead of the next semester, so I decided to try to use these classes and to make a text-based game.

I would like to know what I could do better. Should I remove or add some class functions? Should I do something differently, maybe not even related to classes?

Main.cpp

#include "MobClass.h"
#include "Player.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <windows.h>

using namespace std;
player battle(player account);
player calcEXP(player account,classMob monster);
player levelUp(player account);
void death();

int main()
{
    string name;
    int option1;
    cout << "Welcome, please enter your name\n";
    cin >> name;
    string location[4] = {"in a hole","in a cave","in the mauntains","in a castle"};
    player account(name,location[0],1,0);
    cout <<"\nWelcome "<<account.getName() << " you find your self " << account.getArea() << "\nand you are not sure how you ended up here\n";
    while (1)
    {
        Sleep(500);
        cout <<"write 1 to walk forward or 2 to walk left or 3 to walk right\n";
        cin >> option1;
        if (option1 >=1 && option1 <=3)
        {
            Sleep(50*(option1));
            srand(time(NULL));
            if (rand() %3 == option1-1){
                account = battle(account);
            }

        }
        else{
            cout << "\n#@#Error#@# Please enter a number between 1 and 3\n\n";
            cin.clear();
            cin.ignore();
        }
    }
    return 0;

}




player battle(player account)
{
    string option;
    string location[4] = {"in a hole","in a cave","in the mauntains","in a castle"};
    string monsters[5][3] = {{"worm","lizard","snake"},{"rat","snake","trolls"},{"Dragon","Dragon","Dragon"},{"Evil knight","The mad king","Joffrey Baratheon"}};
    Sleep(20);
    srand(time(NULL));
    int ranM = (rand() % 3); //random monster
    int ranD = (rand() % 5)+1; //random diff
    classMob monster(monsters[account.getLevel()-1][ranM],account.getLevel(),account.getArea(),ranD);
    cout <<"Suddently you meet a "<< monster.getName() <<", be ready for battle" << "\n";
    Sleep(2000);
    do 
    {
        cout << "\n\n\n ######################################\nHP:"<< account.getHealth() << "                                         "<< monster.getName()<<"HP:"<<monster.getHealth()<<" difficulty:"<<monster.getDifficulty() << "\n";
        cout << "Write A for attack or R for retreat" << "\n";
        cin >> option;
        srand(time(NULL));
        if (option == "R" || option == "r")
        {
            if ((rand() % 2) == 1){
                cout << "retreat sucessfull" << "\n";
                monster.setHealth(0);
            }
            else{
                cout << "retreat failed, the monster get a free attack and you lose 5 health\n";
                account.setHealth(account.getHealth()-5);
                option ="A";
            }
        }
        if (option == "A" || option == "a")
        {
            int attack =rand()%(account.getDamage());
            srand(time(NULL));
            int mobAttack = rand()%(monster.getDamage());
            monster.setHealth(monster.getHealth()-attack);
            account.setHealth(account.getHealth()-mobAttack);
            cout << "you attack the monster for " << attack << " damage\n";
            Sleep(500);
            cout << "the monster counter attacks for " << mobAttack << " damage\n";
            Sleep(500);
        }
    } while (monster.getHealth() >0 && account.getHealth() > 0);
    cout << "\n\n\n ######################################\nHP:"<< account.getHealth() << "                                         "<< monster.getName()<<"HP:"<<monster.getHealth()<<" difficulty:"<<monster.getDifficulty() << "\n";
    if (account.getHealth() <= 0)
    {
        death();
        exit(0);
    }
    account = calcEXP(account,monster);
    return account;
}



void death()
{
    cout << "Sorry you failed your epic quest\n";
}

player calcEXP(player account,classMob monster)
{
    cout << "#########\ncalculating EXP\n#########\n";
    Sleep(500);
    account.setEXP(account.getEXP() + monster.getEXP());
    cout << "EXP: " <<account.getEXP() << "/" << account.getEXPReq() << "\n";
    if (account.getEXP() >= account.getEXPReq())
    {
        levelUp(account);
    }
    return account;
}

player levelUp(player account)
{
    account.setLevel(account.getLevel()+1);
    account.setEXPReq();
    account.setMaxHealth();
    account.setHealth(account.getMaxHealth());
    cout << "Level up! you are now level: " << account.getLevel() << "!\n";
    return account;
}

Player.h

#include <string>
class player
{
public:
    player(std::string,std::string,int,int);
    void setName(std::string);
    void setArea(std::string);
    void setLevel(int);
    void setEXP(double);
    void setHealth(double);
    void setMaxHealth();
    void setDamage();
    std::string getName();
    std::string getArea();
    int getLevel();
    double getHealth();
    double getMaxHealth();
    int getDamage();
    int getEXP();
    void setEXP(int);
    int getEXPReq();
    void setEXPReq();
private:
    std::string playerName;
    std::string playerArea;
    int playerLevel;
    double playerHealth;
    double playerMaxHealth;
    int playerDamage;
    int EXP;
    int EXPReq;
};

player.cpp

#include <string>
#include "Player.h"
player::player(std::string name,std::string area,int level = 1,int EXP = 0)
{
    setName(name);
    setArea(area);
    setLevel(level);
    setEXP(EXP);
    setMaxHealth();
    setHealth(playerMaxHealth);
    setDamage();
    setEXPReq();
}

void player::setName(std::string name)
{
    playerName = name;
}
void player::setArea(std::string area)
{
    playerArea = area;
}
void player::setLevel(int level)
{
    playerLevel = level;
}
void player::setHealth(double health)
{
    playerHealth = health;
}
void player::setMaxHealth()
{
    playerMaxHealth = (100 * getLevel());
}
void player::setDamage()
{
    playerDamage = (30 * getLevel());
}

std::string player::getName()
{
    return playerName;
}

std::string player::getArea()
{
    return playerArea;
}

int player::getLevel()
{
    return playerLevel;
}
double player::getHealth()
{
    return playerHealth;
}
double player::getMaxHealth()
{
    return playerMaxHealth;
}
int player::getDamage()
{
    return playerDamage;
}

int player::getEXP()
{
    return EXP;
}
void player::setEXP(int _EXP)
{
    EXP = _EXP;
}
int player::getEXPReq()
{
    return EXPReq;
}
void player::setEXPReq()
{
    EXPReq = 70+((getLevel()*getLevel())*35);
}

mobclass.h

#include <string>

class classMob
{
public:
    classMob(std::string,int,std::string,int); // name,lvl,area,difficulty
    void setName(std::string);
    void setLevel(int);
    void setArea(std::string);
    void setDamage();
    void setHealth(double);
    void setMaxHealth();
    void setDifficulty(int);
    std::string getName();
    int getLevel();
    std::string getArea();
    int getDamage();
    double getHealth();
    double getMaxHealth();
    int getDifficulty();
    int getEXP();
    void setEXP();
private:
    std::string mobName;
    std::string mobArea;
    int mobLevel;
    int mobDamage;
    double mobHealth;
    double mobMaxHealth;
    int mobDifficulty;
    int EXP;
};

mobclass.cpp

#include <string>
#include "MobClass.h"
classMob::classMob(std::string name,int lvl,std::string area,int difficulty)
{
    setName(name);
    setLevel(lvl);
    setArea(area);
    setDifficulty(difficulty);
    setDamage();
    setMaxHealth();
    setHealth(mobMaxHealth);
    setEXP();
}

void classMob::setName(std::string name)
{
    mobName = name;
}

void classMob::setLevel(int level)
{
    mobLevel = level;
}

void classMob::setArea(std::string area)
{
    mobArea = area;
}

void classMob::setDifficulty(int difficulty)
{
    mobDifficulty = difficulty;
}

void classMob::setDamage()
{
    mobDamage = (3 *( getLevel())+((getDifficulty()*getLevel())/2));
}

void classMob::setHealth(double health)
{
    mobHealth = health;
}
void classMob::setMaxHealth()
{
    mobMaxHealth = (15 *(getDifficulty() + getLevel()));
}
std::string classMob::getName()
{
    return mobName;
}

int classMob::getLevel()
{
    return mobLevel;
}
std::string classMob::getArea()
{
    return mobArea;
}
int classMob::getDifficulty()
{
    return mobDifficulty;
}
int classMob::getDamage()
{
    return mobDamage;
}
double classMob::getHealth()
{
    return mobHealth;
}
double classMob::getMaxHealth()
{
    return mobMaxHealth;
}

int classMob::getEXP()
{
    return EXP;
}

void classMob::setEXP()
{
    EXP = (getLevel() * 35);
}

Answer

  • Try not to get in the habit of using using namespace std. Read this for more information.

  • For clarity, have your #includes organized. Read this blog post or this answer for more information.

  • Add a newline between each section of code. For instance, separate all user input and loops. For variables, it’s best to initialize them late as late as possible in case the function needs to terminate prematurely. Again, keep them with the corresponding code.

  • mobclass.h already includes <string>, so you don’t need to include it again in the .cpp file.

  • You have a lot of accessors and mutators. Since these are short one-line implementations, you can define them in the header like this:

    void setEXP() {EXP = (getlevel() * 35;}
    int getEXP() const {return EXP;}
    

    As such, you will no longer need to implement these in the .cpp file. When they’re in the header, they’ll automatically be inline. It should also make it easier if you ever need to implement newer functions. In the header, you could also keep the accessors and mutators together for clarity.

  • I like what @Kaivo Anastetiks said about classMob‘s constructor, but I would like to add on that a bit. You have a few options for this:

    1. keep it in the .cpp file (with those changes)
    2. put it in the header (with the classMob:: part removed)
    3. put it under the class declaration in the same file
  • It’s better to use getline() instead of cin for getting an std::string value from the user:

    getline(std::cin, name);
    
  • srand() should ONLY be called ONCE in the program, preferably at the top of main(). If you keep it as is, rand() will be “not-so-random” because the seed will keep resetting to 0.

  • That really long output line in battle()‘s do-while loop could be wrapped so that it doesn’t extend out that far.

  • For death(): there’s no need to have a function just output a message. Either have it do something else relevant, or just remove it.

  • For the player’s death (in general): I would prefer the function to fall back to main() instead of explicitly exiting. This is because:

    1. it’s clearer to let main() terminate the program whenever possible
    2. it could be hard to tell where the player’s death is determined

    You would then need to change main()‘s loop to handle this. You could even have battle() return a bool to indicate the battle outcome (the player has won or has lost). Try this at the end:

    account = calcEXP(account, monster);
    
    if (account.getHealth() <= 0)
        return false;
    
    return true;
    

    You could even create a bool member function for determining if the player’s health was depleted:

    bool healthDepleted() const {return playerHealth <= 0;}
    
  • If you’re just mutating data members in calcEXP() and levelUp(), they don’t need to return anything. Just make those functions void.

  • Your “saving” problem is due to your functions receiving the objects by value. It should be received by reference instead. You were only passing in a copy and modifying it, only to have those changes discarded each time those functions ended. This change will allow you to modify the original objects:

    bool battle(player &account);
    void calcEXP(player &account, classMob &monster);
    
  • After looking at mobClass‘s definition, it appears that you may not need those mutators. You should consider a mobClass instance as an individual monster, just as a player is just one player. As such, you just need to construct each mobClass once with the default stats. The accessors are still okay.

  • Once again, I forgot about this: create a Game class. Since the human player doesn’t need to know how the game’s internal mechanisms work, you would no longer need those extra functions in the driver. Instead, main() will create a Game and the class will handle the rest. Here’s (roughly) what main() could look like:

    int main()
    {
        std::srand(std::time(NULL));
    
        Game game;
        game.play();
    }
    

    That may be too little for main(), but the idea is that Game will handle everything. Every function in Game, except for play(), should be private. Game should contain a player as a data member and instantiated in Game‘s constructor. If you end up implementing your map idea (or anything similar), that would be instantiated in the constructor as well. You may still keep those extra driver functions, but they should be called in play() instead.

    As for classMob, you could create an std::vector of objects (you cannot predict how many monsters will “spawn” before the player dies). New monsters would be added and, when killed by the player, removed. If you maintain a counter, you could even track the number of monsters killed before defeat.

Attribution
Source : Link , Question Author : Sumsar1812 , Answer Author : Community

Leave a Comment