Skip to content

Commit

Permalink
Color on (#121)
Browse files Browse the repository at this point in the history
* color On function
  • Loading branch information
twonirwana authored Sep 2, 2024
1 parent 0baebb2 commit 42d90d4
Show file tree
Hide file tree
Showing 15 changed files with 122 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ All functions are case insensitiv.
|sort desc |`desc(<expression1>, <expression2> ...)` |`desc(4d6)` |sorts all elements descending of one or more inner expressions. Text is compared alphabetically
|cancel |`cancel(<expression>, <listA>, <listB>)` |`cancel(8d10, 10, 1)` |the elements of listA and listB (can also be single elements) cancel each other and remove each other from the result.
|replace |`replace(<expression>, <find>, <replace> ...)` |`replace(8d10, [9/10], 'bonus')` | each element in `<expression>` that matches on of the elements in `<find>` will be replaced with the elements in `<replace>`. `<replace>` can be an empty list `[]` or literal `''` and thereby removing the found elements. It is possible to add multiple <find>/<replace> pairs to replace different elements in one replace. If the <replace> expression contains dice then they will only be rolled on a matching find element and for each matching element again.
|color on |`colorOn(<expression>, <in>, <color> ...)` |`replace(8d10, [9/10], 'red')` | each element in `<expression>` that is in the elements in `in` will be get the color `<color>`. `<color>` can be an empty list `[]` or literal `''` and thereby removing the current color. It is possible to add multiple <in>/<color> pairs to set different color for different values. If the <color> expression contains dice then they will only be rolled on a matching in element and for each matching in again.
|explode |`exp(<roll>,<rerollOnList>,<numberOfRerolls>` |`exp(d[1/2/3],[2/3])` or `exp(d[1/2/3],[2/3], 2)` | Rerolls the `<roll>` expression if any of its result elements are in the elements of `<rerollOnList>` and returns the original result and all reroll results. If `<roll>` contain more then one die then all are rerolled, even if only on result of one die matches the reroll list. `<numberOfRerolls>` define the maximum number of rerolls bevor the function stops with rerolls and returns the current result. `<numberOfRerolls>` must be a number between 0 and 100. If `<numberOfRerolls>` is not provided as argument then 100 will be used.
|if |`if(<boolean>,<true>,<false>)` |`if(1d6=?6,'six','not six')` or `if(1d6=?6,'six')` or `val('$r',1d6), if('$r'=?1,'one','$r'=?2,'two','else')` |if `<boolean>` equal true then return the `<true>` expression or else the `<false>` expression. The `<false>` expression is optional, if it is missing and `<boolean>` is `false` then the result empty. It is possible to add more than `<boolean>,<true>` pair in the function, the result will be the `<true>` of the first true `<boolean>`, coming from left. All <boolean> must be non-empty and contain only on element (therefor can't contain only `val`). `val` are will only set in the first <true>. Use the following structure to use `if` to set different value in a `val`: `if(1d6>?4, val('$a',10), val('$a',-10))`, this will set '$a' to 10 if the 1d6 roll is bigger than 4 and to -10 otherwise.
|group count |`groupC(<expression1>, <expression2> ...)` |`groupC(20d6)` | counts all elements of with the same value and provides the results as list in the format of `<count>x<value>`
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
1 change: 1 addition & 0 deletions src/main/java/de/janno/evaluator/dice/DiceEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public DiceEvaluator(@NonNull NumberSupplier numberSupplier, int maxNumberOfDice
.add(new IfGreater(maxNumberOfElements, keepChildrenRolls))
.add(new IfIn(maxNumberOfElements, keepChildrenRolls))
.add(new Replace(maxNumberOfElements, keepChildrenRolls))
.add(new ColorOn(maxNumberOfElements, keepChildrenRolls))
.add(new Explode(maxNumberOfElements, keepChildrenRolls))
.add(new IfLesser(maxNumberOfElements, keepChildrenRolls))
.add(new GroupCount(maxNumberOfElements, keepChildrenRolls))
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/de/janno/evaluator/dice/Roll.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ private void validate(ImmutableList<RandomElement> randomElements) {
}
}

public Optional<String> asSingleValue() {
if (elements.size() == 1) {
return Optional.of(elements.getFirst().getValue());
}
return Optional.empty();
}

public Optional<Integer> asInteger() {
if (elements.size() == 1) {
return elements.getFirst().asInteger();
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/de/janno/evaluator/dice/RollBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ static void extendRollBuilder(@NonNull RollContext rollContext, @NonNull RollBui
/**
* Creates a concrete roll from a roll builder (applies all random function aka throwing the dice).
* <p>
* Some functions or operators (e.g. val repeatList) produces empty results, they musst be filtered out or the argument count in functions are not correct.
* Some functions or operators (e.g. val) produces empty results, they are filtered and therefore don't influence the argument count in functions.
* This makes it possible use a val function inside an if function because if the val would return an empty list then it would be an argument for if.
* This is not a problem in operators because there the number of arguments is always correct because val is already pushed on the result stack
*/
@NonNull
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/de/janno/evaluator/dice/ValidatorUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ public static void checkContainsSingleElement(@NonNull ExpressionPosition expres
}
}

public static void checkContainsNoOrSingleElement(@NonNull ExpressionPosition expressionPosition, @NonNull Roll roll, @NonNull String location) throws ExpressionException {
if (!(roll.getElements().size() == 1 || roll.getElements().isEmpty())) {
throw new ExpressionException(String.format("'%s' requires as %s a single or no element but was '%s'%s", expressionPosition.getValue(), location, roll.getElements().stream()
.map(RollElement::getValue).toList(),
getSumHelp(roll)
), expressionPosition);
}
}

public static void checkRollSize(@NonNull ExpressionPosition expressionPosition, @NonNull List<Roll> rolls, int minInc, int maxInc) throws ExpressionException {
if (rolls.size() < minInc || rolls.size() > maxInc) {
String range = minInc == maxInc ? String.valueOf(minInc) : "%d-%d".formatted(minInc, maxInc);
Expand All @@ -59,6 +68,17 @@ public static void checkRollSize(@NonNull ExpressionPosition expressionPosition,
}
}

public static void checkContainsSingleRoll(@NonNull ExpressionPosition expressionPosition, @NonNull Optional<List<Roll>> rolls, int position) throws ExpressionException {
if (rolls.isEmpty()) {
throw new ExpressionException(String.format("'%s' requires as %s inputs but was empty", expressionPosition.getValue(), position), expressionPosition);
}
if (rolls.get().size() != 1) {
throw new ExpressionException(String.format("'%s' requires a single argument as %s input but was '%s'", expressionPosition.getValue(), position, rolls.get().stream()
.map(Roll::getElements).toList()
), expressionPosition);
}
}

public static void checkAllElementsAreSameTag(@NonNull ExpressionPosition expressionPosition, @NonNull Roll... rolls) throws ExpressionException {
Set<String> allElementTags = Arrays.stream(rolls).flatMap(r -> r.getElements().stream()).map(RollElement::getTag).collect(Collectors.toSet());
if (allElementTags.size() != 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ColorFunction(int maxNumberOfElements, boolean keepChildrenRolls) {
Roll p1 = rolls.getFirst();
Roll p2 = rolls.get(1);
checkContainsSingleElement(expressionPosition, p2, "second argument");
String color = p2.getElements().getFirst().getValue();
String color = p2.asSingleValue().orElseThrow();
RandomElementsBuilder builder = RandomElementsBuilder.empty(rollContext);
rolls.forEach(r -> builder.addWithColor(r, color));
return Optional.of(ImmutableList.of(new Roll(toExpression(),
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/de/janno/evaluator/dice/function/ColorOn.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package de.janno.evaluator.dice.function;

import com.google.common.collect.ImmutableList;
import de.janno.evaluator.dice.*;
import lombok.NonNull;

import java.util.List;
import java.util.Optional;

import static de.janno.evaluator.dice.ValidatorUtil.checkContainsNoOrSingleElement;
import static de.janno.evaluator.dice.ValidatorUtil.checkContainsSingleRoll;

public class ColorOn extends Function {
public ColorOn(int maxNumberOfElements, boolean keepChildrenRolls) {
super("colorOn", 3, Integer.MAX_VALUE, maxNumberOfElements, keepChildrenRolls);
}

@Override
public @NonNull RollBuilder evaluate(@NonNull List<RollBuilder> arguments, @NonNull ExpressionPosition expressionPosition) throws ExpressionException {
return new RollBuilder() {
@Override
public @NonNull Optional<List<Roll>> extendRoll(@NonNull RollContext rollContext) throws ExpressionException {
if (arguments.size() % 2 == 0) {
throw new ExpressionException(String.format("'%s' requires an odd number of arguments but was %d", getName(), arguments.size()), expressionPosition);
}
Optional<List<Roll>> input = arguments.getFirst().extendRoll(rollContext);
if (input.isEmpty()) {
return Optional.of(List.of());
}
ImmutableList.Builder<Roll> allRolls = ImmutableList.<Roll>builder().addAll(input.get());
ImmutableList<RollElement> inputRollElements = input.get().stream().flatMap(r -> r.getElements().stream()).collect(ImmutableList.toImmutableList());
for (int i = 1; i < arguments.size() - 1; i = i + 2) {

Optional<List<Roll>> inRolls = arguments.get(i).extendRoll(rollContext);
checkContainsSingleRoll(expressionPosition, inRolls, i + 1);
final Roll inRoll = inRolls.orElseThrow().getFirst();
allRolls.add(inRoll);

ImmutableList.Builder<RollElement> currentIterationElements = ImmutableList.builder();
for (RollElement rollElement : inputRollElements) {
if (inRoll.isElementsContainsElementWithValueAndTag(rollElement)) {
Optional<List<Roll>> colorRolls = arguments.get(i + 1).extendRoll(rollContext);
checkContainsSingleRoll(expressionPosition, colorRolls, i + 2);
Roll colorRoll = colorRolls.orElseThrow().getFirst();
checkContainsNoOrSingleElement(expressionPosition, colorRoll, "%d argument".formatted(i + 2));
final String color = colorRoll.asSingleValue().orElse(RollElement.NO_COLOR);
allRolls.add(colorRoll);
currentIterationElements.add(new RollElement(rollElement.getValue(), rollElement.getTag(), color));
} else {
currentIterationElements.add(rollElement);
}
}
inputRollElements = currentIterationElements.build();
}

return Optional.of(ImmutableList.of(new Roll(toExpression(),
inputRollElements,
RandomElementsBuilder.fromRolls(allRolls.build(), rollContext),
allRolls.build(),
expressionPosition,
maxNumberOfElements, keepChildrenRolls)));

}

@Override
public @NonNull String toExpression() {
return getExpression(expressionPosition, arguments);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public ConcatFunction(int maxNumberOfElements, boolean keepChildrenRolls) {
public @NonNull Optional<List<Roll>> extendRoll(@NonNull RollContext rollContext) throws ExpressionException {
List<Roll> rolls = extendAllBuilder(arguments, rollContext);
if (rolls.isEmpty()) {
return Optional.empty();
return Optional.of(List.of());
}
String joined = rolls.stream()
.map(Roll::getResultString)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/de/janno/evaluator/dice/function/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public Value(int maxNumberOfElements, boolean keepChildrenRolls) {
if (rolls.getFirst().getElements().isEmpty()) {
throw new ExpressionException(String.format("'%s' requires a non-empty input as first argument", expressionPosition.getValue()), expressionPosition);
}
String valName = rolls.getFirst().getElements().getFirst().getValue();
String valName = rolls.getFirst().asSingleValue().orElseThrow();

String expression = toExpression();
rollContext.putVariable(valName, new Roll(expression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Color(int maxNumberOfElements, boolean keepChildrenRolls) {
Roll right = rolls.get(1);
checkAllElementsAreSameTag(expressionPosition, left, right);
checkContainsSingleElement(expressionPosition, right, "second argument");
String color = right.getElements().getFirst().getValue();
String color = right.asSingleValue().orElseThrow();
//colors are applied to the random elements, so they can be used for dice images
RandomElementsBuilder builder = RandomElementsBuilder.empty(rollContext);
rolls.forEach(r -> builder.addWithColor(r, color));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Tag(int maxNumberOfElements, boolean keepChildrenRolls) {
Roll right = rolls.get(1);
checkAllElementsAreSameTag(expressionPosition, left, right);
checkContainsSingleElement(expressionPosition, right, "second argument");
String tag = right.getElements().getFirst().getValue();
String tag = right.asSingleValue().orElseThrow();

return Optional.of(ImmutableList.of(new Roll(toExpression(),
left.getElements().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public RepeatList(int maxNumberOfElements, boolean keepChildrenRolls) {
throw new ExpressionException(String.format("The number of list repeat must between 0-20 but was %d", left), expressionPosition);
}
if (left == 0) {
return Optional.empty();
return Optional.of(List.of());
}

RollBuilder right = operands.get(1);
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
| Max | max() | max(3d4, 8) |
| Chancel | chancel() | chancel(8d10,10,1) |
| Replace | replace() | replace(6d10,1,2) |
| Color On | colorOn() | colorOn(d6,6,'red')|
| Explode | exp() | exp(d6,1,2) |
| If | if() | if(d6=?6,'Y','N') |
| Group Count | groupC() | groupC(20d6) |
Expand Down
12 changes: 12 additions & 0 deletions src/test/java/de/janno/evaluator/dice/DiceEvaluatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,11 @@ private static Stream<Arguments> generateErrorData() {
Arguments.of("1 - [3/a]", "'-' requires as right input only decimals or elements that are on the left side '[1]' but was '[3, a]'"),
Arguments.of("val('a')", "'val' requires as 2 inputs but was '[[a]]'"),
Arguments.of("val('',d6)", "'val' requires a non-empty input as first argument"),
Arguments.of("colorOn(2d6,'white')", "'colorOn' requires an odd number of arguments but was 2"),
Arguments.of("colorOn(1,1,[white/red])", "'colorOn' requires as 3 argument a single or no element but was '[white, red]'"),
Arguments.of("colorOn(1,val('a',1),'white')", "'colorOn' requires as 2 inputs but was empty"),
Arguments.of("colorOn(1,2x2,'white')", "'colorOn' requires a single argument as 2 input but was '[[2], [2]]'"),
Arguments.of("colorOn(1,1,val('a',1))", "'colorOn' requires as 3 inputs but was empty"),

Arguments.of("d", "Operator d has right associativity but the right value was: empty")

Expand Down Expand Up @@ -765,6 +770,13 @@ private static Stream<Arguments> generateStringDiceDataWithRandomElements() {
Arguments.of("val('$s',1), if(0=?1, '') + '$s'", List.of(), "1", "[]"),
Arguments.of("val('$s',1) if(0=?1, '') '$s'", List.of(), "1", "[]"),

Arguments.of("colorOn(3d6,[1/2],'white')", List.of(6, 2, 1), "6, 2-c:white, 1-c:white", "[9de0i0r0=6∈[1...6], 9de0i1r0=2∈[1...6], 9de0i2r0=1∈[1...6]]"),
Arguments.of("colorOn(3d6 col 'red', [1/2],'')", List.of(6, 2, 1), "6-c:red, 2, 1", "[9de0i0r0=6∈[1...6], 9de0i1r0=2∈[1...6], 9de0i2r0=1∈[1...6]]"),
Arguments.of("colorOn('',[1/2],'white')", List.of(), "", "[]"),
Arguments.of("colorOn(val('a',1),1,'white')", List.of(), "", "[]"),
Arguments.of("colorOn(3d6,[1/2],'white')", List.of(6, 5, 5), "6, 5, 5", "[9de0i0r0=6∈[1...6], 9de0i1r0=5∈[1...6], 9de0i2r0=5∈[1...6]]"),
Arguments.of("colorOn(4d6 col 'red',[1/2],'white', 3, 'black', 1, 'green')", List.of(4, 3, 2, 1), "4-c:red, 3-c:black, 2-c:white, 1-c:green", "[9de0i0r0=4∈[1...6], 9de0i1r0=3∈[1...6], 9de0i2r0=2∈[1...6], 9de0i3r0=1∈[1...6]]"),

//systems
Arguments.of(THE_ONE_RING, List.of(), " ⬟= ᚠ", "[34de0i0r0=12∈[1...12]]"),
Arguments.of(VAMPIRE_V5, List.of(), "successes: 12 messy critical", "[10de0i0r0=10∈[1...10], 10de0i1r0=10∈[1...10], 10de0i2r0=10∈[1...10], 25de0i0r0=10∈[1...10], 25de0i1r0=10∈[1...10], 25de0i2r0=10∈[1...10]]")
Expand Down

0 comments on commit 42d90d4

Please sign in to comment.