Hello and welcome to Lesson 11 of Learn to Program with C#: From a Gaming Perspective
This
lesson is a carry over from our previous lesson. We're still working
with our RpgTutorial game and hopefully you tried making 3 new methods.
One to print our choices, one to process the choices and one to do the
damage.
3 new Methods
So
your methods may differ greatly from mine. That's quite alright.
Depending on how you figure damage should be calculated, when things
should happen, you'll get different answers. I'll go ahead and list
mine now.
string userchoice;
int damage;
Random rand;
First I added 3 new variables to our class.
string PrintChoice()
{
string choice;
Console.WriteLine();
Console.Write(@"
_____________________
Please choose an action:
(A)ttack:
(D)efend:
_____________________");
Console.WriteLine();
choice = Console.ReadLine();
return choice;
}
Now
we have our PrintChoice() method. I made it return a string at the same
time to keep our battle loop clean. The WriteLines() just keep our
output looking nice.
void ProcessChoice(string choice,Hero hero, Monster monster)
{
switch (choice)
{
case "A":
case "a":
Console.WriteLine();
Console.WriteLine("{0} attacks!",hero.Identifier);//print what it is
DealDamage(hero, monster);//send it to our damage method
monster.CurrentHealth -= damage;//take it from the monster
Console.WriteLine("{0} hits the {1} for {2}hp of damage"
, hero.Identifier, monster.Identifier, damage);//show result
break;
case "D":
case "d":
Console.WriteLine();
Console.WriteLine("{0} defends!", hero.Identifier);
//we will make defending more beneficial later
//Many times you will see this shown like below:
//To Do: Add logic to make defending beneficial
break;
default:
Console.WriteLine("I'm sorry, I didn't recognize that.");
Console.WriteLine();//if the choice isn't valid
choice = PrintChoice();//the we'll keep asking till
Console.WriteLine();//they get it right
ProcessChoice(choice,hero,monster);
break;
}
}
Here
our ProcessChoice() method uses the userchoice string and translates it
into an action. As noted Defend just outputs a string right now.
Default will keep looping till we get a valid choice. Attack shows us
attacking, calls the DealDamage() method, and subtracts the health from
the monster. We are feeding this method a string, and our two
characters. Mainly so that we can pass them onto the DealDamage()
method.
int DealDamage(Hero hero, Monster monster)
{
int max;
int min;
rand = new Random();
max = hero.AttackDamage - monster.Defense;
if (max <= 0)
{
max = 1;//right now this isn't a concern, but this is a bit
//of future proofing our damage mechanism
}
min = (int)(hero.AttackDamage * .8) - monster.Defense;//**I'll explain this one**
if (min <= 0)
{
min = 1;
}
damage = rand.Next(min, max);//calculate the damage
return damage;//send it on back
}
So
our DealDamage method takes both characters and uses the hero's
attackdamage and the monster's defense to figure out how much damage
should be done. max and min are just local variables, so there's no
need to have them anywhere but inside of our method. This line however:
min = (int)(hero.AttackDamage * .8) - monster.Defense;
might
be confusing to you. Without the (int) in front of this, we would get
an error. Why? because the compiler doesn't know for sure wether we
want to keep min an int or wether we are trying to make it a double (not
a valid option by the way). This is what is known as a cast. The goal
here is to use 80% of the attack damage (rounded down of course because
that's how int's roll) so that we have a good min and max to work
with.
All
of this boils down to design choice though. You could use 90% or
calculate it some other way entirely. This is the kind of stuff that
makes or breaks your game. Does it make sense this way? Is it more fun
another way? Do we want to have a method to determine if the hero
misses or not, depending on agility? This is the kind of thing that
gives your game it's character.
If
you add these methods to our game and put PrintChoice and ProcessChoice
into the do loop like below the game will run, albeit one sided.
PrintStatus(hero, monster);
userchoice = PrintChoice();
Console.WriteLine();
ProcessChoice(userchoice, hero, monster);
Console.ReadLine();
Console.Clear();//This clears our screen so the next turn is a fresh screen
What's missing?
There's
still a lot of things we can add. We can add spells (make some new
classes for them, with new options and a new print out menu), we can add
a hit/miss method, we can make defend more beneficial, but what do we need?
Well first we need a new method to determine if isAlive is false, and
then we need some AI or else our game is just a bully beating up a
helpless monster so before we tackle AI let's make a method that takes
health and decides if the character is alive or not. Let's make this
one more universal and just pass it an int and return a bool. Try it on
your own first and compare.
bool CheckHealth(int health)
{
bool alive;
if (health > 0)
{
alive = true;
}
else
{
alive = false;
}
return alive;
}
So
that's a pretty simple method which is useable by either the hero or the
monster which is ideal. When we add it to our loop the whole thing
looks like this:
namespace RpgTutorial
{
class Battle
{
string userchoice;
int damage;
Random rand;
public Battle(Hero hero, Monster monster)
{
Console.WriteLine("{0} is facing a {1}.", hero.Identifier, monster.Identifier);
BattleLoop(hero, monster);
}
public void BattleLoop(Hero hero, Monster monster)
{
do
{
PrintStatus(hero, monster);
userchoice = PrintChoice();
Console.WriteLine();
ProcessChoice(userchoice, hero, monster);
monster.isAlive = CheckHealth(monster.CurrentHealth);
Console.ReadLine();
Console.Clear();//This clears our screen so the next turn is a fresh screen
}
while (hero.isAlive == true && monster.isAlive == true);
}
void PrintStatus(Hero hero, Monster monster)
{
Console.Write(@"
********************************
HP/MaxHP MP/MaxMP
{0}: {1}/{2}hp {3}/{4}mp
{5}: {6}/{7}hp {8}/{9}mp
********************************
", hero.Identifier, hero.CurrentHealth, hero.MaxHealth, hero.CurrentMagic, hero.MaxMagic,
monster.Identifier, monster.CurrentHealth, monster.MaxHealth, monster.CurrentMagic, monster.MaxMagic);
}
string PrintChoice()
{
string choice;
Console.WriteLine();
Console.Write(@"
_____________________
Please choose an action:
(A)ttack:
(D)efend:
_____________________");
Console.WriteLine();
choice = Console.ReadLine();
return choice;
}
void ProcessChoice(string choice,Hero hero, Monster monster)
{
switch (choice)
{
case "A":
case "a":
Console.WriteLine();
Console.WriteLine("{0} attacks!",hero.Identifier);//print what it is
DealDamage(hero, monster);//send it to our damage method
monster.CurrentHealth -= damage;//take it from the monster
Console.WriteLine("{0} hits the {1} for {2}hp of damage"
, hero.Identifier, monster.Identifier, damage);//show result
break;
case "D":
case "d":
Console.WriteLine();
Console.WriteLine("{0} defends!", hero.Identifier);
//we will make defending more beneficial later
//Many times you will see this shown like below:
//To Do: Add logic to make defending beneficial
break;
default:
Console.WriteLine("I'm sorry, I didn't recognize that.");
Console.WriteLine();//if the choice isn't valid
choice = PrintChoice();//the we'll keep asking till
Console.WriteLine();//they get it right
ProcessChoice(choice,hero,monster);
break;
}
}
int DealDamage(Hero hero, Monster monster)
{
int max;
int min;
rand = new Random();
max = hero.AttackDamage - monster.Defense;
if (max <= 0)
{
max = 1;//right now this isn't a concern, but this is a bit
//of future proofing our damage mechanism
}
min = (int)(hero.AttackDamage * .8) - monster.Defense;//**I'll explain this one**
if (min <= 0)
{
min = 1;
}
damage = rand.Next(min, max);//calculate the damage
return damage;//send it on back
}
bool CheckHealth(int health)
{
bool alive;
if (health > 0)
{
alive = true;
}
else
{
alive = false;
}
return alive;
}
}
}
No comments:
Post a Comment