forked from profectus/Profectus
Implement step-wise functions
This commit is contained in:
parent
773401069a
commit
675b30fdd0
2 changed files with 99 additions and 4 deletions
|
@ -432,6 +432,46 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a step-wise formula. After {@ref start} the formula will have an additional modifier.
|
||||
* This function assumes the incoming {@ref value} will be continuous and monotonically increasing.
|
||||
* @param value The value before applying the step
|
||||
* @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<T extends FormulaSource>(
|
||||
value: T,
|
||||
start: ProcessedComputable<DecimalSource>,
|
||||
formulaModifier: (value: Ref<DecimalSource>) => GenericFormula
|
||||
) {
|
||||
const lhsRef = ref<DecimalSource>(0);
|
||||
const formula = formulaModifier(lhsRef);
|
||||
function evalStep(lhs: DecimalSource) {
|
||||
if (Decimal.lt(lhs, unref(start))) {
|
||||
return lhs;
|
||||
}
|
||||
lhsRef.value = Decimal.sub(lhs, unref(start));
|
||||
return Decimal.add(formula.evaluate(), unref(start));
|
||||
}
|
||||
function invertStep(value: DecimalSource, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
if (Decimal.gt(value, unref(start))) {
|
||||
value = Decimal.add(
|
||||
formula.invert(Decimal.sub(value, unref(start))),
|
||||
unref(start)
|
||||
);
|
||||
}
|
||||
return lhs.invert(value);
|
||||
}
|
||||
throw "Could not invert due to no input being a variable";
|
||||
}
|
||||
return new Formula(
|
||||
[value],
|
||||
evalStep,
|
||||
formula.isInvertible() && !formula.hasVariable() ? invertStep : undefined
|
||||
);
|
||||
}
|
||||
|
||||
public static constant(value: InvertibleFormulaSource): InvertibleFormula {
|
||||
return new Formula([value]) as InvertibleFormula;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Formula, { GenericFormula, InvertibleFormula, unrefFormulaSource } from "game/formulas";
|
||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { ref } from "vue";
|
||||
import { Ref, ref } from "vue";
|
||||
|
||||
type FormulaFunctions = keyof GenericFormula & keyof typeof Formula & keyof typeof Decimal;
|
||||
|
||||
|
@ -53,10 +53,10 @@ function testConstant(
|
|||
beforeAll(() => {
|
||||
formula = formulaFunc();
|
||||
});
|
||||
test("evaluates correctly", () =>
|
||||
test("Evaluates correctly", () =>
|
||||
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
||||
test("invert is pass-through", () => expect(formula.invert(25)).compare_tolerance(25));
|
||||
test("is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
||||
test("Invert is pass-through", () => expect(formula.invert(25)).compare_tolerance(25));
|
||||
test("Is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -475,3 +475,58 @@ describe("Variables", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Step-wise", () => {
|
||||
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.step(constant, 10, value => Formula.sqrt(value)).isInvertible()).toBe(true);
|
||||
expect(Formula.step(constant, 10, value => Formula.sqrt(value)).hasVariable()).toBe(false);
|
||||
});
|
||||
|
||||
test("Formula with variable is marked as such", () => {
|
||||
expect(Formula.step(variable, 10, value => Formula.sqrt(value)).isInvertible()).toBe(true);
|
||||
expect(Formula.step(variable, 10, value => Formula.sqrt(value)).hasVariable()).toBe(true);
|
||||
});
|
||||
|
||||
test("Non-invertible formula modifier marks formula as such", () => {
|
||||
expect(Formula.step(constant, 10, value => Formula.abs(value)).isInvertible()).toBe(false);
|
||||
expect(Formula.step(constant, 10, value => Formula.abs(value)).hasVariable()).toBe(false);
|
||||
});
|
||||
|
||||
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
||||
expect(
|
||||
Formula.step(constant, 10, value => Formula.add(value, variable)).isInvertible()
|
||||
).toBe(false);
|
||||
expect(
|
||||
Formula.step(constant, 10, value => Formula.add(value, variable)).hasVariable()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
describe("Pass-through underneath start", () => {
|
||||
test("Evaluates correctly", () =>
|
||||
expect(
|
||||
Formula.step(constant, 20, value => Formula.sqrt(value)).evaluate()
|
||||
).compare_tolerance(10));
|
||||
test("Inverts correctly with variable in input", () =>
|
||||
expect(
|
||||
Formula.step(variable, 20, value => Formula.sqrt(value)).invert(10)
|
||||
).compare_tolerance(10));
|
||||
});
|
||||
|
||||
describe("Evaluates correctly beyond start", () => {
|
||||
test("Evaluates correctly", () =>
|
||||
expect(
|
||||
Formula.step(variable, 8, value => Formula.add(value, 2)).evaluate()
|
||||
).compare_tolerance(12));
|
||||
test("Inverts correctly", () =>
|
||||
expect(
|
||||
Formula.step(variable, 8, value => Formula.add(value, 2)).invert(12)
|
||||
).compare_tolerance(10));
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue