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.
#include <iostream> #include "RollingDie.h" #include "Arena.h" #include "Warrior.h" using namespace std; 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 ¨ 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++.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 20x (1.05 MB)
Application includes source codes in language C++