Third Edition Earthdawn Dice Roller in Java

So, I play an RPG called Earthdawn; it’s a lot like D&D, but for real nerds.

One of the things we all do in my gaming group is write our own dice rollers; rolling actual dice is SO passé–and there’s an ongoing argument about whether or not a seeded random is more or less random than the natural flaws in dice and rolling surfaces. Java is the language in which I learned to write math, so I somewhat naturally write algorithms in Java without thinking. It’s easy enough to translate this into C# or whatever.

Ok, so, here’s the algorithm. In Earthdawn, dice rolls are predicated on the step level of the difficulty. You may have an attack roll at step 18 and a damage roll at step 22. In 3rd edition Earthdawn, that translates to rolling d12+d10+d8 to attack, and 2d12+2d6 for damage. Here’s the chart (click to embiggen):

As you can see, there’s some kind of progression here; it turns out that the algorithm is a simple infinite series. There’s a jump in the number of dice every seven steps. Hence, the algorithm has a few simple steps:

(1) Divide the step number by 7.
(2) Determine and store the floor and the modulus.
(3) Roll a number of d12s equal to (floor – 1).
(4) Roll dice equal to the corresponding modulus (the first addition of dice past the 7 threshold will be 2d6, so if the modulus is 1, 2d6 are rolled and added).

That is the step algorithm such that no lookup is now necessary; Earthdawn has exploding dice and epic
fails, however, so two things are necessary. Look at the exploding dice method; if you roll the maximum value of a die, you can roll it again. You can keep rolling that die until a value shows that is less than the maximum value, such that a d6 rolled with a result of 6 can be rerolled. On the second roll, 6 results. On the third roll, 2 results, so the total value of that die roll is 14. For epic fails, if you roll more than one die and all dice show ones, you have epically failed (similar to a fumble in D&D, and with equivalent disastrous results).

Here’s my dice roller; click to embiggen:

So, here’s the code. It’s intended to be self-contained (you can see that I use a pic of Captain Malcolm Reynolds; just drop a pic in your file structure and reference it in the code if you want a background). Obviously this is bare-bones; you can adapt it at your leisure and to whatever GUI you desire. Any suggestions are welcome; I am always debugging this (and the JavaScript I’m using to display syntax highlighting is a little cranky, so forgive any indentation issues). If you can think of a way to optimize this algorithm, let me know; I always need bragging rights over the guys 😉

package dice;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import 
javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;

@SuppressWarnings("serial")
public class ImagePanel extends JPanel implements ActionListener{

	int six = 6;
	int eight = 8;
	int ten = 10;
	int twelve = 12;
	int dice = 0; //counter for total number of dice
	
	public int middle; //step value entered by me.
	public boolean fail; //whether or not an epic fail happened.
	public JFrame frame;
	public Random r;
	Image img = new ImageIcon("img/MalcolmReynolds13.jpg").getImage();
	SpinnerModel stepEntry = new SpinnerNumberModel(1, 1, 300, 1);
	SpinnerModel karmaCounter = new SpinnerNumberModel(25, 0, 25, 1);
	JSpinner stepSpinner = new JSpinner(stepEntry);
	JSpinner karmaSpinner = new JSpinner(karmaCounter);
	
	private JTextField diceResult;
	private JButton myButton;
	
private JCheckBox myCheck;
	private JLabel enterStep = new JLabel("Enter Step Here.");
	
	public static void main(String[] args) {
		ImagePanel panel = new ImagePanel(new ImageIcon("img/MalcolmReynolds13.jpg").getImage());
		JFrame frame = new JFrame("Tarah's Dice Roller Of Awesomeness");
		
			frame.getContentPane().add(panel);
		    frame.pack();
		    frame.setVisible(true);
	}

	public ImagePanel(String img) {
		this(new ImageIcon(img).getImage());
	}

	public ImagePanel(Image img){
		this.img = img;
		Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
		setPreferredSize(size);
		setMinimumSize(size);
		setMaximumSize(size);
		setSize(size);
		JPanel panel = new JPanel();
		add(panel, "Center");
		myButton = new JButton("Roll The Dice.");
		myButton.addActionListener(this);
		myButton.setBorder(BorderFactory.createLineBorder(Color.black));
		diceResult = new JTextField("Roll Result", 9);
		
diceResult.setBorder(BorderFactory.createLineBorder(Color.black));
		myCheck = new JCheckBox("Use Karma.", false);
		myCheck.setBorder(BorderFactory.createLineBorder(Color.black));
		GridLayout myGrid = new GridLayout(3, 2);
		panel.setLayout(myGrid);
		panel.setBorder(BorderFactory.createLineBorder(Color.black));
		panel.add(enterStep);
		panel.add(stepSpinner);
		panel.add(myButton);
		panel.add(diceResult);
		panel.add(myCheck);
		panel.add(karmaSpinner);
		setVisible(true);
	}

	public void paintComponent(Graphics g) {
		g.drawImage(img, 0, 0, null);
	}
	
	public void actionPerformed(ActionEvent e) {
		boolean useKarma = false;
		middle = (Integer)stepSpinner.getValue();
		System.out.println("actionPerformed() thinks the step number is: " + middle);
		if (myCheck.isSelected() == true) {
			System.out.println("Using Karma.");
			useKarma = true;
			int decrease = ((Integer)karmaCounter.getValue()) - 1;
			karmaCounter.
setValue(decrease);
		}
		String s = Integer.toString(rollTheDice(useKarma, middle, fail));
	    diceResult.setText(s);

	} 

/////////////////////////////////////////////
/////////////////MATHYNESS///////////////////
/////////////////////////////////////////////
	
	//This is the Earthdawn Exploding Dice Method.
	public int d (int die){

		int sides = die;
		int result = 0;
        int roll;
    
		do {
			r = new Random();
            roll = r.nextInt(sides) + 1;
            result = result + roll;
            System.out.println("This is a d" + sides + " roll with result: " + result);
       } while (roll == sides);

		return result;
	}
	
	public int oneToSeven (int o) {
		int result = 0;
		if (o == 1) {
			result = d(six) - 3;
			if (result < 1) {
				result = 1;
			}
		}
		if (o == 2) {
			result = d(six) - 2;
			if (result < 1) {
				result = 1;
			}
		}
		if (o == 3) {
			
result = d(six) - 1;
			if (result < 1) {
				result = 1;
			}
		}
		if (o == 4) {
			result = d(six);
		}
		if (o == 5) {
			result = d(eight);
		}
		if (o == 6) {
			result = d(ten);
		}
		if (o == 7) {
			result = d(twelve);
		}
		return result;
	}
	
	public int prefix (int p) {
		int prefixTotal = 0;

		for (int i=1; i= 8) {
			int d12s = prefix(full);
			int rest = suffix(mod);
			result = d12s + rest;
			
			if (result == full+1) {
				fail = true;
			}
		}
		return result;
	}
	
	public int rollTheDice(boolean addKarma, int stepValue, boolean epicFail) {
		boolean addK = addKarma;
		dice = 0;
		int result = 0;
		result = step(stepValue);
		epicFail = fail;
		if (addK == true) {
			int dK = d(six);
			result = result + dK;
			if (epicFail == true && dK == 1) {
				JOptionPane.showMessageDialog(frame, "Epic FAIL.");
			}
		}
		else if (addK != true && epicFail == true) {
			JOptionPane.showMessageDialog(frame, "Epic FAIL.");
		}
		return 
result;
	}
}