summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/Die.java75
-rw-r--r--dice/src/main/java/bjc/dicelang/neodice/DiePool.java321
2 files changed, 396 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
index a331e4e..a986ca2 100644
--- a/dice/src/main/java/bjc/dicelang/neodice/Die.java
+++ b/dice/src/main/java/bjc/dicelang/neodice/Die.java
@@ -1,8 +1,11 @@
package bjc.dicelang.neodice;
import java.util.*;
+import java.util.function.*;
import java.util.stream.*;
+import bjc.esodata.*;
+
/**
* Represents a single polyhedral die.
* @author Ben Culkin
@@ -31,6 +34,30 @@ public interface Die {
};
/**
+ * Returns a die which will reroll this die as long as the provided condition is true.
+ *
+ * @param condition The condition to reroll the die on.
+ *
+ * @return A die that rerolls when the given condition is met.
+ */
+ default Die reroll(IntPredicate condition) {
+ return new RerollDie(this, condition, (lst) -> lst.get(lst.size()));
+ }
+
+ /**
+ * Returns a die which will reroll this die up to a specified number of times,
+ * as long as the provided condition is true.
+ *
+ * @param condition The condition to reroll the die on.
+ * @param limit The maximum number of times to reroll the die.
+ *
+ * @return A die that rerolls when the given condition is met.
+ */
+ default Die reroll(IntPredicate condition, int limit) {
+ return new RerollDie(this, condition, (lst) -> lst.get(lst.size()), limit);
+ }
+
+ /**
* Create an iterator which gives rolls of this dice.
*
* @param rng The source for random numbers.
@@ -93,4 +120,52 @@ final class TimesDiePool implements DiePool {
return Objects.equals(contained, other.contained) && numDice == other.numDice;
}
+}
+
+final class RerollDie implements Die {
+ private final Die contained;
+
+ private final IntPredicate condition;
+ private final Function<MinMaxList<Integer>, Integer> chooser;
+
+ private int limit = Integer.MAX_VALUE;
+
+
+ public RerollDie(Die contained, IntPredicate condition,
+ Function<MinMaxList<Integer>, Integer> chooser) {
+ this.contained = contained;
+
+ this.condition = condition;
+ this.chooser = chooser;
+ }
+
+ public RerollDie(Die contained, IntPredicate condition,
+ Function<MinMaxList<Integer>, Integer> chooser, int limit) {
+ this.contained = contained;
+
+ this.condition = condition;
+ this.chooser = chooser;
+
+ this.limit = limit;
+ }
+
+ @Override
+ public int roll(Random rng) {
+ int roll = contained.roll(rng);
+
+ MinMaxList<Integer> newRolls = new MinMaxList<Integer>(
+ Comparator.naturalOrder(), roll);
+
+ int rerollCount = 0;
+ while (condition.test(roll) && rerollCount < limit) {
+ roll = contained.roll(rng);
+ newRolls.add(roll);
+
+ rerollCount += 1;
+ }
+
+ return chooser.apply(newRolls);
+ }
+
+ // No toString, because IntPredicate can't be converted to a string
} \ 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
index b7ee834..70dfa50 100644
--- a/dice/src/main/java/bjc/dicelang/neodice/DiePool.java
+++ b/dice/src/main/java/bjc/dicelang/neodice/DiePool.java
@@ -46,6 +46,10 @@ public interface DiePool {
throw new UnsupportedOperationException("Can't get composite dice");
}
+ /*
+ * These die pool operations transform this pool in some way.
+ */
+
/**
* Returns a version of this die pool which returns its results in sorted
* order.
@@ -75,6 +79,101 @@ public interface DiePool {
}
/**
+ * Return a die pool which rolls this one, then drops a number of the first values.
+ *
+ * @param number The number of first values to drop.
+ *
+ * @return A die pool which has the first entries dropped.
+ */
+ default DiePool dropFirst(int number) {
+ return new DropFirstPool(this, number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then drops a number of the last values.
+ *
+ * @param number The number of last values to drop.
+ *
+ * @return A die pool which has the last entries dropped.
+ */
+ default DiePool dropLast(int number) {
+ return new DropLastPool(this, number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then keeps a number of the first values.
+ *
+ * @param number The number of first values to keep.
+ *
+ * @return A die pool which has the first entries kept.
+ */
+ default DiePool keepFirst(int number) {
+ return new KeepFirstDiePool(this, number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then keeps a number of the last values.
+ *
+ * @param number The number of last values to keep.
+ *
+ * @return A die pool which has the last entries kept.
+ */
+ default DiePool keepLast(int number) {
+ return new KeepLastDiePool(this, number);
+ }
+
+ /*
+ * These die-pool operations are formed exclusively through other die pool
+ * operations.
+ */
+
+ /**
+ * Return a die pool which rolls this one, then drops a number of the lowest values.
+ *
+ * @param number The number of lowest values to drop.
+ *
+ * @return A die pool which has the lowest entries dropped.
+ */
+ default DiePool dropLowest(int number) {
+ return this.sorted(false).dropFirst(number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then drops a number of the lowest values.
+ *
+ * @param number The number of lowest values to drop.
+ *
+ * @return A die pool which has the lowest entries dropped.
+ */
+ default DiePool dropHighest(int number) {
+ return this.sorted(false).dropLast(number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then keeps a number of the lowest values.
+ *
+ * @param number The number of lowest values to keep.
+ *
+ * @return A die pool which has the lowest entries kept.
+ */
+ default DiePool keepLowest(int number) {
+ return this.sorted(false).keepFirst(number);
+ }
+
+ /**
+ * Return a die pool which rolls this one, then keeps a number of the highest values.
+ *
+ * @param number The number of highest values to keep.
+ *
+ * @return A die pool which has the highest entries kept.
+ */
+ default DiePool keepHighest(int number) {
+ return this.sorted(false).keepLast(number);
+ }
+
+ /* These are misc. operations that don't form new dice pools. */
+
+ /**
* Get an iterator which iterates over a single roll of this die pool.
*
* @param rng The source of random numbers.
@@ -114,6 +213,11 @@ final class SortedDiePool implements DiePool {
return rolls;
}
}
+
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
@Override
public String toString() {
@@ -155,6 +259,11 @@ final class FilteredDiePool implements DiePool {
return Arrays.stream(rolls).filter(filter).toArray();
}
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
+
// No toString, since there isn't any sensible to output the filter
@Override
@@ -173,4 +282,216 @@ final class FilteredDiePool implements DiePool {
return Objects.equals(filter, other.filter)
&& Objects.equals(pool, other.pool);
}
+}
+
+final class DropFirstPool implements DiePool {
+ private final int number;
+ private final DiePool pool;
+
+ public DropFirstPool(DiePool pool, int number) {
+ this.pool = pool;
+ this.number = number;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ if (number >= rolls.length) {
+ return new int[0];
+ } else {
+ int[] newRolls = new int[rolls.length - number];
+
+ for (int index = number - 1; index < rolls.length; index++) {
+ newRolls[index - number] = rolls[index];
+ }
+
+ return newRolls;
+ }
+ }
+
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%sdF%d", pool, number);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ DropFirstPool other = (DropFirstPool) obj;
+
+ return number == other.number && Objects.equals(pool, other.pool);
+ }
+}
+
+final class DropLastPool implements DiePool {
+ private final int number;
+ private final DiePool pool;
+
+ public DropLastPool(DiePool pool, int number) {
+ this.pool = pool;
+ this.number = number;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ if (number >= rolls.length) {
+ return new int[0];
+ } else {
+ int[] newRolls = new int[rolls.length - number];
+
+ for (int index = 0; index < rolls.length - number; index++) {
+ newRolls[index] = rolls[index];
+ }
+
+ return newRolls;
+ }
+ }
+
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%sdL%d", pool, number);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ DropLastPool other = (DropLastPool) obj;
+
+ return number == other.number && Objects.equals(pool, other.pool);
+ }
+}
+
+final class KeepFirstDiePool implements DiePool {
+ private final int number;
+ private final DiePool pool;
+
+ public KeepFirstDiePool(DiePool pool, int number) {
+ this.pool = pool;
+ this.number = number;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ if (rolls.length >= number) {
+ return rolls;
+ } else {
+ int[] newRolls = new int[number];
+
+ for (int index = 0; index < number; index++) {
+ newRolls[index] = rolls[index];
+ }
+
+ return newRolls;
+ }
+ }
+
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%skF%d", pool, number);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ KeepFirstDiePool other = (KeepFirstDiePool) obj;
+
+ return number == other.number && Objects.equals(pool, other.pool);
+ }
+}
+
+final class KeepLastDiePool implements DiePool {
+ private final int number;
+ private final DiePool pool;
+
+ public KeepLastDiePool(DiePool pool, int number) {
+ this.pool = pool;
+ this.number = number;
+ }
+
+ @Override
+ public int[] roll(Random rng) {
+ int[] rolls = pool.roll(rng);
+
+ if (rolls.length >= number) {
+ return rolls;
+ } else {
+ int[] newRolls = new int[number];
+
+ for (int index = number; index > index; index--) {
+ newRolls[index] = rolls[rolls.length - index];
+ }
+
+ return newRolls;
+ }
+ }
+
+ @Override
+ public Die[] contained() {
+ return pool.contained();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%skL%d", pool, number);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, pool);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ KeepLastDiePool other = (KeepLastDiePool) obj;
+
+ return number == other.number && Objects.equals(pool, other.pool);
+ }
} \ No newline at end of file