From 6b76d2ff5a3df3931c0983d915eed33e83e892e0 Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Wed, 25 Oct 2017 12:30:48 -0300 Subject: Move dice to new module --- .../main/java/bjc/dicelang/dice/CompoundDie.java | 60 ++++ .../java/bjc/dicelang/dice/CompoundingDie.java | 115 ++++++++ dice/src/main/java/bjc/dicelang/dice/DiceBox.java | 310 +++++++++++++++++++++ dice/src/main/java/bjc/dicelang/dice/Die.java | 39 +++ .../main/java/bjc/dicelang/dice/DieExpression.java | 69 +++++ dice/src/main/java/bjc/dicelang/dice/DieList.java | 31 +++ .../main/java/bjc/dicelang/dice/ExplodingDice.java | 124 +++++++++ dice/src/main/java/bjc/dicelang/dice/FudgeDie.java | 67 +++++ dice/src/main/java/bjc/dicelang/dice/MathDie.java | 121 ++++++++ .../src/main/java/bjc/dicelang/dice/ScalarDie.java | 47 ++++ .../src/main/java/bjc/dicelang/dice/SimpleDie.java | 119 ++++++++ .../main/java/bjc/dicelang/dice/SimpleDieList.java | 77 +++++ 12 files changed, 1179 insertions(+) create mode 100644 dice/src/main/java/bjc/dicelang/dice/CompoundDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/CompoundingDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/DiceBox.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/Die.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/DieExpression.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/DieList.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/ExplodingDice.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/FudgeDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/MathDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/ScalarDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/SimpleDie.java create mode 100644 dice/src/main/java/bjc/dicelang/dice/SimpleDieList.java (limited to 'dice/src/main') 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 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 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 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!!", 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 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 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 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 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 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 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 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 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 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!", 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(); + } +} -- cgit v1.2.3