mirror of
https://github.com/thepaperpilot/Planar-Pioneers.git
synced 2024-11-24 01:11:45 +00:00
Made calculateMaxAffordable, calculateCost, and cost requirements interface a bit cleaner
This commit is contained in:
parent
d7a2049ca2
commit
f8095a9694
6 changed files with 283 additions and 191 deletions
|
@ -52,8 +52,6 @@ export interface ChallengeOptions {
|
|||
reset?: GenericReset;
|
||||
/** The requirement(s) to complete this challenge. */
|
||||
requirements: Requirements;
|
||||
/** Whether or not completing this challenge should grant multiple completions if requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */
|
||||
maximize?: Computable<boolean>;
|
||||
/** The maximum number of times the challenge can be completed. */
|
||||
completionLimit?: Computable<DecimalSource>;
|
||||
/** Shows a marker on the corner of the feature. */
|
||||
|
@ -124,7 +122,6 @@ export type Challenge<T extends ChallengeOptions> = Replace<
|
|||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
||||
requirements: GetComputableType<T["requirements"]>;
|
||||
maximize: GetComputableType<T["maximize"]>;
|
||||
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
||||
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
|
@ -210,10 +207,7 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
}
|
||||
};
|
||||
challenge.canComplete = computed(() =>
|
||||
Decimal.max(
|
||||
maxRequirementsMet((challenge as GenericChallenge).requirements),
|
||||
unref((challenge as GenericChallenge).maximize) ? Decimal.dInf : 1
|
||||
)
|
||||
maxRequirementsMet((challenge as GenericChallenge).requirements)
|
||||
);
|
||||
challenge.complete = function (remainInChallenge?: boolean) {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
|
@ -254,7 +248,6 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
|
||||
processComputable(challenge as T, "canStart");
|
||||
setDefault(challenge, "canStart", true);
|
||||
processComputable(challenge as T, "maximize");
|
||||
processComputable(challenge as T, "completionLimit");
|
||||
setDefault(challenge, "completionLimit", 1);
|
||||
processComputable(challenge as T, "mark");
|
||||
|
|
|
@ -67,8 +67,6 @@ export interface RepeatableOptions {
|
|||
mark?: Computable<boolean | string>;
|
||||
/** Toggles a smaller design for the feature. */
|
||||
small?: Computable<boolean>;
|
||||
/** Whether or not clicking this repeatable should attempt to maximize amount based on the requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */
|
||||
maximize?: Computable<boolean>;
|
||||
/** The display to use for this repeatable. */
|
||||
display?: Computable<RepeatableDisplay>;
|
||||
}
|
||||
|
@ -87,7 +85,6 @@ export interface BaseRepeatable {
|
|||
canClick: ProcessedComputable<boolean>;
|
||||
/**
|
||||
* How much amount can be increased by, or 1 if unclickable.
|
||||
* Capped at 1 if {@link RepeatableOptions.maximize} is false.
|
||||
**/
|
||||
amountToIncrease: Ref<DecimalSource>;
|
||||
/** A function that gets called when this repeatable is clicked. */
|
||||
|
@ -111,7 +108,6 @@ export type Repeatable<T extends RepeatableOptions> = Replace<
|
|||
style: GetComputableType<T["style"]>;
|
||||
mark: GetComputableType<T["mark"]>;
|
||||
small: GetComputableType<T["small"]>;
|
||||
maximize: GetComputableType<T["maximize"]>;
|
||||
display: Ref<CoercableComponent>;
|
||||
}
|
||||
>;
|
||||
|
@ -195,9 +191,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
|||
return currClasses;
|
||||
});
|
||||
repeatable.amountToIncrease = computed(() =>
|
||||
unref((repeatable as GenericRepeatable).maximize)
|
||||
? maxRequirementsMet(repeatable.requirements)
|
||||
: 1
|
||||
Decimal.clampMin(maxRequirementsMet(repeatable.requirements), 1)
|
||||
);
|
||||
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
||||
const onClick = repeatable.onClick;
|
||||
|
@ -274,7 +268,6 @@ export function createRepeatable<T extends RepeatableOptions>(
|
|||
processComputable(repeatable as T, "style");
|
||||
processComputable(repeatable as T, "mark");
|
||||
processComputable(repeatable as T, "small");
|
||||
processComputable(repeatable as T, "maximize");
|
||||
|
||||
for (const decorator of decorators) {
|
||||
decorator.postConstruct?.(repeatable);
|
||||
|
|
|
@ -1400,35 +1400,40 @@ export function printFormula(formula: FormulaSource): string {
|
|||
}
|
||||
|
||||
/**
|
||||
* Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@link spendResources} is changed to false, the calculation will be much faster with higher numbers.
|
||||
* Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@link cumulativeCost} is changed to false, the calculation will be much faster with higher numbers.
|
||||
* @param formula The formula to use for calculating buy max from
|
||||
* @param resource The resource used when purchasing (is only read from)
|
||||
* @param spendResources Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards fewer purchases
|
||||
* @param summedPurchases How many of the most expensive purchases should be manually summed for better accuracy. If unspecified uses 10 when spending resources and 0 when not
|
||||
* @param cumulativeCost Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards fewer purchases
|
||||
* @param directSum How many of the most expensive purchases should be manually summed for better accuracy. If unspecified uses 10 when spending resources and 0 when not
|
||||
* @param maxBulkAmount Cap on how many can be purchased at once. If equal to 1 or lte to {@link directSum} then the formula does not need to be invertible. Defaults to Infinity.
|
||||
*/
|
||||
export function calculateMaxAffordable(
|
||||
formula: InvertibleFormula,
|
||||
formula: GenericFormula,
|
||||
resource: Resource,
|
||||
spendResources?: true,
|
||||
summedPurchases?: number
|
||||
): ComputedRef<DecimalSource>;
|
||||
export function calculateMaxAffordable(
|
||||
formula: InvertibleIntegralFormula,
|
||||
resource: Resource,
|
||||
spendResources: Computable<boolean>,
|
||||
summedPurchases?: number
|
||||
): ComputedRef<DecimalSource>;
|
||||
export function calculateMaxAffordable(
|
||||
formula: InvertibleFormula,
|
||||
resource: Resource,
|
||||
spendResources: Computable<boolean> = true,
|
||||
summedPurchases?: number
|
||||
cumulativeCost: Computable<boolean> = true,
|
||||
directSum?: Computable<number>,
|
||||
maxBulkAmount: Computable<DecimalSource> = Decimal.dInf
|
||||
) {
|
||||
const computedSpendResources = convertComputable(spendResources);
|
||||
const computedCumulativeCost = convertComputable(cumulativeCost);
|
||||
const computedDirectSum = convertComputable(directSum);
|
||||
const computedmaxBulkAmount = convertComputable(maxBulkAmount);
|
||||
return computed(() => {
|
||||
let affordable;
|
||||
if (unref(computedSpendResources)) {
|
||||
if (!formula.isIntegrable() || !formula.isIntegralInvertible()) {
|
||||
const maxBulkAmount = unref(computedmaxBulkAmount);
|
||||
if (Decimal.eq(maxBulkAmount, 1)) {
|
||||
return Decimal.gte(resource.value, formula.evaluate()) ? Decimal.dOne : Decimal.dZero;
|
||||
}
|
||||
|
||||
const cumulativeCost = unref(computedCumulativeCost);
|
||||
const directSum = unref(computedDirectSum) ?? (cumulativeCost ? 10 : 0);
|
||||
let affordable: DecimalSource = 0;
|
||||
if (Decimal.gt(maxBulkAmount, directSum)) {
|
||||
if (!formula.isInvertible()) {
|
||||
throw new Error(
|
||||
"Cannot calculate max affordable of non-invertible formula with more maxBulkAmount than directSum"
|
||||
);
|
||||
}
|
||||
if (cumulativeCost) {
|
||||
if (!formula.isIntegralInvertible()) {
|
||||
throw new Error(
|
||||
"Cannot calculate max affordable of formula with non-invertible integral"
|
||||
);
|
||||
|
@ -1436,22 +1441,25 @@ export function calculateMaxAffordable(
|
|||
affordable = Decimal.floor(
|
||||
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
||||
).sub(unref(formula.innermostVariable) ?? 0);
|
||||
if (summedPurchases == null) {
|
||||
summedPurchases = 10;
|
||||
}
|
||||
} else {
|
||||
if (!formula.isInvertible()) {
|
||||
throw new Error("Cannot calculate max affordable of non-invertible formula");
|
||||
}
|
||||
affordable = Decimal.floor(formula.invert(resource.value));
|
||||
if (summedPurchases == null) {
|
||||
summedPurchases = 0;
|
||||
}
|
||||
}
|
||||
if (summedPurchases > 0 && Decimal.lt(calculateCost(formula, affordable, true, 0), 1e308)) {
|
||||
affordable = affordable.sub(summedPurchases).clampMin(0);
|
||||
let summedCost = calculateCost(formula, affordable, true, 0);
|
||||
while (true) {
|
||||
affordable = Decimal.clampMax(affordable, maxBulkAmount);
|
||||
if (directSum > 0) {
|
||||
affordable = Decimal.sub(affordable, directSum).clampMin(0);
|
||||
let summedCost;
|
||||
if (cumulativeCost) {
|
||||
summedCost = calculateCost(formula as InvertibleFormula, affordable, true, 0);
|
||||
} else {
|
||||
summedCost = formula.evaluate(
|
||||
Decimal.add(unref(formula.innermostVariable) ?? 0, affordable)
|
||||
);
|
||||
}
|
||||
while (
|
||||
Decimal.lt(affordable, maxBulkAmount) &&
|
||||
Decimal.lt(affordable, Number.MAX_SAFE_INTEGER)
|
||||
) {
|
||||
const nextCost = formula.evaluate(
|
||||
affordable.add(unref(formula.innermostVariable) ?? 0)
|
||||
);
|
||||
|
@ -1468,67 +1476,76 @@ export function calculateMaxAffordable(
|
|||
}
|
||||
|
||||
/**
|
||||
* Utility for calculating the cost of a formula for a given amount of purchases. If {@link spendResources} is changed to false, the calculation will be much faster with higher numbers.
|
||||
* Utility for calculating the cost of a formula for a given amount of purchases. If {@link cumulativeCost} is changed to false, the calculation will be much faster with higher numbers.
|
||||
* @param formula The formula to use for calculating buy max from
|
||||
* @param amountToBuy The amount of purchases to calculate the cost for
|
||||
* @param spendResources Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards higher cost
|
||||
* @param summedPurchases How many purchases to manually sum for improved accuracy. If not specified, defaults to 10 when spending resources and 0 when not
|
||||
* @param cumulativeCost Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards higher cost
|
||||
* @param directSum How many purchases to manually sum for improved accuracy. If not specified, defaults to 10 when cost is cumulative and 0 when not
|
||||
*/
|
||||
export function calculateCost(
|
||||
formula: InvertibleFormula,
|
||||
amountToBuy: DecimalSource,
|
||||
spendResources?: true,
|
||||
summedPurchases?: number
|
||||
cumulativeCost?: true,
|
||||
directSum?: number
|
||||
): DecimalSource;
|
||||
export function calculateCost(
|
||||
formula: InvertibleIntegralFormula,
|
||||
amountToBuy: DecimalSource,
|
||||
spendResources: boolean,
|
||||
summedPurchases?: number
|
||||
cumulativeCost: boolean,
|
||||
directSum?: number
|
||||
): DecimalSource;
|
||||
export function calculateCost(
|
||||
formula: InvertibleFormula,
|
||||
amountToBuy: DecimalSource,
|
||||
spendResources = true,
|
||||
summedPurchases?: number
|
||||
cumulativeCost = true,
|
||||
directSum?: number
|
||||
) {
|
||||
// Single purchase
|
||||
if (Decimal.eq(amountToBuy, 1)) {
|
||||
return formula.evaluate();
|
||||
}
|
||||
|
||||
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);
|
||||
directSum ??= cumulativeCost ? 10 : 0;
|
||||
newValue = newValue.sub(directSum).clampMin(origValue);
|
||||
let cost: DecimalSource = 0;
|
||||
if (spendResources) {
|
||||
if (Decimal.gt(amountToBuy, summedPurchases)) {
|
||||
|
||||
// Indirect sum
|
||||
if (Decimal.gt(amountToBuy, directSum)) {
|
||||
if (!formula.isInvertible()) {
|
||||
throw new Error("Cannot calculate cost with indirect sum of non-invertible formula");
|
||||
}
|
||||
if (cumulativeCost) {
|
||||
if (!formula.isIntegrable()) {
|
||||
throw new Error(
|
||||
"Cannot calculate cost with spending resources of non-integrable formula"
|
||||
"Cannot calculate cost with cumulative cost of non-integrable formula"
|
||||
);
|
||||
}
|
||||
cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
|
||||
}
|
||||
if (targetValue.gt(1e308)) {
|
||||
// Too large of a number for summedPurchases to make a difference,
|
||||
// Too large of a number for directSum to make a difference,
|
||||
// just get the cost and multiply by summed purchases
|
||||
return Decimal.add(
|
||||
cost,
|
||||
Decimal.sub(targetValue, newValue).times(formula.evaluate(newValue))
|
||||
);
|
||||
}
|
||||
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
||||
cost = Decimal.add(cost, formula.evaluate(i));
|
||||
}
|
||||
} else {
|
||||
cost = formula.evaluate(newValue);
|
||||
newValue = newValue.add(1);
|
||||
if (targetValue.gt(1e308)) {
|
||||
// Too large of a number for summedPurchases to make a difference,
|
||||
// Too large of a number for directSum to make a difference,
|
||||
// just get the cost and multiply by summed purchases
|
||||
return Decimal.sub(targetValue, newValue).add(1).times(cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Direct sum
|
||||
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
||||
cost = Decimal.add(cost, formula.evaluate(i));
|
||||
}
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { createLazyProxy } from "util/proxies";
|
|||
import { joinJSX, renderJSX } from "util/vue";
|
||||
import { computed, unref } from "vue";
|
||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
||||
import type { GenericFormula, InvertibleFormula } from "./formulas/types";
|
||||
import type { GenericFormula } from "./formulas/types";
|
||||
import { DefaultValue, Persistent } from "./persistence";
|
||||
|
||||
/**
|
||||
|
@ -86,7 +86,15 @@ export interface CostRequirementOptions {
|
|||
* When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions.
|
||||
* @see {Formula}
|
||||
*/
|
||||
spendResources?: Computable<boolean>;
|
||||
cumulativeCost?: Computable<boolean>;
|
||||
/**
|
||||
* Upper limit on levels that can be performed at once. Defaults to 1.
|
||||
*/
|
||||
maxBulkAmount?: Computable<DecimalSource>;
|
||||
/**
|
||||
* When calculating requirement for multiple levels, how many should be directly summed for increase accuracy. High numbers can cause lag. Defaults to 10 if cumulative cost, 0 otherwise.
|
||||
*/
|
||||
directSum?: Computable<number>;
|
||||
/**
|
||||
* Pass-through to {@link Requirement.pay}. May be required for maximizing support.
|
||||
* @see {@link cost} for restrictions on maximizing support.
|
||||
|
@ -100,7 +108,7 @@ export type CostRequirement = Replace<
|
|||
cost: ProcessedComputable<DecimalSource> | GenericFormula;
|
||||
visibility: ProcessedComputable<Visibility.Visible | Visibility.None | boolean>;
|
||||
requiresPay: ProcessedComputable<boolean>;
|
||||
spendResources: ProcessedComputable<boolean>;
|
||||
cumulativeCost: ProcessedComputable<boolean>;
|
||||
canMaximize: ProcessedComputable<boolean>;
|
||||
}
|
||||
>;
|
||||
|
@ -126,7 +134,12 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
|||
{displayResource(
|
||||
req.resource,
|
||||
req.cost instanceof Formula
|
||||
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||
? calculateCost(
|
||||
req.cost,
|
||||
amount ?? 1,
|
||||
unref(req.cumulativeCost) as boolean,
|
||||
unref(req.directSum) as number
|
||||
)
|
||||
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||
)}{" "}
|
||||
{req.resource.displayName}
|
||||
|
@ -138,7 +151,12 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
|||
{displayResource(
|
||||
req.resource,
|
||||
req.cost instanceof Formula
|
||||
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||
? calculateCost(
|
||||
req.cost,
|
||||
amount ?? 1,
|
||||
unref(req.cumulativeCost) as boolean,
|
||||
unref(req.directSum) as number
|
||||
)
|
||||
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||
)}{" "}
|
||||
{req.resource.displayName}
|
||||
|
@ -150,55 +168,63 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
|||
processComputable(req as T, "cost");
|
||||
processComputable(req as T, "requiresPay");
|
||||
setDefault(req, "requiresPay", true);
|
||||
processComputable(req as T, "spendResources");
|
||||
setDefault(req, "spendResources", true);
|
||||
processComputable(req as T, "cumulativeCost");
|
||||
setDefault(req, "cumulativeCost", true);
|
||||
processComputable(req as T, "maxBulkAmount");
|
||||
setDefault(req, "maxBulkAmount", 1);
|
||||
processComputable(req as T, "directSum");
|
||||
setDefault(req, "pay", function (amount?: DecimalSource) {
|
||||
const cost =
|
||||
req.cost instanceof Formula
|
||||
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||
? calculateCost(
|
||||
req.cost,
|
||||
amount ?? 1,
|
||||
unref(req.cumulativeCost) as boolean,
|
||||
unref(req.directSum) as number
|
||||
)
|
||||
: unref(req.cost as ProcessedComputable<DecimalSource>);
|
||||
req.resource.value = Decimal.sub(req.resource.value, cost).max(0);
|
||||
});
|
||||
|
||||
req.canMaximize = computed(
|
||||
() =>
|
||||
req.cost instanceof Formula &&
|
||||
req.cost.isInvertible() &&
|
||||
(unref(req.spendResources) === false || req.cost.isIntegrable())
|
||||
);
|
||||
req.canMaximize = computed(() => {
|
||||
if (!(req.cost instanceof Formula)) {
|
||||
return false;
|
||||
}
|
||||
const maxBulkAmount = unref(req.maxBulkAmount as ProcessedComputable<DecimalSource>);
|
||||
if (Decimal.lte(maxBulkAmount, 1)) {
|
||||
return false;
|
||||
}
|
||||
const cumulativeCost = unref(req.cumulativeCost as ProcessedComputable<boolean>);
|
||||
const directSum =
|
||||
unref(req.directSum as ProcessedComputable<number>) ?? (cumulativeCost ? 10 : 0);
|
||||
if (Decimal.lte(maxBulkAmount, directSum)) {
|
||||
return true;
|
||||
}
|
||||
if (!req.cost.isInvertible()) {
|
||||
return false;
|
||||
}
|
||||
if (cumulativeCost === true && !req.cost.isIntegrable()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (req.cost instanceof Formula && req.cost.isInvertible()) {
|
||||
const maxAffordable = calculateMaxAffordable(
|
||||
if (req.cost instanceof Formula) {
|
||||
req.requirementMet = calculateMaxAffordable(
|
||||
req.cost,
|
||||
req.resource,
|
||||
unref(req.spendResources) as boolean
|
||||
req.cumulativeCost ?? true,
|
||||
req.directSum,
|
||||
req.maxBulkAmount
|
||||
);
|
||||
req.requirementMet = computed(() => {
|
||||
if (unref(req.canMaximize)) {
|
||||
return maxAffordable.value;
|
||||
} else {
|
||||
if (req.cost instanceof Formula) {
|
||||
return Decimal.gte(req.resource.value, req.cost.evaluate());
|
||||
} else {
|
||||
return Decimal.gte(
|
||||
req.requirementMet = computed(() =>
|
||||
Decimal.gte(
|
||||
req.resource.value,
|
||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
req.requirementMet = computed(() => {
|
||||
if (req.cost instanceof Formula) {
|
||||
return Decimal.gte(req.resource.value, req.cost.evaluate());
|
||||
} else {
|
||||
return Decimal.gte(
|
||||
req.resource.value,
|
||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return req as CostRequirement;
|
||||
});
|
||||
|
@ -328,7 +354,7 @@ export function payByDivision(this: CostRequirement, amount?: DecimalSource) {
|
|||
? calculateCost(
|
||||
this.cost,
|
||||
amount ?? 1,
|
||||
unref(this.spendResources as ProcessedComputable<boolean> | undefined) ?? true
|
||||
unref(this.cumulativeCost as ProcessedComputable<boolean> | undefined) ?? true
|
||||
)
|
||||
: unref(this.cost as ProcessedComputable<DecimalSource>);
|
||||
this.resource.value = Decimal.div(this.resource.value, cost);
|
||||
|
|
|
@ -1073,13 +1073,20 @@ describe("Buy Max", () => {
|
|||
beforeAll(() => {
|
||||
resource = createResource(ref(100000));
|
||||
});
|
||||
describe("Without spending", () => {
|
||||
test("Throws on formula with non-invertible integral", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
/* @ts-ignore */
|
||||
const maxAffordable = calculateMaxAffordable(Formula.neg(10), resource, false);
|
||||
describe("Without cumulative cost", () => {
|
||||
test("Throws on calculating max affordable of non-invertible formula", () => {
|
||||
const purchases = ref(1);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.abs(variable);
|
||||
const maxAffordable = calculateMaxAffordable(formula, resource, false);
|
||||
expect(() => maxAffordable.value).toThrow();
|
||||
});
|
||||
test("Throws on calculating cost of non-invertible formula", () => {
|
||||
const purchases = ref(1);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.abs(variable);
|
||||
expect(() => calculateCost(formula, 5, false, 0)).toThrow();
|
||||
});
|
||||
test("Calculates max affordable and cost correctly", () => {
|
||||
const variable = Formula.variable(0);
|
||||
const formula = Formula.pow(1.05, variable).times(100);
|
||||
|
@ -1089,7 +1096,7 @@ describe("Buy Max", () => {
|
|||
Decimal.pow(1.05, 141).times(100)
|
||||
);
|
||||
});
|
||||
test("Calculates max affordable and cost correctly with summing last purchases", () => {
|
||||
test("Calculates max affordable and cost correctly with direct sum", () => {
|
||||
const variable = Formula.variable(0);
|
||||
const formula = Formula.pow(1.05, variable).times(100);
|
||||
const maxAffordable = calculateMaxAffordable(formula, resource, false, 4);
|
||||
|
@ -1102,11 +1109,20 @@ describe("Buy Max", () => {
|
|||
expect(calculatedCost).compare_tolerance(actualCost);
|
||||
});
|
||||
});
|
||||
describe("With spending", () => {
|
||||
describe("With cumulative cost", () => {
|
||||
test("Throws on calculating max affordable of non-invertible formula", () => {
|
||||
const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource);
|
||||
const purchases = ref(1);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.abs(variable);
|
||||
const maxAffordable = calculateMaxAffordable(formula, resource, true);
|
||||
expect(() => maxAffordable.value).toThrow();
|
||||
});
|
||||
test("Throws on calculating cost of non-invertible formula", () => {
|
||||
const purchases = ref(1);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.abs(variable);
|
||||
expect(() => calculateCost(formula, 5, true, 0)).toThrow();
|
||||
});
|
||||
test("Estimates max affordable and cost correctly with 0 purchases", () => {
|
||||
const purchases = ref(0);
|
||||
const variable = Formula.variable(purchases);
|
||||
|
@ -1163,7 +1179,7 @@ describe("Buy Max", () => {
|
|||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||
).toBeLessThan(0.1);
|
||||
});
|
||||
test("Estimates max affordable and cost more accurately with summing last purchases", () => {
|
||||
test("Estimates max affordable and cost more accurately with direct sum", () => {
|
||||
const purchases = ref(1);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.pow(1.05, variable).times(100);
|
||||
|
@ -1190,7 +1206,7 @@ describe("Buy Max", () => {
|
|||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||
).toBeLessThan(0.02);
|
||||
});
|
||||
test("Handles summing purchases when making few purchases", () => {
|
||||
test("Handles direct sum when making few purchases", () => {
|
||||
const purchases = ref(90);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = Formula.pow(1.05, variable).times(100);
|
||||
|
@ -1218,7 +1234,7 @@ describe("Buy Max", () => {
|
|||
// Since we're summing all the purchases this should be equivalent
|
||||
expect(calculatedCost).compare_tolerance(actualCost);
|
||||
});
|
||||
test("Handles summing purchases when making very few purchases", () => {
|
||||
test("Handles direct sum when making very few purchases", () => {
|
||||
const purchases = ref(0);
|
||||
const variable = Formula.variable(purchases);
|
||||
const formula = variable.add(1);
|
||||
|
@ -1236,7 +1252,7 @@ describe("Buy Max", () => {
|
|||
// Since we're summing all the purchases this should be equivalent
|
||||
expect(calculatedCost).compare_tolerance(actualCost);
|
||||
});
|
||||
test("Handles summing purchases when over e308 purchases", () => {
|
||||
test("Handles direct sum when over e308 purchases", () => {
|
||||
resource.value = "1ee308";
|
||||
const purchases = ref(0);
|
||||
const variable = Formula.variable(purchases);
|
||||
|
@ -1247,7 +1263,7 @@ describe("Buy Max", () => {
|
|||
expect(Decimal.isFinite(calculatedCost)).toBe(true);
|
||||
resource.value = 100000;
|
||||
});
|
||||
test("Handles summing purchases of non-integrable formula", () => {
|
||||
test("Handles direct sum of non-integrable formula", () => {
|
||||
const purchases = ref(0);
|
||||
const formula = Formula.variable(purchases).abs();
|
||||
expect(() => calculateCost(formula, 10)).not.toThrow();
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
Requirement,
|
||||
requirementsMet
|
||||
} from "game/requirements";
|
||||
import Decimal from "util/bignum";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { isRef, ref, unref } from "vue";
|
||||
import "../utils";
|
||||
|
@ -26,8 +27,7 @@ describe("Creating cost requirement", () => {
|
|||
beforeAll(() => {
|
||||
requirement = createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: 10,
|
||||
spendResources: false
|
||||
cost: 10
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -44,7 +44,7 @@ describe("Creating cost requirement", () => {
|
|||
});
|
||||
test("is visible", () => expect(requirement.visibility).toBe(Visibility.Visible));
|
||||
test("requires pay", () => expect(requirement.requiresPay).toBe(true));
|
||||
test("does not spend resources", () => expect(requirement.spendResources).toBe(false));
|
||||
test("does not spend resources", () => expect(requirement.cumulativeCost).toBe(true));
|
||||
test("cannot maximize", () => expect(unref(requirement.canMaximize)).toBe(false));
|
||||
});
|
||||
|
||||
|
@ -56,8 +56,9 @@ describe("Creating cost requirement", () => {
|
|||
cost: Formula.variable(resource).times(10),
|
||||
visibility: Visibility.None,
|
||||
requiresPay: false,
|
||||
maximize: true,
|
||||
spendResources: true,
|
||||
cumulativeCost: false,
|
||||
maxBulkAmount: Decimal.dInf,
|
||||
directSum: 5,
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
pay() {}
|
||||
}));
|
||||
|
@ -69,15 +70,18 @@ describe("Creating cost requirement", () => {
|
|||
requirement.pay.length === 1);
|
||||
test("is not visible", () => expect(requirement.visibility).toBe(Visibility.None));
|
||||
test("does not require pay", () => expect(requirement.requiresPay).toBe(false));
|
||||
test("spends resources", () => expect(requirement.spendResources).toBe(true));
|
||||
test("spends resources", () => expect(requirement.cumulativeCost).toBe(false));
|
||||
test("can maximize", () => expect(unref(requirement.canMaximize)).toBe(true));
|
||||
test("maxBulkAmount is set", () =>
|
||||
expect(unref(requirement.maxBulkAmount)).compare_tolerance(Decimal.dInf));
|
||||
test("directSum is set", () => expect(unref(requirement.directSum)).toBe(5));
|
||||
});
|
||||
|
||||
test("Requirement met when meeting the cost", () => {
|
||||
const requirement = createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: 10,
|
||||
spendResources: false
|
||||
cumulativeCost: false
|
||||
}));
|
||||
expect(unref(requirement.requirementMet)).toBe(true);
|
||||
});
|
||||
|
@ -86,13 +90,23 @@ describe("Creating cost requirement", () => {
|
|||
const requirement = createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: 100,
|
||||
spendResources: false
|
||||
cumulativeCost: false
|
||||
}));
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
});
|
||||
|
||||
describe("canMaximize works correctly", () => {
|
||||
test("Cost function cannot maximize", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: () => 10,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(false));
|
||||
test("Integrable formula cannot maximize if maxBulkAmount is left at 1", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
|
@ -101,82 +115,114 @@ describe("Creating cost requirement", () => {
|
|||
})).canMaximize
|
||||
)
|
||||
).toBe(false));
|
||||
test("Non-invertible formula cannot maximize", () =>
|
||||
test("Non-invertible formula cannot maximize when max bulk amount is above direct sum", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).abs()
|
||||
cost: Formula.variable(resource).abs(),
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(false));
|
||||
test("Invertible formula can maximize if spendResources is false", () =>
|
||||
test("Non-invertible formula can maximize when max bulk amount is lte direct sum", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).abs(),
|
||||
maxBulkAmount: 20,
|
||||
directSum: 20
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(true));
|
||||
test("Invertible formula can maximize if cumulativeCost is false", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).lambertw(),
|
||||
spendResources: false
|
||||
cumulativeCost: false,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(true));
|
||||
test("Invertible formula cannot maximize if spendResources is true", () =>
|
||||
test("Invertible formula cannot maximize if cumulativeCost is true", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).lambertw(),
|
||||
spendResources: true
|
||||
cumulativeCost: true,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(false));
|
||||
test("Integrable formula can maximize if spendResources is false", () =>
|
||||
test("Integrable formula can maximize if cumulativeCost is false", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).pow(2),
|
||||
spendResources: false
|
||||
cumulativeCost: false,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(true));
|
||||
test("Integrable formula can maximize if spendResources is true", () =>
|
||||
test("Integrable formula can maximize if cumulativeCost is true", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).pow(2),
|
||||
spendResources: true
|
||||
cumulativeCost: true,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
})).canMaximize
|
||||
)
|
||||
).toBe(true));
|
||||
});
|
||||
|
||||
test("Requirements met capped by maxBulkAmount", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).times(0),
|
||||
maxBulkAmount: 10
|
||||
})).requirementMet
|
||||
)
|
||||
).compare_tolerance(10));
|
||||
|
||||
test("Direct sum respected", () =>
|
||||
expect(
|
||||
unref(
|
||||
createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: Formula.variable(resource).times(0),
|
||||
maxBulkAmount: 10
|
||||
})).requirementMet
|
||||
)
|
||||
).compare_tolerance(10));
|
||||
});
|
||||
|
||||
describe("Creating visibility requirement", () => {
|
||||
test("Requirement met when visible", () => {
|
||||
const requirement = createVisibilityRequirement({ visibility: Visibility.Visible });
|
||||
test("Creating visibility requirement", () => {
|
||||
const visibility = ref<Visibility.None | Visibility.Visible | boolean>(Visibility.Visible);
|
||||
const requirement = createVisibilityRequirement({ visibility });
|
||||
expect(unref(requirement.requirementMet)).toBe(true);
|
||||
});
|
||||
|
||||
test("Requirement not met when not visible", () => {
|
||||
let requirement = createVisibilityRequirement({ visibility: Visibility.None });
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
requirement = createVisibilityRequirement({ visibility: false });
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Creating boolean requirement", () => {
|
||||
test("Requirement met when true", () => {
|
||||
const requirement = createBooleanRequirement(ref(true));
|
||||
visibility.value = true;
|
||||
expect(unref(requirement.requirementMet)).toBe(true);
|
||||
});
|
||||
|
||||
test("Requirement not met when false", () => {
|
||||
const requirement = createBooleanRequirement(ref(false));
|
||||
visibility.value = Visibility.None;
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
visibility.value = false;
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
});
|
||||
|
||||
test("Creating boolean requirement", () => {
|
||||
const req = ref(true);
|
||||
const requirement = createBooleanRequirement(req);
|
||||
expect(unref(requirement.requirementMet)).toBe(true);
|
||||
req.value = false;
|
||||
expect(unref(requirement.requirementMet)).toBe(false);
|
||||
});
|
||||
|
||||
describe("Checking all requirements met", () => {
|
||||
|
@ -208,7 +254,7 @@ describe("Checking maximum levels of requirements met", () => {
|
|||
createCostRequirement(() => ({
|
||||
resource: createResource(ref(10)),
|
||||
cost: Formula.variable(0),
|
||||
spendResources: false
|
||||
cumulativeCost: false
|
||||
}))
|
||||
];
|
||||
expect(maxRequirementsMet(requirements)).compare_tolerance(0);
|
||||
|
@ -220,7 +266,8 @@ describe("Checking maximum levels of requirements met", () => {
|
|||
createCostRequirement(() => ({
|
||||
resource: createResource(ref(10)),
|
||||
cost: Formula.variable(0),
|
||||
spendResources: false
|
||||
cumulativeCost: false,
|
||||
maxBulkAmount: Decimal.dInf
|
||||
}))
|
||||
];
|
||||
expect(maxRequirementsMet(requirements)).compare_tolerance(10);
|
||||
|
@ -233,12 +280,12 @@ test("Paying requirements", () => {
|
|||
resource,
|
||||
cost: 10,
|
||||
requiresPay: false,
|
||||
spendResources: false
|
||||
cumulativeCost: false
|
||||
}));
|
||||
const payment = createCostRequirement(() => ({
|
||||
resource,
|
||||
cost: 10,
|
||||
spendResources: false
|
||||
cumulativeCost: false
|
||||
}));
|
||||
payRequirements([noPayment, payment]);
|
||||
expect(resource.value).compare_tolerance(90);
|
||||
|
|
Loading…
Reference in a new issue