Lesson 7 - Arena with warriors in C++

In the previous lesson, Arena Warrior - Encapsulation, we created the Warrior class. Our rolling die is already finished from the early lessons. In today's tutorial, we're going to put it all together and create a fully functioning arena. The tutorial is going to be simple and will help you get some more practice on working with objects and algorithmic thinking.

We'll need to write some code that will manage our warriors and print messages to the user. First, we'll figure out where the fighters will actually be declared. In our case, it would be a good idea to put them in a Player class. For starters, let's suppose every player has only one fighter.

Player.h

#include <string>
#include "RollingDie.h"
#include "Warrior.h"
using namespace std;

class Player
{
private:
    Warrior warrior;
    string name;
public:
    Player(string name, RollingDie &die);
    string getName();
    Warrior& getWarrior();
};

Player.cpp

#include "Player.h"
Player::Player(string name, RollingDie &die): warrior(100, 8, 5, die)
{
    this->name = name;
}
string Player::getName()
{
    return this->name;
}
Warrior Player::getWarrior()
{
    return this->warrior;
}

Now let's look at the arena. We're definitely going to need it to simulate a fight of warriors. For simplicity's sake, we'll start with an attacker attacking some other random warrior. At the beginning, we also said that this will be a turn-based game - so we need to remember the number of rounds made. Next, we want to print information about the warriors and the attack. The Arena class will require all of these methods. First, we'll create a field of the int type in the Arena class, name it round and set it to the value of 1 in the constructor. Then, we'll write a print() method to print the information about the arena.

void Arena::print()
{
    cout << "-------------- Arena --------------" << endl;
    cout << "Round: " << this->round << endl;
    cout << "Warriors health:" << endl;
    for (int i = 0; i < this->players_count; i++)
    {
        cout << "\t" << this->players[i]->getName() << ": "; // print warrior name
        if (!this->players[i]->getWarrior().alive()) // determine whether the warriors hasn't died yet
        {
            cout << "dead" << endl;  // if he has, we inform about it
            continue; // and continue with another warrior
        }
        cout << "["; // healthbar start
        // compute the remaining percentage of health the warrior still has
        float health_percent = this->players[i]->getWarrior().getHealth() / this->players[i]->getWarrior().getMaxHealth();
        for (double h = 0; h < 1.0; h += 0.1)
            cout << (h < health_percent ? '#' : ' '); // print health percentage
        // ends the healthbar and prints damage and defense info
        cout << "] (damage: " << this->players[i]->getWarrior().getAttack() << ", defense: " << this->players[i]->getWarrior().getDefense() << ")" << endl;
    }
}

We'll only show the health graphically, in percentage. Therefore, we first calculate the percentage of health the warrior has (the health_percent variable) and then iterate by 10% and write # (if the fighter has it) or a space (if he lost it) - this is done by the ternary operator in the printing.

Console application
-------------- Arena --------------
Round: 1
Warriors health:
        Carl: [##########] (damage: 8, defense: 5)
        Paul: [##########] (damage: 8, defense: 5)
        John: [##########] (damage: 8, defense: 5)

Now we'll move to fight() - the main loop of our game. The fight() method will have no parameters and won't return anything. Inside, there will be a loop calling warrior attacks in rounds and display the information screen and messages. First, we'll make some auxiliary methods (these will be private), which we're going to use later:

Arena.cpp

bool Arena::winnerExists()
{
    return this->countAlive() == 1;
}

int Arena::countAlive()
{
    int alive = 0;
    // for every alive player
    for (int i = 0; i < this->players_count; i++)
        if (this->players[i]->getWarrior().alive())
            alive++; // increment the number of alive by 1
    return alive;
}

The method will tell us how many warriors have survived. If only one of them is alive then he's the winner and the game is over. And now to the promised fight() method:

Arena.cpp

void Arena::fight()
{
    // till the last player standing
    while (!this->winnerExists())
    {
        this->print(); // print player information
        // check all players
        for (int i = 0; i < this->players_count; i++)
        {
            // if the warrior isn't alive, skip him
            if (!this->players[i]->getWarrior().alive())
                continue;
            // it could happen that somebody was killed in the previous round so there may be the last warrior left
            // if this happend, the game is over
            if (this->winnerExists())
                break;
            // computes the index of the closest alive player which we can attack
            int attack_at = (i + 1) % this->players_count;
            while (!this->players[attack_at]->getWarrior().alive())
                attack_at = (attack_at + 1) % this->players_count;
            // attack
            float injury = this->players[i]->getWarrior().attack(this->players[attack_at]->getWarrior());
            // print the fight result
            cout << this->players[i]->getName() << " attacks "
                << this->players[attack_at]->getName() << " for " << injury << " hp" << endl;
        }
        // proceed to the next round
        this->round++;
    }
}

I tried to describe the steps in the comments, so it makes no sense to describe them again in the text. Probably the most problematic is the calculation of the index of the player we'll attack. We start with the warrior at a higher index than the actual warrior (so that the player doesn't attack himself). Then we find out whether this warrior is alive. If it's not, then we'll move to the next warrior. But what to do when we reach the end of the array? We'd like to go back at the beginning (index 0) - this is what the modulo is there for. If we get at 3 (and 3 is the number of players), then modulo automatically decreases the index to 0.

Now we'll just run the application:

main.cpp

#include <iostream>
#include "RollingDie.h"
#include "Arena.h"
#include "Warrior.h"
using namespace std;


int main()
{
    RollingDie die;
    Arena arena(3, die);
    arena.fight();
    arena.print();
    cin.get(); cin.get();
    return 0;
}

If you tried to run the program, it'll run in an infinite loop, but nothing will be changing. As already mentioned, when calling a method, the parameters and return value are copied. This is very important. In player's getWarrior() method, we return the Warrior type. But this means that the calling program won't get our real warrior, but only its copy. We'll correct this by changing the return value to a reference or pointer - in our case, the reference will serve better.

Player.h

class Player
{
private:
    Warrior warrior;
    string name;
public:
    Player(string name, RollingDie &die);
    string getName();
    Warrior& getWarrior();
};

Player.cpp

Warrior& Player::getWarrior()
{
    return this->warrior;
}

Now the program should work to our expectations.

Console application
Enter a player name: Carl
Enter a player name: Paul
Enter a player name: John
-------------- Arena --------------
Round: 1
Warriors health:
        Carl: [###########] (damage: 8, defense: 5)
        Paul: [###########] (damage: 8, defense: 5)
        John: [###########] (damage: 8, defense: 5)
Carl atatcks Paul
Paul attacks John
John attack Carl
-------------- Arena --------------
Round: 2
Warriors health:
        Carl: [########## ] (damage: 8, defense: 5)
        Paul: [########## ] (damage: 8, defense: 5)
        John: [########## ] (damage: 8, defense: 5)
Carl atatcks Paul
Paul attacks John
John attack Carl

Maybe we'd want to see how much health the opponent has taken in the attack. We'll do this simply, we can return the damage that was caused by the Warrior.attack() method. Next, we'd like to see who's the winner at the end of the program. Again, we'll simply add another method to the Arena class, which will print the winner information. As you can see, the OOP approach is very practical and easy to extend.

int main()
{
    RollingDie die;
    Arena arena(6, die);
    arena.fight();
    arena.print();
    arena.printWinner();
    return 0;
}
#ifndef __ROLLINGDIE_H__
#define __ROLLINGDIE_H__

class RollingDie
{
public:
    RollingDie();
    RollingDie(int sides_count);
    ~RollingDie();
    int roll();
    int sides_count;
};
#endif
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "RollingDie.h"

using namespace std;

RollingDie::RollingDie() : RollingDie(6)
{
}

RollingDie::RollingDie(int sides_count)
{
    this->sides_count = sides_count;
    srand(time(NULL));
}

RollingDie::~RollingDie()
{
    cout << "Calling the destructor for the die with " << sides_count << " sides" << endl;
}

int RollingDie::roll()
{
    return rand() % sides_count + 1;
}
#ifndef __WARRIOR_H_
#define __WARRIOR_H_
#include <string>
#include "RollingDie.h"

using namespace std;

class Warrior
{
private:
    float health;
    float max_health;
    float damage;
    float defense;
    RollingDie &die;
public:
    Warrior(float health, float damage, float defense, RollingDie &die);
    bool alive();
    float attack(Warrior &second);
    float getHealth();      // getter
    float getMaxHealth();
    float getAttack();
    float getDefense();
};
#endif
#include "Warrior.h"



Warrior::Warrior(float health, float damage, float defense, RollingDie &die) :
    die(die), health(health), max_health(health), damage(damage), defense(defense)
{}


bool Warrior::alive()
{
    return this->health > 0;
}

float Warrior::attack(Warrior & second)
{
    float defense_second = second.defense + second.die.roll();
    float attack_first = this->damage + this->die.roll();
    float injury = attack_first - defense_second;
    if (injury < 0)
        injury = 0;
    second.health -= injury;
    return injury;
}

float Warrior::getHealth()
{
    return this->health;
}

float Warrior::getMaxHealth()
{
    return this->max_health;
}

float Warrior::getAttack()
{
    return this->damage;
}

float Warrior::getDefense()
{
    return this->defense;
}
#ifndef __PLAYER__H_
#define __PLAYER__H_
#include <string>
#include "RollingDie.h"
#include "Warrior.h"
using namespace std;

class Player
{
private:
    Warrior warrior;
    string name;
public:
    Player(string name, RollingDie &die);
    string getName();
    Warrior& getWarrior();
};
#endif
#include "Player.h"
Player::Player(string name, RollingDie &die) : warrior(100, 8, 5, die)
{
    this->name = name;
}
string Player::getName()
{
    return this->name;
}
Warrior& Player::getWarrior()
{
    return this->warrior;
}
#ifndef __ARENA_H_
#define __ARENA_H_
#include "Player.h"

class Arena
{
public:
    Player** players;
    int players_count;
    int round;
    Arena(int _players_count, RollingDie &die);
    ~Arena();
    void print();
    bool winnerExists();
    int countAlive();
    void fight();
    void printWinner();
};
#endif
#include <iostream>
#include "Arena.h"

Arena::Arena(int _players_count, RollingDie &die) : round(1)
{
    players_count = _players_count; // storing the player count
    players = new Player*[players_count]; // creating an array for the players
    string names[] = {"Carl", "Paul", "John", "Peter", "Simon", "Thomas"};
    for (int i = 0; i < players_count; i++)
    {
        cout << "Enter a player name: " << names[i] << endl;
        players[i] = new Player(names[i], die); // creating the player
    }
}

Arena::~Arena()
{
    for (int i = 0; i < this->players_count; i++)
        delete players[i]; // deleting the players
    delete[] players; // deleting the array
    players = NULL;
}

void Arena::print()
{
    cout << "-------------- Arena --------------" << endl;
    cout << "Round: " << this->round << endl;
    cout << "Warriors health:" << endl;
    for (int i = 0; i < this->players_count; i++)
    {
        cout << "\t" << this->players[i]->getName() << ": "; // print warrior name
        if (!this->players[i]->getWarrior().alive()) // determine whether the warriors hasn't died yet
        {
            cout << "dead" << endl;  // if he has, we inform about it
            continue; // and continue with another warrior
        }
        cout << "["; // healthbar start
        // compute the remaining percentage of health the warrior still has
        float health_percent = this->players[i]->getWarrior().getHealth() / this->players[i]->getWarrior().getMaxHealth();
        for (double h = 0; h < 1.0; h += 0.1)
            cout << (h < health_percent ? '#' : ' '); // print health percentage
        // ends the healthbar and prints damage and defense info
        cout << "] (damage: " << this->players[i]->getWarrior().getAttack() << ", defense: " << this->players[i]->getWarrior().getDefense() << ")" << endl;
    }
}

bool Arena::winnerExists()
{
    return this->countAlive() == 1;
}

int Arena::countAlive()
{
    int alive = 0;
    // for every alive player
    for (int i = 0; i < this->players_count; i++)
        if (this->players[i]->getWarrior().alive())
            alive++; // increment the number of alive by 1
    return alive;
}

void Arena::fight()
{
    // till the last player standing
    while (!this->winnerExists())
    {
        this->print(); // print player information
        // check all players
        for (int i = 0; i < this->players_count; i++)
        {
            // if the warrior isn't alive, skip him
            if (!this->players[i]->getWarrior().alive())
                continue;
            // it could happen that somebody was killed in the previous round so there may be the last warrior left
            // if this happend, the game is over
            if (this->winnerExists())
                break;
            // computes the index of the closest alive player which we can attack
            int attack_at = (i + 1) % this->players_count;
            while (!this->players[attack_at]->getWarrior().alive())
                attack_at = (attack_at + 1) % this->players_count;
            // attack
            float injury = this->players[i]->getWarrior().attack(this->players[attack_at]->getWarrior());
            // print the fight result
            cout << this->players[i]->getName() << " attacks "
                << this->players[attack_at]->getName() << " for " << injury << " hp" << endl;
        }
        // proceed to the next round
        this->round++;
    }
}

void Arena::printWinner()
{
    if (!this->winnerExists())
        return;

    for (int i = 0; i < this->players_count; i++)
        if (this->players[i]->getWarrior().alive())
        {
            cout << endl << "-------------- WINNER --------------" << endl;
            cout << "The winner is: " << this->players[i]->getName() << " with " << this->players[i]->getWarrior().getHealth() << " health" << endl;
            return;
        }
}

Congratulations, if you got up here and really read and understood the tutorials, you have the basics of object-oriented programming and can create reasonable applications :)

We have (at least basically) finished our arena and next time, in the lesson Constant methods in C++, we'll look at constant methods in C++.


 

Download

Downloaded 0x (1.05 MB)
Application includes source codes in language C++

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
Arena Warrior - Encapsulation
All articles in this section
Object-oriented programming in C++
Thumbnail
Next article
Constant methods in C++
Activities (5)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!