Get up to 80 % extra points for free! More info:

Lesson 6 - Arena with warriors in Java

In the previous lesson, Warrior for the arena in Java, we created the Warrior class. Our rolling die is already finished from the early lessons. In today's tutorial, we're going to put it all together and create a fully functioning arena. The tutorial is going to be simple and will help you get some more practice on working with objects.

We'll need to write some code that will manage our warriors and print messages to the user. Of course, we won't put all this in the main() method but we'll keep things organized. We'll create an Arena object where our fight will take place. ArenaFight.java (the file with the main() method) will only provide the necessary objects, and the Arena object will take care of the rest. Let's add the last class to the project - Arena.java.

The class will be rather simple, it'll include three needed instances as the fields: the 2 warriors and the rolling die. These fields will be initialized from the constructor parameters. The class code will be as following (add comments accordingly):

public class Arena {
    private Warrior warrior1;
    private Warrior warrior2;
    private RollingDie die;

    public Arena(Warrior warrior1, Warrior warrior2, RollingDie die) {
        this.warrior1 = warrior1;
        this.warrior2 = warrior2;
        this.die = die;
    }

}

Let's think about the methods. We're definitely going to need a public method to simulate the fight. We'll make the program output fancy and allow the Arena class to access the console directly. We've decided that the printing will be done by the Arena class since it makes sense here. If the printing was performed by warriors, the design would be flawed since the warriors would not be universal. We need a method that prints information about the round and the warriors' health to the console. The damage and defense messages will be printed with a dramatic pause so as to make the fight more intense. We'll create a helper method for this. Let's start with the method that renders the information screen:

private void render() {
    System.out.println("-------------- Arena -------------- \n");
    System.out.println("Warriors health: \n");
    System.out.println(warrior1 + " " + warrior1.healthBar());
    System.out.println(warrior2 + " " + warrior2.healthBar());
}

The method is private, it'll be used only within the class.

Let's create another private method that will print messages with a dramatic pause:

private void printMessage(String message) {
    System.out.println(message);
    try {
        Thread.sleep(500);
    } catch (InterruptedException ex) {
        System.err.println("Unable to put the thread to sleep");
    }
}

The Thread class allows us to work with threads. We use its sleep() method which puts the application thread to sleep for a given number of milliseconds. We'll go over threads in detail in the last couple of courses. We won't bother with try-catch blocks now, they aren't important for us now and we'll get back to them later. Let's just say they have to be there for now.

Let's move on to the fighting part. The fight() method will be parameterless and won't return anything. There will be a loop inside calling the warriors' attacks in turns and printing the information screen with the messages. The method would look something like this:

public void fight() {
    System.out.println("Welcome to the Arena!");
    System.out.println("Today " + warrior1 + " will battle against " + warrior2 + "!\n");
    System.out.println("Let the battle begin...");
    // fight loop
    while (warrior1.alive() && warrior2.alive()) {
        warrior1.attack(warrior2);
        render();
        printMessage(warrior1.getLastMessage()); // attack message
        printMessage(warrior2.getLastMessage()); // defense message
        warrior2.attack(warrior1);
        render();
        printMessage(warrior2.getLastMessage()); // attack message
        printMessage(warrior1.getLastMessage()); // defense message
        System.out.println();
    }
}

The code prints introductory lines and executes the fighting loop after the user presses any key. It's a while loop that repeats as long as both warriors are alive. The first warrior attacks his opponent and his attack internally calls the other warrior's defense. After the attack, we render the information screen. The messages about the attack and defense are printed by our printMessage() method which makes a dramatic pause after the printing. The same thing will happen with the other warrior.

Let's move back to ArenaFight.java. We create'll the needed instances and call the fight() method on the arena:

// creating objects
RollingDie die = new RollingDie(10);
Warrior zalgoren = new Warrior("Zalgoren", 100, 20, 10, die);
Warrior shadow = new Warrior("Shadow", 60, 18, 15, die);
Arena arena = new Arena(zalgoren, shadow, die);
// fight
arena.fight();

You can change the values to whatever you'd like. Here's what the program looks like at runtime:

Console application
-------------- Arena --------------

Warriors health:

Zalgoren [##                  ]
Shadow [                    ]
Shadow attacks with a hit worth 19 hp
Zalgoren blocked the hit

The result is quite impressive. The objects communicate with each other, the health bar decreases as expected, the experience is enhanced by a dramatic pause. However, our arena still has two issues:

  • In the fight loop, the first warrior attacks the other one. Then, the second warrior attacks back, even if he has already been killed by the first warrior. Look at the output above, at the end, Shadow attacked even though he was dead. The while loop terminated just after that. There are no issues with the first warrior, but we have to check whether the second warrior is alive before letting him attack.
  • The second problem is that the warriors always fight in the same order so "Zalgoren" has an unfair advantage. Let's use the rolling die to decide who will start the fight. Since there will always only be two warriors, we can set the warriors' turns based off of whether the rolled number is less or equal to half of the number of die sides. Meaning that if it rolls a number less than 5 on a ten-sided die, the second warrior goes first, otherwise, the first one does.

So now we need to think about a way to swap the warriors depending on which one goes first. It'd be very unreadable to add some conditions into the while loop. Since we know about references in Java, let's just create 2 variables that will contain warrior instances. Let's call them w1 and w2. At the start, we'll assign the values from warrior1 and warrior2 to these variables as needed. If the die condition mentioned above applies, we'll assign warrior2 to w1 and vice versa, then the second warrior will begin. This way, we won't have to change the loop code and it all remains nice and clear.

The updated version preventing the second warrior from attacking when he's already dead and letting the warriors start randomly can look like this:

public void fight() {
    // The original order
    Warrior w1 = warrior1;
    Warrior w2 = warrior2;
    System.out.println("Welcome to the Arena!");
    System.out.println("Today " + warrior1 + " will battle against " + warrior2 + "!\n");
    // swapping the warriors
    boolean warrior2Starts = (die.roll() <= die.getSidesCount() / 2);
    if (warrior2Starts) {
        w1 = warrior2;
        w2 = warrior1;
    }
    System.out.println(w1 + " goes first! \nLet the battle begin...");
    // fight loop
    while (w1.alive() && w2.alive()) {
        w1.attack(w2);
        render();
        printMessage(w1.getLastMessage()); // attack message
        printMessage(w2.getLastMessage()); // defense message
        if (w2.alive()) {
            w2.attack(w1);
            render();
            printMessage(w2.getLastMessage()); // attack message
            printMessage(w1.getLastMessage()); // defense message
        }
        System.out.println();
    }
}

Now, let's take her for a spin!

Console application
-------------- Arena --------------

Warriors health:

Zalgoren [###########         ]
Shadow [                    ]
Zalgoren attacks with a hit worth 27 hp
Shadow defended against the attack but still lost 9 hp, and died

Congratulations! If you've gotten this far and have actually read through, you have the basis of object-oriented programming and should be able to create reasonable applications :)

In the next lesson, Inheritance and polymorphism in Java, we'll explain object-oriented programming in further detail. We mentioned that OOP is based on three core concepts - encapsulation, inheritance, and polymorphism. We're already familiar with the encapsulation, the other two await you in the next lesson.


 

Previous article
Warrior for the arena in Java
All articles in this section
Object-Oriented Programming in Java
Skip article
(not recommended)
Inheritance and polymorphism in Java
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities