18
\$\begingroup\$

A little bit of background; I'm a high school student in my junior year and I attended a tech competition last year when I enrolled in my high school's CS I course. Below is what I submitted to the competition and even though I won in my category, I remember the judges telling me that I could do better by implementing more OOP design and seperate my program into serveral methods/classes.

I don't have much experience with this however, and we are mostly doing review over Java this year in my AP CS course than actually learning new things. Below is my code, and I'd be so thankful if I could get some advice on how to improve it and more specifically, implement methods/classes than having one giant, 900 line class.

import java.util.Scanner;
import java.util.Random;

public class Class1 

{

    @SuppressWarnings("unused")
    public static void main(String[] args) 

    {

        // Main objects

        Scanner console = new Scanner(System.in);
        Random rand = new Random();

        // Game variables

        String[] enemies = { "Kobold", "Kobold Warrior", "Kobold Archer", "Kobold Overseer" };
        String[] shopItems = { "Silver Sword", "Steel Sword", "Iron Helmet", "Iron Chestplate", "Iron Boots", "Iron Gauntlets", "Steel Helmet", "Steel Chestplate", "Steel Boots", "Steel Gauntlets", "Illbane" };
        String randomItem = null;

        int enemyAttackDamage = 25;
        int enemyHealth = 0;

        // Boss Variables

        String[] bossList = { "Red Drake" };

        int redDrakeArmor = 20;
        int redDrakeAttack = 75;
        int redDrakeSpecialAttackValue = 200;

        // Player variables

        int playerHealth = 100;
        int playerAttackDamage = 50;
        int initialPlayerAttack = playerAttackDamage;
        int playerArmorValue = 0;

        int numHealthPotions = 5;                                       // How many potions the player will start with.
        int healthPotionEffect = 30;                                    // How much each potion will heal.
        int healthPotionDropChance = 50;                                // Percentage drop from enemies.

        int numStrengthPotions = 0;
        int strengthPotionEffect = (rand.nextInt(3) + 1);               

        int goldDropChance = 75;                                        // Percentage drop of gold from enemies.
        int goldDropAmount;
        int goldAmount = 1000;

        int crimsonSwordDropChance = 25;

        int buyStrengthPotion;
        int buyHealthPotion;

        int illbaneCount = 4;

        boolean running = true;

        System.out.println("Now entering the Kobolds Lair...");

        GAME:
        while(running) 

        {

            System.out.println("-------------------------------------------------");

            String enemy = enemies[rand.nextInt(enemies.length)];       // Enemy Spawning.

            System.out.println("\t# " + enemy + " appears! #\n");

            if ("Kobold".equals(enemy))                                 // Enemy stat setting.

            {

                enemyHealth = rand.nextInt(100) + 50;

                enemyAttackDamage = 25;

            }

            else if ("Kobold Archer".equals(enemy))

            {

                enemyHealth = rand.nextInt(150) + 70;

                enemyAttackDamage = 30;

            }

            else if ("Kobold Warrior".equals(enemy))

            {

                enemyHealth = rand.nextInt(200) + 90;

                enemyAttackDamage = 40;

            }

            else if ("Kobold Overseer".equals(enemy))

            {

                enemyHealth = rand.nextInt(250) + 150;

                enemyAttackDamage = 50;

            }

            while(enemyHealth > 0)

            {
                // Enemy introduction and presentation of options.

                System.out.println("\tYour HP is: " + playerHealth);
                System.out.println("\t" + enemy + "'s HP: " + enemyHealth);
                System.out.println("\n\tWhat would you like to do?");

                // Player options

                System.out.println("\t1. Attack");
                System.out.println("\t2. Drink health potion");
                System.out.println("\t3. Run!");
                System.out.println("\t4. Drink strength potion");

                String input = console.nextLine();

                if (input.equals("1"))

                {

                    int damageDealt = rand.nextInt(playerAttackDamage);
                    int damageTaken = rand.nextInt(enemyAttackDamage) - (playerArmorValue);

                    enemyHealth -= damageDealt;
                    playerHealth -= damageTaken;

                    if (damageTaken <= 0)
                    {

                        damageTaken = rand.nextInt(5) + 5;

                    }

                    System.out.println("\t> You strike the " + enemy + " for " + damageDealt + " damage.");
                    System.out.println("\t> You recieve " + damageTaken + " in retaliation!");

                    if(playerHealth < 1)

                    {

                        System.out.println("\t> You have taken too much damage, you are too weak to go on!");
                        break;

                    }

                }

                else if (input.equals("2"))

                {

                    if(numHealthPotions > 0)
                    {

                        playerHealth += healthPotionEffect;
                        numHealthPotions--;
                        System.out.println("\t> You drink a health potion and recover " + healthPotionEffect + " health!"
                                           + "\n\t> You now have " + playerHealth + " HP."
                                           + "\n\t> You have " + numHealthPotions + " health potions left.\n)");

                    }

                    else
                    {

                        System.out.println("\t> You have no health potions left! ");

                    }

                }

                else if (input.equals("3"))

                {

                    System.out.println("\tYou run away from the " + enemy + "!");
                    continue GAME; 

                }

                else if (input.equals("4"))

                {

                    int playerAttack = playerAttackDamage;

                    if (numStrengthPotions > 0)

                    playerAttackDamage = playerAttack * strengthPotionEffect;
                    numStrengthPotions--;

                    System.out.println("You drank a strength potion and your attack has multiplied by: " + strengthPotionEffect + " time(s).!");

                }


                else

                {

                    System.out.println("\tInvalid command...");

                }

            }

            if (playerHealth < 1)

            {

                System.out.println("\n\tYou crawl out of the dungeon to live and fight another day.");
                break;

            }

            System.out.println("-------------------------------------------------");
            System.out.println(" # " + enemy + " was defeated! #");
            System.out.println(" # You have " + playerHealth + " HP left. #");

            playerAttackDamage = initialPlayerAttack;

            if(rand.nextInt(100) < healthPotionDropChance)          // Health Potion drop chance; differs from enemy to enemy.

            {

                if ("Kobold Archer".equals(enemy))

                {

                    healthPotionDropChance = 55;

                }

                else if ("Kobold Warrior".equals(enemy))

                {

                    healthPotionDropChance = 60;

                }

                else if ("Kobold Overseer".equals(enemy))

                {

                    healthPotionDropChance = 75;

                }

                numHealthPotions++;

                System.out.println(" # The " + enemy + " dropped a health potion! # ");
                System.out.println(" # You have " + numHealthPotions + " health potion(s). # ");

            }

            if(rand.nextInt(100) < goldDropChance)          // Gold drop chance; also differs from enemy to enemy.

            {

                if ("Kobold Archer".equals(enemy))

                {

                    goldDropChance = 55;

                }

                else if ("Kobold Warrior".equals(enemy))

                {

                    goldDropChance = 60;

                }

                else if ("Kobold Overseer".equals(enemy))

                {

                    goldDropChance = 75;

                }

                goldDropAmount = rand.nextInt(500) + 1;
                goldAmount += goldDropAmount;

                System.out.println(" # The " + enemy + " dropped " + goldDropAmount + " gold! #");
                System.out.println(" # You now have " + goldAmount + " gold. #");

            }

            System.out.println("-------------------------------------------------");            
            System.out.println("What to do now?");          // Next set of options;
            System.out.println("1. Continue fighting");     // 1. Resets loop, keeps progress.
            System.out.println("2. Exit dungeon");          // 2. Breaks loop, progress lost; game over.
            System.out.println("3. Visit the shop");        // 3. Triggers shop sequence.
            System.out.println("4. Sacrifice Illbane...");  // 4. Triggers hard battle.

            String input = console.nextLine();


            while(!input.equals("1") && !input.equals("2") && !input.equals("3") && !input.equals("4"))
            {

                System.out.println("Invalid command...");
                input = console.nextLine();

            }

            if (input.equals("1"))

            {

                System.out.println("You continue on your trek through the dungeon.");

                continue GAME;

            }

            else if (input.equals("2"))

            {

                System.out.println("You exit the dungeon.");
                break;

            }

            else if (input.equals("4") && illbaneCount >= 4)        // Boss Battle Sequence, few special rules...

            {
                    illbaneCount -= 4;

                    System.out.println("-------------------------------------------------");
                    System.out.println("\t# A great beast stirs! #");

                    int redDrakeHealth = 2500;
                    String enemyBoss = bossList[0];

                    System.out.println("\t# " + enemyBoss + " appears! #\n");

                    while(redDrakeHealth > 0)

                    {
                        // Enemy introduction and presentation of options.

                        System.out.println("\tYour HP is: " + playerHealth);
                        System.out.println("\t" + enemyBoss + "'s HP: " + redDrakeHealth);
                        System.out.println("\n\tWhat would you like to do?");

                        // Player options

                        System.out.println("\t1. Attack");
                        System.out.println("\t2. Drink health potion");
                        System.out.println("\t3. Run!");
                        System.out.println("\t4. Drink strength potion");

                        String inputBossFight = console.nextLine();

                        if (inputBossFight.equals("1"))

                        {

                            int bossDamageDealt = rand.nextInt(playerAttackDamage) - (redDrakeArmor);
                            int bossDamageTaken = rand.nextInt(redDrakeAttack) - (playerArmorValue);

                            redDrakeHealth -= bossDamageDealt;
                            playerHealth -= bossDamageTaken;

                            if (bossDamageTaken <= 0)
                            {

                                bossDamageTaken = rand.nextInt(20) + 10;

                            }

                            System.out.println("\t> You strike the " + enemyBoss + " for " + bossDamageDealt + " damage.");
                            System.out.println("\t> You recieve " + bossDamageTaken + " in retaliation!");

                            if(playerHealth < 1)

                            {

                                System.out.println("\t> You have taken too much damage, you are too weak to go on!");
                                break;

                            }

                        }

                        else if (inputBossFight.equals("2"))

                        {

                            if(numHealthPotions > 0)
                            {

                                playerHealth += healthPotionEffect;
                                numHealthPotions--;
                                System.out.println("\t> You drink a health potion and recover " + healthPotionEffect + " health!"
                                                   + "\n\t> You now have " + playerHealth + " HP."
                                                   + "\n\t> You have " + numHealthPotions + " health potions left.\n)");

                            }

                            else
                            {

                                System.out.println("\t> You have no health potions left! ");

                            }

                        }

                        else if (inputBossFight.equals("3"))

                        {

                            System.out.println("\tYou run away from the " + enemy + "!");
                            continue GAME; 

                        }

                        else if (input.equals("4"))

                        {

                            int playerAttack = playerAttackDamage;

                            if (numStrengthPotions > 0)

                            playerAttackDamage = playerAttack * strengthPotionEffect;
                            numStrengthPotions--;

                            System.out.println("You drank a strength potion and your attack has multiplied by: " + strengthPotionEffect + " time(s).!");

                        }


                        else

                        {

                            System.out.println("\tInvalid command...");

                        }

                    }

                    if (playerHealth < 1)

                    {

                        System.out.println("\n\tYou crawl out of the dungeon to live and fight another day.");
                        break;

                    }

                    if (redDrakeHealth <= 100)

                    {

                        System.out.println("\n\t!!!# The Red Drake unleashes it's special attack #!!!");

                        playerHealth -= redDrakeSpecialAttackValue;

                        System.out.println("\t>!!!# You recieve " + redDrakeSpecialAttackValue + " in retaliation from the Drake's fiery breath! #!!!");

                    }

                    System.out.println("-------------------------------------------------");
                    System.out.println(" # " + enemyBoss + " was defeated! #");
                    System.out.println(" # You have " + playerHealth + " HP left. #");

                    playerAttackDamage = initialPlayerAttack;

                    if(rand.nextInt(100) < healthPotionDropChance)

                    {

                        numHealthPotions++;
                        numStrengthPotions++;

                        System.out.println(" # The " + enemyBoss + " dropped a health potion, and a strength potion! # ");
                        System.out.println(" # You have " + numHealthPotions + " health potion(s), and " + numStrengthPotions + "! #");

                    }

                    if(rand.nextInt(100) < goldDropChance)

                    {

                        goldDropAmount = rand.nextInt(500) + 1000;
                        goldAmount += goldDropAmount;

                        System.out.println(" # The " + enemyBoss + " dropped " + goldDropAmount + " gold! #");
                        System.out.println(" # You now have " + goldAmount + " gold. #");

                    }

                    if (rand.nextInt(100) < crimsonSwordDropChance)

                    {

                        System.out.println("\n\t!!!# The fearsome Red Drake has dropped a ruby sword of power, your attack has increased five fold! #!!!");

                        playerAttackDamage *= 5;

                    }

                    else if (illbaneCount != 4)

                    {

                        System.out.println("You do not have enough illbane to make a worthy sacrifice!");

                        continue GAME;

                    }

            }



            else if (input.equals("3"))         // Shop Sequence

            {

                System.out.println("\nWelcome to the sshhop outssider...");
                System.out.println("What would you like to buy?");

                randomItem = shopItems[rand.nextInt(shopItems.length)];

                SHOP:
                System.out.println("\nWould to like to buy a(n): " + randomItem + "?");

                if (randomItem.equals("Silver Sword"))

                {

                    System.out.println("\nThat will be 1000 gold.");

                }

                else if (randomItem.equals("Steel Sword"))

                {

                    System.out.println("\nThat will be 250 gold.");

                }

                else if (randomItem.equals("Iron Helmet"))

                {

                    System.out.println("\nThat will be 150 gold.");

                }

                else if (randomItem.equals("Iron Chestplate"))

                {

                    System.out.println("\nThat will be 200 gold.");

                }

                else if (randomItem.equals("Iron Boots"))

                {

                    System.out.println("\nThat will be 100 gold.");

                }

                else if (randomItem.equals("Iron Gauntlets"))

                {

                    System.out.println("\nThat will be 75 gold.");

                }

                else if (randomItem.equals("Steel Helmet"))

                {

                    System.out.println("\nThat will be 400 gold.");

                }

                else if (randomItem.equals("Steel Chestplate"))

                {

                    System.out.println("\nThat will be 600 gold.");

                }

                else if (randomItem.equals("Steel Boots"))

                {

                    System.out.println("\nThat will be 300 gold.");

                }

                else if (randomItem.equals("Steel Gauntlets"))

                {

                    System.out.println("\nThat will be 250 gold.");

                }

                else if (randomItem.equals("Illbane"))

                {

                    System.out.println("\nThat will be 2500 gold. Interesting...");

                }

                System.out.println("1. Yes");
                System.out.println("2. No");

                String input1 = console.nextLine();

                if (input1.equals("1") && randomItem.equals("Silver Sword"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 1000;
                    playerAttackDamage += 100;

                    shopItems[0] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Steel Sword"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 250;
                    playerAttackDamage += 25;

                    shopItems[1] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Iron Helmet"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 150;
                    playerArmorValue += 10;

                    shopItems[2] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Iron Chestplate"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 200;
                    playerArmorValue += 18;

                    shopItems[3] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Iron Boots"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 100;
                    playerArmorValue += 8;

                    shopItems[4] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Iron Gauntlets"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 75;
                    playerArmorValue += 5;

                    shopItems[5] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Steel Helmet"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 300;
                    playerArmorValue += 15 - 10;

                    shopItems[6] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Steel Chestplate"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 600;
                    playerArmorValue += 30 - 20;

                    shopItems[7] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Steel Boots"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 300;
                    playerArmorValue += 18 - 8;

                    shopItems[8] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Steel Gauntlets"))

                {

                    System.out.println("Thank you for your purchase.");

                    goldAmount -= 250;
                    playerArmorValue += 12 - 5;

                    shopItems[9] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("1") && randomItem.equals("Illbane"))

                {

                    System.out.println("Thank you for your purchase. See what you can do with a couple more of those...");

                    goldAmount -= 2500;
                    illbaneCount++;

                    shopItems[10] = "Empty";

                    continue GAME;

                }

                else if (input1.equals("2"))

                {

                    System.out.println("\nWould you like to buy some potions atleast?");
                    System.out.println("1. Yes");
                    System.out.println("2. No!");

                    String input2 = console.nextLine();



                    POTIONCHOICE:

                    if (input2.equals("2"))

                    {

                        System.out.println("\nAlrighty.");

                        continue GAME;

                    }

                    if (input2.equals("1"))

                    {

                        System.out.println("\nHealth Potions or Strength Potions?");
                        System.out.println("1. Health Potions: 100 gold");
                        System.out.println("2. Strength Potions: 500 gold");
                        System.out.println("3. Nevermind!");

                        String input3 = console.nextLine();

                        if (input2.equals("3"))

                        {

                            System.out.println("Then get on wiv' it!");

                            continue GAME;

                        }

                        else if (input3.equals("1"))

                        {

                            System.out.println("How many would you like to buy?");
                            int inputNumH = console.nextInt();

                            goldAmount -= inputNumH * 100;
                            numHealthPotions += inputNumH;

                            System.out.println("Here you are: " + inputNumH + " health potions.");

                            continue GAME;

                        }

                         else if (input3.equals("2"))

                         {

                             System.out.println("How many would you like to buy?");
                             int inputNumS = console.nextInt();

                             goldAmount -= inputNumS * 500;
                             numStrengthPotions += inputNumS;

                             System.out.println("Here you are: " + inputNumS + " strength potions.");

                             continue GAME;

                         }

                         else if (input3.equals("3"))

                         {

                             System.out.println("Stop wasting my time!");

                             continue GAME;

                         }

                         else

                         {

                             System.out.println("What are you trying to say?!");

                             continue GAME;

                         }



                        }



                }

                System.out.println("\n\t# THANKS FOR PLAYING! # ");

                break;

            }

        }

    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Welcome to Code Review. Your question could potentially be rephrased to be made on-topic by asking "In what ways can I improve this code?" rather than "How do I do [some thing]?", as that is considered asking how to write code that is not written yet. This way, you would open up the possibility for reviewers to focus on any aspect of the code, rather than only the OOP. \$\endgroup\$
    – Phrancis
    Commented Oct 19, 2016 at 4:55
  • \$\begingroup\$ What do we say about labels? xkcd.com/292 \$\endgroup\$ Commented Oct 24, 2016 at 18:58

4 Answers 4

18
\$\begingroup\$

Naming

Names like Class1 send the message that your code isn't complete. Give classes / variables / functions names that describe what they are or what they're responsible for.

Warnings

Don't suppress warnings unless you have a really good reason to do so.

@SuppressWarnings("unused")

Having this on your main method is suppressing warnings about unused variables:

int buyStrengthPotion;
int buyHealthPotion;

And unused labels:

SHOP:
POTIONCHOICE:

As you're developing code you want to try to keep the amount of clutter to a minimum. That means not introducing new elements until you are ready for them and cleaning them up when you don't need them anymore.

Duplication

Keep an eye out for duplication in your code. It's a sign that there is scope to extract some functionality so that it can be reused. For example consider these lines:

System.out.println("\n\tWhat would you like to do?");

// Player options

System.out.println("\t1. Attack");
System.out.println("\t2. Drink health potion");
System.out.println("\t3. Run!");
System.out.println("\t4. Drink strength potion");

These lines essentially output a menu of options to the player (subsequent lines fetch the input and decide what to do). This is a prime candidate for extracting a method to perform a common task. For example a method getPlayerAction which displayed the menu and returned the selected option. You may want to consider using an Enum instead of an int to indicate the selected option. This will make the code easier to read.

You've then got two lines that you use to display the current state of a fight. This:

System.out.println("\tYour HP is: " + playerHealth);
System.out.println("\t" + enemy + "'s HP: " + enemyHealth);

Is almost the same as this:

System.out.println("\tYour HP is: " + playerHealth);
System.out.println("\t" + enemyBoss + "'s HP: " + redDrakeHealth);

Again, this suggests that there's scope for extracting a method here, that has parameters for the various variables (enemy name / health).

Classes

As well as extracting functionality into functions, you can also look at collecting data and functionality together in classes. Just looking at the line:

System.out.println("\t" + enemyBoss + "'s HP: " + redDrakeHealth);

You can see that there is information like the enemies name and the enemies health that seems like they are properties of the same thing. It could be that this points to an Enemy class, or perhaps a Mob / Creature class. A followup question might be, does the Player need its own class / is it a specialised version of the Mob class.

Drop Logic

Your drop logic seems a bit inverted:

if(rand.nextInt(100) < healthPotionDropChance)          // Health Potion drop chance; differs from enemy to enemy.
{
    if ("Kobold Archer".equals(enemy))
    {
        healthPotionDropChance = 55;
    }

You check the random number against the likelihood of a potion being dropped, then afterwards, only if the item has been dropped you adjust the likelihood based on who is being fought. It feels like the likelihood should be calculated before the chance of it having dropped is checked. Again, if the drop likelihood was an attribute of a Mob/Create class it could be calculated during construction of the class.

Let's go shopping

Items in the shop have attributes (name, cost, impact the item has on various attributes). If they were attributes on an Item class it would make it easier to simplify your functionality. This would make bugs less likely and easier to fix. As it stands for example, you're adjusting the players goldAmount after an item is purchased, however there is no check to make sure that the player has sufficient funds before going ahead with the purchase.

Bracing

Whilst the language supports if statements without braces, it can lead to bugs being introduced, particularly during maintenance / feature enhancement. Consider this section of code:

if (numStrengthPotions > 0)

playerAttackDamage = playerAttack * strengthPotionEffect;  // 2
numStrengthPotions--;                                      // 3

From the line grouping, it appears that lines 2 & 3 are supposed to be within the if block, however only line 2 is actually in the dependant on the if. Generally speaking it's better to always include the braces for if statements, even if initially you only need one line, that way when you introduce the second line you don't introduce a bug.

Magic Numbers

Some people really hate the use of raw/magic numbers in code. I'm generally OK with them when they make sense in context, or if there's no better name for the value. If there is a better name, use a constant, however for things like the armor effect of items this might not be necessary (a constant FIVE is no better than using 5 for example). That said, lets look at this code:

player.armorClass += 15 - 10;

It's unclear where 15 and 10 come from. If 15 means something, it should be a named constant. If not, then remove the unnecessary clutter and just have:

player.armorClass += 5;

Nested ifs and input1, input2...

If you look at the following code, you'll notice it checks the value of input2 is '1', then fetches a new value into input3. However, you then check input2 for '3', which it can't be because we know it's '1'. The check may belong somewhere else, or you may be checking input2 when you should be checking input3.

if (input2.equals("1"))
{
    System.out.println("\nHealth Potions or Strength Potions?");
    System.out.println("1. Health Potions: 100 gold");
    System.out.println("2. Strength Potions: 500 gold");
    System.out.println("3. Nevermind!");

    String input3 = console.nextLine();

    if (input2.equals("3"))
    {
        System.out.println("Then get on wiv' it!");

        continue GAME;
    }

Labels

Using labels to control program flow can be confusing and is usually an indication that an element of your program should be broken up into more methods.

Bringing it all together

I've had a go at refactoring the code using some of the suggestions above. This isn't a complete refactoring, but goes someway towards breaking up the code.

Adventure.java

This is the main class, which implements the core functionality of the adventure.

import java.util.Scanner;
import java.util.Random;

public class Adventure 
{

    public static void main(String[] args) 
    {
        Scanner console = new Scanner(System.in);

        Random rand = new Random();
        RandomEngine randomEngine = new SimpleRandomEngine(rand);

        Shop shop = new Shop(randomEngine);
        NPCFactory npcFactory = new NPCFactory(randomEngine);

        Player player = new Player("Player 1", randomEngine, console);

        player.sendTextToMob("Now entering the Kobolds Lair...");

        while(player.isAlive()) 
        {
            player.sendTextToMob("-------------------------------------------------");
            NPC enemy = npcFactory.spawnEnemy(rand);
            player.sendTextToMob("# " + enemy.name + " appears! #\n");

            if(!PerformFight(enemy, player)) {
                continue;
            }

            String input = getUserSelection(player, 
                                            "-------------------------------------------------\n" +
                                            "What to do now?\n" +          
                                            "1. Continue fighting\n" +      
                                            "2. Exit dungeon\n" +  
                                            "3. Visit the shop\n" +         
                                            "4. Sacrifice Illbane...\n", new String[]{"1","2","3","4"});

            if (input.equals("1"))
            {
                player.sendTextToMob("You continue on your trek through the dungeon.");
            }
            else if (input.equals("2"))
            {
                player.sendTextToMob("You exit the dungeon.");
                break;
            }
            else if (input.equals("3"))         // Shop Sequence
            {
                goShopping(player,shop,console);
            }
            else if(player.sacrificeIllbane())
            {
                player.sendTextToMob("-------------------------------------------------");
                player.sendTextToMob("# A great beast stirs! #");

                enemy = npcFactory.spawnBoss(rand);

                player.sendTextToMob("# " + enemy.name + " appears! #\n");

                if(!PerformFight(enemy, player)) {
                    continue;
                }
            }                  
        }
    }

    public static boolean PerformFight(NPC enemy, Player player) {
        while(enemy.isAlive() && player.isAlive())
        {
            // Enemy introduction and presentation of options.
            player.sendTextToMob("Your HP is: " + player.health);
            player.sendTextToMob("" + enemy.name + "'s HP: " + enemy.health);

            String input = getUserSelection(player, "What would you like to do?\n" +
                                                    "1. Attack\n" +
                                                    "2. Drink health potion\n" +
                                                    "3. Run!\n" + 
                                                    "4. Drink strength potion", new String[] {"1","2","3","4"});

            if (input.equals("1"))
            {
                player.performHit(enemy);
                enemy.performHit(player);               
            }
            else if (input.equals("2"))
            {
                player.drinkPotion(PotionType.HEALTH);
            }
            else if (input.equals("3"))
            {
                player.sendTextToMob("You run away from the " + enemy.name + "!");
                return false; 
            }
            else  // input.equals("4")
            {
                player.drinkPotion(PotionType.STRENGTH); 
            }
        }

        if(!player.isAlive())
        {
            player.sendTextToMob("> You have taken too much damage, you are too weak to go on!");
            player.sendTextToMob("\nYou crawl out of the dungeon to live and fight another day.");
            return false;
        }      

        player.wonFight(enemy);

        return true;
    }

    public static boolean getYesNoResponse(Player player, String prompt) {
        return getUserSelection(player, String.format("%s\n1. Yes\n2. No", prompt), new String[]{"1","2"}).equals("1");
    }

    public static boolean isValidInput(String input, String[] validSelections) {
        for(String valid : validSelections) {
            if(input.equals(valid)) return true;
        }
        return false;
    }

    public static String getUserSelection(Player player, String prompt, String[] validSelections) {
        String input = "";
        do {
            if(!input.equals("")) {
                player.sendTextToMob("Sorry, I don't understand.");
            }
            player.sendTextToMob(prompt);

            input = player.getInputFromMob();
        }
        while(!isValidInput(input, validSelections));
        return input;
    }

    public static void goShopping(Player player, Shop shop, Scanner console) {
        player.sendTextToMob("\nWelcome to the sshhop outssider...");
        player.sendTextToMob("What would you like to buy?");

        Item randomItem = shop.getRandomItemForSale();

        if (getYesNoResponse(player, String.format("\nWould you like to buy a %s ?\nThat will be %d gold.\n", randomItem.name, randomItem.value)))
        {
            if(player.canAfford(randomItem.value)) {
                shop.purchase(randomItem);
                player.purchasedItem(randomItem);
                player.sendTextToMob("Thank you for your purchase.");
                if(randomItem.isIllBane()) {
                    player.sendTextToMob("See what you can do with a couple more of those...");                         
                }
            } else {
                player.sendTextToMob("You don't have enough money!");
            }
            return;
        }
        else
        {
            if(getYesNoResponse(player, "\nWould you like to buy some potions at least?"))
            {
                String input = getUserSelection(player, "\nHealth Potions or Strength Potions?\n" +
                                                 "1. Health Potions: 100 gold\n" + 
                                                 "2. Strength Potions: 500 gold\n" + 
                                                 "3. Nevermind!", new String[]{"1","2","3"});

                if (input.equals("1") || input.equals("2"))
                {
                    player.sendTextToMob("How many would you like to buy?");
                    int inputNumH = Integer.parseInt(player.getInputFromMob());

                    Item potion = shop.getPotion(input.equals("1") ? PotionType.HEALTH : PotionType.STRENGTH);

                    if(player.canAfford(inputNumH * potion.value)) 
                    {                       
                        player.purchasedPotions(potion, inputNumH);

                        player.sendTextToMob("Here you are: " + inputNumH + " " + potion.name + ".");
                    } 
                    else 
                    {
                        player.sendTextToMob("You don't have enough money!");
                    }                    
                    return;
                }
                else
                {
                     player.sendTextToMob("Stop wasting my time!");
                     return;
                }
            } 
            else 
            {
                player.sendTextToMob("\nAlrighty.");
            }
        }
        return;     
    }    
}

PotionType.java

public enum PotionType {
    HEALTH (0, "Health"),
    STRENGTH(1, "Strength"),
    NONE(2, "None");

    PotionType(int id, String name) {
        this.id = id;
        this.name= name;
    }

    int id;
    String name;
}

ItemType.java

public enum ItemType {
    EQUIPMENT, POTION, ILLBANE
}

Item.java

The item class represents all items that can exist within the realm. They may be potions, equipment or special.

public class Item {
    String name;
    int value;
    int attackDamage;
    int attackMultiplier;
    int acAdjust;
    PotionType potionType;
    ItemType itemType;

    public Item(String name, int value, int attackDamage, int acAdjust, int attackMultiplier) {
        this(name, value, attackDamage, acAdjust, ItemType.EQUIPMENT, PotionType.NONE, attackMultiplier);
    }    

    public Item(String name, int value, ItemType itemType) {
        this(name,value,0,0, itemType, PotionType.NONE,0);
    }

    public Item(String name, int value, PotionType potionType) {
        this(name,value,0,0, ItemType.POTION, potionType,0);
    }

    private Item(String name, int value, int attackDamage, int acAdjust, ItemType itemType, PotionType potionType, int attackMultiplier) {
        this.name = name;
        this.value = value;
        this.attackDamage = attackDamage;
        this.acAdjust = acAdjust;
        this.itemType = itemType;
        this.potionType = potionType;
        this.attackMultiplier = attackMultiplier;
    }   

    public PotionType potionType() {
        return potionType;
    }
    public boolean isIllBane() {
        return itemType == ItemType.ILLBANE;
    }
    public boolean isPotion() {
        return itemType == ItemType.POTION;
    }
    public boolean isEquipment() {
        return itemType == ItemType.EQUIPMENT;
    }
}

Shop.java

The shop is responsible for generating and providing items for sale.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Shop {
    public Shop(RandomEngine randomEngine) {
        this.randomEngine = randomEngine;
        populateShop();     
    }

    public Item getRandomItemForSale() {
        Item itemForSale = shopItems.get(randomEngine.nextInt(0, shopItems.size()));
        return itemForSale;
    }

    public Item getPotion(PotionType potionType) {
        return potions.get(potionType);
    }

    public void purchase(Item itemToPurchase) {
        shopItems.remove(itemToPurchase);
    }

    public void purchasePotion(PotionType potionType, int numberToPurchase) {
        // NOP?!?
    }

    private void populateShop() {
        shopItems.add(new Item("Silver Sword", 1000, 100, 0, 0));
        shopItems.add(new Item("Steel Sword", 250, 250, 0, 0));
        shopItems.add(new Item("Iron Helmet", 150,0,10, 0));
        shopItems.add(new Item("Iron Chestplate", 200, 0, 18, 0));
        shopItems.add(new Item("Iron Boots", 100, 0, 8, 0));
        shopItems.add(new Item("Iron Gauntlets", 75, 0, 5, 0));
        shopItems.add(new Item("Steel Helmet", 400, 0, 5, 0));
        shopItems.add(new Item("Steel Chestplate", 600, 0, 10, 0));
        shopItems.add(new Item("Steel Boots", 300, 0, 10, 0));
        shopItems.add(new Item("Steel Gauntlets", 250, 0, 7, 0));
        shopItems.add(new Item("Illbane", 2500, ItemType.ILLBANE));

        potions.put(PotionType.STRENGTH, new Item("Strength Potion", 500, PotionType.STRENGTH));
        potions.put(PotionType.HEALTH, new Item("Health Potion", 100, PotionType.HEALTH));
    }

    private ArrayList<Item> shopItems = new ArrayList<Item>();
    private Map<PotionType, Item> potions = new HashMap<PotionType, Item>();
    private RandomEngine randomEngine;

}

RandomEngine.java

public interface RandomEngine {
    public int nextInt(int min, int max);
}

SimpleRandomEngine.java

This is a simple wrapper class for Rand.

import java.util.Random;

public class SimpleRandomEngine implements RandomEngine {
    private Random rand;

    public SimpleRandomEngine(Random rand) {
        this.rand = rand;
    }

    public int nextInt(int min, int max) {
        return rand.nextInt(max-min) + min; 
    }
}

Mob.java

A base class for all mobiles / characters / players within the dungeon.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public abstract class Mob {
    private int attackDamage;
    private int armorClass;

    protected int health;
    protected int goldAmount;
    protected String name;

    protected RandomEngine randomEngine;
    protected Map<PotionType, Integer> potions = new HashMap<PotionType, Integer>();
    protected ArrayList<Item> items = new ArrayList<Item>();


    public Mob(int health, int attackDamage, int goldAmount, int armorClass, String name, int healthPotions, int strengthPotions, RandomEngine randomEngine) {
        this.health = health;
        this.attackDamage = attackDamage;
        this.goldAmount = goldAmount;
        this.armorClass = armorClass;
        this.name = name;
        this.randomEngine = randomEngine;

        potions.put(PotionType.HEALTH, 5);
        potions.put(PotionType.STRENGTH, 1);        
    }

    public Mob(int health, int attackDamage, int goldAmount, int armorClass, String name, RandomEngine randomEngine) {
        this(health, attackDamage,goldAmount,armorClass,name,0,0,randomEngine);
    }

    public boolean isAlive() {
        return health > 0;
    }

    public void performHit(Mob victim) {
        if(health <= 0) {
            return; // The dead can't fight!
        }

        int damageDealt = getAttackDamage() - victim.armorClass;
        if(damageDealt < 0) {
            damageDealt = 0;
        }

        victim.health -= damageDealt;

        sendTextToMob("> You strike the " + victim.name + " for " + damageDealt + " damage.");
        victim.sendTextToMob("> The " +  name + " hits you for " + damageDealt + " damage.");       
    }

    protected void equipItem(Item item) {
        items.add(item);
        attackDamage += item.attackDamage;
        armorClass += item.acAdjust;
        if(item.attackMultiplier > 0) {
            attackDamage *= item.attackMultiplier;
        }
    }

    public void addToInventory(PotionType potionType, int count) {
        potions.replace(potionType, potions.get(potionType) + count);       
    }

    public void addToInventory(PotionType potionType) {
        potions.replace(potionType, potions.get(potionType) + 1);       
    }

    public int getNumberOfPotions(PotionType potionType) {
        return potions.get(potionType);
    }

    public int getAttackDamage() {
        return randomEngine.nextInt(1, attackDamage);
    }

    public abstract void sendTextToMob(String text);
    public abstract String getInputFromMob();
}

NPC.java

Represents creates / enemies within the dungeon.

public class NPC extends Mob {
    public NPC(String name, int health, int attackDamage, int potionDropChance, int goldDropChance, int goldAmount, int armorClass, int specialAttackThreshold, int specialAttack, int specialDropChance, RandomEngine randomEngine) {
        super(health, attackDamage, 0, armorClass, name, randomEngine);


        if(randomEngine.nextInt(0, 100) < goldDropChance)          
        {
            this.goldAmount = goldAmount;
        }

        if(randomEngine.nextInt(0,100) < potionDropChance)       
        {
            addToInventory(PotionType.HEALTH);
            addToInventory(PotionType.STRENGTH);
        }

        if (randomEngine.nextInt(0,100) < specialDropChance)
        {           
            equipItem(new Item("ruby sword of power", 10000, 0, 0, 5));
        }


        this.specialAttack = specialAttack;
        this.specialAttackThreshold = specialAttackThreshold;
    }

    public void performHit(Mob victim) {
        super.performHit(victim);

        if(specialAttackThreshold > 0 && health < specialAttackThreshold && health > 0) {
            int specialAttackDamage = randomEngine.nextInt(10, specialAttack);

            victim.sendTextToMob("\n!!!# The " + name + " unleashes its special attack #!!!");

            health -= specialAttackDamage;

            victim.sendTextToMob(">!!!# You recieve " + specialAttackDamage + " in retaliation from the " + name + "'s fiery breath! #!!!");            
        }
    }


    public void sendTextToMob(String text) {
        // NOP
    }

    public String getInputFromMob() {
        return "";  // Mobs never want to do anything! 
    }

    private int specialAttack;
    private int specialAttackThreshold;
}

NPCFactory.java

Stores templates for creating normal and boss enemies and provides methods for creating random creatures.

import java.util.ArrayList;
import java.util.Random;

public class NPCFactory {
    public NPCFactory(RandomEngine randomEngine){
        this.randomEngine = randomEngine;

        mobTemplates.add(new MobTemplate("Kobold", 50, 100, 25, -1, -1));
        mobTemplates.add(new MobTemplate("Kobold Warrior", 70, 150, 30, 55, 55));
        mobTemplates.add(new MobTemplate("Kobold Archer", 90, 200, 40, 60, 60));
        mobTemplates.add(new MobTemplate("Kobold Overseer", 150, 250, 50, 75, 75));

        bossTemplates.add(new MobTemplate("Red Drake", 2500,1, 75, 50, 50, 20, 100, 200, 25));      
    }

    public NPC spawnEnemy(Random rand) {
        MobTemplate template = mobTemplates.get(rand.nextInt(mobTemplates.size()));
        return new NPC(template.name, template.baseHealth + rand.nextInt(template.healthAdjust), template.attackDamage, template.potionDropChance, template.goldDropChance, rand.nextInt(500) + 1, template.armorClass, template.specialAttackThreshold, template.specialAttack, template.specialDropChance, randomEngine);
    }

    public NPC spawnBoss(Random rand) {
        MobTemplate template = bossTemplates.get(rand.nextInt(bossTemplates.size()));
        return new NPC(template.name, template.baseHealth + rand.nextInt(template.healthAdjust), template.attackDamage, template.potionDropChance, template.goldDropChance, rand.nextInt(500) + 1000, template.armorClass, template.specialAttackThreshold, template.specialAttack, template.specialDropChance, randomEngine);
    }


    private class MobTemplate {
        public final String name;
        public int baseHealth;
        public int healthAdjust;
        public int attackDamage;
        public int potionDropChance;
        public int goldDropChance;
        public int armorClass;
        public int specialAttackThreshold;
        public int specialAttack;
        public int specialDropChance;       

        MobTemplate(String name, int baseHealth, int healthAdjust, int attackDamage, int potionDropChance, int goldDropChance) {
            this(name,baseHealth,healthAdjust,attackDamage,potionDropChance,goldDropChance,0,0,0,-1);
        }

        MobTemplate(String name, int baseHealth, int healthAdjust, int attackDamage, int potionDropChance, int goldDropChance, int armorClass, int specialAttackThreshold, int specialAttack, int specialDropChance) {
            this.name = name;
            this.baseHealth = baseHealth;
            this.healthAdjust = healthAdjust;
            this.attackDamage = attackDamage;
            this.potionDropChance = potionDropChance;
            this.goldDropChance = goldDropChance;
            this.armorClass = armorClass;
            this.specialAttackThreshold = specialAttackThreshold;
            this.specialAttack = specialAttack;
            this.specialDropChance = specialDropChance;
        }
    }

    private ArrayList<MobTemplate> mobTemplates = new ArrayList<MobTemplate>();
    private ArrayList<MobTemplate> bossTemplates = new ArrayList<MobTemplate>();
    private RandomEngine randomEngine;

}

Player.java

A wrapper for the player. Notice that interactions through the console are isolated to two functions (sendTextToMob and getInputFromMob) so that the interface can be easily refactored to alternate sources/destinations (such as a socket interface).

import java.util.Map;
import java.util.Scanner;

public class Player extends Mob {
    public Player(String name, RandomEngine randomEngine, Scanner scanner) {
        super (200, 50, 1000, 0, name, randomEngine);
        illBaneCount = 4;
        strengthEffect = 1;
        this.scanner = scanner;
    }

    public void purchasedItem(Item item) {
        goldAmount-= item.value;
        if(item.isEquipment()) {
            equipItem(item);
        } else {
            addToInventory(item, 1);
        }       
    }

    public boolean canAfford(int amount) {
        return goldAmount >= amount;
    }

    public void purchasedPotions(Item potion, int count) {
        goldAmount-= potion.value * count;

        addToInventory(potion, count);  
    }

    public void drinkPotion(PotionType potionType) {
        final int healthPotionEffect = 30;                                    // How much each potion will heal.
        int potionCount = potions.get(potionType);
        String potionName = potionType == PotionType.HEALTH ? "health" : "strength";

        if(potionCount <= 0) {
            sendTextToMob("> You have no " + potionName + " potions left! ");
            return;
        }

        potions.replace(potionType, potionCount - 1);
        if(potionType == PotionType.HEALTH) 
        {
            health += healthPotionEffect;

            sendTextToMob("> You drink a health potion and recover health!"
                    + "\n> You now have " + health + " HP.");

        }
        else if(potionType == PotionType.STRENGTH)
        {
            strengthEffect += randomEngine.nextInt(1, 4);
            sendTextToMob("You drank a strength potion and your attack is now: " + getAttackDamage() + "!");                
        }
        sendTextToMob("> You have " + getNumberOfPotions(potionType) + " " + potionName + " potions left.\n)");
    }


    public int getAttackDamage() {
        return super.getAttackDamage() * strengthEffect;
    }

    public void wonFight(Mob victim) {
        // Expire spells that only last a single combat
        strengthEffect = 1;

        sendTextToMob("-------------------------------------------------");
        sendTextToMob(" # " + victim.name + " was defeated! #");
        sendTextToMob(" # You have " + health + " HP left. #");     

        // Loot the victim
        if(victim.goldAmount > 0) {
            goldAmount += victim.goldAmount;
            sendTextToMob(" # The " + victim.name + " dropped " + victim.goldAmount + " gold! #");
            sendTextToMob(" # You now have " + goldAmount + " gold. #");
        }

        for(Item item : victim.items) {
            sendTextToMob("\n!!!# The " + victim.name + " has dropped a " + item.name + "!");
            equipItem(item);
        }
        for(Map.Entry<PotionType, Integer> entry : victim.potions.entrySet()) {
            addToInventory(entry.getKey(), entry.getValue());

            sendTextToMob(" # The " + victim.name + " dropped a " + entry.getKey().name + " potion! # ");
            sendTextToMob(" # You have " + getNumberOfPotions(entry.getKey()) + " " + entry.getKey().name + " potion(s). # ");
        }
    }


    public void sendTextToMob(String text) {
        System.out.println(text);
    }

    public String getInputFromMob() {
        return scanner.nextLine(); 
    }

    public boolean sacrificeIllbane() {
        if(illBaneCount >= 4) 
        {
            illBaneCount -= 4;
            return true;
        }
        sendTextToMob("You do not have enough illbane to make a worthy sacrifice!");            
        return false;
    }

    private void addToInventory(Item item, int count) {
        if(item.isIllBane()) {
            illBaneCount += count;
        } else if(item.isPotion()){
            addToInventory(item.potionType, count);
        }
    }    

    private Scanner scanner;
    private int illBaneCount;
    private int strengthEffect;
}
\$\endgroup\$
1
  • \$\begingroup\$ I suppose the drop logic does seem inverted, and the shop is a little messy... \$\endgroup\$
    – user120597
    Commented Oct 19, 2016 at 17:52
10
\$\begingroup\$

There is a lot of way to implement object oriented principles. Maybe you can think like this: Tell your program in a sentence and every noun is a potential object. E.g.: A warrior fights with kobolds and bosses during he can use weapons and potions which can be bought in the shop for gold. Maybe you don't want to/have to use all of them this is just a guideline. Another example in pseudo code: If you use an ENUM for the random items in the shop you can get rid of the endless if/else and type

pseudo code
ITEM_ENUM item = random_item
print.out "Do you want to buy" + item.name + "?"
print.out "It will cost" + item.price

This you can easily expanse you item list.

\$\endgroup\$
1
  • \$\begingroup\$ Yeah, the shop was probably the most painful part. \$\endgroup\$
    – user120597
    Commented Oct 19, 2016 at 17:52
10
\$\begingroup\$

Let's start by taking a look at where you buy items from a store. It's a pretty long if-else chain, with each block similar to this:

/* ... */
else if (input1.equals("1") && randomItem.equals("Steel Boots"))
{

    System.out.println("Thank you for your purchase.");

    goldAmount -= 300;
    playerArmorValue += 18 - 8;

    shopItems[8] = "Empty";

    continue GAME;

}
/* ... */

Note that each item has a name and a price/value. In case of armor, it also has an armor value. Armor items are a special, more specific type of item. We could sort them into classes like this:

class Item {
    String name;
    int value;
}

class Armor extends Item {
    int armorValue;
}

(I've elided the constructors for brevity.) The same can be done with enemies and the player. They have current health, armor value, damage potential, and so on:

class Creature {
    int health;
    int armor;
    int power;
    /** Amount of gold the creature is carrying.
      * Enemies drop this when defeated.
      * Players use it to shop. */
    int gold;
}

class Player extends Creature {
    List<Item> items;
}

This is all just data, though. To be meaningful, we will also need methods/functions that can use the state in these classes to produce results, to modify state, or create desired side effects.

Below is an example of how functionality for attacking creatures could be done. Note that the methods only read or modify the fields of 'this' object, and not (directly) those of other objects -- this is a good rule of thumb for what a method should do, and where it should go.

class Creature {
    /* ... */

    public void attack(Creature target) {
        int damage = rand.nextInt(power);
        target.attacked(damage, this);
    }

    void attacked(int damage, Creature attacker) {
        damage -= armor;
        if ( damage > 0 ) {
            health -= damage;
            if ( health <= 0 ) {
                // creature perished, reward attacker
                attacker.addGold(gold);
                gold = 0;
            }
        }
    }

    void addGold(int amount) {
        gold += amount;
    }
}

Does a type of creature have a specific behavior different from others? Subclass for a special case:

class Dragon extends Creature {
    /* ... */

    @Override
    public void attack(Creature target) {
        // dragons always attack with full power
        target.attacked(power, this);
    }
}

When you start extracting data and code like this, your main method will shrink, and you will find it much easier to scale to more creatures, more items, and even more complicated dungeons to crawl through.

\$\endgroup\$
2
  • \$\begingroup\$ How do the extensions work? For example, Dragon extends Creature. Does it take all the methods from Creature and the code in Dragon is directed towards a more specific class? \$\endgroup\$
    – user120597
    Commented Oct 19, 2016 at 17:55
  • 2
    \$\begingroup\$ @user120597 Subclasses inherit everything (methods, fields) from their superclass. Indeed, Dragon will inherit everything from Creature ("be" a creature), with the option to add fields and methods, or override existing methods. The Java tutorial from Oracle has a chapter on inheritance. \$\endgroup\$
    – JvR
    Commented Oct 19, 2016 at 18:08
6
\$\begingroup\$
            String enemy = enemies[rand.nextInt(enemies.length)];       // Enemy Spawning.

            System.out.println("\t# " + enemy + " appears! #\n");


            if ("Kobold".equals(enemy))                                 // Enemy stat setting.

            {

                enemyHealth = rand.nextInt(100) + 50;

                enemyAttackDamage = 25;

            }

            else if ("Kobold Archer".equals(enemy))

            {

                enemyHealth = rand.nextInt(150) + 70;

                enemyAttackDamage = 30;

            }

            else if ("Kobold Warrior".equals(enemy))

            {

                enemyHealth = rand.nextInt(200) + 90;

                enemyAttackDamage = 40;

            }

            else if ("Kobold Overseer".equals(enemy))

            {

                enemyHealth = rand.nextInt(250) + 150;

                enemyAttackDamage = 50;

            }

First, I'd much rather this had less vertical white space.

        String enemy = enemies[rand.nextInt(enemies.length)];       // Enemy Spawning.
        System.out.println("\t# " + enemy + " appears! #\n");

        // Enemy stat setting.
        if ("Kobold".equals(enemy)) {
            enemyHealth = rand.nextInt(100) + 50;
            enemyAttackDamage = 25;
        } else if ("Kobold Archer".equals(enemy)) {
            enemyHealth = rand.nextInt(150) + 70;
            enemyAttackDamage = 30;
        } else if ("Kobold Warrior".equals(enemy)) {
            enemyHealth = rand.nextInt(200) + 90;
            enemyAttackDamage = 40;
        } else if ("Kobold Overseer".equals(enemy)) {
            enemyHealth = rand.nextInt(250) + 150;
            enemyAttackDamage = 50;
        }

White space can be useful in grouping lines of code. But if you use too much of it, it loses its impact.

It's more common to put curly braces on the same line as control structures in Java, although separate lines are used as well.

Note that this change alone reduces the code length from 900 to 500 or so lines.

Second, I'd much prefer to have the type of enemy held as an enum rather than an unbounded string. With an enum and an enemy object, this section of code drops to just

    Enemy enemy = Enemy.spawn();
    System.out.println("\t# " + enemy + " appears! #\n");

The Enemy class:

public class Enemy {

    private int health;
    private int attack;
    private EnemyType type;

    Enemy(int health, int attack, EnemyType type) {
        this.health = health;
        this.attack = attack;
        this.type = type;
    }

    public int getHealth() {
        return health;
    }

    public void takeDamage(int damage) {
        health -= damage;
    }

    public int getAttack() {
        return attack;
    }

    public int getDropChance() {
        return type.getDropChance();
    }

    public EnemyType getType() {
        return type;
    }

    public boolean hasHealth() {
        return health > 0;
    }

    private static Enemy spawn() {
        EnemyType[] enemies = EnemyType.values();
        EnemyType type = enemies[rand.nextInt(enemies.length)];
        return new Enemy(type.generateHealth(), type.getAttack(), type);
    }

    public String fetchName() {
        return type.toString();
    }

}

An Enemy object tracks the monster's health and attack damage for you.

The static spawn method gets the initial details from a randomly chosen EnemyType.

The EnemyType enum:

public enum EnemyType {

    KOBOLD(50, 100, 25, 50),
    KOBOLD_WARRIOR(70, 150, 30, 55),
    KOBOLD_ARCHER(90, 200, 40, 60),
    KOBOLD_OVERSEER(150, 250, 50, 75);

    private int base;
    private int variation;
    private int attack;
    private int dropChance;

    EnemyType(int base, int variation, int attack, int dropChance) {
        this.base = base;
        this.variation = variation;
        this.attack = attack;
        this.dropChance = dropChance;
    }

    public int generateHealth() {
        return rand.nextInt(variation) + base;
    }

    public int getAttack() {
        return attack;
    }

    public int getDropChance() {
        return dropChance;
    }

    public String toString() {
        return name().toLowerCase().replace('_', ' ');
    }

}

An enum supports storing separate values for each member of the enum. So rather than writing custom if/else structures, we can compactly specify the values.

Rather than setting manual variables, this pattern allows us to enemy.getHealth() and enemy.getAttack().

\$\endgroup\$
1
  • \$\begingroup\$ "Note that this change alone reduces the code length from 900 to 500 or so lines." >>> For almost effortless thing and 400 lines reduction. \$\endgroup\$
    – rdllopes
    Commented Oct 20, 2016 at 9:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.