From 4d7f03d543b00e5903f5f2d507e0840acd6fd920 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Fri, 5 May 2023 19:10:23 -0500 Subject: [PATCH] Fix crash when calculating formula cost Happened when spend resource was false and the formula was non-integrable, but the amount to buy were all going to be summed anyways --- src/game/formulas/formulas.ts | 40 ++++++++++++++++++----------------- tests/game/formulas.test.ts | 21 ++++++++++++++++-- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts index 79adfce..76d8ff9 100644 --- a/src/game/formulas/formulas.ts +++ b/src/game/formulas/formulas.ts @@ -1491,33 +1491,35 @@ export function calculateCost( spendResources = true, summedPurchases?: number ) { - let newValue = Decimal.add(amountToBuy, unref(formula.innermostVariable) ?? 0); + const origValue = unref(formula.innermostVariable) ?? 0; + let newValue = Decimal.add(amountToBuy, origValue); + const targetValue = newValue; + summedPurchases ??= spendResources ? 10 : 0; + newValue = newValue.sub(summedPurchases).clampMin(origValue); + let cost: DecimalSource = 0; if (spendResources) { - if (!formula.isIntegrable()) { - throw new Error( - "Cannot calculate cost with spending resources of non-integrable formula" - ); + if (Decimal.gt(amountToBuy, summedPurchases)) { + if (!formula.isIntegrable()) { + throw new Error( + "Cannot calculate cost with spending resources of non-integrable formula" + ); + } + cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral()); } - const targetValue = newValue; - newValue = newValue - .sub(summedPurchases ?? 10) - .clampMin(unref(formula.innermostVariable) ?? 0); - let cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral()); if (targetValue.gt(1e308)) { // Too large of a number for summedPurchases to make a difference, // just get the cost and multiply by summed purchases - return cost.add(Decimal.sub(targetValue, newValue).times(formula.evaluate(newValue))); + return Decimal.add( + cost, + Decimal.sub(targetValue, newValue).times(formula.evaluate(newValue)) + ); } for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) { - cost = cost.add(formula.evaluate(i)); + cost = Decimal.add(cost, formula.evaluate(i)); } - return cost; } else { - const targetValue = newValue; - newValue = newValue - .sub(summedPurchases ?? 0) - .clampMin(unref(formula.innermostVariable) ?? 0); - let cost = formula.evaluate(newValue); + cost = formula.evaluate(newValue); + newValue = newValue.add(1); if (targetValue.gt(1e308)) { // Too large of a number for summedPurchases to make a difference, // just get the cost and multiply by summed purchases @@ -1526,6 +1528,6 @@ export function calculateCost( for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) { cost = Decimal.add(cost, formula.evaluate(i)); } - return cost; } + return cost; } diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts index 462d3d8..c0c067a 100644 --- a/tests/game/formulas.test.ts +++ b/tests/game/formulas.test.ts @@ -1089,9 +1089,21 @@ describe("Buy Max", () => { Decimal.pow(1.05, 141).times(100) ); }); + test("Calculates max affordable and cost correctly with summing last purchases", () => { + const variable = Formula.variable(0); + const formula = Formula.pow(1.05, variable).times(100); + const maxAffordable = calculateMaxAffordable(formula, resource, false, 4); + expect(maxAffordable.value).compare_tolerance(141 - 4); + + const actualCost = new Array(4) + .fill(null) + .reduce((acc, _, i) => acc.add(formula.evaluate(133 + i)), new Decimal(0)); + const calculatedCost = calculateCost(formula, maxAffordable.value, false, 4); + expect(calculatedCost).compare_tolerance(actualCost); + }); }); describe("With spending", () => { - test("Throws on non-invertible formula", () => { + test("Throws on calculating max affordable of non-invertible formula", () => { const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource); expect(() => maxAffordable.value).toThrow(); }); @@ -1220,7 +1232,7 @@ describe("Buy Max", () => { (acc, _, i) => acc.add(formula.evaluate(i + purchases.value)), new Decimal(0) ); - const calculatedCost = calculateCost(formula, maxAffordable.value, true); + const calculatedCost = calculateCost(formula, maxAffordable.value); // Since we're summing all the purchases this should be equivalent expect(calculatedCost).compare_tolerance(actualCost); }); @@ -1235,5 +1247,10 @@ describe("Buy Max", () => { expect(Decimal.isFinite(calculatedCost)).toBe(true); resource.value = 100000; }); + test("Handles summing purchases of non-integrable formula", () => { + const purchases = ref(0); + const formula = Formula.variable(purchases).abs(); + expect(() => calculateCost(formula, 10)).not.toThrow(); + }); }); });