summaryrefslogtreecommitdiff
path: root/dice/src/main
diff options
context:
space:
mode:
authorBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-25 12:30:48 -0300
committerBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-25 12:30:48 -0300
commit6b76d2ff5a3df3931c0983d915eed33e83e892e0 (patch)
treee2d959c0d59a6cae412dafee307cbd25b99a1a9d /dice/src/main
parent80b48c9fcba9c7c9d64501a6e2ac7c5b98fcd1d2 (diff)
Move dice to new module
Diffstat (limited to 'dice/src/main')
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/CompoundDie.java60
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/CompoundingDie.java115
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/DiceBox.java310
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/Die.java39
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/DieExpression.java69
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/DieList.java31
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/ExplodingDice.java124
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/FudgeDie.java67
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/MathDie.java121
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/ScalarDie.java47
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/SimpleDie.java119
-rw-r--r--dice/src/main/java/bjc/dicelang/dice/SimpleDieList.java77
12 files changed, 1179 insertions, 0 deletions
diff --git a/dice/src/main/java/bjc/dicelang/dice/CompoundDie.java b/dice/src/main/java/bjc/dicelang/dice/CompoundDie.java
new file mode 100644
index 0000000..0793872
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/CompoundDie.java
@@ -0,0 +1,60 @@
+package bjc.dicelang.dice;
+
+/**
+ * A die whose rolls result from concatenating two other rolls together.
+ *
+ * @author Ben Culkin
+ */
+public class CompoundDie implements Die {
+ /* The dice that form this die */
+ private final Die left;
+ private final Die right;
+
+ /**
+ * Create a new compound die.
+ *
+ * @param lft
+ * The left die
+ * @param rght
+ * The right die
+ */
+ public CompoundDie(final Die lft, final Die rght) {
+ left = lft;
+ right = rght;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ return left.canOptimize() && right.canOptimize();
+ }
+
+ @Override
+ public long optimize() {
+ long leftOpt = left.optimize();
+ long rightOpt = right.optimize();
+
+ return Long.parseLong(String.format("%d%d", leftOpt, rightOpt));
+ }
+
+ @Override
+ public long roll() {
+ long leftRoll = left.optimize();
+ long rightRoll = right.optimize();
+
+ return Long.parseLong(String.format("%d%d", leftRoll, rightRoll));
+ }
+
+ @Override
+ public long rollSingle() {
+ /* Actually one dice built using two, can't be split. */
+ return roll();
+ }
+
+ @Override
+ public String toString() {
+ String leftString = left.toString();
+ String rightString = right.toString();
+
+ return String.format("%sc%s", leftString, rightString);
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/CompoundingDie.java b/dice/src/main/java/bjc/dicelang/dice/CompoundingDie.java
new file mode 100644
index 0000000..023282e
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/CompoundingDie.java
@@ -0,0 +1,115 @@
+package bjc.dicelang.dice;
+
+import java.util.function.Predicate;
+
+/**
+ * Implements a compounding die.
+ *
+ * This means that the source will be rolled, and then more single rolls will be
+ * added while it meets a qualification.
+ *
+ * @author Ben Culkin
+ */
+public class CompoundingDie implements Die {
+ /* The source die to compound. */
+ private final Die source;
+
+ /* The predicate that marks when to compound. */
+ private final Predicate<Long> compoundOn;
+ /* The string version of the predicate, if one exists. */
+ private final String compoundPattern;
+
+ /**
+ * Create a new compounding die with no pattern.
+ *
+ * @param src
+ * The die to compound from
+ * @param compound
+ * The conditions to compound on
+ */
+ public CompoundingDie(final Die src, final Predicate<Long> compound) {
+ this(src, compound, null);
+ }
+
+ /**
+ * Create a new compounding die with a specified pattern.
+ *
+ * @param src
+ * The die to compound from
+ * @param compound
+ * The conditions to compound on
+ * @param patt
+ * The string pattern the condition came from, for
+ * printing
+ */
+ public CompoundingDie(final Die src, final Predicate<Long> compound, final String patt) {
+ source = src;
+
+ compoundOn = compound;
+ compoundPattern = patt;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ if (source.canOptimize()) {
+ /* We can only be optimized for a result of zero. */
+ return source.optimize() == 0;
+ }
+
+ return false;
+ }
+
+ @Override
+ public long optimize() {
+ /* If we can be optimized, its to zero. */
+ return 0;
+ }
+
+ @Override
+ public long roll() {
+ /* The current result. */
+ long res = source.roll();
+ /* The last result. */
+ long oldRes = res;
+
+ while (compoundOn.test(oldRes)) {
+ /* Compound while the result should be compounded. */
+ oldRes = source.rollSingle();
+
+ /* Accumulate. */
+ res += oldRes;
+ }
+
+ return res;
+ }
+
+ @Override
+ public long rollSingle() {
+ /* Just compound on an initial single role. */
+ long res = source.rollSingle();
+ /* The last result. */
+ long oldRes = res;
+
+ while (compoundOn.test(oldRes)) {
+ /* Compound while the result should be compounded. */
+ oldRes = source.rollSingle();
+
+ /* Accumulate. */
+ res += oldRes;
+ }
+
+ return res;
+ }
+
+ @Override
+ public String toString() {
+ String sourceString = source.toString();
+
+ /* Can't print a parseable version. */
+ if (compoundPattern == null) {
+ return String.format("%s!!<complex-pattern>", sourceString);
+ }
+
+ return String.format("%s!!%s", sourceString, compoundPattern);
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/DiceBox.java b/dice/src/main/java/bjc/dicelang/dice/DiceBox.java
new file mode 100644
index 0000000..8d00d96
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/DiceBox.java
@@ -0,0 +1,310 @@
+package bjc.dicelang.dice;
+
+import java.util.Random;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+/**
+ * Contains static methods for producing dice from strings.
+ *
+ * @author Ben Culkin
+ */
+public class DiceBox {
+ static final Random rng = new Random();
+
+ /**
+ * Parse a die expression from a string.
+ *
+ * @param expString
+ * The string to parse.
+ *
+ * @return The die expression from the string, or null if it wasn't one
+ */
+ public static DieExpression parseExpression(final String expString) {
+ try {
+ return doParseExpression(expString);
+ } catch (Exception ex) {
+ /*
+ * @TODO 10/08/17 Ben Culkin :DieErrors :ErrorRefactor
+ * Use different types of exceptions to provide
+ * better error messages.
+ */
+ String exMessage = ex.getMessage();
+
+ System.out.printf("ERROR: Could not parse die expression (Cause: %s)\n", exMessage);
+ ex.printStackTrace();
+
+ return null;
+ }
+ }
+
+ private static DieExpression doParseExpression(final String expString) {
+ /* Only bother with valid expressions. */
+ if (!isValidExpression(expString)) {
+ return null;
+ }
+
+ if (scalarDiePattern.matcher(expString).matches()) {
+ /* Parse scalar die. */
+ /* @TODO 10/08/17 Ben Culkin :SubstringIndexOf
+ * This substring/index of call should be
+ * abstracted into its own method so as to make the
+ * code more explanatory and ensure that things
+ * like the return code of indexOf are correctly
+ * checked.
+ */
+ final String dieString = expString.substring(0, expString.indexOf('s'));
+
+ final long lar = Long.parseLong(dieString);
+
+ final Die scal = new ScalarDie(lar);
+
+ return new DieExpression(scal);
+ } else if (simpleDiePattern.matcher(expString).matches()) {
+ /* Parse simple die groups. */
+ final String[] dieParts = expString.split("d");
+
+ final long right = Long.parseLong(dieParts[1]);
+ final long left;
+
+ if (dieParts[0].equals("")) {
+ /* Handle short-form expressions. */
+ left = 1;
+ } else {
+ left = Long.parseLong(dieParts[0]);
+ }
+
+ final Die scal = new SimpleDie(left, right);
+
+ return new DieExpression(scal);
+ } else if (fudgeDiePattern.matcher(expString).matches()) {
+ /* Parse fudge dice. */
+ /* :SubstringIndexOf */
+ final String nDice = expString.substring(0, expString.indexOf('d'));
+ final Die fudge = new FudgeDie(Long.parseLong(nDice));
+
+ return new DieExpression(fudge);
+ } else if (compoundDiePattern.matcher(expString).matches()) {
+ /* Parse compound die expressions. */
+ final String[] dieParts = expString.split("c");
+
+ /* @TODO 10/08/17 :SplitParse
+ * Should this split string/parse split parts be
+ * abstracted into something else that handles
+ * doing the splitting correctly, as well as
+ * making sure that the resulting DieExpressions
+ * are of the right type?
+ */
+ final DieExpression left = parseExpression(dieParts[0]);
+ final DieExpression right = parseExpression(dieParts[1]);
+
+ /* :ErrorRefactor */
+ if (left.isList) {
+ System.out.printf("ERROR: Expected a scalar die expression for lhs of compound die, got a list expression instead (%s)\n",
+ left);
+ } else if (right.isList) {
+ System.out.printf("ERROR: Expected a scalar die expression for rhs of compound die, got a list expression instead (%s)\n",
+ right);
+ }
+
+ final Die compound = new CompoundDie(left.scalar, right.scalar);
+
+ return new DieExpression(new CompoundDie(left.scalar, right.scalar));
+ } else if (compoundingDiePattern.matcher(expString).matches()) {
+ /* Parse compounding die expressions. */
+ final String[] dieParts = expString.split("!!");
+
+ final DieExpression left = parseExpression(dieParts[0]);
+ final Predicate<Long> right = deriveCond(dieParts[1]);
+
+ final Die scal = new CompoundingDie(left.scalar, right, dieParts[1]);
+
+ return new DieExpression(scal);
+ } else if (explodingDiePattern.matcher(expString).matches()) {
+ /* Parse exploding die expressions. */
+ final String[] dieParts = expString.split("!");
+
+ final DieExpression left = parseExpression(dieParts[0]);
+ final Predicate<Long> right = deriveCond(dieParts[1]);
+
+ final DieList lst = new ExplodingDice(left.scalar, right, dieParts[1], false);
+
+ return new DieExpression(lst);
+ } else if (penetratingDiePattern.matcher(expString).matches()) {
+ /* Parse penetrating die expressions. */
+ final String[] dieParts = expString.split("p!");
+
+ final DieExpression left = parseExpression(dieParts[0]);
+ final Predicate<Long> right = deriveCond(dieParts[1]);
+
+ final DieList lst = new ExplodingDice(left.scalar, right, dieParts[1], true);
+
+ return new DieExpression(lst);
+ } else if (diceListPattern.matcher(expString).matches()) {
+ /* Parse simple die lists. */
+ final String[] dieParts = expString.split("dl");
+
+ final DieExpression left = parseExpression(dieParts[0]);
+ final DieExpression right = parseExpression(dieParts[1]);
+
+ final DieList lst = new SimpleDieList(left.scalar, right.scalar);
+ return new DieExpression(lst);
+ }
+
+ /* Unhandled type of die expression. */
+ System.out.printf("INTERNAL ERROR: Valid die expression '%s' not parsed\n", expString);
+ return null;
+ }
+
+ /* The strings and patterns used for matching. */
+ /* @TODO 10/08/17 Ben Culkin :RegexResource
+ * These regexes and patterns should be moved to something
+ * external, probably using the SimpleProperties-based system that
+ * BJC-Utils2 uses.
+ */
+ /* Defines a comparison predicate. */
+ private static final String comparePoint = "[<>=]\\d+";
+
+ /*
+ * Defines a scalar die.
+ *
+ * This is just a number.
+ */
+ private static final String scalarDie = "[\\+\\-]?\\d+sd";
+ private static final Pattern scalarDiePattern = Pattern.compile(
+ String.format("\\A%s\\Z", scalarDie));
+
+ /*
+ * Defines a simple die.
+ *
+ * This is a group of one or more dice of the same size.
+ */
+ private static final String simpleDie = "(?:\\d+)?d\\d+";
+ private static final Pattern simpleDiePattern = Pattern.compile("\\A" +
+ simpleDie + "\\Z");
+
+ /*
+ * Defines a fudge die.
+ *
+ * This is like a simple die, but all the die give -1, 0, or 1 as
+ * results.
+ */
+ private static final String fudgeDie = "(?:\\d+)?dF";
+ private static final Pattern fudgeDiePattern = Pattern.compile("\\A" + fudgeDie +
+ "\\Z");
+
+ /*
+ * Defines a compound die.
+ *
+ * This is like using two d10's to simulate a d100
+ */
+ private static final String compoundDie = simpleDie + "c(?:(?:" +
+ simpleDie + ")|(?:\\d+))";
+ private static final Pattern compoundDiePattern = Pattern.compile("\\A" +
+ compoundDie + "\\Z");
+
+ /*
+ * Defines a compound group.
+ *
+ * This is used for forming die list type expressions.
+ */
+ private static final String compoundGroup = "(?:(?:" + scalarDie + ")|(?:" + simpleDie +
+ ")|(?:" + compoundDie
+ + ")|(?:" + fudgeDie + "))";
+
+ /*
+ * Defines a compounding die.
+ *
+ * This is like an exploding die, but is a single die, not a group of
+ * them.
+ */
+ private static final String compoundingDie = compoundGroup + "!!" +
+ comparePoint;
+ private static final Pattern compoundingDiePattern = Pattern.compile("\\A" +
+ compoundingDie + "\\Z");
+
+ /*
+ * Defines an exploding die.
+ *
+ * This is a die that you reroll the component of if it meets a certain
+ * condition.
+ */
+ private static final String explodingDie = compoundGroup + "!" +
+ comparePoint;
+ private static final Pattern explodingDiePattern = Pattern.compile("\\A" +
+ explodingDie + "\\Z");
+
+ /*
+ * Defines a penetrating die.
+ *
+ * This is like an exploding die, but the exploded result gets a -1
+ * penalty.
+ */
+ private static final String penetratingDie = compoundGroup + "!" +
+ comparePoint;
+ private static final Pattern penetratingDiePattern = Pattern.compile("\\A" +
+ penetratingDie + "\\Z");
+
+ /*
+ * Defines a die list.
+ *
+ * This is an array of dice of the specified size.
+ */
+ private static final String diceList = compoundGroup + "dl" + compoundGroup;
+ private static final Pattern diceListPattern = Pattern.compile("\\A" + diceList +
+ "\\Z");
+
+ /**
+ * Check if a given string is a valid die expression.
+ *
+ * @param exp
+ * The string to check validity of.
+ *
+ * @return Whether or not the string is a valid command.
+ */
+ public static boolean isValidExpression(final String exp) {
+ /* @NOTE
+ * Should this matcher/matches expression be abstracted in
+ * some way?
+ */
+ if (scalarDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (simpleDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (fudgeDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (compoundDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (compoundingDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (explodingDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (penetratingDiePattern.matcher(exp).matches()) {
+ return true;
+ } else if (diceListPattern.matcher(exp).matches()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /* Derive a predicate from a compare point */
+ private static Predicate<Long> deriveCond(final String patt) {
+ final long num = Long.parseLong(patt.substring(1));
+
+ /* @NOTE
+ * Should this be extended in some way, to provide other
+ * operators?
+ */
+ switch (patt.charAt(0)) {
+ case '<':
+ return (roll) -> (roll < num);
+ case '=':
+ return (roll) -> (roll == num);
+ case '>':
+ return (roll) -> (roll > num);
+ default:
+ return (roll) -> (false);
+ }
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/Die.java b/dice/src/main/java/bjc/dicelang/dice/Die.java
new file mode 100644
index 0000000..630e8b9
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/Die.java
@@ -0,0 +1,39 @@
+package bjc.dicelang.dice;
+
+/**
+ * Represents one or more dice that produce a scalar result.
+ *
+ * @author Ben Culkin
+ */
+public interface Die {
+ /**
+ * Can this die be optimized to a single number?
+ *
+ * @return Whether this die can be optimized or not.
+ */
+ boolean canOptimize();
+
+ /**
+ * Optimize this die to a single number.
+ *
+ * Calling optimize on dice that return false for canOptimize produces
+ * undefined behavior
+ *
+ * @return The optimized form of this die
+ */
+ long optimize();
+
+ /**
+ * Roll this die.
+ *
+ * @return A possible roll of this die
+ */
+ long roll();
+
+ /**
+ * Roll only a single portion of this die.
+ *
+ * @return A possible roll of a single portion of this die.
+ */
+ long rollSingle();
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/DieExpression.java b/dice/src/main/java/bjc/dicelang/dice/DieExpression.java
new file mode 100644
index 0000000..b8faeda
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/DieExpression.java
@@ -0,0 +1,69 @@
+package bjc.dicelang.dice;
+
+import java.util.Arrays;
+
+/*
+ * @NOTE
+ * I'm not a particularly large fan of sticking everything on this class
+ * and just documenting which fields are tied together in a non-obvious
+ * way. I think a class hierarchy might be better, but I am unsure of the
+ * details.
+ */
+/**
+ * Represents either a die or a die list.
+ *
+ * @author Ben Culkin
+ */
+public class DieExpression {
+ /** Is this expression a list? */
+ public final boolean isList;
+
+ /** The scalar value in this expression, if there is one. */
+ public Die scalar;
+ /** The list value in this expression, if there is one. */
+ public DieList list;
+
+ /**
+ * Create a scalar die expression.
+ *
+ * @param scal
+ * The scalar value of this expression.
+ */
+ public DieExpression(final Die scal) {
+ isList = false;
+ scalar = scal;
+ }
+
+ /**
+ * Create a list die expression.
+ *
+ * @param lst
+ * The list value of this expression.
+ */
+ public DieExpression(final DieList lst) {
+ isList = true;
+ list = lst;
+ }
+
+ @Override
+ public String toString() {
+ if (isList) {
+ return list.toString();
+ }
+
+ return scalar.toString();
+ }
+
+ /**
+ * Get the value of this expression as a string.
+ *
+ * @return The value of the expression as a string.
+ */
+ public String value() {
+ if (isList) {
+ return Arrays.toString(list.roll());
+ }
+
+ return Long.toString(scalar.roll());
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/DieList.java b/dice/src/main/java/bjc/dicelang/dice/DieList.java
new file mode 100644
index 0000000..48006d4
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/DieList.java
@@ -0,0 +1,31 @@
+package bjc.dicelang.dice;
+
+/**
+ * Represents a group of dice.
+ *
+ * @author Ben Culkin.
+ */
+public interface DieList {
+ /**
+ * Can this list be optimized?
+ *
+ * @return Whether or not this list can be optimized.
+ */
+ boolean canOptimize();
+
+ /**
+ * Optimize this list, if it can be done.
+ *
+ * Invoking this on unoptimizable expression is undefined.
+ *
+ * @return The optimized form of this list.
+ */
+ long[] optimize();
+
+ /**
+ * Roll this group of dice.
+ *
+ * @return A possible roll of this group.
+ */
+ long[] roll();
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/ExplodingDice.java b/dice/src/main/java/bjc/dicelang/dice/ExplodingDice.java
new file mode 100644
index 0000000..e891a1c
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/ExplodingDice.java
@@ -0,0 +1,124 @@
+package bjc.dicelang.dice;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * An exploding die.
+ *
+ * Represents a die list that keeps getting another added die as long as a
+ * condition is met.
+ *
+ * @author Ben Culkin
+ */
+public class ExplodingDice implements DieList {
+ /* The source die to use. */
+ private final Die source;
+
+ /* The conditions for exploding. */
+ private final Predicate<Long> explodeOn;
+ private final String explodePattern;
+ /* Whether or not to apply a -1 penalty to explosions. */
+ private final boolean explodePenetrates;
+
+ /**
+ * Create a new exploding die.
+ *
+ * @param src
+ * The source die for exploding.
+ * @param explode
+ * The condition to explode on.
+ */
+ public ExplodingDice(final Die src, final Predicate<Long> explode) {
+ this(src, explode, null, false);
+ }
+
+ /**
+ * Create a new exploding die that may penetrate.
+ *
+ * @param src
+ * The source die for exploding.
+ * @param explode
+ * The condition to explode on.
+ * @param penetrate
+ * Whether or not for explosions to penetrate (-1 to
+ * exploded die).
+ */
+ public ExplodingDice(final Die src, final Predicate<Long> explode,
+ final boolean penetrate) {
+ this(src, explode, null, penetrate);
+ }
+
+ /**
+ * Create a new exploding die that may penetrate.
+ *
+ * @param src
+ * The source die for exploding.
+ * @param explode
+ * The condition to explode on.
+ * @param penetrate
+ * Whether or not for explosions to penetrate (-1 to
+ * exploded die).
+ * @param patt
+ * The string the condition came from, for printing.
+ */
+ public ExplodingDice(final Die src, final Predicate<Long> explode, final String patt,
+ final boolean penetrate) {
+ source = src;
+ explodeOn = explode;
+ explodePattern = patt;
+ explodePenetrates = penetrate;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ return false;
+ }
+
+ @Override
+ public long[] optimize() {
+ return new long[0];
+ }
+
+ @Override
+ public long[] roll() {
+ final long res = source.roll();
+ long oldRes = res;
+
+ final List<Long> resList = new LinkedList<>();
+ resList.add(res);
+
+ while (explodeOn.test(oldRes)) {
+ oldRes = source.rollSingle();
+
+ if (explodePenetrates) {
+ oldRes -= 1;
+ }
+
+ resList.add(oldRes);
+ }
+
+ final long resArr[] = new long[resList.size()];
+
+ int i = 0;
+ for(long rll : resList) {
+ resArr[i] = rll;
+ i += 1;
+ }
+
+ return resArr;
+ }
+
+ @Override
+ public String toString() {
+ String penString = explodePenetrates ? "p" : "";
+ String sourceString = source.toString();
+
+ if (explodePattern == null) {
+ return String.format("%s%s!<complex-pred>", sourceString, penString);
+ }
+
+ return String.format("%s%s!%s", sourceString, penString, explodePattern);
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/FudgeDie.java b/dice/src/main/java/bjc/dicelang/dice/FudgeDie.java
new file mode 100644
index 0000000..23951b2
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/FudgeDie.java
@@ -0,0 +1,67 @@
+package bjc.dicelang.dice;
+
+/**
+ * A fudge die, one that has -1, 0 and 1 as its sides.
+ *
+ * @author EVE
+ *
+ */
+public class FudgeDie implements Die {
+ /* The number of dice to roll. */
+ private final Die numDice;
+
+ /**
+ * Create a new fudge die.
+ *
+ * @param nDice
+ * The number of dice to roll.
+ */
+ public FudgeDie(final long nDice) {
+ numDice = new ScalarDie(nDice);
+ }
+
+ /**
+ * Create a new fudge die.
+ *
+ * @param nDice
+ * The number of dice to roll.
+ */
+ public FudgeDie(final Die nDice) {
+ numDice = nDice;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ return numDice.canOptimize() && numDice.optimize() == 0;
+ }
+
+ @Override
+ public long optimize() {
+ return 0;
+ }
+
+ @Override
+ public long roll() {
+ long res = 0;
+
+ final long nDice = numDice.roll();
+
+ for (int i = 0; i < nDice; i++) {
+ res += rollSingle();
+ }
+
+ return res;
+ }
+
+ @Override
+ public long rollSingle() {
+ return DiceBox.rng.nextInt(3) - 1;
+ }
+
+ @Override
+ public String toString() {
+ String dieString = numDice.toString();
+
+ return String.format("%sdF", dieString);
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/MathDie.java b/dice/src/main/java/bjc/dicelang/dice/MathDie.java
new file mode 100644
index 0000000..e4f2953
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/MathDie.java
@@ -0,0 +1,121 @@
+package bjc.dicelang.dice;
+
+/**
+ * A die that represents two dice with an applied math operator.
+ *
+ * @author EVE
+ *
+ */
+public class MathDie implements Die {
+ /*
+ * @TODO 10/08/17 Ben Culkin :MathGeneralize
+ * Why do we have the operator types hardcoded, instead of just
+ * having a general thing for applying a binary operator to dice?
+ * Fix this by changing it to the more general form.
+ */
+ /**
+ * The types of a math operator.
+ *
+ * @author EVE
+ *
+ */
+ public static enum MathOp {
+ /** Add two dice. */
+ ADD,
+ /** Subtract two dice. */
+ SUBTRACT,
+ /** Multiply two dice. */
+ MULTIPLY;
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case ADD:
+ return "+";
+
+ case SUBTRACT:
+ return "-";
+
+ case MULTIPLY:
+ return "*";
+
+ default:
+ return this.name();
+ }
+ }
+ }
+
+ private final MathDie.MathOp type;
+
+ private final Die left;
+ private final Die right;
+
+ /**
+ * Create a new math die.
+ *
+ * @param op
+ * The operator to apply.
+ *
+ * @param lft
+ * The left operand.
+ *
+ * @param rght
+ * The right operand.
+ */
+ public MathDie(final MathDie.MathOp op, final Die lft, final Die rght) {
+ type = op;
+
+ left = lft;
+ right = rght;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ return left.canOptimize() && right.canOptimize();
+ }
+
+ private long performOp(final long lft, final long rght) {
+ switch (type) {
+ case ADD:
+ return lft + rght;
+
+ case SUBTRACT:
+ return lft - rght;
+
+ case MULTIPLY:
+ return lft * rght;
+
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public long optimize() {
+ final long lft = left.optimize();
+ final long rght = right.optimize();
+
+ return performOp(lft, rght);
+ }
+
+ @Override
+ public long roll() {
+ final long lft = left.roll();
+ final long rght = right.roll();
+
+ return performOp(lft, rght);
+ }
+
+ @Override
+ public long rollSingle() {
+ final long lft = left.rollSingle();
+ final long rght = right.rollSingle();
+
+ return performOp(lft, rght);
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " " + type.toString() + " " + right.toString();
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/ScalarDie.java b/dice/src/main/java/bjc/dicelang/dice/ScalarDie.java
new file mode 100644
index 0000000..cbe9d3f
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/ScalarDie.java
@@ -0,0 +1,47 @@
+package bjc.dicelang.dice;
+
+/**
+ * A scalar die, that always returns a given number.
+ *
+ * @author EVE
+ *
+ */
+public class ScalarDie implements Die {
+ /* The die value. */
+ private final long val;
+
+ /**
+ * Create a new scalar die with a set value.
+ *
+ * @param vl
+ * The value to use.
+ */
+ public ScalarDie(final long vl) {
+ val = vl;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ return true;
+ }
+
+ @Override
+ public long optimize() {
+ return val;
+ }
+
+ @Override
+ public long roll() {
+ return val;
+ }
+
+ @Override
+ public long rollSingle() {
+ return val;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d", val);
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/SimpleDie.java b/dice/src/main/java/bjc/dicelang/dice/SimpleDie.java
new file mode 100644
index 0000000..6cae423
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/SimpleDie.java
@@ -0,0 +1,119 @@
+package bjc.dicelang.dice;
+
+/**
+ * A simple group of dice.
+ *
+ * @author EVE
+ *
+ */
+public class SimpleDie implements Die {
+ /* The number of dice to roll. */
+ private final Die numDice;
+ /*
+ * The size of each dice to roll.
+ *
+ * Rolled once per role, not once for each dice rolled.
+ *
+ * @NOTE
+ * Would having some way to roll it once for each dice rolled be
+ * useful in any sort of case?
+ */
+ private final Die diceSize;
+
+ /**
+ * Create a new dice group.
+ *
+ * @param nDice
+ * The number of dice.
+ *
+ * @param size
+ * The size of the dice.
+ */
+ public SimpleDie(final long nDice, final long size) {
+ this(new ScalarDie(nDice), new ScalarDie(size));
+ }
+
+ /**
+ * Create a new dice group.
+ *
+ * @param nDice
+ * The number of dice.
+ *
+ * @param size
+ * The size of the dice.
+ */
+ public SimpleDie(final Die nDice, final long size) {
+ this(nDice, new ScalarDie(size));
+ }
+
+ /**
+ * Create a new dice group.
+ *
+ * @param nDice
+ * The number of dice.
+ *
+ * @param size
+ * The size of the dice.
+ */
+ public SimpleDie(final long nDice, final Die size) {
+ this(new ScalarDie(nDice), size);
+ }
+
+ /**
+ * Create a new dice group.
+ *
+ * @param nDice
+ * The number of dice.
+ *
+ * @param size
+ * The size of the dice.
+ */
+ public SimpleDie(final Die nDice, final Die size) {
+ numDice = nDice;
+ diceSize = size;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ if (diceSize.canOptimize() && diceSize.optimize() <= 1) {
+ return numDice.canOptimize();
+ }
+
+ return false;
+ }
+
+ @Override
+ public long optimize() {
+ final long optSize = diceSize.optimize();
+
+ if (optSize == 0) {
+ return 0;
+ }
+
+ return numDice.optimize();
+ }
+
+ @Override
+ public long roll() {
+ long total = 0;
+
+ final long nDice = numDice.roll();
+ final long dSize = diceSize.roll();
+
+ for (int i = 0; i < nDice; i++) {
+ total += Math.abs(DiceBox.rng.nextLong()) % dSize + 1;
+ }
+
+ return total;
+ }
+
+ @Override
+ public long rollSingle() {
+ return Math.abs(DiceBox.rng.nextLong()) % diceSize.roll() + 1;
+ }
+
+ @Override
+ public String toString() {
+ return numDice + "d" + diceSize;
+ }
+}
diff --git a/dice/src/main/java/bjc/dicelang/dice/SimpleDieList.java b/dice/src/main/java/bjc/dicelang/dice/SimpleDieList.java
new file mode 100644
index 0000000..8391054
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/dice/SimpleDieList.java
@@ -0,0 +1,77 @@
+package bjc.dicelang.dice;
+
+/**
+ * A simple list of dice.
+ *
+ * @author EVE
+ *
+ * @TODO 10/08/17 Ben Culkin :DieListGeneralize
+ * DieList in general should be changed to be able to be
+ * constructed from an arbitrary die using rollSingle and things
+ * like that.
+ */
+public class SimpleDieList implements DieList {
+ /* The number of dice to roll. */
+ private final Die numDice;
+ /*
+ * The size of each die to roll.
+ *
+ * Checked once per roll, not once per dice rolled.
+ */
+ private final Die size;
+
+ /**
+ * Create a new list of dice.
+ *
+ * @param nDice
+ * The number of dice in the list.
+ *
+ * @param sze
+ * The size of dice in the list.
+ */
+ public SimpleDieList(final Die nDice, final Die sze) {
+ numDice = nDice;
+ size = sze;
+ }
+
+ @Override
+ public boolean canOptimize() {
+ if (size.canOptimize() && size.optimize() <= 1) {
+ return numDice.canOptimize();
+ }
+
+ return false;
+ }
+
+ @Override
+ public long[] optimize() {
+ final int sze = (int) numDice.optimize();
+ final long res = size.optimize();
+
+ final long[] ret = new long[sze];
+
+ for (int i = 0; i < sze; i++) {
+ ret[i] = res;
+ }
+
+ return ret;
+ }
+
+ @Override
+ public long[] roll() {
+ final int num = (int) numDice.roll();
+
+ final long[] ret = new long[num];
+
+ for (int i = 0; i < num; i++) {
+ ret[i] = size.roll();
+ }
+
+ return ret;
+ }
+
+ @Override
+ public String toString() {
+ return numDice.toString() + "dl" + size.toString();
+ }
+}