First of all I'd like to thank those who gave me advice on how to improve my code in my previous thread. I took a lot of your suggestions and used them to improve my code. Now my code count is a little bit over 800 lines and I could use your help again. The code is below.
Program.cs
using System;
using System.Text;
using Game.Classes;
namespace Game.Program
{
partial class Program
{
static Random random = new Random();
static void Main(string[] args)
{
// Entities for the battle
Entity player = new Entity("Ntvu", 120, 6000, 6500, 550);
Entity dog = new Entity("Dog", 120, 35000, 820, 3800);
Entity cat = new Entity("Cat", 50, 200000, 500000, 250);
// Game loop
while (!GameOver(player, dog, cat))
{
ActivateAilments(player, dog, cat);
MessageStatus(player, dog, cat);
MessageMenu(player, dog, cat);
}
// Display final message and quit
if (player.Dead)
Console.WriteLine("Unfortunately you have been slaughtered. But feel free to try again!");
else
Console.WriteLine("Congratulations! You have defeated your enemies and won the game!");
Console.ReadLine();
}
}
}
Utils.cs
using System;
using System.Text;
using Game.Classes;
namespace Game.Program
{
partial class Program
{
/* Separates text using two sets of newlines, adjust as necessary */
static void Break()
{
Console.Write("\n\n");
}
/* Prompt the user to press the enter key before continuing */
static void Pause()
{
Console.WriteLine();
Console.WriteLine("Press enter to continue");
Console.ReadLine();
Break();
}
/* Keeps a value between 0 and the maximum value for 32-bit integers */
public static int Clamp(int value)
{
return Clamp(value, 0, Int32.MaxValue);
}
/* Keeps a value between a minimum and a maximum */
public static int Clamp(int value, int min, int max)
{
if (value < min)
{
return min;
}
else if (value > max)
{
return max;
}
return value;
}
/* Takes the user's input in Int32 form and handles formatting errors transparently */
static int MenuInput()
{
// Some menu options could use 0 as an option, so we use -1 instead to prevent these kinds of conflicts
int choice = -1;
Console.Write("> ");
// If the call to Int32.Parse() fails, then it will throw a FormatException exception
// The catch statement will catch that exception and resume program execution transparently
// without interrupting anything or displaying any errors, etc.
try
{
choice = Int32.Parse(Console.ReadLine());
}
catch (FormatException)
{
choice = -1;
}
return choice;
}
/* Creates a menu based on the arguments passed, takes the user's input, and returns it */
static int Menu(params string[] menuOptions)
{
int choice = 0;
// Keep looping until we get valid input
while (choice < 1 || choice > menuOptions.Length)
{
Break();
// Display all menu options in list form
for (int menuIndex = 0; menuIndex < menuOptions.Length && menuOptions[menuIndex].Trim().Length > 0; menuIndex++)
{
Console.WriteLine("{0}: {1}", menuIndex + 1, menuOptions[menuIndex]);
}
choice = MenuInput();
}
return choice;
}
/* Creates a target menu for selecting active monsters based on the information in the array of Entity objects passed */
static int TargetMenu(Entity[] targetList)
{
int choice = -1;
while (choice < 0 || choice > targetList.Length)
{
Break();
Console.WriteLine("Select a target: \n");
// Go through all the targets and list their information
for (int targetIndex = 0; targetIndex < targetList.Length; targetIndex++)
{
// Do not list dead targets
if (targetList[targetIndex].Dead)
continue;
Console.WriteLine("{0}: {1} - {2}/{3}", targetIndex + 1, targetList[targetIndex].Name, targetList[targetIndex].Health, targetList[targetIndex].MaxHealth);
}
// Add in a cancel option (0) to exit from the target menu
Console.WriteLine("0: Cancel");
// Take user input
choice = MenuInput();
// If the cancel option was chosen, then return -1 to signal it
if (choice == 0)
{
return -1;
}
// Make sure that the target is not dead
if (choice >= 1 && choice <= targetList.Length)
{
if (targetList[choice - 1].Dead)
{
choice = -1;
continue;
}
}
}
// The indexes were increased by one during display, but in reality they begin at 0 and not 1
return choice - 1;
}
}
}
Messages.cs
using System;
using System.Text;
using Game.Classes;
namespace Game.Program
{
partial class Program
{
/* Displays status (name, health, ailments, etc) for an entity or list of entities */
static void MessageStatus(params Entity[] entityList)
{
foreach (Entity entity in entityList)
{
// Do not list dead entities
if (entity.Dead)
continue;
string ailments = "";
if (entity.Poisoned) { ailments += "Poisoned, "; }
if (entity.Stunned) { ailments += "Stunned, "; }
if (ailments.Length > 0) { ailments = ailments.Substring(0, ailments.Length - 2); }
Console.WriteLine("{0} (Lvl {1}) [{2}] - {3}/{4}", entity.Name, entity.Level, ailments, entity.Health, entity.MaxHealth);
}
Break();
}
/* Display message saying that a target has been attacked */
static void MessageAttack(Entity attacker, Entity target, int attacker_damage)
{
Console.WriteLine("{0} hit {1} for {2}", attacker.Name, target.Name, attacker_damage);
}
/* Display message saying that a target has been killed */
static void MessageDead(string name)
{
Console.WriteLine("{0} was killed.", name);
}
/* The main battle menu where the player selects an option and carries it forward */
static void MessageMenu(Entity player, params Entity[] monsters)
{
_BattleMenu:
// Build menu and get the player's input (option)
int choice = Menu("Attack", "Defend", "Skill", "Potions", "Run");
Break();
switch (choice)
{
case 1:
{
// If there's more than one monster then select a target first
int target = monsters.Length > 1 ? TargetMenu(monsters) : 0;
// If the cancel button was pressed (return value of -1) then cancel the choice
if (target == -1)
{
goto _BattleMenu;
}
DoAttack(player, monsters[target]);
break;
}
case 2:
{
// It doesn't matter which monster is targeted, either way the player can't damage anything
DoDefense(player, monsters[0]);
break;
}
case 3:
// display skill list
break;
case 4:
// display item list
break;
case 5:
Environment.Exit(0);
break;
}
Pause();
}
}
}
Battle.cs
using System;
using System.Text;
using Game.Classes;
namespace Game.Program
{
partial class Program
{
/* Calculates the damage based on the attacker's attack, target's defense and random variables */
static int CalculateDamage(int attack, int defense)
{
// Damage = (attack * 2) + (extra damage of 0 to 1/3 the attack) - (defense)
return Clamp(attack * 2 + random.Next(0, attack / 3) - defense);
}
/* Determines whether game is over or not, returns true if it is, otherwise false */
static bool GameOver(Entity player, params Entity[] monsters)
{
// If the player died then it's an immediate game over
if (player.Dead)
return true;
int deadMonsters = 0;
foreach (Entity monster in monsters)
{
if (monster.Dead)
deadMonsters++;
}
// If all monsters are killed, then it's also game over
if (deadMonsters == monsters.Length)
{
return true;
}
return false;
}
/* Checks for ailments and if activated, uses their effects */
static void ActivateAilments(params Entity[] entityList)
{
foreach (Entity entity in entityList)
{
int damage = 0;
if (entity.Poisoned)
{
// Damage of poison = 1/10 of health
damage = entity.Health / 10;
entity.Damage(damage);
Console.WriteLine("{0} took {1} damage from poison.", entity.Name, damage);
}
if (entity.Stunned)
{
// Damage of stun = 1/2 to 1/50 of health
damage = entity.Health / random.Next(2, 50);
entity.Damage(damage);
Console.WriteLine("{0} took {1} damage from stun.", entity.Name, damage);
}
if (damage > 0)
{
// Pause to alert player that a target has been damaged
Pause();
}
}
}
/* Attack option from the menu - player attacks target and target attacks back */
static void DoAttack(Entity attacker, Entity target)
{
// Calculate attack damage
int attackerAttack = CalculateDamage(attacker.Attack, target.Defense);
int targetAttack = CalculateDamage(target.Attack, attacker.Defense);
// Player attacks the monster and damages it
target.Damage(attackerAttack);
MessageAttack(attacker, target, attackerAttack);
// The target could be dead, check to make sure. If it is then return to prevent it from attacking
if (target.Dead)
{
MessageDead(target.Name);
return;
}
// The monster attacks the player
attacker.Damage(targetAttack);
MessageAttack(target, attacker, targetAttack);
}
/* Defense option from the menu - player defends and target attacks */
static void DoDefense(Entity attacker, Entity target)
{
// Save a copy of the attack and defense before changing it temporarily
int attack = attacker.Attack;
int defense = attacker.Defense;
// When defending, the defense is 5 times as high and attack is lowered to 0 to prevent player from doing damage
attacker.Attack = 0;
attacker.Defense *= 5;
// Message
Console.WriteLine("{0} temporarily increases defense to {1} and decreases attack to 0.", attacker.Name, attacker.Defense);
// Start attack sequence
DoAttack(attacker, target);
// Restore original attack and defense
attacker.Attack = attack;
attacker.Defense = defense;
}
}
}
Item.cs
using System;
using System.Text;
using Game.Program;
namespace Game.Classes
{
/*
* The basic item class.
* All items must derive from this class and implement an override function for Use()
*/
abstract class Item
{
public const int MAX_AMOUNT = 9999;
private string name;
private string description;
private int amount;
private int cost;
// Creates a new item with preset values
public Item(string name, string description, int amount, int cost)
{
Name = name;
Description = description;
Amount = amount;
Cost = cost;
}
// Uses
public abstract void Use(Entity entity);
/* -- Properties -- */
public string Name
{
get { return name; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException("Value cannot be null in property Item.Name");
name = value;
}
}
public string Description
{
get { return description; }
set { description = value; }
}
public int Amount
{
get { return amount; }
set { amount = Program.Program.Clamp(value, 0, MAX_AMOUNT); }
}
public int Cost
{
get { return cost; }
set { cost = Program.Program.Clamp(value); }
}
/* -- Properties -- */
}
}
Weapon.cs
using System;
using System.Text;
using Game.Program;
namespace Game.Classes
{
class Weapon : Item
{
private int attack;
public Weapon(string name, int cost, int attack) : base(name, name + " with attack rate of " + attack.ToString(), 1, cost)
{
Attack = attack;
}
public override void Use(Entity user)
{
// Not exactly sure what to do with this for now...
}
/* -- Properties -- */
public int Attack
{
get { return attack; }
set { attack = Program.Program.Clamp(value); }
}
/* -- Properties -- */
}
}
Armor.cs
using System;
using System.Text;
using Game.Program;
namespace Game.Classes
{
class Armor : Item
{
private int defense;
public Armor(string name, int cost, int defense) : base(name, name + " with defense rate of " + defense.ToString(), 1, cost)
{
Defense = defense;
}
public override void Use(Entity user)
{
// Not exactly sure what to do with this for now...
}
/* -- Properties -- */
public int Defense
{
get { return defense; }
set { defense = Program.Program.Clamp(value); }
}
/* -- Properties -- */
}
}
Potion.cs
using System;
using System.Text;
using Game.Program;
namespace Game.Classes
{
class Potion : Item
{
private int healthRecover;
private int manaRecover;
public Potion(string name, int cost, int amount, int healthRecover, int manaRecover)
: base(name, "Recovers " + healthRecover.ToString() + " HP and " + manaRecover.ToString() + " MP", amount, cost)
{
HealthRecover = healthRecover;
ManaRecover = manaRecover;
}
// Call potion.Use(
public override void Use(Entity user)
{
// Do not use if there is none left
if (Amount == 0)
return;
user.Heal(HealthRecover);
Amount--;
Console.WriteLine("{0} drank the {1} and recovered {2} HP", user.Name, Name, HealthRecover);
}
/* -- Properties -- */
public int HealthRecover
{
get { return healthRecover; }
set { healthRecover = Program.Program.Clamp(value); }
}
public int ManaRecover
{
get { return manaRecover; }
set { manaRecover = Program.Program.Clamp(value); }
}
/* -- Properties -- */
}
}
Entity.cs
using System;
using System.Text;
using Game.Program;
namespace Game.Classes
{
/*
* The entity class represents a character.
* Eg: Players, monsters, etc.
*/
class Entity
{
public const int MAX_LEVELS = 120;
private string name;
private int level;
private int health, maxHealth;
private int mana, maxMana;
private int attack, defense;
private bool poisoned, stunned;
private Weapon weapon = new Weapon("Brass Dagger", 50, 60);
private Armor armor = new Armor("Brass Set", 150, 45);
/* Creates an entity with preset values */
public Entity(string name, int level, int health, int attack, int defense)
{
Name = name;
Level = level;
MaxHealth = health;
Health = health;
Attack = attack;
Defense = defense;
}
/* Increases health */
public void Heal(int heal)
{
Health += Program.Program.Clamp(heal, 0, Int32.MaxValue);
}
/* Decreases health */
public void Damage(int damage)
{
Health -= Program.Program.Clamp(damage, 0, Int32.MaxValue);
}
/* -- Properties -- */
public string Name
{
get { return name; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException("Value cannot be null in property Entity.Name");
name = value;
}
}
public int Level
{
get { return level; }
set { level = Program.Program.Clamp(value, 1, MAX_LEVELS); }
}
public int Health
{
get { return health; }
set { health = Program.Program.Clamp(value, 0, maxHealth); }
}
public int MaxHealth
{
get { return maxHealth; }
set { maxHealth = Program.Program.Clamp(value); }
}
public int Mana
{
get { return mana; }
set { mana = Program.Program.Clamp(value, 0, maxMana); }
}
public int MaxMana
{
get { return maxMana; }
set { maxMana = Program.Program.Clamp(value); }
}
public int Attack
{
get { return attack + weapon.Attack; }
set { attack = Program.Program.Clamp(value); }
}
public int Defense
{
get { return defense + armor.Defense; }
set { defense = Program.Program.Clamp(value); }
}
public bool Poisoned
{
get { return poisoned; }
set { poisoned = value; }
}
public bool Stunned
{
get { return stunned; }
set { stunned = value; }
}
public bool Dead
{
get { return health <= 0; }
}
public Weapon Weapon
{
get { return weapon; }
set
{
if (!(value is Weapon) || value == null)
throw new Exception("Value is not valid in property Entity.Weapon");
weapon = value;
}
}
public Armor Armor
{
get { return armor; }
set
{
if (!(value is Armor) || value == null)
throw new Exception("Value is not valid in property Entity.Armor");
armor = value;
}
}
/* -- Properties -- */
}
}