From f3ed96f8cfb2138905cebfb82470fed1b673711f Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Tue, 14 Feb 2023 02:41:33 -0600 Subject: [PATCH] Make clamping functions pass-throughs for inverting --- src/game/formulas.ts | 95 ++++++++++++++++++++++++++--- tests/game/formulas.test.ts | 116 ++++++++++++++++++++---------------- 2 files changed, 153 insertions(+), 58 deletions(-) diff --git a/src/game/formulas.ts b/src/game/formulas.ts index 253e0db..080dba8 100644 --- a/src/game/formulas.ts +++ b/src/game/formulas.ts @@ -56,6 +56,10 @@ export function unrefFormulaSource(value: FormulaSource, variable?: DecimalSourc return value instanceof Formula ? value.evaluate(variable) : unref(value); } +function passthrough(value: DecimalSource) { + return value; +} + function invertNeg(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.neg(value)); @@ -1216,19 +1220,63 @@ export default class Formula { } public static max(value: FormulaSource, other: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, other], evaluate: Decimal.max }); + return new Formula({ + inputs: [value, other], + evaluate: Decimal.max, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static min(value: FormulaSource, other: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, other], evaluate: Decimal.min }); + return new Formula({ + inputs: [value, other], + evaluate: Decimal.min, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static minabs(value: FormulaSource, other: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, other], evaluate: Decimal.minabs }); + return new Formula({ + inputs: [value, other], + evaluate: Decimal.minabs, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static maxabs(value: FormulaSource, other: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, other], evaluate: Decimal.maxabs }); + return new Formula({ + inputs: [value, other], + evaluate: Decimal.maxabs, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static clamp( @@ -1236,15 +1284,48 @@ export default class Formula { min: FormulaSource, max: FormulaSource ): GenericFormula { - return new Formula({ inputs: [value, min, max], evaluate: Decimal.clamp }); + return new Formula({ + inputs: [value, min, max], + evaluate: Decimal.clamp, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static clampMin(value: FormulaSource, min: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, min], evaluate: Decimal.clampMin }); + return new Formula({ + inputs: [value, min], + evaluate: Decimal.clampMin, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static clampMax(value: FormulaSource, max: FormulaSource): GenericFormula { - return new Formula({ inputs: [value, max], evaluate: Decimal.clampMax }); + return new Formula({ + inputs: [value, max], + evaluate: Decimal.clampMax, + invert: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource, + invertIntegral: passthrough as ( + value: DecimalSource, + ...inputs: [FormulaSource, FormulaSource] + ) => DecimalSource + }); } public static pLog10(value: FormulaSource): GenericFormula { diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts index c00d43b..f5f12a3 100644 --- a/tests/game/formulas.test.ts +++ b/tests/game/formulas.test.ts @@ -124,22 +124,14 @@ const invertibleOneParamFunctionNames = [ "root", "slog" ] as const; -const nonInvertibleOneParamFunctionNames = [ - "max", - "min", - "maxabs", - "minabs", - "clampMin", - "clampMax", - "layeradd10" -] as const; +const nonInvertibleOneParamFunctionNames = ["layeradd10"] as const; const integrableOneParamFunctionNames = ["add", "sub", "mul", "div", "log", "pow", "root"] as const; const nonIntegrableOneParamFunctionNames = [...nonInvertibleOneParamFunctionNames, "slog"] as const; const invertibleIntegralOneParamFunctionNames = integrableOneParamFunctionNames; const nonInvertibleIntegralOneParamFunctionNames = nonIntegrableOneParamFunctionNames; const invertibleTwoParamFunctionNames = ["tetrate", "layeradd", "iteratedexp"] as const; -const nonInvertibleTwoParamFunctionNames = ["clamp", "iteratedlog", "pentate"] as const; +const nonInvertibleTwoParamFunctionNames = ["iteratedlog", "pentate"] as const; const nonIntegrableTwoParamFunctionNames = [ ...invertibleTwoParamFunctionNames, ...nonInvertibleZeroParamFunctionNames @@ -308,24 +300,28 @@ describe("Creating Formulas", () => { }); } - describe("Invertible 0-param", () => { - invertibleZeroParamFunctionNames.forEach(names => - describe(names, () => { - checkFormula(names, [0] as const); - testValues.forEach(i => testFormulaCall(names, [i] as const)); - }) + describe("0-param", () => { + [...invertibleZeroParamFunctionNames, ...nonInvertibleZeroParamFunctionNames].forEach( + names => + describe(names, () => { + checkFormula(names, [0] as const); + testValues.forEach(i => testFormulaCall(names, [i] as const)); + }) ); }); - describe("Non-Invertible 0-param", () => { - nonInvertibleZeroParamFunctionNames.forEach(names => - describe(names, () => { - checkFormula(names, [0] as const); - testValues.forEach(i => testFormulaCall(names, [i] as const)); - }) - ); - }); - describe("Invertible 1-param", () => { - invertibleOneParamFunctionNames.forEach(names => + describe("1-param", () => { + ( + [ + ...invertibleOneParamFunctionNames, + ...nonInvertibleOneParamFunctionNames, + "max", + "min", + "maxabs", + "minabs", + "clampMin", + "clampMax" + ] as const + ).forEach(names => describe(names, () => { checkFormula(names, [0, 0] as const); testValues.forEach(i => @@ -334,30 +330,14 @@ describe("Creating Formulas", () => { }) ); }); - describe("Non-Invertible 1-param", () => { - nonInvertibleOneParamFunctionNames.forEach(names => - describe(names, () => { - checkFormula(names, [0, 0] as const); - testValues.forEach(i => - testValues.forEach(j => testFormulaCall(names, [i, j] as const)) - ); - }) - ); - }); - describe("Invertible 2-param", () => { - invertibleTwoParamFunctionNames.forEach(names => - describe(names, () => { - checkFormula(names, [0, 0, 0] as const); - testValues.forEach(i => - testValues.forEach(j => - testValues.forEach(k => testFormulaCall(names, [i, j, k] as const)) - ) - ); - }) - ); - }); - describe("Non-Invertible 2-param", () => { - nonInvertibleTwoParamFunctionNames.forEach(names => + describe("2-param", () => { + ( + [ + ...invertibleTwoParamFunctionNames, + ...nonInvertibleTwoParamFunctionNames, + "clamp" + ] as const + ).forEach(names => describe(names, () => { checkFormula(names, [0, 0, 0] as const); testValues.forEach(i => @@ -527,6 +507,21 @@ describe("Inverting", () => { ); }); + describe("Inverting pass-throughs", () => { + test("max", () => expect(Formula.max(variable, constant).invert(10)).compare_tolerance(10)); + test("min", () => expect(Formula.min(variable, constant).invert(10)).compare_tolerance(10)); + test("minabs", () => + expect(Formula.minabs(variable, constant).invert(10)).compare_tolerance(10)); + test("maxabs", () => + expect(Formula.maxabs(variable, constant).invert(10)).compare_tolerance(10)); + test("clampMax", () => + expect(Formula.clampMax(variable, constant).invert(10)).compare_tolerance(10)); + test("clampMin", () => + expect(Formula.clampMin(variable, constant).invert(10)).compare_tolerance(10)); + test("clamp", () => + expect(Formula.clamp(variable, constant, constant).invert(10)).compare_tolerance(10)); + }); + test("Inverting nested formulas", () => { const formula = Formula.add(variable, constant).times(constant); expect(formula.invert(100)).compare_tolerance(0); @@ -705,6 +700,25 @@ describe("Inverting integrals", () => { const formula = Formula.add(variable, constant).times(constant); expect(formula.invertIntegral(1500)).compare_tolerance(10); }); + + describe("Inverting integral pass-throughs", () => { + test("max", () => + expect(Formula.max(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("min", () => + expect(Formula.min(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("minabs", () => + expect(Formula.minabs(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("maxabs", () => + expect(Formula.maxabs(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("clampMax", () => + expect(Formula.clampMax(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("clampMin", () => + expect(Formula.clampMin(variable, constant).invertIntegral(10)).compare_tolerance(10)); + test("clamp", () => + expect( + Formula.clamp(variable, constant, constant).invertIntegral(10) + ).compare_tolerance(10)); + }); }); describe("Step-wise", () => { @@ -980,7 +994,7 @@ describe("Buy Max", () => { }); describe("Without spending", () => { test("Throws on non-invertible formula", () => { - const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource, false); + const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource); expect(() => maxAffordable.value).toThrow(); }); // https://www.desmos.com/calculator/5vgletdc1p