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;
}
}