From 99235c7014367c2dff27960c58368cdd82fff8a9 Mon Sep 17 00:00:00 2001 From: ylou <1237500+ylou@users.noreply.github.com> Date: Sat, 1 May 2021 02:37:20 -0700 Subject: [PATCH] Allow simple math input in AmountSpecifyingScreen --- .../screen/AmountSpecifyingScreen.java | 62 ++++++---- .../screen/FluidAmountScreen.java | 12 +- .../screen/ItemAmountScreen.java | 12 +- .../refinedstorage/screen/PriorityScreen.java | 12 +- .../screen/grid/CraftingSettingsScreen.java | 12 +- .../util/EquationEvaluator.java | 107 ++++++++++++++++++ .../util/EquationEvaluatorTest.java | 64 +++++++++++ 7 files changed, 221 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/refinedmods/refinedstorage/util/EquationEvaluator.java create mode 100644 src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java diff --git a/src/main/java/com/refinedmods/refinedstorage/screen/AmountSpecifyingScreen.java b/src/main/java/com/refinedmods/refinedstorage/screen/AmountSpecifyingScreen.java index 926f519ba..eb0a64874 100644 --- a/src/main/java/com/refinedmods/refinedstorage/screen/AmountSpecifyingScreen.java +++ b/src/main/java/com/refinedmods/refinedstorage/screen/AmountSpecifyingScreen.java @@ -3,6 +3,7 @@ package com.refinedmods.refinedstorage.screen; import com.mojang.blaze3d.vertex.PoseStack; import com.refinedmods.refinedstorage.RS; import com.refinedmods.refinedstorage.render.RenderSettings; +import com.refinedmods.refinedstorage.util.EquationEvaluator; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; import net.minecraft.network.chat.Component; @@ -38,6 +39,25 @@ public abstract class AmountSpecifyingScreen ex protected abstract int getMaxAmount(); + protected int getMinAmount() { + if (canAmountGoNegative()) { + return -getMaxAmount(); + } + return 1; + } + + protected int parseAmount() { + return (int) Math.ceil(EquationEvaluator.evaluate(amountField.getText())); + } + + protected int clampAmount(int amount) { + return Math.max(getMinAmount(), Math.min(getMaxAmount(), amount)); + } + + protected boolean isAmountInBounds(int amount) { + return getMinAmount() <= amount && amount <= getMaxAmount(); + } + protected Pair getAmountPos() { return Pair.of(7 + 2, 50 + 1); } @@ -137,29 +157,32 @@ public abstract class AmountSpecifyingScreen ex } private void onIncrementButtonClicked(int increment) { - int oldAmount = 0; - try { - oldAmount = Integer.parseInt(amountField.getValue()); - } catch (NumberFormatException e) { + int oldAmount = parseAmount(); + int newAmount = oldAmount + increment; + if (!canAmountGoNegative() && oldAmount == 1) { + newAmount--; + } + amountField.setText(String.valueOf(clampAmount(newAmount))); + } catch (IllegalArgumentException e) { // NO OP } - - int newAmount = increment; - - if (!canAmountGoNegative()) { - newAmount = Math.max(1, ((oldAmount == 1 && newAmount != 1) ? 0 : oldAmount) + newAmount); - } else { - newAmount = oldAmount + newAmount; - } - - if (newAmount > getMaxAmount()) { - newAmount = getMaxAmount(); - } - - amountField.setValue(String.valueOf(newAmount)); } + private void onOkButtonPressed(boolean shiftDown) { + try { + int amount = parseAmount(); + if (isAmountInBounds(amount)) { + onValidAmountSave(shiftDown, amount); + close(); + } + } catch (IllegalArgumentException e) { + // NO OP + } + } + + protected void onValidAmountSave(boolean shiftDown, int amount) {} + @Override public void tick(int x, int y) { // NO OP @@ -179,9 +202,6 @@ public abstract class AmountSpecifyingScreen ex renderString(poseStack, 7, 7, title.getString()); } - protected void onOkButtonPressed(boolean shiftDown) { - // NO OP - } @Override public boolean mouseScrolled(double x, double y, double delta) { diff --git a/src/main/java/com/refinedmods/refinedstorage/screen/FluidAmountScreen.java b/src/main/java/com/refinedmods/refinedstorage/screen/FluidAmountScreen.java index e41ef0639..ac84365ad 100644 --- a/src/main/java/com/refinedmods/refinedstorage/screen/FluidAmountScreen.java +++ b/src/main/java/com/refinedmods/refinedstorage/screen/FluidAmountScreen.java @@ -87,15 +87,7 @@ public class FluidAmountScreen extends AmountSpecifyingScreen operators = new ArrayDeque<>(); + Deque operands = new ArrayDeque<>(); + String lastToken = null; + while (tokens.hasMoreTokens()) { + String rawToken = tokens.nextToken().trim(); + if (rawToken.isEmpty()) + continue; + + boolean isUnaryMinus = rawToken.equals("-") && (lastToken == null + || (!lastToken.equals("u") && isOperator(lastToken)) || lastToken.equals("(")); + final String token = isUnaryMinus ? "u" : rawToken; + if (isOperator(token)) { + popOperators(input, operators, operands, + top -> (!top.equals("(") && getPrecedence(token) <= getPrecedence(top))); + operators.push(token); + } else if (token.equals("(")) { + operators.push(token); + } else if (token.equals(")")) { + popOperators(input, operators, operands, top -> !top.equals("(")); + if (!operators.pop().equals("(")) { + throw new IllegalArgumentException("Unbalanced right parentheses in expression: " + input); + } + } else { + try { + operands.push(Double.parseDouble(token)); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException("Could not evaluate expression: " + input); + } + } + lastToken = token; + } + popOperators(input, operators, operands, top -> true); + return operands.pop(); + } + + private static void popOperators(String input, Deque operators, Deque operands, + Predicate predicate) { + try { + while (!operators.isEmpty() && predicate.test(operators.peek())) { + String op = operators.pop(); + if (op.equals("u")) { + operands.push(-operands.pop()); + continue; + } + operands.push(evaluateExpression(op, operands.pop(), operands.pop())); + } + } catch (NoSuchElementException exception) { + throw new IllegalArgumentException("Missing operand in expression: " + input); + } + } + + private static double evaluateExpression(String op, double b, double a) { + switch (op) { + case "+": + return a + b; + case "-": + return a - b; + case "*": + return a * b; + case "/": + return a / b; + } + throw new IllegalArgumentException("Unexpected arithmetic operation " + op); + } + + private static boolean isOperator(String op) { + switch (op) { + case "+": + case "-": + case "*": + case "/": + case "u": + return true; + } + return false; + } + + private static int getPrecedence(String op) { + switch (op) { + case "+": + case "-": + return 0; + case "*": + case "/": + return 1; + case "u": + return 2; + } + return -1; + } +} diff --git a/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java b/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java new file mode 100644 index 000000000..67f9f2295 --- /dev/null +++ b/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java @@ -0,0 +1,64 @@ +package com.refinedmods.refinedstorage.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class EquationEvaluatorTest { + + private void assertFuzzyEquals(double expected, double actual) { + assertEquals(expected, actual, 0.001D); + } + + @Test + void testConstants() { + assertFuzzyEquals(1, EquationEvaluator.evaluate("1")); + assertFuzzyEquals(122, EquationEvaluator.evaluate("122")); + assertFuzzyEquals(4.2, EquationEvaluator.evaluate("4.20")); + } + + @Test + void testNegativeConstants() { + assertFuzzyEquals(-1, EquationEvaluator.evaluate("-1")); + assertFuzzyEquals(-2.5, EquationEvaluator.evaluate("-2.5")); + } + + @Test + void testBasicOperations() { + assertFuzzyEquals(2, EquationEvaluator.evaluate("1+1")); + assertFuzzyEquals(1, EquationEvaluator.evaluate("3 - 2")); + assertFuzzyEquals(6, EquationEvaluator.evaluate("2*3")); + assertFuzzyEquals(2, EquationEvaluator.evaluate("6 / 3")); + assertFuzzyEquals(-2, EquationEvaluator.evaluate("2-4")); + + assertFuzzyEquals(32, EquationEvaluator.evaluate("64 + -4 * 8")); + assertFuzzyEquals(8, EquationEvaluator.evaluate("16 + -4 + -4")); + } + + @Test + void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> EquationEvaluator.evaluate("hello")); + assertThrows(IllegalArgumentException.class, () -> EquationEvaluator.evaluate("1 / (")); + assertThrows(IllegalArgumentException.class, () -> EquationEvaluator.evaluate("1+-*/")); + assertThrows(IllegalArgumentException.class, () -> EquationEvaluator.evaluate("----1")); + } + + @Test + void testWhitespace() { + assertFuzzyEquals(2, EquationEvaluator.evaluate(" 1 + 1")); + assertFuzzyEquals(1, EquationEvaluator.evaluate(" 3 - 2 ")); + } + + @Test + void testParentheses() { + assertFuzzyEquals(42, EquationEvaluator.evaluate("10 + 8*(2 + 2)")); + assertFuzzyEquals(20, EquationEvaluator.evaluate("5 * ((1 + 1) * 2)")); + } + + @Test + void testOrderOfOperations() { + assertFuzzyEquals(1, EquationEvaluator.evaluate("1 - 2 + 3 * 4 / 6")); + assertFuzzyEquals(4.0 / 3, EquationEvaluator.evaluate("(((1 - 2) + 3) * 4) / 6")); + } +}