So, down to business. First we need to factor out some characteristics from the Monster and Character classes. What do they both have? An attack method, health and name variables, etc. So we put those in our Being.h. We will also add several more variables that will allow for further growth in the game, such as magic and a more complex attack system.
#ifndef Being_h
#define Being_h
#include <iostream>
using namespace std;
class Being
{public:
string name;
int attackBonus;
int range;
float attackMod;
int health;
int healthMax;
int mp;
int mpMax;
int arrows;
void attack(Being& target);
void rangedAttack(Being& target);
void heal();
};
#endif
Now this is a bit of a jump ahead, so I’ll explain what each of these variables mean in the implementation. The first part is the attack method, which will definitely be the most complicated.
#include "Being.h"
void Being::attack(Being& target)
{
int damage = (int)(attackMod*( ( (rand()%100)*(range+1) /100) +attackBonus));
target.health -= damage;
cout << name << " attacks " << target.name << " doing " << damage << " damage!" << endl;
cout << target.name << "'s health: " << target.health << endl;
}
The money is all in the one line with a bunch of math in it. We are attacking a Being instead of a specific class like Character or Monster, so this will enable Monsters to attack each other without having to write another attack method. This line will combine all our combat variables, attackMod, range, and attackBonus, to figure out how much damage the Being inflicts. Let’s break this down.
(rand()%100) //get random number from 0-99
(rand()%100) *(range+1) //multiply that number by range+1
((rand()%100) *(range+1)) /100 //divide that number by 100
int damage = (int)(attackMod*( ( (rand()%100)*(range+1) /100) //multiply by attackMod, parse to int, then assign the value to the damage variable
[code]
Then we subtract the damage from the other Being’s health. The rest is basically output of variables about the whom attacked whom, how much damage was done, and how much health the target now has left. The beauty of this is that it allows us to easily tell how much damage a Being can do. Their damage will be between attackBonus and attackBonus+range. So if a Monster had an AB of 3 and a range of 2, it would do 2-5 damage. The attackMod method will be used later for casting spells that either double or halve a Being’s attack power.
We will keep the rangedAttack method simple, instead of having attackBonus and range variables for the ranged attack as well, we will just use fixed variables. (later, you can implement the attack system for ranged attack if you like. I didn’t include it in the tutorial because it would just be a repetition of code and add more variables to keep track of.)
[code]
void Being::rangedAttack(Being& target)
{
if (arrows>0)
{
arrows--;
int damage=rand()%8;
if (damage==0)
cout << name << “ misses “ << target.name;
else
{
if (damage==7)
damage=15;
damage*= attackMod;
target.health -= damage;
cout << name << " shoots " << target.name << " doing " << damage << " damage!" << endl;
cout<< target.name << "'s health: " << target.health << endl;
}
}
else
cout << name << " is out of arrows!" << endl;
}
If we still have arrows, we decrement the arrow count and assign damage a random value from 0-7. If that value is zero, the target was missed and we tell the user. Otherwise, if the damage was 7, the highest possible value, we consider that a critical hit and up damage to 15. We multiply damage by attackMod, subtract from the target’s health, and perform some output about what happened.
And last we throw in a heal method, which just assigns the health and mp value to their maximums. We will use this with spells and stuff later.
void Being::heal()
{
health=healthMax;
mp=mpMax;
}
Now that we’ve done all this work in Being, it greatly simplifies our Character and Monster classes, as now all they need is just constructors, and maybe a method to display things about them.
So all our Monster.h has is a constructor:
#ifndef Monster_h
#define Monster_h
#include "Being.h"
#include <iostream>
using namespace std;
class Monster : public Being
{
public:
Monster(string, int, int, int);
};
#endif
We could’ve included <iostream> and namespace std only in the Being class, as it is included in all the other classes, but it doesn’t hurt to put it in all the classes, and it can save some trouble in the testing process.
Monster.cpp
Monster::Monster(string newname, int newHealth, int newAttackBonus, int NewRange)
{
name=newname;
health=newHealth;
attackBonus=newAttackBonus;
range=NewRange;
attackMod=1;
arrows=10;
}
The constructor just sets the instance variables to its arguments. We don’t actually need the arrows variable as we won’t implement Monster ranged attacks in this tutorial. (but it’s there anyway!
Character.h is very similar, with just one other method:
#ifndef Character_h
#define Character_h
#include "Being.h"
#include <iostream>
using namespace std;
class Character : public Being
{
public:
Character(string, int, int, int, int, int);
void display();
};
#endif
The character constructor takes a string that will be the Character’s name, as well as the other variables are going to be the Character’s stats. We will allow the user to pick some of their Character’s statistics at the beginning of the game and use those to create the Character.
#include "Character.h"
Character::Character(string newname, int newAttackBonus, int newArrows, int newMp, int newHealth, int newRange)
{
name=newname;
health=healthMax=newHealth;
mp=mpMax=newMp;
arrows=newArrows;
attackMod=1
attackBonus=newAttackBonus;
range=newRange;
}
void Character::display()
{
cout << name << " health: " << health << " arrows: " << arrows << endl;
}
The Character constructor has one more int in it for the mp values.
Our Combat headers and .cpp files remain the same as before, except we add a health check to the combatChoice function (which I forgot in the last tutorial
#ifndef Combat_h
#define Combat_h
#include <iostream>
using namespace std;
#include "Character.h"
#include "Monster.h"
class Combat
{public:
Monster& M;
Combat(Monster&);
void combatChoice(Character& C);
void combat1(Character& C);
};
#endif
#include "Combat.h"
#include "Monster.h"
#include "Character.h"
Combat::Combat(Monster& newM) : M(newM)
{
}
void Combat::combatChoice(Character& C)
{
if (C.health>0)
{
C.display();
cout << "What do you do? 1 attack, 2 fire arrow" << endl;
short choice;
cin >> choice;
switch (choice)
{
case 1:
C.attack(M);
break;
case 2:
C.rangedAttack(M);
break;
}
}
else
cout << C.name << " is dead!" << endl;
}
void Combat::combat1(Character& C)
{
while (M.health>0 && C.health>0 )
{
M.attack(C);
combatChoice(C);
}
if (M.health<0)
cout << "Congratulations! You killed the monster!" << endl;
if (C.health<0)
cout << "YOU HAVE DIED! GAME OVER" << endl;;
}
So now onto our last file and addition in main. (Since both Monster and Character inherit from Being now, they don’t specify each other as the target of their attack method and specify Being instead. So we don’t need our little main.h hack this time around.)
The only addition we make to main is a function chooseCharacterStats which allows the users to pick the stats of their Character. (you never would’ve guess, would you?)
#include <iostream>
using namespace std;
#include "Monster.h"
#include "Character.h"
#include "Combat.h"
Character chooseCharacterStats()
{
string n;
int range=1, attackBonus=2, arrows=5, health=100, mp=10, choice;
cout << "What is your name?" << endl;
cin >> n;
cout << "Your current stats are, attackBonus: " << attackBonus << " arrows: " << arrows << " mp: "<< mp;
cout << " health: " << health << " range: " << range << endl;
cout << "you have 5 tokens which you can spend on increasing your Character's statistics. You can"
<< "spend 1 for 1 attack-bonus, 3 arrows, 4 mp, or 10 health. You can also spend 2 to get 3 range" << endl;
cout << "Choose 1 for attack bonus, 2 for arrows, 3 for mp, 4 for health, and 5 for range." << endl;
for (int tokens=5; tokens>0; tokens--)
{
cout << "tokens: " << tokens << endl;
cin >> choice;
switch (choice)
{
case 1:
attackBonus++;
cout << "attackBonus: " << attackBonus << endl;
break;
case 2:
arrows+=3;
cout << "arrows: " << arrows << endl;
break;
case 3:
mp+=4;
cout << "mp: " << mp << endl;
break;
case 4:
health+=10;
cout << "health: " << health << endl;
break;
case 5:
if (tokens>2)
{
tokens--;
range+=3;
cout << "range: " << range << endl;
}
else
{
cout << "Not enough tokens!" << endl;
tokens++;
}
break;
default:
cout << "Please choose a number from 1 to 5" << endl;
break;
}
}// end for loop
cout << "Congratulations, you may now set forth into the world, young adventurer." << endl;
return Character(n,attackBonus,arrows,mp,health, range);
}
Monster goblin("goblin",50,2,1);
int main (int argc, char * const argv[]) {
Character C=chooseCharacterStats();
Combat combat(goblin);
combat.combat1(C);
return 0;
}
This looks complicated, but it really is just repetitive code to do simple input output. In our chooseCharacterStats function, we first declare a string n to hold the Character’s name, the normal stats, and a choice variable to hold user input. We get the user’s desired name, then output the stat-selection process.
We enter a loop which continues until tokens becomes 0. We will use similar input and switch statements for many menus in our game. The token count is shown, and we act on the user’s input. The first 4 cases simply increment the chosen variable and display the result. case 5 checks to see if the user has 2 tokens, and if they do it increments range and decrements tokens. Since the loop decrements tokens by one as well, we will effective have reduced tokens by 2 by -- ing in the switch as well. If the user doesn’t have enough tokens, we tell them and ++ their token count, to level it out when the loop -- ‘s it. We make a Character out of their chosen stats, and return it. The other little bit of main is the same as last time.
You might want to comment out this feature when testing the executable a lot, because it gets annoying when you have to pick the stats every time you start it.
So that’s all I have for you today, I hope you enjoyed and found the tutorial helpful! Any and all comments and suggestions are welcome, so please post them!





MultiQuote






|