C++ Learning by Making an RPG Playground

robotspacefish

Jess

Posted on December 6, 2020

C++ Learning by Making an RPG Playground

My favorite way to begin learning the syntax of a new language is to make a little RPG-type console game. As part of my quest to learn C++ I made one to fight random enemies.

How the Game Works

The player has 100 health, 10 strength, and can either attack or run. A random enemy will appear for the player to fight. Each enemy type has a different amount of health and their strength is 1/3 of whatever their health is.

This is a sample of how it plays. Each time you defeat an enemy a new random enemy will appear and you repeat until you either die or press 0 to quit.

============================================================

A Slime with 10HP and 3 strength approaches.
You have 100HP and 10 strength.


What would you like to do?
1. Attack
2. Run
0. Quit


Enter your selection: 1


You attack the Slime for 10 damage.

You defeated the Slime and received 8 gold.
You have 8 total gold.

============================================================

A Skelly with 20HP and 6 strength approaches.
You have 100HP and 10 strength.


What would you like to do?
1. Attack
2. Run
0. Quit


Enter your selection: 1


You attack the Skelly for 10 damage.
The Skelly strikes back for 6 damage.

============================================================

The Skelly has 10HP left.
You have 94HP and 10 strength.


What would you like to do?
1. Attack
2. Run
0. Quit


Enter your selection: 1


You attack the Skelly for 10 damage.

You defeated the Skelly and received 18 gold.
You have 26 total gold.

============================================================
Enter fullscreen mode Exit fullscreen mode

Code Explained

As I'm still learning and just playing around, this isn't the most optimized code at all. As I learn more I'll probably keep using this as a playground, adding more features and refactoring.

Enums are cool

I briefly described enumerations in my previous post on C++ data types.

They basically allow you to use names instead of integers, but the names still represent an integer.

I used an enum to represent my character types:

enum characterType { PLAYER, OGRE, GOBLIN, SLIME, SKELLY };

Each type between the braces equals an integer that corresponds to their location: PLAYER = 0, OGRE = 1, etc.

Then I created a function to return a random integer between 1 and 4. I didn't want to return 0 because that would give me PLAYER. I actually didn't use PLAYER (yet), but I have it there just in case.

int randomEnemyType() 
{
    return rand() % 4 + 1;
}
Enter fullscreen mode Exit fullscreen mode

This way I could get a random number and pass it into other functions, like this one:

int getHealthByType(int type) 
{
    switch (type) 
    {
        case OGRE:
            return 40;
        case GOBLIN:
            return 30;
        case SKELLY:
            return 20;
        case SLIME:
            return 10;
        default:
            return 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

If type is equal to 1 it will return 40 for OGRE, since OGRE = 1.

I also created another enum: enum moveType { ATTACK = 1, RUN }. When the player is prompted to enter their selection, if they select 1 it corresponds to ATTACK, and 2 corresponds to RUN.

Enemy Class

I created the class Enemy which has 3 member variables: health, type, and gold. Since these are private I have some getter functions to retrieve the data when needed. When a new Enemy object (or instance) is created, the constructor runs and type is set from the randomEnemyType function, health is set by passing type into getHealthByType, and gold is set randomly with rand() % 20, which is the range 0 - 19.

class Enemy
{
public:
    Enemy()
    {
        type = randomEnemyType();
        health = getHealthByType(type);
        gold = rand() % 20;
    }

    void takeDamage(int str) 
    {
        health -= str;
    }

    int getStrength()
    {
        return maxHealth()/3;
    }

    void print()
    {
        std::cout << "Enemy Type: " << getEnemyTypeAsString(type) << ", health: " << health << "\n";
    }

    std::string getTypeString() 
    {
        return getEnemyTypeAsString(type);
    }

    void generateNew()
    {
        type = randomEnemyType();
        health = getHealthByType(type);
        gold = rand() % 20;
    }

    int getHealth() 
    {
        return health;
    }

    bool isDead() 
    {
        return health <= 0;
    }

    int maxHealth() 
    {
        return getHealthByType(type);
    }

    int getGold()
    {
        return gold;
    }

private:
    int health;
    int type;
    int gold;
};
Enter fullscreen mode Exit fullscreen mode

I created the member function generateNew to set the enemy member variables to new values once an enemy is defeated instead of destroying and creating a new instance of Enemy. If I was writing this in JavaScript or Ruby I probably would've created a class method to create the instance but I'm still learning how to work with classes here.

Main

In main I set a few variables for the player: health, stength, and gold. Since enemies also have these attributes I will refactor this. I'm thinking of something like a Character parent class to extend Player and Enemy from.

I also have a variable input of type integer which is set to whatever the user selection is. Then a switch statement handles what happens next. enemy.takeDamage runs, taking in the player's strength. In that Enemy member function the player's strength is subtracted from the enemy's health. If the enemy dies the player collects the enemy's gold and a new enemy is generated. If the enemy still lives it attacks the player and the while loop repeats, allowing the player to attack, run, or quit again.

switch (input) 
{
    case ATTACK:
        std::cout << "\n\nYou attack the " << enemyType << " for " << strength << " damage.\n";
        enemy.takeDamage(strength);

        if (enemy.isDead())
        {
            int enemyGold = enemy.getGold();
            gold += enemyGold;
            std::cout << "\nYou defeated the " << enemyType << " and received " << enemyGold << " gold.\n";
            std::cout << "You have " << gold << " total gold.\n";

            enemy.generateNew();
        } else {
            health -= damage;

            std::cout << "The " << enemyType << " strikes back for " << damage << " damage.\n";

            if (health <= 0) {
                std::cout << "\nYou died.\n";
                return 0;
            }
        }
        break;
    case RUN:
        std::cout << "You are too scared of the " << enemyType << " and you run away.\n";

        enemy.generateNew();
        break; 
}
Enter fullscreen mode Exit fullscreen mode

Bracket Sadness

I'm a fan of brackets because I like to know at a quick glance what belongs to a block of code. Personally, it's my preference to start the opening bracket for functions and blocks on the same line. From what I can tell it seems to be more standard in C++ to start your function/block brace on the following line, at least in Visual Studio and Unreal. I'm not a fan :( but I'm trying to get used to it. I read a point somewhere that someone made (maybe on Stack Overflow) that people tend to put a space between an opening block and the first line of code within it anyway, so why not use the opening bracket for that? That made sense to me, but I'm still going to be grumpy about it until I get used to it. 😆


That's the basic gist of the game so far. It's a pretty fun way to learn a new language. I think next I'll have to dig into arrays and add a player inventory!

Further Reading

Generate random number - rand

Enumerations

💖 💪 🙅 🚩
robotspacefish
Jess

Posted on December 6, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related