Home Let's Build an RPG! Deconstructing B/X D&D Adventure Generator! B/X D&D Character Generator Various Java Contact
 

First PagePrevious PageBack to overviewNext PageLast Page

Generating Stats

Alrighty! We've built a bag o' dice, so let's create a data structure to store character data, and then roll some stats!

Game Mechanics

Since this is a rather retro sort of game, we'll use some retro-style game mechanics to add to the flavor! Yum, descending Armor Class!

We'll more or less be making this stuff up as we go, but the idea is that our game mechanics will feel like old 1980's boxed set editions of That Really Popular But Trademarked Fantasy RPG Involving Dungeons and Giant Lizards that Fly Around and Sneeze Fire on Hapless Villagers.

There is an OSR retro-clone of the 1981 boxed-set edition of that game that we might glean some ideas from, called Labyrinth Lord. The no-art version is a free download! It might be handy to have a copy of that to flip through sometimes for ideas!
Labyrinth Lord Website

For now, we'll assume that there is only one character class, the Human Fighter. We'll add more character classes later.

Characters will have 6 vital statistics, Strength, Intelligence, Wisdom, Dexterity, Constitution, and Charisma. For now, we'll just roll them all in order on 3d6, but we'll probably add other stat generation methods later!

The stats will give a +/- Stat modifier, depending on the stat. These modifiers will be applied to things like combat bonuses and hit-points, depending on the particular stat.

Our stat modifiers table will look like:

Stat Range Stat Modifier Textual Description
3 -3 Terrible
4-5 -2 Poor
6-8 -1 Below Average
9-12 0 Average
13-15 +1 Above Average
16-17 +2 Excellent
18 +3 Amazing

Design

Now we delve into the nitty gritty of implementing our character. Let's do a little forward-thinking first, to make sure we implement this semi-decently the first time and don't have to go back and change a whole bunch of stuff later.

Even though we're only looking to build a simple character vs. character combat loop for now, our character is eventually going to be fighting Epic Battles against various monsters in the dungeon. So we probably ought to do a little thinking on how characters and the monsters that they will eventually be fighting with relate to each other.

A character seems to have many attributes in common with monsters. Heck, one of the “monsters” listed in the random encounter tables is a party of non-player characters!

Both characters and monsters have those 6 vital statistics, although these stats aren't listed for the monsters in their stat blocks in the various rule books. But monsters do get stat modifiers, as can be seen in their combat and hit point bonuses in the stat blocks. Since our game is a computer game, we can let the mechanics get a little more complicated than we can in tabletop games without the gameplay slowing down. So we'll give all of our critters stats, too! That will make each individual monster a little more unique.

Monsters have hit points, too, a d8 per hit die. They roll on the same saving throw tables that player characters do. They roll attacks on an attack matrix according to their number of hit dice, like player characters roll on according to their level. Some of them can use equipment, wear armor, and wield weapons, just like player characters can.

It sure sounds to me like characters and monsters are pretty much the same thing! The only real difference is that characters gain experience and level up, while monsters do not!

So, given all that, let's define some things:

  • A Critter is something that is “alive”, or at least moves around and acts alive (like zombies and magically animated carnivorous carpets and stuff).
  • A Character is a type of Critter that has a Character Class and can gain experience and level up. A Character may be controlled by the player (a player character), or by the computer (a non-player character).

Implementation

We're going to implement this initial PlayerCharacter using an interface and a concrete class (or just class for short, as opposed to an abstract class, which we will learn more about later).

CAUTION: Don't get the Programming notion of a “class” confused with the RPG Mechanics notion of a “character class”! I'll try to always refer to the RPG-mechanics notion fully as a “character class”, so that we don't get them mixed up.

“What the heck are these interfaces and classes?!?” you ask?

Wikipedia says:

  • In object-oriented languages the term “interface” is often used to define an abstract type that contains no data, but exposes behaviors defined as methods. A class having all the methods corresponding to that interface is said to implement that interface.

That didn't help much though, did it?! Let's break it down with a practical example.

As we decided above, Characters are really just a type of Critter. Goblins are also a type of critter, as are Dragons and Trolls.

So we're going to write an interface that describes what sorts of statistics generic Critters have, and how we can access and manipulate them in a uniform way.

Then we will write a (Java) class for our Character that will “implement” that interface, defining exactly what those statistics are for the character, and what happens when we access and manipulate them.

Later on, we will create other critters, like those goblins and dragons and trolls above, that also implement the Critter interface.

We do it this way to make it easier to write the combat system (and other things later, too)! By defining an interface this way, the combat system needn't be concerned with what sorts of critters are fighting with each other. To the combat system, they're all just Critters. The nitty gritty differences between the different critters are encapsulated in their concrete classes.

This idea of encapsulation is an important concept in Object Oriented programming. The idea is that we try to keep everything specific to our Goblins within the file that defines Goblins, and all the stuff that is specific to Dragons within the file that defines Dragons, etc etc. As opposed to having a little bit of it in the combat system and a little bit of it in the magic system, and little other bits here and there and everywhere.

When we encapsulate everything about a specific sort of critter in that critter's file, then it's all in one place. We don't have to sift through many source code files to change/fix things later when the project gets bigger. We can just change things in that one file, and there are (hopefully!) no unexpected side-effects in other code, resulting in fewer bugs.

That's how it is supposed to work in an ideal world, anyway. In reality, it is sometimes hard to achieve perfect encapsulation. But as long as we make a good effort to try, and clearly document the instances where we violate it, we'll get a lot more done in the long run, faster, with fewer bugs.

Critter (Interface)

So, let's start out with an interface for our critters, Critter.java:

package net.dizzydragon.rustybox; 
 
public interface Critter 
{ 
 
} 

All of our critters will have stats, so this is a good place to define the specific set of stats, too.

package net.dizzydragon.rustybox; 
 
public interface Critter 
{ 
	public enum Stat 
	{ 
		STRENGTH, 
		INTELLIGENCE, 
		WISDOM, 
		DEXTERITY, 
		CONSTITUTION, 
		CHARISMA 
	} 
} 

This does not define what a critter's stats are, just what stats it has. “Stat” is an Enumeration Type, or enum. The TL;DR is, think of an enum as a “set of constants” to help with strict type enforcement (resulting in less room for bugs!). In Java, enums are actually more than that, but the advanced features of Java enums are seldom used!

We want to be able to see what the stats of a critter are, and get the critter's stat modifiers, so we'll add some method prototypes for that.

package net.dizzydragon.rustybox; 
 
public interface Critter 
{ 
	public enum Stat 
	{ 
		STRENGTH, 
		INTELLIGENCE, 
		WISDOM, 
		DEXTERITY, 
		CONSTITUTION, 
		CHARISMA 
	} 
 
	public int getStat( Stat stat ); 
	public int getStatModifier( Stat stat ); 
} 

The keyword “public” means that whatever item it is associated with is visible to all other code. There are other visibility keywords, such as “protected”, “private”, and nothing (indicating package-private; not used very often). These are used to help with encapsulation. Don't worry too much about protected and package-private for now.

That's it! We have our Critter interface defined. We'll add more to it later as we expand the game. But this is all we need for now. We'll write a silly “game” over the next few chapters, where we roll two characters and watch them fight to the death!

Java may seem rather verbose (it is!) and strict, if you are coming from a compact, non-strict scripting language like PHP or something. This may feel bothersome at first, and indeed it is for small programs, but when a project gets really large the strict typing and well-defined interfaces and access methods make things much, much easier to debug.

Here's our Critter after cleanup and documentation!

/* 
 * Copyright (C) 2012 L. Adamson 
 *  
 * This software is provided 'as-is', without any express or implied warranty. 
 * In no event will the authors be held liable for any damages arising from the 
 * use of this software. 
 *  
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it 
 * freely, subject to the following restrictions: 
 *  
 * 1. The origin of this software must not be misrepresented; you must not claim 
 * that you wrote the original software. If you use this software in a product, 
 * an acknowledgment in the product documentation would be appreciated but is 
 * not required. 
 *  
 * 2. Altered source versions must be plainly marked as such, and must not be 
 * misrepresented as being the original software. 
 *  
 * 3. This notice may not be removed or altered from any source distribution. 
 *  
 * L. Adamson leaf@dizzydragon.net 
 */ 
 
package net.dizzydragon.rustybox; 
 
/** 
 * Critter is an interface defining a generic something that is "alive", or at 
 * least moves around and acts alive (like zombies and animated rugs and stuff). 
 */ 
public interface Critter 
{ 
 
	/** 
	 * The six vital statistics of a player character or critter. 
	 */ 
	public enum Stat 
	{ 
		/** 
		 * Strength measures the raw muscle power of a character. It factors 
		 * into melee to-hit and damage, among other things. 
		 */ 
		STRENGTH, 
		/** 
		 * Intelligence measures the ability of a character to learn and 
		 * remember. It factors into the number of languages known, among other 
		 * things. 
		 */ 
		INTELLIGENCE, 
		/** Wisdom measures a character's common-sense and intuition. */ 
		WISDOM, 
		/** 
		 * Dexterity is a measure of a character's accuracy, speed, and 
		 * coordination. It factors into ranged to-hit and initiative, among 
		 * other things. 
		 */ 
		DEXTERITY, 
		/** 
		 * Constitution is a measure of a character's health and resilience. It 
		 * factors into maximum hit-points, among other things. 
		 */ 
		CONSTITUTION, 
		/** 
		 * Charisma is a measure of a character's likeability and force of 
		 * personality. It factors into monster reactions, among other things. 
		 */ 
		CHARISMA 
	} 
 
	/** 
	 * Gets the numerical value of the stat in question. 
	 *  
	 * @param stat 
	 *            the stat to be gotten 
	 * @return the value of the specified stat 
	 */ 
	public int getStat( Stat stat ); 
 
	/** 
	 * Gets the numerical value of the stat modifier of the stat in question. 
	 *  
	 * @param stat 
	 *            the stat whose modifier is to be gotten 
	 * @return the stat modifier of the specified stat 
	 */ 
	public int getStatModifier( Stat stat ); 
}

PlayerCharacter (Class)

Now let's implement our simplified, stripped down character, PlayerCharacter.java.

This file is a little longer, so I'll break it down into smaller blocks as we talk about it, but I'll put it all together at the end!

First, let's define the class, in a new file in the proper package, of course.

package net.dizzydragon.rustybox; 
 
public class PlayerCharacter implements Critter 
{ 
 
}

Here we see the implements keyword. This tells Java that this class will implement the specification that we defined in the critter interface. As a result, objects of the Character type can be referenced by variables of the more generic Critter type. This will be useful in our combat system, when we are handling many different sorts of critters with the same code!

However, you'll notice that Eclipse is complaining. If you hover your mouse over the underlined error, you'll see that it is saying, “The type Character must implement the inherited abstract method Critter.getStat()”. This is because our class must implement the methods defined by the critter interface!

To quickly generate some stubs for those methods, click on the “Add Unimplemented Methods” link that you can see below the error message.

Then the code looks like this:

package net.dizzydragon.rustybox; 
 
public class PlayerCharacter implements Critter 
{ 
 
	@Override 
	public int getStat( Stat stat ) 
	{ 
		// TODO Auto-generated method stub 
		return 0; 
	} 
 
	@Override 
	public int getStatModifier( Stat stat ) 
	{ 
		// TODO Auto-generated method stub 
		return 0; 
	} 
 
}

Now the error is gone, but we must write bodies for these methods!

But first, let's add a private variable to hold our character's stats!

Here's a code snippet.

package net.dizzydragon.rustybox; 
 
public class PlayerCharacter implements Critter 
{ 
	final private int[] stats = new int[Stat.values().length]; 
 
// ... ...

The private keyword here specifies that the variable (you may also hear them called “fields”) is only accessible by code in this same file.

Why are we doing it that way, you ask?

We could make this variable “public”, and then code in other files could access it directly. But that is bad programming practice, because it violates the principle of encapsulation that we were talking about before. When we define our variables as private and then access our data through the accessor methods defined by our interface, then we can do several things that are not possible when accessing public variables directly:

  • We can write code in the accessor methods that verifies that the input and output values are in the appropriate range. The computer science community calls this data validation. Doing this vastly simplifies debugging. Do it! You won't be sorry!
  • We can change the internal implementation of the data without breaking code outside of this file, since external code can only access the data through the published interface.

You'll see that Eclipse is complaining that the values of this private field is never used. That's because we're not finished! We'll fix it in a bit.

So what's with all of the weird brackets and crap on the stats variable?

That is an Array. Read more about them by clicking that link, if you're not familiar with them.

We could hard-code the length of our array to 6, since there are six vital statistics. But what would happen if we added, say, a Comeliness stat, and forgot to change the size of the array to 7? We'd have a bug, that's what!

Instead, we're telling it to create an array of a size equal to the number of values defined in the Stat enumeration. That's what Stat.values().length does. Stat.values() gives us an array containing all of the constants defined in Stat. Adding .length gives us the length of that array, which is the same as the number of constants defined in Stat!

Ok! Now that that's done, let's start filling in our method stubs!

	@Override 
	public int getStat( Stat stat ) 
	{ 
		return stats[stat.ordinal()]; 
	}

The @Override annotation here tells the compiler that it should expect this method to be prototyped in another file, either an interface (which is the case here), or a class that we are extending (we'll explore that later, when we create some abstract classes to help prevent code duplication). We don't have to specify this annotation, but if we do, then if we change the interface, the compiler will let us know that we have to also change this file to match the changes to the interface, and that will help us catch/prevent bugs more easily!

See that stat.ordinal() thing? What's that?!?

Hover your mouse over “ordinal” in Eclipse, and the context-sensitive help will tell you. Context-sensitive help is awesome! I'll leave it to you to figure out what is going on there, as an exercise. >:3 But the gist of it is, we're using some Magic to index an int array with enumerations.

Continuing on…

	@Override 
	public int getStatModifier( Stat stat ) 
	{ 
		int value = getStat(stat); 
		if( value <= 3 ) 
			return -3; 
		else if( value <= 5 ) 
			return -2; 
		else if( value <= 8 ) 
			return -1; 
		else if( value <= 12 ) 
			return 0; 
		else if( value <= 15 ) 
			return 1; 
		else if( value <= 17 ) 
			return 2; 
		else if( value >= 18 ) 
			return 3; 
		throw new RuntimeException( "Bug: This should never happen!!!" ); 
	}

This just implements the stat modifier table, with an if-else block. Pretty straightforward!

The line that throws the exception is never reached, because all possible values of the stat are covered in the preceding code. But it is there anyway. If we go back and change something later and make a mistake such that not all possible values are being checked, that exception will help us home in on the bug pretty quick!

Constructor

Alright! This is all wonderful and good, but we're not actually rolling the character's stats anywhere!

So let's define a Constructor to do that.

package net.dizzydragon.rustybox; 
 
import net.dizzydragon.rustybox.util.Dice; 
 
public class PlayerCharacter implements Critter 
{ 
	final private int[] stats = new int[Stat.values().length]; 
 
	public PlayerCharacter() 
	{ 
		for( int i=0; i<stats.length; i++ ) 
			stats[i] = Dice.d(3,6); 
	} 
 
// ... ...

We use a for loop to roll 3d6 for each stat, simple as that!

It for now! Here's the whole file after cleanup and commenting.

/* 
 * Copyright (C) 2012 L. Adamson 
 *  
 * This software is provided 'as-is', without any express or implied warranty. 
 * In no event will the authors be held liable for any damages arising from the 
 * use of this software. 
 *  
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it 
 * freely, subject to the following restrictions: 
 *  
 * 1. The origin of this software must not be misrepresented; you must not claim 
 * that you wrote the original software. If you use this software in a product, 
 * an acknowledgment in the product documentation would be appreciated but is 
 * not required. 
 *  
 * 2. Altered source versions must be plainly marked as such, and must not be 
 * misrepresented as being the original software. 
 *  
 * 3. This notice may not be removed or altered from any source distribution. 
 *  
 * L. Adamson leaf@dizzydragon.net 
 */ 
 
package net.dizzydragon.rustybox; 
 
import net.dizzydragon.rustybox.util.Dice; 
 
/** 
 * A very simple Player Character. 
 */ 
public class PlayerCharacter implements Critter 
{ 
	/** The character's vital statistics. */ 
	final private int[] stats = new int[Stat.values().length]; 
 
	/** 
	 * Rolls a new character.  Stats are straight 3d6 down the line. 
	 */ 
	public PlayerCharacter() 
	{ 
		for( int i=0; i<stats.length; i++ ) 
			stats[i] = Dice.d(3,6); 
	} 
 
	/* (non-Javadoc) 
	 * @see net.dizzydragon.rustybox.Critter#getStat(net.dizzydragon.rustybox.Critter.Stat) 
	 */ 
	@Override 
	public int getStat( Stat stat ) 
	{ 
		return stats[stat.ordinal()]; 
	} 
 
	/* (non-Javadoc) 
	 * @see net.dizzydragon.rustybox.Critter#getStatModifier(net.dizzydragon.rustybox.Critter.Stat) 
	 */ 
	@Override 
	public int getStatModifier( Stat stat ) 
	{ 
		int value = getStat(stat); 
		if( value <= 3 ) 
			return -3; 
		else if( value <= 5 ) 
			return -2; 
		else if( value <= 8 ) 
			return -1; 
		else if( value <= 12 ) 
			return 0; 
		else if( value <= 15 ) 
			return 1; 
		else if( value <= 17 ) 
			return 2; 
		else if( value >= 18 ) 
			return 3; 
		throw new RuntimeException( "Bug: This should never happen!!!" ); 
	} 
}

Test It!

Let's modify our Test.java to display a stat block!

/* 
 * Copyright (C) 2012 L. Adamson 
 *  
 * This software is provided 'as-is', without any express or implied warranty. 
 * In no event will the authors be held liable for any damages arising from the 
 * use of this software. 
 *  
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it 
 * freely, subject to the following restrictions: 
 *  
 * 1. The origin of this software must not be misrepresented; you must not claim 
 * that you wrote the original software. If you use this software in a product, 
 * an acknowledgment in the product documentation would be appreciated but is 
 * not required. 
 *  
 * 2. Altered source versions must be plainly marked as such, and must not be 
 * misrepresented as being the original software. 
 *  
 * 3. This notice may not be removed or altered from any source distribution. 
 *  
 * L. Adamson leaf@dizzydragon.net 
 */ 
 
package test; 
 
import net.dizzydragon.rustybox.Critter; 
import net.dizzydragon.rustybox.Critter.Stat; 
import net.dizzydragon.rustybox.simpleio.SimpleIO; 
import net.dizzydragon.rustybox.simpleio.SimpleRunnable; 
import net.dizzydragon.rustybox.PlayerCharacter; 
 
public class Test extends SimpleRunnable 
{ 
	private static final long	serialVersionUID	= 1L; 
 
	public static void main( String[] args ) 
	{ 
		runLocal( new Test() ); 
	} 
 
	@Override 
	public void run( SimpleIO io ) 
	{ 
		// Generate a character. 
		Critter character = new PlayerCharacter(); 
		// Loop through all of the character's stats and print them. 
		for( Stat stat : Stat.values() ) 
		{ 
			// The name of the stat, with capitalization fixed, followed by the stat's value. 
			io.print( caps( stat.toString() ) + ": " + character.getStat( stat ) ); 
			// If the stat modifier is nonzero, then we print it, too. 
			int mod = character.getStatModifier( stat ); 
			if( mod != 0 ) 
			{ 
				io.print( " (" ); 
				// Positive numbers don't automatically print with a "+" in front of them, so we add it. 
				if( mod > 0 ) 
					io.print( "+" ); 
				io.print( mod ); 
				io.print( ")" ); 
			} 
			io.println(); 
		} 
	} 
 
	// This is a silly function that capitalizes the first letter of a string, 
	// and lowercases all the rest. We use it when we print out enumeration 
	// constants, because they are generally ALL-CAPS, and that is ugly! 
	private static String caps( String s ) 
	{ 
		return s.substring( 0, 1 ).toUpperCase() + s.substring( 1 ).toLowerCase(); 
	} 
}

Run it and see what happens!

What's Next?

FIXME In Progress.

Source Code

The source code for this chapter can be found in the Subversion Repository.

If you are interested in changing/modifying the code, you can download it into your Eclipse workspace using Subclipse as detailed here: TUTORIAL: Using Subclipse.

First PagePrevious PageBack to overviewNext PageLast Page

rustybox/book/004_generating_stats.txt · Last modified: 2012/09/09 00:29 (external edit)

Copyright © 2009, 2012-2013 by L. Adamson, unless otherwise stated.
If you see something here that you like, and find it useful or learn something, please consider making a quick, easy, and secure donation via PayPal.
Your support is what keeps this whole thing going!