Let's Make: BattleGame! [Part 2 - The Meat]

Published May 07, 2013
Advertisement
[color=#ff8c00]

Introduction!

[/color]
Hello again, people! biggrin.png

In Part 2, we're going to fill out all the .cpp files we made last tutorial. These are the meat and potatoes, the thing that makes it all run, what'll give us our game (mostly)!
Hopefully the last tutorial was easy to follow, and you've already prepared the headers for everything we'll be doing here.


[color=#ff8c00]

Coding Time!

[/color]
Alright, first off, we're going to create the attack types we defined in Being.h. Open up the Being.cpp you made yesterday, and type this:#include "Being.h"using namespace std;void Being::meleeAttack(Being& target) { target.health -= baseDamage; cout << name << " dealt " << damage << " damage to " << target.name << "!" << endl; cout << name << "'s HP: " << health << "\n" << endl;}void Being::gunAttack(Being& target) { target.health -= gunDamage; ammo--; cout << "You shot " << target.name << ", dealing " << Damage << " damage!" << endl; cout << "Your HP: " << health << "\n" << endl;}void Being::useHP(Being& target) { short healed = 10; health += healed; potions--; cout << name << " used a health potion, and it heals for " << healed << " health!\n" << endl;}
Everything here should be pretty self-explanatory; in each option, we're damaging the target we defined as a parameter in the methods. For potions and ammo, we're subtracting one from their current ammo/potion count, so that the player doesn't have an unlimited supply.

But we're not done in Being.cpp yet; we still need to define all those 'get' methods, as well as our Reset() method. Underneath our attack types, write this:string Being::getName() { return name;}int Being::getHealth() { return health;}int Being::getAmmo() { return ammo;}int Being::getPotions() { return potions;}void Being::Reset() { health = maxHealth; ammo = maxAmmo; potions = maxPotions;}
This should be self-explanatory as well! For each 'get' method, we're returning whatever value it is that we need. We're doing it this way, so as to keep our stats from being modified outside the entity. We're using the Reset method to bring our player back to how he started. You'll see why a bit later.


[color=#ff8c00]

Monsters Are Not People.

[/color]
Now that we've got Being.h written up, it's time for our Monster and Character to get their meat!

Open up the Character.cpp and type this in there:#include "character.h"Character::Character(std::string newName, int newHealth, int newDamage, int newAmmo, int newPotions) { name = newName; health = newHealth; maxHealth = newHealth; baseDamage = newDamage; gunDamage = 7; ammo = newAmmo; maxAmmo = newAmmo; potions = newPotions; maxPotions = newPotions;}void Character::Display() { std::cout << "You have " << health << " health, " << ammo << " ammo, and " << potions << " potion(s) left." << std::endl;}
Alrighty, so what's going on here? The first method, Character(), is the classes' constructor. Calling this method constructs a new instance/object of Character. The parameters we set in the constructor will be the Characters' stats/values, as you can see.
Our Display method is just to show the player what his or her current status is.

Time for Monster.cpp!#include "Monster.h"Monster::Monster(string newName, short newHealth, short newDamage) { name = newName; health = newHealth; baseDamage = newDamage;}
Hahaha, I get a little laugh at how small the Monster class is. For now, this is all we need to define. Monsters aren't Characters. tongue.png


[color=#ff8c00]

Mortal Combat~.... Again!

[/color]
I felt like reusing our previous title for the Combat part of this. happy.png

Combat is simply the container for all our battle-related options and actions. We're going to have to define our constructor, which will actually be empty but is important because it creates the new instance of our Monster. We'll have to display the results of a particular action, see if the monster/player is dead yet and also give the player their set of options. Lots to do!

Let's write our constructor in Combat.cpp:#include "Combat.h"using namespace std;Combat::Combat(Monster& newM) : M(newM) { }
Even though it's empty, this part is really important; we're creating a new instance of Monster here (newM) and then passing it by reference (M).

Let's move on to our combatChoice(). This will be slightly lengthy, so grab a cup of joe, first. smileys-coffee-578570.gif

Write this code under the constructor:void Combat::combatChoice(Character& C) { bool action = false; //To check for if a player performed an action. while (!action) { //While the player hasn't performed an action... if (C.getHealth() <= 0) { //If they're dead, then cout << "----------------- You died... ------------------" << endl; //Say so cout << "Oh dear, it seems you have died... Game Over." << endl; } else { //Otherwise... cout << "\n\n----------------- Battle Options ------------------" << endl; C.Display(); //Display the player's current status. cout << "What do you want to do? \n" //Give them a list of actions... << "Type the number of the action and press enter. \n" << "[1] Melee Attack \n" << "[2] Gun Attack \n" << "[3] Health Potion" << endl; int choice; //To track what number they submit. cin >> choice; //Let them type a number and hit Enter. switch (choice) { //Create a switch statement to go over the player's options case 1: //If they typed 1 cout << "\n\n----------------- Battle Results ------------------" << endl; C.meleeAttack(M); //Perform the Melee attack type, with the monster as the target action = true; //Mark that we performed an action to end the loop. break; case 2: //If they typed 2 cout << "\n\n----------------- Battle Results ------------------" << endl; if(C.getAmmo() == 0) { //If the player doesn't have anymore ammo... cout << "You're out of ammo! \n" << endl; //Say so. break; //End without anything happening; this will send us to choose another option } else { C.gunAttack(M); //Else, if we have ammo, use the Gun type attack. action = true; //Mark we performed an action! break; } case 3: //And if they typed 3... cout << "\n\n----------------- Battle Results ------------------" << endl; if(C.getPotions() == 0) { //If we're outta potions... cout << "You're out of potions! \n" << endl; //Say so. break; //End early; sends us back to option choosing. } else { C.useHP(C); //Else we'll use a Health Potion on ourselves. action = true; //Mark we performed an action. break; } } } }}
Okay, so I commented on most of what's going on here. Hopefully, you can follow along without too much trouble. We're just going over a loop that allows our players to choose their action. The switch statement switches between different cases based on what the player's typed, or what the value of choice is.

Now, we'll finish up with the last method:void Combat::combat1(Character& C) { while (M.getHealth() > 0 && C.getHealth() > 0) { M.meleeAttack(C); combatChoice(C); } if(M.getHealth() <= 0) { cout << "----------------- You won! -------------------" << endl; cout << "Congratulations, you have killed the monster!" << endl; }}

You could say that this method 'initializes' our combat. The while loop checks to see if both the Monster and Character are still alive, and if they are then we loop though the Monster's meleeAttack on the Character, and the Character's choices for combat. The if statement here is just saying the player won if the Monster is dead.

[color=#ff8c00]

Conclusions.

[/color]

Alright, so we typed a lot today and we're on the threshold of fighting our own monster! In the next Part of this series, we'll be writing the code for Main.cpp that will run all of this and give us a monster to fight.

ph34r.png



I'm planning on releasing Part 3 by tonight, since it'll be pretty quick. The tutorial after Part 3 will be adding dynamic damage, which I will explain at that time. happy.png

Anyhow, thanks for reading, and leave feedback! biggrin.png
2 likes 4 comments

Comments

BeerNutts

You really shouldn't be using srand() more than 1 time, at the start of your game. Otherwise, you have 2 problems:

#1, if an Attack function is called twice within one second, rand() will return the exact same value both times

#2, it disrupts the pseudo-random generator by continually reseeding the rand value.

My other suggestion would be to create a generic Being constructor that sets up the common values between characters and monsters (name, health, etc).

Good luck and have fun

May 07, 2013 05:47 PM
SkylearJ

Thanks for your feedback, BeerNutts!

I actually didn't mean to put the random values in just yet; forgot to edit those out when I copied it from my project. Thanks for pointing those out, 'cause now I can improve my code once more. :P

As for using Being's constructor to construct both Monsters and Characters; I don't know how to apply that. Could you send me a PM with an example if you have the time? ^_^

Thanks again!

May 07, 2013 06:26 PM
Matt-D

Other than my comments for Part 1, here's one more fix -- make sure your constructors use "initialization lists" instead of "assignment", here's why:

http://www.parashift.com/c++-faq/init-lists.html

Regarding (pseudo-)random number generation -- there's an easier (and cleaner) way now (also with better performance and higher quality), see:

http://isocpp.org/blog/2013/03/n3551-random-number-generation

[PDF] http://isocpp.org/files/papers/n3551.pdf

In particular, take a look at the following examples: roll_a_fair_die, roll_2_fair_dice, roll_fair_dice, roll_n_sided_die, pick_a_number (for int and double, easy to adapt for different types).

May 09, 2013 04:13 PM
Tome Steve Srsan

Ive only a beginner myself but i have to say, Thank you for doing this as i'm doing C++ in my spare time and I am loving this :) thank you very much for doing this journal, i will be following along with it all the way though, just keep up the good work :)

May 21, 2013 04:50 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement