For this challenge, refactor or rewrite the battle simulator from earlier. We want to make our code more organised, by grouping our variables and functions into classes. Other than the use of classes, the following requirements are unchanged from the previous challenge:
The Hero
The Monster
Guidance and Restrictions
Global Variables Note: I'd strongly recommend storing the Monster
and Hero
as global variables for now, and have our functions access them from there. Trying to solve this challenge by passing our objects into functions is unlikely to work, based on the techniques we have learnt thus far. We will resolve this in a future lesson.
The expected result is the monster winning with approximately 25 health remaining. The expected output should be something like:
The Hero is dead! The Monster won!
If you completed the previous challenge, continue on from where you left off. If not, a basic starting point is given below.
#include <iostream>
using namespace std;
// TODO: store objects as global variables here
void Combat() {}
void LogResult() {}
int main() {}
We've expanded the starting point with the classes and objects we might want to use. Also, some comments have been provided, to lay out a rough plan of what we might want to code.
#include <iostream>
using namespace std;
class Hero {
// TODO
};
class Monster {
// TODO
};
Hero Hero;
Monster Monster;
void Combat() {
// TODO: Monster Takes Damage From Hero
// TODO: Hero Takes Damage From Monster
// TODO: Update Monster's damage and stats
}
void LogResult() {
// TODO: check who is dead, or if both are dead
// TODO: log an appropriate message
}
int main() {
// TODO: call the combat function until someone is dead
// Then, call the LogResult function
}
I've updated the above foundation with some more code in the main
and LogResult
function. Some code still needs to be written in those functions (marked with TODO comments).
We also need to provide the implementation of the Combat
function, as well as both of our classes:
#include <iostream>
using namespace std;
class Hero {
// TODO
};
class Monster {
// TODO
};
Hero Hero;
Monster Monster;
void Combat() {
// TODO: Monster Takes Damage From Hero
// TODO: Hero Takes Damage From Monster
// TODO: Update Monster's damage and stats
}
void LogResult() {
if (/* TODO - Are both dead? */) {
cout << "Both are dead - it's a tie!";
} else if (/* TODO: is the monster dead? */) {
cout << "The Monster is dead! The Hero won!";
} else if (/* TODO: Is the hero dead? */) {
cout << "The Hero is dead! The Monster won!";
}
}
int main() {
while (/* TODO: are both alive? */) {
Combat();
}
LogResult();
}
I've completed the main
, Combat
and LogResult
function below. I've also added the headings for all the functions we might need to our classes. All that remains to do is to provide implementations for those functions, and to create some variables within the classes.
I've left TODO comments to guide that process:
#include <iostream>
using namespace std;
class Hero {
public:
int GetDamage() {
// TODO - return the value of a private Damage variable
}
void TakeDamage(int BaseDamage) {
// TODO - reduce health by the appropriate amount
// Remember to consider the Armour value
}
bool isDead() {
// TODO - return true or false depending on the Health value
}
private:
int Health { 500 };
// TODO: set up variables for Damage, Armour
};
class Monster {
public:
int GetDamage() {
// TODO - return the amount of damage the monster does
// This can be calculated from a private Damage variable
// Remember to factor in whether the monster is enraged
}
void TakeDamage(int BaseDamage) {
// TODO - reduce health by the incoming damage amount
}
bool isDead() {
// TODO - return true or false depending on the Health value
}
void ApplyEndOfTurnEffects() {
// TODO - update Damage, and if needed, set us to be enraged
}
private:
// TODO: set up variables for Health, Damage, isEnraged
};
Hero Hero;
Monster Monster;
void Combat() {
Monster.TakeDamage(Hero.GetDamage());
Hero.TakeDamage(Monster.GetDamage());
Monster.ApplyEndOfTurnEffects();
}
void LogResult() {
if (Monster.isDead() && Hero.isDead()) {
cout << "Both are dead - it's a tie!";
} else if (Monster.isDead()) {
cout << "The Monster is dead! The Hero won!";
} else if (Hero.isDead()) {
cout << "The Hero is dead! The Monster won!";
}
}
int main() {
while (!Hero.isDead() && !Monster.isDead()) {
Combat();
}
LogResult();
}
The expected outcome is that the Monster wins, with 25 health remaining. There are many possible ways to code any challenge - one possible solution might look like what is given below:
#include <iostream>
using namespace std;
class Hero {
public:
int GetDamage() {
return Damage;
}
void TakeDamage(int BaseDamage) {
int DamageAfterArmour = BaseDamage * (1-Armour);
Health -= DamageAfterArmour;
}
bool isDead() { return Health <= 0; }
private:
int Health { 500 };
int Damage { 75 };
float Armour { 0.25f };
};
class Monster {
public:
int GetDamage() {
return Damage * (isEnraged ? 2 : 1);
}
void TakeDamage(int BaseDamage) {
Health -= BaseDamage;
}
bool isDead() { return Health <= 0; }
void ApplyEndOfTurnEffects() {
Damage += 20;
if (Health <= 200) isEnraged = true;
}
private:
int Health { 400 };
int Damage { 50 };
bool isEnraged { false};
};
Hero Hero;
Monster Monster;
void Combat() {
Monster.TakeDamage(Hero.GetDamage());
Hero.TakeDamage(Monster.GetDamage());
Monster.ApplyEndOfTurnEffects();
}
void LogResult() {
if (Monster.isDead() && Hero.isDead()) {
cout << "Both are dead - it's a tie!";
} else if (Monster.isDead()) {
cout << "The Monster is dead! The Hero won!";
} else if (Hero.isDead()) {
cout << "The Hero is dead! The Monster won!";
}
}
int main() {
while (!Hero.isDead() && !Monster.isDead()) {
Combat();
}
LogResult();
}
Great job if you completed the above challenge! Whichever solution you used, you may have noticed things ended up a bit untidier than would be ideal.
If you used two classes, like I did in the solution provided, you likely found yourself writing similar, or even identical code, in both of the classes.
If you tried to do the challenge with a single class, the functions may have got a little bit more complicated than you'd ideally have liked, as the Monster and the Hero didn't quite have the same behaviours.
Either of these are problems - duplicate code and unnecessary complexity are both things we want to avoid.
In the next chapter, we will introduce the next principle of object oriented programming - the concept of inheritance. This will allow us to organise and connect our classes in such a way that we can solve these problems.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way