forked from profectus/Profectus
Compare commits
9 commits
04a5e963ab
...
62794b81a1
Author | SHA1 | Date | |
---|---|---|---|
62794b81a1 | |||
|
b40d4bef32 | ||
1f22f506dd | |||
d3faec6a66 | |||
a39e65852d | |||
1e2b20a70f | |||
2e0e221010 | |||
4092cd6d56 | |||
cba79df80d |
7 changed files with 189 additions and 43 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import Node from "components/Node.vue";
|
||||||
import Spacer from "components/layout/Spacer.vue";
|
import Spacer from "components/layout/Spacer.vue";
|
||||||
import { jsx } from "features/feature";
|
import { jsx } from "features/feature";
|
||||||
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
|
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
|
||||||
|
@ -48,19 +49,35 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
links: tree.links,
|
links: tree.links,
|
||||||
display: jsx(() => (
|
display: jsx(() => (
|
||||||
<>
|
<>
|
||||||
{player.devSpeed === 0 ? <div>Game Paused</div> : null}
|
{player.devSpeed === 0 ? (
|
||||||
|
<div>
|
||||||
|
Game Paused
|
||||||
|
<Node id="paused" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
|
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
|
||||||
<div>Dev Speed: {format(player.devSpeed)}x</div>
|
<div>
|
||||||
|
Dev Speed: {format(player.devSpeed)}x
|
||||||
|
<Node id="devspeed" />
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{player.offlineTime != null && player.offlineTime !== 0 ? (
|
{player.offlineTime != null && player.offlineTime !== 0 ? (
|
||||||
<div>Offline Time: {formatTime(player.offlineTime)}</div>
|
<div>
|
||||||
|
Offline Time: {formatTime(player.offlineTime)}
|
||||||
|
<Node id="offline" />
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div>
|
||||||
{Decimal.lt(points.value, "1e1000") ? <span>You have </span> : null}
|
{Decimal.lt(points.value, "1e1000") ? <span>You have </span> : null}
|
||||||
<h2>{format(points.value)}</h2>
|
<h2>{format(points.value)}</h2>
|
||||||
{Decimal.lt(points.value, "1e1e6") ? <span> points</span> : null}
|
{Decimal.lt(points.value, "1e1e6") ? <span> points</span> : null}
|
||||||
</div>
|
</div>
|
||||||
{Decimal.gt(pointGain.value, 0) ? <div>({oomps.value})</div> : null}
|
{Decimal.gt(pointGain.value, 0) ? (
|
||||||
|
<div>
|
||||||
|
({oomps.value})
|
||||||
|
<Node id="oomps" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{render(tree)}
|
{render(tree)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -108,7 +108,7 @@ document.onkeydown = function (e) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
key = "ctrl+" + key;
|
key = "ctrl+" + key;
|
||||||
}
|
}
|
||||||
const hotkey = hotkeys[key];
|
const hotkey = hotkeys[key] ?? hotkeys[key.toLowerCase()];
|
||||||
if (hotkey && unref(hotkey.enabled)) {
|
if (hotkey && unref(hotkey.enabled)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
hotkey.onPress();
|
hotkey.onPress();
|
||||||
|
|
|
@ -224,7 +224,7 @@ export interface BaseTree {
|
||||||
id: string;
|
id: string;
|
||||||
/** The link objects for each of the branches of the tree. */
|
/** The link objects for each of the branches of the tree. */
|
||||||
links: Ref<Link[]>;
|
links: Ref<Link[]>;
|
||||||
/** Cause a reset on this node and propagate it through the tree according to {@link resetPropagation}. */
|
/** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */
|
||||||
reset: (node: GenericTreeNode) => void;
|
reset: (node: GenericTreeNode) => void;
|
||||||
/** A flag that is true while the reset is still propagating through the tree. */
|
/** A flag that is true while the reset is still propagating through the tree. */
|
||||||
isResetting: Ref<boolean>;
|
isResetting: Ref<boolean>;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { jsx } from "features/feature";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { formatSmall } from "util/bignum";
|
import Decimal, { formatSmall } from "util/bignum";
|
||||||
import type { WithRequired } from "util/common";
|
import type { RequiredKeys, WithRequired } from "util/common";
|
||||||
import type { Computable, ProcessedComputable } from "util/computed";
|
import type { Computable, ProcessedComputable } from "util/computed";
|
||||||
import { convertComputable } from "util/computed";
|
import { convertComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
|
@ -38,16 +38,11 @@ export interface Modifier {
|
||||||
description?: ProcessedComputable<CoercableComponent>;
|
description?: ProcessedComputable<CoercableComponent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Utility type that represents the output of all modifiers that represent a single operation. */
|
||||||
* Utility type used to narrow down a modifier type that will have a description and/or enabled property based on optional parameters, T and S (respectively).
|
export type OperationModifier<T> = WithRequired<
|
||||||
*/
|
Modifier,
|
||||||
export type ModifierFromOptionalParams<T, S> = undefined extends T
|
"invert" | "getFormula" | Extract<RequiredKeys<T>, keyof Modifier>
|
||||||
? undefined extends S
|
>;
|
||||||
? Omit<WithRequired<Modifier, "invert" | "getFormula">, "description" | "enabled">
|
|
||||||
: Omit<WithRequired<Modifier, "invert" | "enabled" | "getFormula">, "description">
|
|
||||||
: undefined extends S
|
|
||||||
? Omit<WithRequired<Modifier, "invert" | "description" | "getFormula">, "enabled">
|
|
||||||
: WithRequired<Modifier, "invert" | "enabled" | "description" | "getFormula">;
|
|
||||||
|
|
||||||
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
||||||
export interface AdditiveModifierOptions {
|
export interface AdditiveModifierOptions {
|
||||||
|
@ -65,9 +60,9 @@ export interface AdditiveModifierOptions {
|
||||||
* Create a modifier that adds some value to the input value.
|
* Create a modifier that adds some value to the input value.
|
||||||
* @param optionsFunc Additive modifier options.
|
* @param optionsFunc Additive modifier options.
|
||||||
*/
|
*/
|
||||||
export function createAdditiveModifier<T extends AdditiveModifierOptions>(
|
export function createAdditiveModifier<T extends AdditiveModifierOptions, S = OperationModifier<T>>(
|
||||||
optionsFunc: OptionsFunc<T>
|
optionsFunc: OptionsFunc<T>
|
||||||
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
) {
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
|
const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
|
||||||
feature,
|
feature,
|
||||||
|
@ -111,7 +106,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions>(
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
|
/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
|
||||||
|
@ -130,9 +125,10 @@ export interface MultiplicativeModifierOptions {
|
||||||
* Create a modifier that multiplies the input value by some value.
|
* Create a modifier that multiplies the input value by some value.
|
||||||
* @param optionsFunc Multiplicative modifier options.
|
* @param optionsFunc Multiplicative modifier options.
|
||||||
*/
|
*/
|
||||||
export function createMultiplicativeModifier<T extends MultiplicativeModifierOptions>(
|
export function createMultiplicativeModifier<
|
||||||
optionsFunc: OptionsFunc<T>
|
T extends MultiplicativeModifierOptions,
|
||||||
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
S = OperationModifier<T>
|
||||||
|
>(optionsFunc: OptionsFunc<T>) {
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
|
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
|
||||||
feature,
|
feature,
|
||||||
|
@ -175,7 +171,7 @@ export function createMultiplicativeModifier<T extends MultiplicativeModifierOpt
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
|
/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
|
||||||
|
@ -196,9 +192,10 @@ export interface ExponentialModifierOptions {
|
||||||
* Create a modifier that raises the input value to the power of some value.
|
* Create a modifier that raises the input value to the power of some value.
|
||||||
* @param optionsFunc Exponential modifier options.
|
* @param optionsFunc Exponential modifier options.
|
||||||
*/
|
*/
|
||||||
export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
export function createExponentialModifier<
|
||||||
optionsFunc: OptionsFunc<T>
|
T extends ExponentialModifierOptions,
|
||||||
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
S = OperationModifier<T>
|
||||||
|
>(optionsFunc: OptionsFunc<T>) {
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
|
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
|
||||||
optionsFunc.call(feature, feature);
|
optionsFunc.call(feature, feature);
|
||||||
|
@ -263,7 +260,7 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -274,11 +271,9 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
* @see {@link createModifierSection}.
|
* @see {@link createModifierSection}.
|
||||||
*/
|
*/
|
||||||
export function createSequentialModifier<
|
export function createSequentialModifier<
|
||||||
T extends Modifier[],
|
T extends Modifier,
|
||||||
S = T extends WithRequired<Modifier, "invert">[]
|
S = WithRequired<Modifier, Extract<RequiredKeys<T>, keyof Modifier>>
|
||||||
? WithRequired<Modifier, "description" | "invert">
|
>(modifiersFunc: () => T[]) {
|
||||||
: Omit<WithRequired<Modifier, "description">, "invert">
|
|
||||||
>(modifiersFunc: () => T): S {
|
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const modifiers = modifiersFunc();
|
const modifiers = modifiersFunc();
|
||||||
|
|
||||||
|
@ -296,10 +291,14 @@ export function createSequentialModifier<
|
||||||
: undefined,
|
: undefined,
|
||||||
getFormula: modifiers.every(m => m.getFormula != null)
|
getFormula: modifiers.every(m => m.getFormula != null)
|
||||||
? (gain: FormulaSource) =>
|
? (gain: FormulaSource) =>
|
||||||
modifiers
|
modifiers.reduce((acc, curr) => {
|
||||||
|
if (curr.enabled == null || curr.enabled === true) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return curr.getFormula!(acc);
|
||||||
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
.reduce((acc, curr) => Formula.if(acc, curr.enabled ?? true,
|
return Formula.if(acc, curr.enabled, acc => curr.getFormula!(acc));
|
||||||
acc => curr.getFormula!(acc), acc => acc), gain)
|
}, gain)
|
||||||
: undefined,
|
: undefined,
|
||||||
enabled: modifiers.some(m => m.enabled != null)
|
enabled: modifiers.some(m => m.enabled != null)
|
||||||
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||||
|
@ -317,7 +316,7 @@ export function createSequentialModifier<
|
||||||
))
|
))
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
}) as unknown as S;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures a modifier section via {@link createModifierSection}. */
|
/** An object that configures a modifier section via {@link createModifierSection}. */
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
export type RequiredKeys<T> = {
|
||||||
|
[K in keyof T]-?: NonNullable<unknown> extends Pick<T, K> ? never : K;
|
||||||
|
}[keyof T];
|
||||||
|
export type OptionalKeys<T> = {
|
||||||
|
[K in keyof T]-?: NonNullable<unknown> extends Pick<T, K> ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
export type OmitOptional<T> = Pick<T, RequiredKeys<T>>;
|
||||||
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
||||||
|
|
||||||
export type ArrayElements<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer S>
|
export type ArrayElements<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer S>
|
||||||
|
|
111
tests/features/tree.test.ts
Normal file
111
tests/features/tree.test.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
|
import { Ref, ref } from "vue";
|
||||||
|
import "../utils";
|
||||||
|
import {
|
||||||
|
createTree,
|
||||||
|
createTreeNode,
|
||||||
|
defaultResetPropagation,
|
||||||
|
invertedResetPropagation,
|
||||||
|
branchedResetPropagation
|
||||||
|
} from "features/trees/tree";
|
||||||
|
import { createReset, GenericReset } from "features/reset";
|
||||||
|
|
||||||
|
describe("Reset propagation", () => {
|
||||||
|
let shouldReset: Ref<boolean>, shouldNotReset: Ref<boolean>;
|
||||||
|
let goodReset: GenericReset, badReset: GenericReset;
|
||||||
|
beforeAll(() => {
|
||||||
|
shouldReset = ref(false);
|
||||||
|
shouldNotReset = ref(false);
|
||||||
|
goodReset = createReset(() => ({
|
||||||
|
thingsToReset: [],
|
||||||
|
onReset() {
|
||||||
|
shouldReset.value = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
badReset = createReset(() => ({
|
||||||
|
thingsToReset: [],
|
||||||
|
onReset() {
|
||||||
|
shouldNotReset.value = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
shouldReset.value = false;
|
||||||
|
shouldNotReset.value = false;
|
||||||
|
});
|
||||||
|
test("No resets", () => {
|
||||||
|
expect(() => {
|
||||||
|
const a = createTreeNode(() => ({}));
|
||||||
|
const b = createTreeNode(() => ({}));
|
||||||
|
const c = createTreeNode(() => ({}));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a], [b], [c]]
|
||||||
|
}));
|
||||||
|
tree.reset(a);
|
||||||
|
}).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Do not propagate resets", () => {
|
||||||
|
const a = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const b = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const c = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a], [b], [c]]
|
||||||
|
}));
|
||||||
|
tree.reset(b);
|
||||||
|
expect(shouldNotReset.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Default propagation", () => {
|
||||||
|
const a = createTreeNode(() => ({ reset: goodReset }));
|
||||||
|
const b = createTreeNode(() => ({}));
|
||||||
|
const c = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a], [b], [c]],
|
||||||
|
resetPropagation: defaultResetPropagation
|
||||||
|
}));
|
||||||
|
tree.reset(b);
|
||||||
|
expect(shouldReset.value).toBe(true);
|
||||||
|
expect(shouldNotReset.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Inverted propagation", () => {
|
||||||
|
const a = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const b = createTreeNode(() => ({}));
|
||||||
|
const c = createTreeNode(() => ({ reset: goodReset }));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a], [b], [c]],
|
||||||
|
resetPropagation: invertedResetPropagation
|
||||||
|
}));
|
||||||
|
tree.reset(b);
|
||||||
|
expect(shouldReset.value).toBe(true);
|
||||||
|
expect(shouldNotReset.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Branched propagation", () => {
|
||||||
|
const a = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const b = createTreeNode(() => ({}));
|
||||||
|
const c = createTreeNode(() => ({ reset: goodReset }));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a, b, c]],
|
||||||
|
resetPropagation: branchedResetPropagation,
|
||||||
|
branches: [{ startNode: b, endNode: c }]
|
||||||
|
}));
|
||||||
|
tree.reset(b);
|
||||||
|
expect(shouldReset.value).toBe(true);
|
||||||
|
expect(shouldNotReset.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Branched propagation not bi-directional", () => {
|
||||||
|
const a = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const b = createTreeNode(() => ({}));
|
||||||
|
const c = createTreeNode(() => ({ reset: badReset }));
|
||||||
|
const tree = createTree(() => ({
|
||||||
|
nodes: [[a, b, c]],
|
||||||
|
resetPropagation: branchedResetPropagation,
|
||||||
|
branches: [{ startNode: c, endNode: b }]
|
||||||
|
}));
|
||||||
|
tree.reset(b);
|
||||||
|
expect(shouldNotReset.value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -133,14 +133,14 @@ describe("Exponential Modifiers", () =>
|
||||||
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
|
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
|
||||||
|
|
||||||
describe("Sequential Modifiers", () => {
|
describe("Sequential Modifiers", () => {
|
||||||
function createModifier(
|
function createModifier<T extends Partial<ModifierConstructorOptions>>(
|
||||||
value: Computable<DecimalSource>,
|
value: Computable<DecimalSource>,
|
||||||
options: Partial<ModifierConstructorOptions> = {}
|
options?: T
|
||||||
): WithRequired<Modifier, "invert" | "getFormula"> {
|
) {
|
||||||
return createSequentialModifier(() => [
|
return createSequentialModifier(() => [
|
||||||
createAdditiveModifier(() => ({ ...options, addend: value })),
|
createAdditiveModifier(() => ({ ...(options ?? {}), addend: value })),
|
||||||
createMultiplicativeModifier(() => ({ ...options, multiplier: value })),
|
createMultiplicativeModifier(() => ({ ...(options ?? {}), multiplier: value })),
|
||||||
createExponentialModifier(() => ({ ...options, exponent: value }))
|
createExponentialModifier(() => ({ ...(options ?? {}), exponent: value }))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +199,17 @@ describe("Sequential Modifiers", () => {
|
||||||
// So long as one is true or undefined, enable should be true
|
// So long as one is true or undefined, enable should be true
|
||||||
expect(unref(modifier.enabled)).toBe(true);
|
expect(unref(modifier.enabled)).toBe(true);
|
||||||
});
|
});
|
||||||
|
test("respects enabled", () => {
|
||||||
|
const value = ref(10);
|
||||||
|
const enabled = ref(false);
|
||||||
|
const modifier = createSequentialModifier(() => [
|
||||||
|
createMultiplicativeModifier(() => ({ multiplier: 5, enabled }))
|
||||||
|
]);
|
||||||
|
const formula = modifier.getFormula(Formula.variable(value));
|
||||||
|
expect(formula.evaluate()).compare_tolerance(value.value);
|
||||||
|
enabled.value = true;
|
||||||
|
expect(formula.evaluate()).not.compare_tolerance(value.value);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("applies smallerIsBetter correctly", () => {
|
describe("applies smallerIsBetter correctly", () => {
|
||||||
|
|
Loading…
Reference in a new issue