From 30aec8a93cb86b9d5fad23cff4a084f13fdebf1c Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Thu, 19 Jan 2023 08:36:16 -0600 Subject: [PATCH] Implement conditional formulas --- src/game/formulas.ts | 61 +++++++++++++++++++++++++++++++------ tests/game/formulas.test.ts | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/src/game/formulas.ts b/src/game/formulas.ts index 7453c05..6b183e0 100644 --- a/src/game/formulas.ts +++ b/src/game/formulas.ts @@ -1,5 +1,5 @@ import Decimal, { DecimalSource } from "util/bignum"; -import { ProcessedComputable } from "util/computed"; +import { Computable, convertComputable, ProcessedComputable } from "util/computed"; import { ref, Ref, unref } from "vue"; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -439,26 +439,27 @@ export default class Formula { * @param start The value at which to start applying the step * @param formulaModifier How this step should modify the formula. The incoming value will be the unmodified formula value _minus the start value_. So for example if an incoming formula evaluates to 200 and has a step that starts at 150, the formulaModifier would be given 50 as the parameter */ - public static step( - value: T, - start: ProcessedComputable, + public static step( + value: FormulaSource, + start: Computable, formulaModifier: (value: Ref) => GenericFormula ) { const lhsRef = ref(0); const formula = formulaModifier(lhsRef); + const processedStart = convertComputable(start); function evalStep(lhs: DecimalSource) { - if (Decimal.lt(lhs, unref(start))) { + if (Decimal.lt(lhs, unref(processedStart))) { return lhs; } - lhsRef.value = Decimal.sub(lhs, unref(start)); - return Decimal.add(formula.evaluate(), unref(start)); + lhsRef.value = Decimal.sub(lhs, unref(processedStart)); + return Decimal.add(formula.evaluate(), unref(processedStart)); } function invertStep(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { - if (Decimal.gt(value, unref(start))) { + if (Decimal.gt(value, unref(processedStart))) { value = Decimal.add( - formula.invert(Decimal.sub(value, unref(start))), - unref(start) + formula.invert(Decimal.sub(value, unref(processedStart))), + unref(processedStart) ); } return lhs.invert(value); @@ -472,6 +473,46 @@ export default class Formula { ); } + public static if( + value: FormulaSource, + condition: Computable, + formulaModifier: (value: Ref) => GenericFormula + ) { + const lhsRef = ref(0); + const formula = formulaModifier(lhsRef); + const processedCondition = convertComputable(condition); + function evalStep(lhs: DecimalSource) { + if (unref(processedCondition)) { + lhsRef.value = lhs; + return formula.evaluate(); + } else { + return lhs; + } + } + function invertStep(value: DecimalSource, lhs: FormulaSource) { + if (!hasVariable(lhs)) { + throw "Could not invert due to no input being a variable"; + } + if (unref(processedCondition)) { + return lhs.invert(formula.invert(value)); + } else { + return lhs.invert(value); + } + } + return new Formula( + [value], + evalStep, + formula.isInvertible() && !formula.hasVariable() ? invertStep : undefined + ); + } + public static conditional( + value: FormulaSource, + condition: Computable, + formulaModifier: (value: Ref) => GenericFormula + ) { + return Formula.if(value, condition, formulaModifier); + } + public static constant(value: InvertibleFormulaSource): InvertibleFormula { return new Formula([value]) as InvertibleFormula; } diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts index cb5f4d4..a998e26 100644 --- a/tests/game/formulas.test.ts +++ b/tests/game/formulas.test.ts @@ -530,3 +530,58 @@ describe("Step-wise", () => { ).compare_tolerance(10)); }); }); + +describe("Conditionals", () => { + let variable: GenericFormula; + let constant: GenericFormula; + beforeAll(() => { + variable = Formula.variable(10); + constant = Formula.constant(10); + }); + + test("Formula without variable is marked as such", () => { + expect(Formula.if(constant, true, value => Formula.sqrt(value)).isInvertible()).toBe(true); + expect(Formula.if(constant, true, value => Formula.sqrt(value)).hasVariable()).toBe(false); + }); + + test("Formula with variable is marked as such", () => { + expect(Formula.if(variable, true, value => Formula.sqrt(value)).isInvertible()).toBe(true); + expect(Formula.if(variable, true, value => Formula.sqrt(value)).hasVariable()).toBe(true); + }); + + test("Non-invertible formula modifier marks formula as such", () => { + expect(Formula.if(constant, true, value => Formula.abs(value)).isInvertible()).toBe(false); + expect(Formula.if(constant, true, value => Formula.abs(value)).hasVariable()).toBe(false); + }); + + test("Formula modifiers with variables mark formula as non-invertible", () => { + expect( + Formula.if(constant, true, value => Formula.add(value, variable)).isInvertible() + ).toBe(false); + expect( + Formula.if(constant, true, value => Formula.add(value, variable)).hasVariable() + ).toBe(false); + }); + + describe("Pass-through with condition false", () => { + test("Evaluates correctly", () => + expect( + Formula.if(constant, false, value => Formula.sqrt(value)).evaluate() + ).compare_tolerance(10)); + test("Inverts correctly with variable in input", () => + expect( + Formula.if(variable, false, value => Formula.sqrt(value)).invert(10) + ).compare_tolerance(10)); + }); + + describe("Evaluates correctly with condition true", () => { + test("Evaluates correctly", () => + expect( + Formula.if(variable, true, value => Formula.add(value, 2)).evaluate() + ).compare_tolerance(12)); + test("Inverts correctly", () => + expect( + Formula.if(variable, true, value => Formula.add(value, 2)).invert(12) + ).compare_tolerance(10)); + }); +});