Enumerations in Java explained (using Pokémon as an example)

Enumerations explained (using Pokemon as an example)

What are enums anyway, and what are they used for? Well, just think of it as yet another tool in your handy Java toolbox of things you can consider using to improve your efficiency and organisation of your program.

In this article, we are going to explore what enums are, and how they can be used in Java, by considering a piece of Java code that uses enums to recreate the type effectiveness system found in Pokémon.

What are enumerators?

“An enum is basically… if a class and an array had a baby.”

By Alex Lee (YouTuber)

Enumerations are, in essence, a data type that allows you to create a group of pre-defined constants in which its values do not change. The constants that you create with enumerations are inherently public, static and final. This is great for creating a set of values that will not need to be changed throughout your program, such as names of planets, seasons of the year, and since we’re using Pokemon as an example in this article, Pokemon names & types!

Java allows you to use constructors, methods, and instance variables within enumerators. You can also add parameters to your enumerators. However, you are not able to instantiate an enum using a “new” keyword since ultimately, it is a predefined set of values – so you cannot simply create new values. Instead, you can call values within an enum using EnumName.VALUE.

Programming Pokémon’s type effectiveness system

Don’t have Java installed on your computer? You can use this Online Java Compiler.

To help you get a more intrinsic understanding of enumerators, we’re going to be coding a very basic Java system that implements Pokémon’s type effectiveness system. In this program, we’ll make it such that new, wild Pokémon can be created, assigned a name, type, as well as health points (HP). Most importantly, we’ll make it so that when they receive damage, depending on the type of the incoming attack, the damage that is dealt will be amplified or reduced.

1. Creating the enumerator

Since we’re going to be creating enumerators that represent Pokémon types, it’ll be best if we nest it within a class called Pokemon so that we access it through the Pokemon class. This means that instead of just referring to Type when we want to use the enumerator, we’ll have to access it using Pokemon.Type instead. This makes things a little more well-organised. We’ll also declare 2 variables — health and name — for each Pokémon.

We’ll also go ahead and code 3 of the basic Pokémon types into the enum:

public class Pokemon {

	public int health;
	public String name;

	public enum Type {
		FIRE,
		WATER, 
		GRASS;
	}
}

Great! We’ve just made 3 types of Pokemon in our program. Now, if you’ve ever played Pokémon, you’ll know about the type-effectiveness system, where certain types are weak to, or strong against another type. Although there are 18 different Pokémon types, we’re just going to code 3 types in our example here:

  1. Fire: Which is strong against Grass, but weak to Water.
  2. Water: Which is strong against Fire, but weak to Grass.
  3. Grass: Which is strong against Water, but weak to Fire.

If a Pokémon move is the same type as the Pokémon that’s receiving the damage, the damage is reduced; and if we go backwards, Fire-type moves would not be as effective on Water-type Pokemon, so damage received is also reduced, and so on. For this program we’ll assume the level of effectiveness (or damage factor) for the latter two scenarios are the same.

2. Creating the damage system and assigning damage multipliers

In order to craft out this damage system, we’re going to have to create parameters for each enum value, and assign damage factor values for each Pokémon type. The damage factor basically means what the original damage will be multiplied by based on the type-effectiveness system. So let’s create a damage table to lay out the rules for how much damage should be received:

public class Pokemon {

	public int health;
	public String name;

	public enum Type {
 		 // format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);
	}
}

Article continues after the advertisement:


From left to right, we’ve now assigned the Fire-damage factor, Water-damage factor and Grass-damage factor to each column. To implement these numbers and tell what Java they refer to, we’ll have to create a constructor for the Types which now have damage factors (a, b, c).

How we want to read FIRE (0.5, 2, 0.5) is, “In the instance of a Fire-type receiving

  1. Fire-type damage, we’ll multiply the original damage by a = 0.5,
  2. Water-type damage, we’ll multiply the original damage by b = 2,
  3. Grass-type damage, we’ll multiply the original damage by c = 0.5

We’re also going to create an array to point to each Type to use later on when we’re simulating battle. Take note that this piece of code is still within the Type enum. Here’s what the code will look like:

public class Pokemon {

	public int health;
	public String name;

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double fireDamageFactor, double waterDamageFactor, double grassDamageFactor) {
			damageTable[0] = fireDamageFactor; 
			// 0 being the first index referring to the first enum a.k.a FIRE
			damageTable[1] = waterDamageFactor; 
			// 1 being the second index referring to the second enum a.k.a WATER
			damageTable[2] = grassDamageFactor; 
			// 2 being the third index referring to the third enum a.k.a GRASS
		}
	}
}

3. Creating a getter for the damage multiplier

Now, we’re going to need to be able to call up the actual numbers we’ve listed in the parameters we’ve created, so let’s create a getter function that will return us the value of the relevant multiplier.

public class Pokemon {

	public int health;
	public String name;

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double fireDamageFactor, double waterDamageFactor, double grassDamageFactor) {
			damageTable[0] = fireDamageFactor; 
			// 0 being the first index referring to the first enum a.k.a FIRE
			damageTable[1] = waterDamageFactor; 
			// 1 being the second index referring to the second enum a.k.a WATER
			damageTable[2] = grassDamageFactor; 
			// 2 being the third index referring to the third enum a.k.a GRASS
		}

		// E.g.: Type.Fire.getDamageMultiplier(Type.Water);
		public double getDamageMultiplier(Type incomingDamageType) {
			// ordinal() is the index of the enum's damage factor.
			// E.g. Type.WATER.ordinal() will be 1 a.k.a waterDamageFactor
			return damageTable[incomingDamageType.ordinal()];
		}
	}
}

As mentioned in the comments of the code, the getter function is to simplify how we will get the numeric value of the multiplier that we’ve assigned to each enum. We declare incomingDamageType as the Pokémon Type receiving the damage (i.e the enumerator — FIRE, WATER or GRASS), then use ordinal() to point to the correct column (i.e fireDamageFactor, waterDamageFactor, and grassDamageFactor) to retrieve the correct value. Now, we can use this getter function later on in the program to retrieve the correct multiplier value!


Article continues after the advertisement:


4. Creating the constructor and Damage() function

Now, let’s start coding outside of the Type enum and in the Pokemon class. Creating the constructor for the Pokemon class is pretty straightforward — we want to be able to create instances of Pokémon, and we want them to have some basic information. In this simplified case, a Pokémon must have a name (String), health points (int) and a Type (FIRE, WATER, or GRASS). We’ll also declare a Type variable called type.

public class Pokemon {

	public int health;
	public String name;

	// The type of the Pokemon
	public Type type;

	// Constructor for the class
	public Pokemon(String name, int hp, Type t) {
		type = t;
		this.name = name;
		health = hp;
	}

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double fireDamageFactor, double waterDamageFactor, double grassDamageFactor) {
			damageTable[0] = fireDamageFactor; 
			// 0 being the first index referring to the first enum a.k.a FIRE
			damageTable[1] = waterDamageFactor; 
			// 1 being the second index referring to the second enum a.k.a WATER
			damageTable[2] = grassDamageFactor; 
			// 2 being the third index referring to the third enum a.k.a GRASS
		}

		// E.g.: Type.Fire.getDamageMultiplier(Type.Water);
		public double getDamageMultiplier(Type incomingDamageType) {
			// ordinal() is the index of the enum's damage factor.
			// E.g. Type.WATER.ordinal() will be 1 a.k.a waterDamageFactor
			return damageTable[incomingDamageType.ordinal()];
		}
	}
}

The Damage() function will need to take into account basic / original amount of damage dealt, and the type of incoming damage so we stay true to the damage table.

First, the original damage amount must be multiplied the correct damage factor to find the real damage done, so we’re going to use the getter we created to retrieve the correct damage factor value. Then, we’ll reduce the Pokémon’s original HP by the amount of damage finally received. Lastly, we’ll make sure that if the damage reduces the HP to a negative, that it’ll just simply reduce the HP of the Pokémon to 0. It’ll all look something like this:

public class Pokemon {

	public int health;
	public String name;

	// The type of the Pokemon
	public Type type;

	// Constructor for the class
	public Pokemon(String name, int hp, Type t) {
		type = t;
		this.name = name;
		health = hp;
	}

	// Damage Function: Deals damage to this Pokemon and returns the actual amount of damage dealt.
	public int Damage(int amount, Type damageType) {     
		// We are modifying the amount of damage based on the damage type.
		amount *= type.getDamageMultiplier(damageType);

		// Reducing the health of the Pokemon.
		health -= amount;

		// Ensure that health does not get set to a negative value
		if (health < 0) health = 0;
		return amount;
	}

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double fireDamageFactor, double waterDamageFactor, double grassDamageFactor) {
			damageTable[0] = fireDamageFactor; 
			// 0 being the first index referring to the first enum a.k.a FIRE
			damageTable[1] = waterDamageFactor; 
			// 1 being the second index referring to the second enum a.k.a WATER
			damageTable[2] = grassDamageFactor; 
			// 2 being the third index referring to the third enum a.k.a GRASS
		}

		// E.g.: Type.Fire.getDamageMultiplier(Type.Water);
		public double getDamageMultiplier(Type incomingDamageType) {
			// ordinal() is the index of the enum's damage factor.
			// E.g. Type.WATER.ordinal() will be 1 a.k.a waterDamageFactor
			return damageTable[incomingDamageType.ordinal()];
		}
	}
}

Article continues after the advertisement:


5. Creating a driver (main) function to test out the class

Let’s finally simulate battle! First, we’ll create a new wild Pokémon to fight and fill out the constructor with a name, amount of HP and assign it a type. I’m obviously creative with names, so I’ve named this Pokémon “Charmander” and assigned it with 100 health. Surprise — it’s a Fire type.

We’ll then create a variable called dmg so we can store the value that the Damage() function returns, which is the actual damage dealt. This is simply to help us check if our damage system is working correctly and dealing the right amount of damage.

We’ll then assign the base amount of damage your Pokemon can deal and the damage Type it’s using. For the example, I’ve made it a base 90 damage Water-type move. Following our earlier logic, the real damage would be super effective against Charmander, and deal a whooping 180 damage to it. A simple if conditional will help us print either of 2 scenarios: Charmander’s current HP after receiving the damage; or if its HP is 0, “Charmander’s HP is xx and it fainted!”. The code will look like this:

public class TheGame {
	public static void main(String[] args) {
		// Create a fire pokemon "FireZippo" with 100 HP.
		Pokemon p = new Pokemon("FireZippo",100, Type.FIRE);
		System.out.printf("A %s appeared! HP: %d, Pokemon Type: %s", p.name, p.health, p.type);

		// Optional: To print your Pokemon type and the damage it deals to FireZippo.
		int dmg = p.Damage(90, Type.WATER);
		System.out.printf("%nYour pokemon used a water-type move. %n%s has received %d damage!", p.name, dmg);

		// Check if health is 0.
		if (p.health > 0) {
			System.out.printf(" %n%s's HP is now %d.", p.name, p.health);
		} else if (p.health == 0) {
			System.out.printf(" %n%s's HP is 0 and it fainted!", p.name);
		}
	}
}

This is what the output will look like.

A Charmander appeared! HP: 100, Pokemon Type: FIRE
Your pokemon used a water-type move.
Charmander has received 180 damage!
Charmander's HP is 0 and it fainted!

Adding the rest of the Pokemon types

If you want to take this program further, you can also try programming all 18 types into the Pokemon class, although you will have to change the structure of the code a little bit, so that the Type constructor takes a variable number of arguments instead of a fixed number of arguments.

This makes it easier when you are adding all the different types, as you do not need to extend the constructor for every type that you add.

public class Pokemon {

	public int health;
	public String name;

	// The type of the Pokemon
	public Type type;

	// Constructor for the class
	public Pokemon(String name, int hp, Type t) {
		type = t;
		this.name = name;
		health = hp;
	}

	// Damage Function: Deals damage to this Pokemon and returns the actual amount of damage dealt.
	public int Damage(int amount, Type damageType) {     
		// We are modifying the amount of damage based on the damage type.
		amount *= type.getDamageMultiplier(damageType);

		// Reducing the health of the Pokemon.
		health -= amount;

		// Ensure that health does not get set to a negative value
		if (health < 0) health = 0;

		return amount;
	}

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double fireDamageFactor, double waterDamageFactor, double grassDamageFactor double... args) {
			damageTable[0] = fireDamageFactor; 
			// 0 being the first index referring to the first enum a.k.a FIRE
			damageTable[1] = waterDamageFactor; 
			// 1 being the second index referring to the second enum a.k.a WATER
			damageTable[2] = grassDamageFactor; 
			// 2 being the third index referring to the third enum a.k.a GRASS
			damageTable = args;
		}

		// E.g.: Type.Fire.getDamageMultiplier(Type.Water);
		public double getDamageMultiplier(Type incomingDamageType) {
			// ordinal() is the index of the enum's damage factor.
			// E.g. Type.WATER.ordinal() will be 1 a.k.a waterDamageFactor
			return damageTable[incomingDamageType.ordinal()];
		}
	}
}

You can also throw the main() function into the Pokemon class, so that you don’t have to maintain multiple classes to get this code to work.


Article continues after the advertisement:


public class Pokemon {

	public int health;
	public String name;

	// The type of the Pokemon
	public Type type;

	// Constructor for the class
	public Pokemon(String name, int hp, Type t) {
		type = t;
		this.name = name;
		health = hp;
	}

	// Damage Function: Deals damage to this Pokemon and returns the actual amount of damage dealt.
	public int Damage(int amount, Type damageType) {     
		// We are modifying the amount of damage based on the damage type.
		amount *= type.getDamageMultiplier(damageType);

		// Reducing the health of the Pokemon.
		health -= amount;

		// Ensure that health does not get set to a negative value
		if (health < 0) health = 0;

		return amount;
	}

	public enum Type {
 		// format: TYPE(fireDamageFactor, waterDamageFactor, grassDamageFactor)
		FIRE (0.5, 2, 0),
		WATER (0.5, 0.5, 2), 
 		GRASS (2, 0.5, 0.5);

		private double[] damageTable = new double[3];

		Type(double... args) {
			damageTable = args;
		}

		// E.g.: Type.Fire.getDamageMultiplier(Type.Water);
		public double getDamageMultiplier(Type incomingDamageType) {
			// ordinal() is the index of the enum's damage factor.
			// E.g. Type.WATER.ordinal() will be 1 a.k.a waterDamageFactor
			return damageTable[incomingDamageType.ordinal()];
		}
	}

	public static void main(String[] args) {
		// Create a fire pokemon "FireZippo" with 100 HP.
		Pokemon p = new Pokemon("FireZippo",100, Type.FIRE);
		System.out.printf("A %s appeared! HP: %d, Pokemon Type: %s", p.name, p.health, p.type);

		// Optional: To print your Pokemon type and the damage it deals to FireZippo.
		int dmg = p.Damage(90, Type.WATER);
		System.out.printf("%nYour pokemon used a water-type move. %n%s has received %d damage!", p.name, dmg);

		// Check if health is 0.
		if (p.health > 0) {
			System.out.printf(" %n%s's HP is now %d.", p.name, p.health);
		} else if (p.health == 0) {
			System.out.printf(" %n%s's HP is 0 and it fainted!", p.name);
		}
	}
}

Conclusion

And there we have it! A simple Pokemon battle system using enums to encapsulate the functionality of Pokémon’s type-effectiveness system. Enums are great for this scenario since we have fixed Pokemon types, and want the damage factors to stay consistent throughout the game in relation to those types. Hopefully this clarifies what enums are used for and how they can work in your program if you do choose to use them.

Let me know what you think in the comments!

Leave a Reply

Your email address will not be published.

For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

I agree to these terms.