Allow simple math input in AmountSpecifyingScreen

This commit is contained in:
ylou
2021-05-01 02:37:20 -07:00
committed by Raoul
parent f92e20f795
commit 99235c7014
7 changed files with 221 additions and 60 deletions

View File

@@ -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<T extends AbstractContainerMenu> 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<Integer, Integer> getAmountPos() {
return Pair.of(7 + 2, 50 + 1);
}
@@ -137,29 +157,32 @@ public abstract class AmountSpecifyingScreen<T extends AbstractContainerMenu> 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<T extends AbstractContainerMenu> ex
renderString(poseStack, 7, 7, title.getString());
}
protected void onOkButtonPressed(boolean shiftDown) {
// NO OP
}
@Override
public boolean mouseScrolled(double x, double y, double delta) {

View File

@@ -87,15 +87,7 @@ public class FluidAmountScreen extends AmountSpecifyingScreen<FluidAmountContain
}
@Override
protected void onOkButtonPressed(boolean shiftDown) {
try {
int amount = Integer.parseInt(amountField.getValue());
RS.NETWORK_HANDLER.sendToServer(new SetFluidFilterSlotMessage(containerSlot, StackUtils.copy(stack, amount)));
close();
} catch (NumberFormatException e) {
// NO OP
}
protected void onValidAmountSave(boolean shiftDown, int amount) {
RS.NETWORK_HANDLER.sendToServer(new SetFluidFilterSlotMessage(containerSlot, StackUtils.copy(stack, amount)));
}
}

View File

@@ -87,15 +87,7 @@ public class ItemAmountScreen extends AmountSpecifyingScreen<AmountContainerMenu
}
@Override
protected void onOkButtonPressed(boolean shiftDown) {
try {
int amount = Integer.parseInt(amountField.getValue());
RS.NETWORK_HANDLER.sendToServer(new SetFilterSlotMessage(containerSlot, ItemHandlerHelper.copyStackWithSize(stack, amount)));
close();
} catch (NumberFormatException e) {
// NO OP
}
protected void onValidAmountSave(boolean shiftDown, int amount) {
RS.NETWORK_HANDLER.sendToServer(new SetFilterSlotMessage(containerSlot, ItemHandlerHelper.copyStackWithSize(stack, amount)));
}
}

View File

@@ -67,15 +67,7 @@ public class PriorityScreen extends AmountSpecifyingScreen<AbstractContainerMenu
}
@Override
protected void onOkButtonPressed(boolean noPreview) {
try {
int amount = Integer.parseInt(amountField.getValue());
BlockEntitySynchronizationManager.setParameter(priority, amount);
close();
} catch (NumberFormatException e) {
// NO OP
}
protected void onValidAmountSave(boolean shiftDown, int amount) {
BlockEntitySynchronizationManager.setParameter(priority, amount);
}
}

View File

@@ -62,15 +62,9 @@ public class CraftingSettingsScreen extends AmountSpecifyingScreen<CraftingSetti
}
@Override
protected void onOkButtonPressed(boolean shiftDown) {
try {
int quantity = Integer.parseInt(amountField.getValue());
protected void onValidAmountSave(boolean shiftDown, int amount) {
RS.NETWORK_HANDLER.sendToServer(new GridCraftingPreviewRequestMessage(stack.getId(), amount, shiftDown, stack instanceof FluidGridStack));
RS.NETWORK_HANDLER.sendToServer(new GridCraftingPreviewRequestMessage(stack.getId(), quantity, shiftDown, stack instanceof FluidGridStack));
okButton.active = false;
} catch (NumberFormatException e) {
// NO OP
}
okButton.active = false;
}
}

View File

@@ -0,0 +1,107 @@
package com.refinedmods.refinedstorage.util;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.function.Predicate;
/**
* Implements evaluation for simple math expressions using the shunting yard
* algorithm.
*/
public class EquationEvaluator {
public static double evaluate(String input) {
StringTokenizer tokens = new StringTokenizer(input, "+-*/()", true);
Deque<String> operators = new ArrayDeque<>();
Deque<Double> 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<String> operators, Deque<Double> operands,
Predicate<String> 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;
}
}

View File

@@ -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"));
}
}