Improved my C# code and need advice on how to go from here

Started by
0 comments, last by Ntvu 14 years, 2 months ago
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 -- */
    }
}


Advertisement
Again, please point out anything you find that detracts from the quality of the code, the good points, the bad points, what I could improve on. Also it would be nice if you could rate the code out of 10 (1 = worst, 10 = best) and also give a light evaluation of my programming skills, such as my strengths and weaknesses, etc.

------

I'm also going to post an idea I had before. I'm not exactly sure whether it's a good idea to do it though.

I was thinking about making a Battle class to handle battles (if I choose to do this I will rename the namespace to something else). Do you think this would be a good idea? I was thinking I could have methods inside class Battle such as:

PreCycle - Handles things that occur before actions can be made (such as ailment effects like poison, stun, and other messages)

MainCycle - Handles the actual battle. In this stage the player chooses an option, chooses a target if he's attacking and attacks the monster. Monster attacks back after he attacks.

PostCycle - For things that happen after the player and all monsters have attacked.

And of course all these methods will be private as they'll be handled by the main loop. My idea was something like this:

// Battle.csclass Battle{    Entity player = null;    Entity[] monsters;     Random random = new Random();    ...    public void Start(Entity p, params[] Entity m)      {        player = p;         monsters = new Entity[m.Length];         for (int c = 0; c < m.Length; c++)        {            monsters[c] = m;        }    }    public bool Loop()    {        if (GameOver())   // GameOver is a private function            return false;        PreCycle();        MainCycle();        PostCycle();        return true;    }}// Program.csclass Program{    ...    static void Main()    {        Entity player = ...        Entity monster1 = ...        Entity monster2 = ...        Battle battle = new Battle();        battle.Start(player, monster1, monster2);               while (battle.Loop());        Console.ReadLine();    }}

This topic is closed to new replies.

Advertisement