public
Last active

Dice Roller

  • Download Gist
gistfile1.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
import java.util.Random;
 
import static java.text.MessageFormat.format;
 
/**
* http://www.reddit.com/r/dailyprogrammer/comments/10pf0j/9302012_challenge_102_easy_dice_roller/
*/
public class Main {
 
 
public static void main(String[] args) throws DiceParserException {
 
// Run though a bunch of example dice, printing the string, expectation, and a bunch of roles.
String[] examples = {"d6", "4d8", "d20-8", "10d6-2", "d20+7", "d8", "1d8+0", "1d8", "1d20+4", "5d10+10",
"10d6", "10d6-2", "10d6+2", "d6+2", "4d45+26", "d34-15", "3d12-5", "d42+32", "3d6", "d3"};
 
for (String example : examples) {
 
DiceRoller roller = DiceRoller.valueOf(example);
 
System.out.println(format("\"{0}\" -> \"{1}\" (expected min={2}, max={3}, mean={4})",
example, roller, roller.min(), roller.max(), roller.mean()));
 
final int repeats = 50;
int sum = 0, max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
for (int i = 0; i < repeats; i++) {
int roll = roller.roll();
min = Math.min(min, roll);
max = Math.max(max, roll);
sum += roll;
System.out.print(roll + " ");
}
System.out.println();
System.out.println(format("estimate min={0}, max={1}, mean={2}",
min, max, ((double) sum / repeats)));
System.out.println();
 
assert min >= roller.min();
assert max <= roller.max();
}
 
// Check numDice optionality
assert DiceRoller.valueOf("d8").equals(DiceRoller.valueOf("1d8"));
 
// Check adjustment optionality
assert DiceRoller.valueOf("d8+0").equals(DiceRoller.valueOf("d8"));
}
 
/**
* A random number generator that produce values by drawing numDice uniformly distributed random integers from the
* range adjustment+1 to adjustment+numSides (inclusive).
*/
public static class DiceRoller {
 
private final Random random = new Random();
 
private final int numDice;
 
private final int numSides;
 
private final int adjustment;
 
public DiceRoller(int numDice, int numSides, int adjustment) {
this.numDice = numDice;
this.numSides = numSides;
this.adjustment = adjustment;
}
 
public int roll() {
int result = adjustment;
for (int i = 0; i < numDice; i++)
result += 1 + random.nextInt(numSides);
return result;
}
 
public int min() {
return numDice + adjustment;
}
 
public int max() {
return (numDice * numSides) + adjustment;
}
 
public double mean() {
return numDice * (1. + (numSides - 1) / 2.) + adjustment;
}
 
public String toString() {
final StringBuilder builder = new StringBuilder();
if (numDice != 1)
builder.append(numDice);
builder.append('d');
builder.append(numSides);
if (adjustment != 0) {
builder.append(adjustment < 0 ? '-' : '+');
builder.append(adjustment);
}
return builder.toString();
}
 
public static DiceRoller valueOf(String string) throws DiceParserException {
return new DiceParser(string).parse();
}
 
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DiceRoller)) return false;
DiceRoller that = (DiceRoller) o;
return adjustment == that.adjustment && numDice == that.numDice && numSides == that.numSides;
}
 
@Override
public int hashCode() {
return 31 * (31 * numDice + numSides) + adjustment;
}
}
 
/**
* Parser that takes strings from the dice roll language, producing a DiceRoller instance. Generally it's easier to
* call DiceRoller.valueOf(String), rather than directly use this object.
*/
public static class DiceParser {
 
private CharSequence chars;
 
private int offset = 0;
 
public DiceParser(CharSequence chars) {
this.chars = chars;
offset = 0;
}
 
public DiceRoller parse() throws DiceParserException {
 
final int numDice = isDigitNext() ? parseInteger() : 1;
if (numDice < 1)
throw new DiceParserException(this, "Not enough dice.");
 
if (!isNextAnyOf('d', 'D'))
throw new DiceParserException(this, "Expecting 'D' character.");
next();
 
final int numSides = parseInteger();
final int adjustment;
if (isNextAnyOf('+', '-')) {
next();
adjustment = parseInteger();
} else {
adjustment = 0;
}
 
if (hasNext())
throw new DiceParserException(this, "Expecting end of character sequence.");
 
return new DiceRoller(numDice, numSides, adjustment);
}
 
public int parseInteger() throws DiceParserException {
if (!isDigitNext())
throw new DiceParserException(this, "Expecting digit.");
int value = 0;
do {
value = value * 10 + (next() - '0');
} while (isDigitNext());
return value;
}
 
public boolean isDigitNext() {
return hasNext() && Character.isDigit(chars.charAt(offset));
}
 
public boolean isNextAnyOf(char... characters) {
if (!hasNext())
return false;
for (char character : characters)
if (chars.charAt(offset) == character)
return true;
return false;
}
 
public char next() throws DiceParserException {
if (!hasNext())
throw new DiceParserException(this, "Unexpected end of character sequence.");
return chars.charAt(offset++);
}
 
public boolean hasNext() {
return offset < chars.length();
}
 
}
 
/**
* Exception thrown by DiceParser when something goes wrong.
*/
public static class DiceParserException extends Exception {
 
private final CharSequence characters;
private final int offset;
 
public DiceParserException(DiceParser parser) {
this.characters = parser.chars;
this.offset = parser.offset;
}
 
public DiceParserException(DiceParser parser, String message) {
super(message);
this.characters = parser.chars;
this.offset = parser.offset;
}
 
public DiceParserException(DiceParser parser, String message, Throwable throwable) {
super(message, throwable);
this.characters = parser.chars;
this.offset = parser.offset;
}
 
public DiceParserException(DiceParser parser, Throwable throwable) {
super(throwable);
this.characters = parser.chars;
this.offset = parser.offset;
}
 
@Override
public String getMessage() {
return format("Error parsing \"{0}\" at offset {1}: {2}",
characters, offset, super.getMessage());
}
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.