Skip to content

Commit

Permalink
DieId (#101)
Browse files Browse the repository at this point in the history
* Adding a dieId to identify a random element and use the dieId to predetermine the result of a random element
* Returning the exact position in the expression on an exception
  • Loading branch information
twonirwana authored May 2, 2024
1 parent d5162d2 commit a3cc671
Show file tree
Hide file tree
Showing 84 changed files with 2,758 additions and 2,079 deletions.
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ For example `1d4+3d6` is the appending of roll of 1d4 and 3d6, but `(1d4+3=)d6)`

The number of dice is limited to 1000 and every number approve 9 digits or with more than 9 digit after the decimal dot result in an error.

Boolean values will be represented by `'true'` and `'false'`.
Boolean values will be represented by `'true'` (or `1`) and `'false'` (or `0`) .

It is possible to set tags and color.
Colors have no direct effect and will be also set to all random elements of the expression.
Expand Down
103 changes: 58 additions & 45 deletions src/main/java/de/janno/evaluator/dice/DiceEvaluator.java

Large diffs are not rendered by default.

111 changes: 43 additions & 68 deletions src/main/java/de/janno/evaluator/dice/DiceHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,100 +3,75 @@
import com.google.common.collect.ImmutableList;
import de.janno.evaluator.dice.random.NumberSupplier;
import lombok.NonNull;
import lombok.Value;

import java.util.ArrayList;
import java.util.List;

public final class DiceHelper {
public static @NonNull ImmutableList<Integer> explodingDice(int number, int sides, @NonNull NumberSupplier numberSupplier) throws ExpressionException {
public static @NonNull ImmutableList<RandomElement> explodingDice(int number,
int sides,
@NonNull NumberSupplier numberSupplier,
@NonNull RollId rollId,
int maxNumberOfElements,
String expression) throws ExpressionException {
if (sides == 0) {
return ImmutableList.of();
}
if (sides < 0) {
throw new ExpressionException("Sides of dice to roll must be positive");
throw new ExpressionException("Sides of dice to roll must be positive", rollId.getExpressionPosition());
}
ImmutableList.Builder<Integer> resultBuilder = ImmutableList.builder();
int diceToRoll = number;
while (diceToRoll > 0) {
List<Integer> roll = rollDice(diceToRoll, sides, numberSupplier);
resultBuilder.addAll(roll);
diceToRoll = (int) roll.stream().filter(i -> i == sides).count();
}
return resultBuilder.build();
}

public static @NonNull ImmutableList<RollElement> explodedAddDie2RollElements(@NonNull List<ExplodedAddDie> in) {
return in.stream()
.map(ExplodedAddDie::getValue)
.map(String::valueOf)
.map(i -> new RollElement(i, RollElement.NO_TAG, RollElement.NO_COLOR))
.collect(ImmutableList.toImmutableList());
}

public static @NonNull ImmutableList<RandomElement> explodedAddDie2RandomElements(@NonNull List<ExplodedAddDie> in) {
return in.stream()
.flatMap(r -> r.getDiceThrowResults().stream().map(String::valueOf)
.map(i -> new RollElement(i, RollElement.NO_TAG, RollElement.NO_COLOR))
.map(re -> new RandomElement(re, 1, r.getSidesOfDie())))
.collect(ImmutableList.toImmutableList());
}

public static @NonNull ImmutableList<RollElement> toRollElements(@NonNull List<Integer> in) {
return in.stream()
.map(String::valueOf)
.map(i -> new RollElement(i, RollElement.NO_TAG, RollElement.NO_COLOR))
.collect(ImmutableList.toImmutableList());
}

public static @NonNull ImmutableList<ExplodedAddDie> explodingAddDice(int number, int sides, @NonNull NumberSupplier numberSupplier) throws ExpressionException {
if (sides == 0) {
return ImmutableList.of();
}
if (sides < 0) {
throw new ExpressionException("Sides of dice to roll must be positive");
}
ImmutableList.Builder<ExplodedAddDie> resultBuilder = ImmutableList.builder();
List<RandomElement> resultBuilder = new ArrayList<>();
//order of the exploded dice is not relevant, the random elements get sorted later
for (int i = 0; i < number; i++) {
resultBuilder.add(rollExplodingAddDie(sides, numberSupplier));
resultBuilder.addAll(rollExplodingDie(sides, numberSupplier, rollId, i, maxNumberOfElements, expression));
if (resultBuilder.size() > maxNumberOfElements) {
throw new ExpressionException("To many elements in roll '%s', max is %d but there where %d".formatted(expression, maxNumberOfElements, resultBuilder.size()), rollId.getExpressionPosition());
}
}
return resultBuilder.build();
return ImmutableList.copyOf(resultBuilder);
}

public static ExplodedAddDie rollExplodingAddDie(int sides, @NonNull NumberSupplier numberSupplier) throws ExpressionException {
int current = numberSupplier.get(0, sides);
int res = current;
ImmutableList.Builder<Integer> resultBuilder = ImmutableList.builder();
resultBuilder.add(current);
while (current == sides) {
current = numberSupplier.get(0, sides);
res += current;
resultBuilder.add(current);
private static List<RandomElement> rollExplodingDie(int sides, NumberSupplier numberSupplier, RollId rollId, int index, int maxNumberOfElements, String expression) throws ExpressionException {
List<RandomElement> resultBuilder = new ArrayList<>();
int rerollCounter = 0;
RandomElement currentRoll = rollDie(sides, numberSupplier, rollId, index, rerollCounter++);
final String rerollValue = String.valueOf(sides);
resultBuilder.add(currentRoll);
while (currentRoll.getRollElement().getValue().equals(rerollValue)) {
currentRoll = rollDie(sides, numberSupplier, rollId, index, rerollCounter++);
resultBuilder.add(currentRoll);
if (resultBuilder.size() > maxNumberOfElements) {
throw new ExpressionException("To many elements in roll '%s', max is %d but there where %d".formatted(expression, maxNumberOfElements, resultBuilder.size()), rollId.getExpressionPosition());
}
}
return new ExplodedAddDie(res, sides, resultBuilder.build());
return ImmutableList.copyOf(resultBuilder);
}

public static @NonNull ImmutableList<Integer> rollDice(int number, int sides, @NonNull NumberSupplier numberSupplier) throws ExpressionException {
public static @NonNull ImmutableList<RandomElement> rollDice(int number, int sides, @NonNull NumberSupplier numberSupplier, @NonNull RollId rollId) throws ExpressionException {
if (sides == 0) {
return ImmutableList.of();
}
if (sides < 0) {
throw new ExpressionException("Sides of dice to roll must be positive");
throw new ExpressionException("Sides of dice to roll must be positive", rollId.getExpressionPosition());
}
ImmutableList.Builder<Integer> resultBuilder = ImmutableList.builder();
ImmutableList.Builder<RandomElement> randomElementBuilder = ImmutableList.builder();
for (int i = 0; i < number; i++) {
resultBuilder.add(numberSupplier.get(0, sides));
randomElementBuilder.add(rollDie(sides, numberSupplier, rollId, i, 0));
}
return resultBuilder.build();
return randomElementBuilder.build();
}

public static @NonNull RollElement pickOneOf(List<RollElement> list, @NonNull NumberSupplier numberSupplier) throws ExpressionException {
return list.get(numberSupplier.get(0, list.size()) - 1);
private static RandomElement rollDie(int sides, @NonNull NumberSupplier numberSupplier, @NonNull RollId rollId, int index, int reroll) throws ExpressionException {
final DieId dieId = DieId.of(rollId, index, reroll);
final int value = numberSupplier.get(0, sides, dieId);
return new RandomElement(new RollElement(String.valueOf(value), RollElement.NO_TAG, RollElement.NO_COLOR), 1, sides, dieId);

}

@Value
public static class ExplodedAddDie {
int value;
int sidesOfDie;
ImmutableList<Integer> diceThrowResults;
public static @NonNull RandomElement pickOneOf(List<RollElement> list, @NonNull NumberSupplier numberSupplier, @NonNull DieId dieId) throws ExpressionException {
return new RandomElement(list.get(numberSupplier.get(0, list.size(), dieId) - 1), list.stream()
.map(RollElement::getValue)
.collect(ImmutableList.toImmutableList()), dieId);
}

}
33 changes: 33 additions & 0 deletions src/main/java/de/janno/evaluator/dice/DieId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.janno.evaluator.dice;

import com.google.common.annotations.VisibleForTesting;
import lombok.NonNull;
import lombok.Value;

@Value(staticConstructor = "of")
public class DieId implements Comparable<DieId> {
@NonNull
RollId rollId;
int dieIndex;
int reroll;

@VisibleForTesting
public static DieId of(int expressionPositionStartInc, String value, int reEvaluateCounter, int dieIndex, int reroll) {
return new DieId(RollId.of(ExpressionPosition.of(expressionPositionStartInc, value), reEvaluateCounter), dieIndex, reroll);
}

@Override
public String toString() {
return rollId + "i" + dieIndex + "r" + reroll;
}

@Override
public int compareTo(DieId o) {
if (!this.rollId.equals(o.getRollId())) {
return this.rollId.compareTo(o.getRollId());
} else if (this.dieIndex != o.getDieIndex()) {
return Integer.compare(this.dieIndex, o.getDieIndex());
}
return Integer.compare(this.reroll, o.reroll);
}
}
13 changes: 11 additions & 2 deletions src/main/java/de/janno/evaluator/dice/ExpressionException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package de.janno.evaluator.dice;

public class ExpressionException extends Exception{
public ExpressionException(String message) {
import lombok.Getter;
import lombok.NonNull;

@Getter
public class ExpressionException extends Exception {

@NonNull
private final ExpressionPosition expressionPosition;

public ExpressionException(@NonNull String message, @NonNull ExpressionPosition expressionPosition) {
super(message);
this.expressionPosition = expressionPosition;
}
}
51 changes: 51 additions & 0 deletions src/main/java/de/janno/evaluator/dice/ExpressionPosition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.janno.evaluator.dice;

import com.google.common.base.Joiner;
import lombok.NonNull;
import lombok.Value;

import javax.annotation.Nullable;


@Value
public class ExpressionPosition implements Comparable<ExpressionPosition> {
int startInc;
@NonNull
String value;
/**
* A non operator/function extension on the left, like a parentheses. Needed to build the expression back together.
*/
@Nullable
String leftExtension;
/**
* A non operator/function extension on the right, like a parentheses. Needed to build the expression back together.
*/
@Nullable
String rightExtension;

public static ExpressionPosition of(final int startInc, final String value) {
return new ExpressionPosition(startInc, value, null, null);
}

public ExpressionPosition extendLeft(final String leftValue) {
return new ExpressionPosition(this.startInc, this.value, leftValue, this.rightExtension);
}

public ExpressionPosition extendRight(final String rightValue) {
return new ExpressionPosition(this.startInc, this.value, this.leftExtension, rightValue);
}

@Override
public String toString() {
return startInc + value;
}

public String toStringWithExtension() {
return Joiner.on("").skipNulls().join(leftExtension, value, rightExtension);
}

@Override
public int compareTo(ExpressionPosition o) {
return Integer.compare(startInc, o.startInc);
}
}
8 changes: 4 additions & 4 deletions src/main/java/de/janno/evaluator/dice/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,16 @@ public Function(@NonNull String name, int minArgumentCount, int maxArgumentCount
}


protected static String getExpression(String name, List<RollBuilder> arguments) {
return "%s%s".formatted(name, arguments.stream().map(RollBuilder::toExpression).collect(Collectors.joining(",")));
protected static String getExpression(ExpressionPosition expressionPosition, List<RollBuilder> arguments) {
return "%s%s".formatted(expressionPosition.toStringWithExtension(), arguments.stream().map(RollBuilder::toExpression).collect(Collectors.joining(",")));
}

/**
* Creates a RollBuilder for the arguments
*
* @param arguments all function arguments
* @param inputValue the given input for the function name, is needed the get the used upper/lower case in the result expression
* @param expressionPosition the part of the expression for this function, is needed the get the used upper/lower case in the result expression
* @return the RollBuilder that can be called to get result rolls
*/
public abstract @NonNull RollBuilder evaluate(@NonNull List<RollBuilder> arguments, @NonNull String inputValue) throws ExpressionException;
public abstract @NonNull RollBuilder evaluate(@NonNull List<RollBuilder> arguments, @NonNull ExpressionPosition expressionPosition) throws ExpressionException;
}
18 changes: 9 additions & 9 deletions src/main/java/de/janno/evaluator/dice/Operator.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,30 +83,30 @@ private static <T> Optional<T> getIndexIfExists(List<T> list, int index) {
return Optional.ofNullable(list.get(index));
}

protected static String getBinaryOperatorExpression(String name, List<RollBuilder> operands) {
protected static String getBinaryOperatorExpression(ExpressionPosition expressionPosition, List<RollBuilder> operands) {
String left = getIndexIfExists(operands, 0).map(RollBuilder::toExpression).orElse("");
String right = getIndexIfExists(operands, 1).map(RollBuilder::toExpression).orElse("");
return String.format("%s%s%s", left, name, right);
return String.format("%s%s%s", left, expressionPosition.toStringWithExtension(), right);
}

protected static String getLeftUnaryExpression(String name, List<RollBuilder> operands) {
protected static String getLeftUnaryExpression(ExpressionPosition expressionPosition, List<RollBuilder> operands) {
String left = getIndexIfExists(operands, 0).map(RollBuilder::toExpression).orElse("");
return String.format("%s%s", left, name);
return String.format("%s%s", left, expressionPosition.toStringWithExtension());
}

protected static String getRightUnaryExpression(String name, List<RollBuilder> operands) {
protected static String getRightUnaryExpression(ExpressionPosition expressionPosition, List<RollBuilder> operands) {
String right = getIndexIfExists(operands, 0).map(RollBuilder::toExpression).orElse("");
return String.format("%s%s", name, right);
return String.format("%s%s", expressionPosition.toStringWithExtension(), right);
}

/**
* Creates a RollBuilder for the arguments
*
* @param operands 1 or 2 operants
* @param inputValue the given input for the operant, is needed the get the used upper/lower case in the result expression
* @param operands 1 or 2 operants
* @param expressionPosition the part of the expression for this operant, is needed the get the used upper/lower case in the result expression
* @return the RollBuilder that can be called to get result rolls
*/
public abstract @NonNull RollBuilder evaluate(@NonNull List<RollBuilder> operands, @NonNull String inputValue) throws ExpressionException;
public abstract @NonNull RollBuilder evaluate(@NonNull List<RollBuilder> operands, @NonNull ExpressionPosition expressionPosition) throws ExpressionException;

public boolean supportUnaryOperation() {
return unaryAssociativity != null;
Expand Down
23 changes: 14 additions & 9 deletions src/main/java/de/janno/evaluator/dice/RandomElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.google.common.collect.ImmutableList;
import lombok.NonNull;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.annotation.Nullable;


@Value
Expand All @@ -15,40 +15,45 @@ public class RandomElement {
@Nullable
ImmutableList<String> randomSelectedFrom;

@NonNull
DieId dieId;

@Nullable
Integer minInc;

@Nullable
Integer maxInc;

public RandomElement(@NonNull RollElement rollElement, @NonNull ImmutableList<String> randomSelectedFrom) {
this(rollElement, randomSelectedFrom, null, null);
public RandomElement(@NonNull RollElement rollElement, @NonNull ImmutableList<String> randomSelectedFrom, @NonNull DieId dieId) {
this(rollElement, randomSelectedFrom, null, null, dieId);

}

public RandomElement(@NonNull RollElement rollElement, int minInc, int maxInc) {
this(rollElement, null, minInc, maxInc);
public RandomElement(@NonNull RollElement rollElement, int minInc, int maxInc, @NonNull DieId dieId) {
this(rollElement, null, minInc, maxInc, dieId);
}

private RandomElement(@NonNull RollElement rollElement,
@Nullable ImmutableList<String> randomSelectedFrom,
@Nullable Integer minInc,
@Nullable Integer maxInc) {
@Nullable Integer maxInc,
@NonNull DieId dieId) {
this.rollElement = rollElement;
this.randomSelectedFrom = randomSelectedFrom;
this.minInc = minInc;
this.maxInc = maxInc;
this.dieId = dieId;
}

public RandomElement copyWithTagAndColor(@NonNull String color) {
return new RandomElement(new RollElement(this.rollElement.getValue(), this.rollElement.getTag(), color), this.randomSelectedFrom, this.minInc, this.maxInc);
return new RandomElement(new RollElement(this.rollElement.getValue(), this.rollElement.getTag(), color), this.randomSelectedFrom, this.minInc, this.maxInc, this.dieId);
}

public String toString() {
if (randomSelectedFrom != null) {
return "%s∈%s".formatted(rollElement.toString(), randomSelectedFrom);
return "%s=%s∈%s".formatted(dieId, rollElement.toString(), randomSelectedFrom);
} else {
return "%s∈[%d...%d]".formatted(rollElement.toString(), minInc, maxInc);
return "%s=%s∈[%d...%d]".formatted(dieId, rollElement.toString(), minInc, maxInc);
}
}
}
Loading

0 comments on commit a3cc671

Please sign in to comment.