summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Culkin <scorpress@gmail.com>2020-11-14 22:07:50 -0500
committerBen Culkin <scorpress@gmail.com>2020-11-14 22:07:50 -0500
commit84f0803068ba50bcba67de42634a4bf3d0e9e170 (patch)
tree6c489ae7d727558333664ac8ff834d398a424322
parent45f36834bec138429abaecd37021e7d243d7506a (diff)
Begin new dice implementation
For various reasons, I'm not happy with the other implementations. Here's hoping that this one is better
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/Die.java96
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/DieFactory.java63
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/DiePool.java176
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/DiePoolFactory.java82
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/package-info.java11
-rw-r--r--dice/src/test/java/bjc/dicelang/neodice/DiePoolTest.java33
-rw-r--r--dice/src/test/java/bjc/dicelang/neodice/DieTest.java45
7 files changed, 506 insertions, 0 deletions
diff --git a/dice/src/main/java/bjc/dicelang/neodice/Die.java b/dice/src/main/java/bjc/dicelang/neodice/Die.java
new file mode 100644
index 0000000..a331e4e
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/neodice/Die.java
@@ -0,0 +1,96 @@
+package bjc.dicelang.neodice;
+
+import java.util.*;
+import java.util.stream.*;
+
+/**
+ * Represents a single polyhedral die.
+ * @author Ben Culkin
+ *
+ */
+@FunctionalInterface
+public interface Die {
+ /**
+ * Rolls this die.
+ *
+ * @param rng The source for random numbers
+ *
+ * @return The result of rolling the die.
+ */
+ public int roll(Random rng);
+
+ /**
+ * Returns a die pool which rolls this die the specified number of times.
+ *
+ * @param numTimes The number of times to roll this die.
+ *
+ * @return A die pool that rolls this die the specified number of times.
+ */
+ default DiePool times(int numTimes) {
+ return new TimesDiePool(this, numTimes);
+ };
+
+ /**
+ * Create an iterator which gives rolls of this dice.
+ *
+ * @param rng The source for random numbers.
+ *
+ * @return An iterator which gives rolls of this dice.
+ */
+ default Iterator<Integer> iterator(Random rng) {
+ return IntStream.generate(() -> this.roll(rng)).iterator();
+ }
+}
+
+final class TimesDiePool implements DiePool {
+ private final Die contained;
+ private final int numDice;
+
+ public TimesDiePool(Die contained, int numDice) {
+ this.contained = contained;
+ this.numDice = numDice;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] results = new int[numDice];
+
+ for (int index = 0; index < numDice; index++) {
+ results[index] = contained.roll(rng);
+ }
+
+ return results;
+ }
+
+ @Override
+ public Die[] contained() {
+ Die[] results = new Die[numDice];
+
+ for (int index = 0; index < numDice; index++) {
+ results[index] = contained;
+ }
+
+ return results;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d%s", numDice, contained);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(contained, numDice);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ TimesDiePool other = (TimesDiePool) obj;
+
+ return Objects.equals(contained, other.contained) && numDice == other.numDice;
+ }
+} \ No newline at end of file
diff --git a/dice/src/main/java/bjc/dicelang/neodice/DieFactory.java b/dice/src/main/java/bjc/dicelang/neodice/DieFactory.java
new file mode 100644
index 0000000..d9bae7e
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/neodice/DieFactory.java
@@ -0,0 +1,63 @@
+package bjc.dicelang.neodice;
+
+import java.util.*;
+
+/**
+ * Various static functions which create instances of Die.
+ *
+ * @author Ben Culkin
+ *
+ */
+public class DieFactory {
+ /**
+ * Create a simple polyhedral die with a fixed number of sides.
+ *
+ * @param sides The number of sides for the die.
+ *
+ * @return A die which returns a result from 1 to sides.
+ */
+ public static Die polyhedral(int sides) {
+ return new PolyhedralDie(sides);
+ }
+}
+
+final class PolyhedralDie implements Die {
+ private final int sides;
+
+ public PolyhedralDie(int sides) {
+ this.sides = sides;
+ }
+
+ @Override
+ public int roll(Random rng) {
+ // Dice are one-based, not zero-based.
+ return rng.nextInt(sides) + 1;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("d%d", sides);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + sides;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ PolyhedralDie other = (PolyhedralDie) obj;
+
+ if (sides != other.sides) return false;
+ else return true;
+ }
+
+
+} \ No newline at end of file
diff --git a/dice/src/main/java/bjc/dicelang/neodice/DiePool.java b/dice/src/main/java/bjc/dicelang/neodice/DiePool.java
new file mode 100644
index 0000000..b7ee834
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/neodice/DiePool.java
@@ -0,0 +1,176 @@
+package bjc.dicelang.neodice;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * Represents a pool of dice.
+ *
+ * @author Ben Culkin
+ *
+ */
+@FunctionalInterface
+public interface DiePool {
+ /**
+ * Roll each die in the pool, and return the results.
+ *
+ * Note that this array is not guaranteed to be the same size every time it
+ * is rolled, because there are some pool types that could add/remove dice.
+ *
+ * @param rng The source for random numbers
+ *
+ * @return The result of rolling each die in the pool.
+ */
+ public int[] roll(Random rng);
+
+ /**
+ * Gets the dice contained in this pool.
+ *
+ * Note that the length of this array may not be the same as the length of
+ * the array returned by roll, because certain pool types may add additional
+ * dice.
+ *
+ * Also note that this array (and the Die instances contained in it) should
+ * not be modified. That may work for certain pool types, but it isn't
+ * guaranteed to work, and can lead to unintuitive behavior. For instances,
+ * certain pool types may return an array where multiple elements of it refer
+ * to the same Die instance.
+ *
+ * The default implementation throws an UnsupportedOperationException.
+ *
+ * @return The dice contained in this pool.
+ *
+ * @throws UnsupportedOperationException If the composite dice can't be retrieved.
+ */
+ default Die[] contained() {
+ throw new UnsupportedOperationException("Can't get composite dice");
+ }
+
+ /**
+ * Returns a version of this die pool which returns its results in sorted
+ * order.
+ *
+ * At the moment, sorting in descending order is somewhat less efficent than
+ * sorting in ascending order, because Java doesn't provide a built-in
+ * descending sort for primitive arrays.
+ *
+ * @param isDescending True to sort in descending order, false to sort in ascending order.
+ *
+ * @return The die pool, which returns its results in sorted order.
+ */
+ default DiePool sorted(boolean isDescending) {
+ return new SortedDiePool(this, isDescending);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then filters out any results that
+ * don't match the provided predicate.
+ *
+ * @param matcher The predicate that determines
+ *
+ * @return A die pool which contains only entries that pass the predicate.
+ */
+ default DiePool filtered(IntPredicate matcher) {
+ return new FilteredDiePool(this, matcher);
+ }
+
+ /**
+ * Get an iterator which iterates over a single roll of this die pool.
+ *
+ * @param rng The source of random numbers.
+ *
+ * @return An iterator over a single roll of this die pool.
+ */
+ default Iterator<Integer> iterator(Random rng) {
+ return Arrays.stream(this.roll(rng)).iterator();
+ }
+}
+
+final class SortedDiePool implements DiePool {
+ private final boolean isDescending;
+ private final DiePool pool;
+
+ public SortedDiePool(DiePool pool, boolean isDescending) {
+ this.pool = pool;
+ this.isDescending = isDescending;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ Arrays.sort(rolls);
+
+ if (isDescending) {
+ int[] newRolls = new int[rolls.length];
+
+ int newIndex = newRolls.length;
+ for (int index = 0; index < rolls.length; index++) {
+ newRolls[newIndex--] = rolls[index];
+ }
+
+ return newRolls;
+ } else {
+ return rolls;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s (sorted %s)", pool,
+ isDescending ? " descending" : "ascending");
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(isDescending, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ SortedDiePool other = (SortedDiePool) obj;
+
+ return isDescending == other.isDescending
+ && Objects.equals(pool, other.pool);
+ }
+}
+
+final class FilteredDiePool implements DiePool {
+ private final DiePool pool;
+ private final IntPredicate filter;
+
+ public FilteredDiePool(DiePool pool, IntPredicate filter) {
+ this.pool = pool;
+ this.filter = filter;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ return Arrays.stream(rolls).filter(filter).toArray();
+ }
+
+ // No toString, since there isn't any sensible to output the filter
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(filter, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ FilteredDiePool other = (FilteredDiePool) obj;
+
+ return Objects.equals(filter, other.filter)
+ && Objects.equals(pool, other.pool);
+ }
+} \ No newline at end of file
diff --git a/dice/src/main/java/bjc/dicelang/neodice/DiePoolFactory.java b/dice/src/main/java/bjc/dicelang/neodice/DiePoolFactory.java
new file mode 100644
index 0000000..6d9314d
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/neodice/DiePoolFactory.java
@@ -0,0 +1,82 @@
+package bjc.dicelang.neodice;
+
+import java.util.*;
+
+/**
+ * Various static functions which create instances of DiePool.
+ *
+ * @author Ben Culkin
+ *
+ */
+public class DiePoolFactory {
+ /**
+ * Create a die pool containing the provided dice.
+ *
+ * @param dice The dice to put into the pool.
+ *
+ * @return A pool which contains the provided dice.
+ */
+ public static DiePool containing(Die... dice) {
+ return new FixedDiePool(dice);
+ }
+}
+
+final class FixedDiePool implements DiePool {
+ private final Die[] dice;
+
+ public FixedDiePool(Die[] dice) {
+ this.dice = dice;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] results = new int[dice.length];
+
+ for (int index = 0; index < dice.length; index++) {
+ results[index] = dice[index].roll(rng);
+ }
+
+ return results;
+ }
+
+ @Override
+ public Die[] contained() {
+ return dice;
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < dice.length; i++) {
+ Die die = dice[i];
+
+ builder.append(die);
+
+ // Don't add an extra trailing comma
+ if (i < dice.length - 1) builder.append(", ");
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(dice);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ FixedDiePool other = (FixedDiePool) obj;
+
+ return Arrays.equals(dice, other.dice);
+ }
+} \ No newline at end of file
diff --git a/dice/src/main/java/bjc/dicelang/neodice/package-info.java b/dice/src/main/java/bjc/dicelang/neodice/package-info.java
new file mode 100644
index 0000000..f6ec673
--- /dev/null
+++ b/dice/src/main/java/bjc/dicelang/neodice/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * This package contains yet another implementation of dice and dice expressions.
+ *
+ * The other ones just didn't seem to quite serve the expected functions.
+ */
+
+/**
+ * @author Ben Culkin
+ *
+ */
+package bjc.dicelang.neodice; \ No newline at end of file
diff --git a/dice/src/test/java/bjc/dicelang/neodice/DiePoolTest.java b/dice/src/test/java/bjc/dicelang/neodice/DiePoolTest.java
new file mode 100644
index 0000000..1a217d3
--- /dev/null
+++ b/dice/src/test/java/bjc/dicelang/neodice/DiePoolTest.java
@@ -0,0 +1,33 @@
+package bjc.dicelang.neodice;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.junit.*;
+
+@SuppressWarnings("javadoc")
+public class DiePoolTest {
+ private static final Random rng = new Random();
+
+ @Test
+ public void containedOneDieYieldsOneDie() {
+ Die oneSidedDie = DieFactory.polyhedral(1);
+ DiePool pool = DiePoolFactory.containing(oneSidedDie);
+
+ assertArrayEquals(
+ "A contained pool created with one die, yields that die",
+ new Die[] {oneSidedDie}, pool.contained());
+ }
+
+ @Test
+ public void containedOneDieRollsOneDie() {
+ Die oneSidedDie = DieFactory.polyhedral(1);
+ DiePool pool = DiePoolFactory.containing(oneSidedDie);
+
+ for (int i = 0; i < 10; i++) {
+ assertArrayEquals("One-die pools roll one die",
+ new int[] {1}, pool.roll(rng));
+ }
+ }
+}
diff --git a/dice/src/test/java/bjc/dicelang/neodice/DieTest.java b/dice/src/test/java/bjc/dicelang/neodice/DieTest.java
new file mode 100644
index 0000000..fc69f99
--- /dev/null
+++ b/dice/src/test/java/bjc/dicelang/neodice/DieTest.java
@@ -0,0 +1,45 @@
+package bjc.dicelang.neodice;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.junit.*;
+
+@SuppressWarnings("javadoc")
+public class DieTest {
+ private final static Random rng = new Random();
+
+ @Test
+ public void onesidedDiceReturnOne() {
+ Die die = DieFactory.polyhedral(1);
+
+ for (int i = 0; i < 10; i++) {
+ assertEquals("One-sided dice always return 1", 1, die.roll(rng));
+ }
+ }
+
+ @Test
+ public void polyhedralDiceStayInRange() {
+ Die die = DieFactory.polyhedral(6);
+
+ for (int i = 0; i < 50; i++) {
+ int result = die.roll(rng);
+
+ boolean inRange = result <= 6 && result >= 1;
+
+ assertTrue("Six-sided dice always return a value from 1 to 6", inRange);
+ }
+ }
+
+ public void polyhedralDiceEqualityFunctionsProperly() {
+ Die dieA1 = DieFactory.polyhedral(1);
+ Die dieA2 = DieFactory.polyhedral(1);
+ Die dieB1 = DieFactory.polyhedral(2);
+
+ assertEquals("Polyhedral dice with the same number of sides are equal",
+ dieA1, dieA2);
+ assertNotEquals("Polyhedral dice with a diffeent number of sides aren't equal",
+ dieA1, dieB1);
+ }
+}