Lesson 5 - Warrior for the arena in Java

Java OOP Warrior for the arena in Java

In the previous lesson, Reference and value data types in Java, we went over the differences between reference and primitive data types in Java. We already know how references work and how we can deal with objects. Which will be very useful for us today. We're going to finish up our arena in the next two lessons. We already have a rolling die, but we're still missing two essential objects: the warrior and the arena itself. Today, we're going to mainly focus on the warrior. First, we'll have to decide what he (or she) will be able to do, and then, write our code.

Fields

The warrior will have a name and a starting HP (which stands for health points/hit points, e.g. 80hp). We'll store his maximum health, which will vary each instance, and his current health, e.g. a wounded warrior will have 40hp from 80 total. The warrior will have a set damage and defense, which will both be defined in hp. When a warrior, with 20hp damage, attacks another warrior with 10hp defense, he takes 10 points of his health. The warrior will have a reference to the rolling die instance. We will always roll the die and add a particular random number to his/her attack/defense to make the game more unpredictable. Of course, each warrior could have their own rolling die, but I wanted this to be as close to a real board game as possible and show you how OOP simulates reality. The warriors will share a single rolling die instance, which will add an element of randomness to the game and makes the game a bit more realistic. Last of all, we'll make the warriors send messages about what is happening in the battle. The message will look something like this: "Zalgoren attacks with a hit worth 25 hp." However, we'll put the message part off for now, we'll mainly focus on creating the warrior object.

Now that we've got a good idea of what we want, let's get right into it! :) Let's add a Warrior class to the ArenaFight project and add fields to it accordingly. All of them will be private:

public class Warrior {
        /** Warrior's name */
        private String name;
        /** Health in HP */
        private int health;
        /** Maximum health in HP */
        private int maxHealth;
        /** Damage in HP */
        private int damage;
        /** Defense in HP */
        private int defense;
        /** The rolling die instance */
        private RollingDie die;

}

Of course, the RollingDie class has to be in our project.

Methods

Let's start off by creating a constructor for the fields. I'll omit further comments in the article to make it clear and short. Just don't forget to add them to your project in the same way as we did to the fields above.

public Warrior(String name, int health, int damage, int defense, RollingDie die) {
        this.name = name;
        this.health = health;
        this.maxHealth = health;
        this.damage = damage;
        this.defense = defense;
        this.die = die;
}

We assume that the warrior has a full health after creation, so the constructor doesn't need a maxHealth parameter. It is easier to just set the maxHealth to whatever the starting health is.

Again, we should think about what our warrior will be able to do before writing anything out. First thing's first, we'll need a textual representation of the warrior, i.e. a way of printing out it's name every time something happens. We can use the toString() method which will return the name of our warrior. Then, we would need a method that returns whether a warrior is alive, a boolean value would work best, and will definitely come in handy. To make it a little more interesting, we'll literally draw the warrior's health to the console, so we we'll have a cool little visual representation:

[#########    ]

The health shown above is at 70%. The methods we mentioned didn't require any parameters so far. We'll get into the damage and defense methods later. Now, let's implement toString(), alive() and healthBar(). We'll start with toString(). Which should look familiar, since we did the exact same thing last time:

@Override
public String toString() {
        return name;
}

Now, let's implement the alive() method, there's nothing difficult about it either. We'll just ask whether health points are greater than 0 and act according to it. It would probably look something like this:

public boolean alive() {
        if (health > 0) {
                return true;
        } else {
                return false;
        }
}

Due to the fact that the expression (health > 0) is actually a logical value, we can return it and the code will become simpler:

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

HealthBar

As I've already mentioned, the healthBar() method will allow us to display the graphical health indicator. We already know it's usually not good practice to have a method print directly to the console, unless printing is its sole responsibility. We'll add the characters to a String variable, return them and print them later. Let's take a look at the code I've written for it:

public String healthBar() {
        String s = "[";
        int total = 20;
        double count = Math.round(((double)health / maxHealth) * total);
        if ((count == 0) && (alive())) {
                count = 1;
        }
        for (int i = 0; i < count; i++) {
                s += "#";
        }
        for (int i = 0; i < total - count; i++) {
                s += " ";
        }
        s += "]";
        return s;
}

We prepare a String s and assign a leading character "[" to it. Then, we specify what the current state of the health bar is and pass it through the total variable (maximum amount of characters the health bar can hold). Basically, all we need now is the rule of three. If maxHealth equals the total number of characters, health stands for count, or the current number of characters. Meaning that, the count variable contains the number of characters representing the current health.

Mathematically, here's what the calculation would look like: count = (health / maxHealth) * total;. We round it up to the nearest whole number and cast one of the operands as a double, so Java wouldn't return the first part of the calculation as a whole number.

There might also be a case where the warrior's health is so low, it would draw out 0 characters, but the warrior would be still alive. In this case, we'll have it draw one character, otherwise, it'd seem like the warrior already died.

Then we just simply concatenate the right number of characters to String s, using a for loop. We'll add spaces to fill the empty part of the health bar. We add spaces using another for loop till we reach the total length. Finally, we add the trailing bracket and return the string.

Now we'll put our classes to the test! Go to ArenaFight.java and create a warrior and a "rolling die" since we need to pass one as a parameter in the warrior's constructor. Then we'll print whether he's alive and draw out its health bar:

RollingDie die = new RollingDie(10);
Warrior warrior = new Warrior("Zalgoren", 100, 20, 10, die);

System.out.println("Warrior: " + warrior); // test toString();
System.out.println("Alive: " + warrior.alive()); // test alive();
System.out.println("health: " + warrior.healthBar()); // test healthBar();

Console application
Warrior: Zalgoren
Alive: true
health: [####################]

Fight

Time to implement methods for attack and defense!

Defense

The defend() method will resist hits whose power will be passed as a parameter. The method should look something like this:

public void defend(int hit) {
        int injury = hit - (defense + die.roll());
        if (injury > 0) {
                health -= injury;
                if (health <= 0) {
                        health = 0;
                }
        }
}

First, we have to calculate the injury. To do this, we have to add our defense and whatever number the die rolled, and subtract the outcome of that from the enemy's attack (hit). If our defense wasn't enough to resist the enemy's attack, (injury > 0), we take points off our health. This condition is important, because if we endured a hit and the injury was -2, our health would be increased, instead. After reducing the health, we check whether it's not negative and eventually set it to zero.

Attack

The attack() method will need an enemy as a parameter. Mainly because we need to call his defend() method which reacts to our attack and reduces the enemy's health. Here we can see the benefits of having references in Java, we can simply pass instances and call methods on them without having to copy these instances. First, let's calculate the hit, like in defense. Our hit will be the damage + whatever value the die rolled. Then we'll call the defend() method on the enemy and pass the hit value to it:

public void attack(Warrior enemy) {
        int hit = damage + die.roll();
        enemy.defend(hit);
}

That's pretty much it. Let's add an attack and have the program redraw the warrior's health. For simplicity, we won't create another warrior just have him attack himself:

RollingDie die = new RollingDie(10);
Warrior warrior = new Warrior("Zalgoren", 100, 20, 10, die);

System.out.println("Warrior: " + warrior); // test toString();
System.out.println("Alive: " + warrior.alive()); // test alive();
System.out.println("health: " + warrior.healthBar()); // test graphicHealth();

warrior.attack(warrior); // attack test
System.out.println("Health after the hit: " + warrior.healthBar());

Console application
Warrior: Zalgoren
Alive: true
health: [####################]
Health after the hit: [##################  ]

It seems to work as expected. Let's proceed to the last part of today's lesson - messages:

Messages

We'll have the program notify the user about attacks and defenses through the console. Printing will not be performed by the Warrior class, it will only return messages as strings. One approach could be to set the return type of attack() and defend() methods to String and return the message when these methods are called. However, what if we wanted to return a message from a method that already returns some other value? A method can't return 2 things...

We'll make a universal solution, the message will be stored in a private variable message and we'll create set and get methods for it. We could make the variable public, but there's no reason to allow its alteration from outside the class. Concatenating complex messages could also become problematic without the set method.

Let's add the message to the class fields:

private String message;

Now, let's create the two methods. Private setMessage() will take a string as a parameter and set the message to the private field:

private void setMessage(String message) {
        this.message = message;
}

There's nothing difficult about it. A public method for getting the message is easy, too:

public String getLastMessage() {
        return message;
}

Let's upgrade our attack() and defend() methods to set the messages, now they look like this:

public void attack(Warrior enemy) {
        int hit = damage + die.roll();
        setMessage(name + " attacks with a hit worth " + hit + " hp");
        enemy.defend(hit);
}

public void defend(int hit) {
        int injury = hit - (defense + die.roll());
        if (injury > 0) {
                health -= injury;
                message = name + " defended against the attack but still lost " + injury + " hp";
                if (health <= 0) {
                        health = 0;
                        message += " and died";
                }

        } else
                message = name + " blocked the hit";
        setMessage(message);
}

Let's add a second warrior, just for completeness' sake:

RollingDie die = new RollingDie(10);
Warrior warrior = new Warrior("Zalgoren", 100, 20, 10, die);

System.out.println("Health: " + warrior.healthBar()); // test healthBar();

// warrior attack phase
Warrior enemy = new Warrior("Shadow", 60, 18, 15, die);
enemy.attack(warrior);
System.out.println(enemy.getLastMessage());
System.out.println(warrior.getLastMessage());

System.out.println("Health: " + warrior.healthBar());

Console application
Health: [####################]
Shadow attacks with a hit worth 27 hp
Zalgoren defended against the attack but still lost 12 hp
Health: [##################  ]

Now we have the rolling die and the warriors. In the next lesson, Arena with warriors in Java, we'll create the arena.


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
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 College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Activities (4)

 

 

Comments

Avatar
Sajjad Sajjad Khan:10. September 21:33

Sir i did not understand all the game

 
Reply 10. September 21:33
To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

1 messages from 1 displayed.