Battle Sim II

Upgrade our Battle Sim project to make use of classes, paving the way for future expansion.
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Ryan McCombe
Posted

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

• Starts with 500 health
• Does 75 damage to the monster per round
• Armour Reduces incoming damage by 25%

The Monster

• Starts with 400 health
• Does 50 damage to the hero per round
• Attack damage increases by 20 at the end of each round (eg, first round is 50, 2nd is 70, 3rd is 90)
• While below 50% health, the monster is enraged and does double damage

Guidance and Restrictions

• Solution should use either one or two classes
• Both characters hit each other at the same time
• Only needs techniques covered in this and the first chapter - classes, variables, maths, boolean logic, loops and functions
• Use global variables for our Hero and Monster objects (see global variables note below)
• It's okay to have rounding errors (eg, from integer division)
• Should not require more than 100 lines of code

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!

Starting Point

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: 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
}
// 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
}
// 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() {
cout << "Both are dead - it's a tie!";
cout << "The Monster is dead! The Hero won!";
cout << "The Hero is dead! The Monster won!";
}
}

int main() {
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() {
cout << "Both are dead - it's a tie!";
cout << "The Monster is dead! The Hero won!";
cout << "The Hero is dead! The Monster won!";
}
}

int main() {
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.

Next Lesson

Structs and Aggregate Initialization

Discover the role of structs, how they differ from classes, and how to initialize them without requiring a constructor.
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Classes and Structs
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

• 59 Lessons
• Over 200 Quiz Questions
• 95% Positive Reviews
• Regularly Updated
• Help and FAQ
Next Lesson

Structs and Aggregate Initialization

Discover the role of structs, how they differ from classes, and how to initialize them without requiring a constructor.