Created
October 6, 2012 13:43
-
-
Save hamishmorgan/3844967 to your computer and use it in GitHub Desktop.
Dice Roller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment