Made calculateMaxAffordable, calculateCost, and cost requirements interface a bit cleaner #17
18 changed files with 751 additions and 374 deletions
39
src/App.vue
39
src/App.vue
|
@ -1,18 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="modal-root" :style="theme" />
|
<div v-if="appErrors.length > 0" class="error-container" :style="theme"><Error :errors="appErrors" /></div>
|
||||||
<div class="app" :style="theme" :class="{ useHeader }">
|
<template v-else>
|
||||||
<Nav v-if="useHeader" />
|
<div id="modal-root" :style="theme" />
|
||||||
<Game />
|
<div class="app" :style="theme" :class="{ useHeader }">
|
||||||
<TPS v-if="unref(showTPS)" />
|
<Nav v-if="useHeader" />
|
||||||
<GameOverScreen />
|
<Game />
|
||||||
<NaNScreen />
|
<TPS v-if="unref(showTPS)" />
|
||||||
<component :is="gameComponent" />
|
<GameOverScreen />
|
||||||
</div>
|
<NaNScreen />
|
||||||
|
<component :is="gameComponent" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
|
import "@fontsource/roboto-mono";
|
||||||
|
import Error from "components/Error.vue";
|
||||||
import { jsx } from "features/feature";
|
import { jsx } from "features/feature";
|
||||||
|
import state from "game/state";
|
||||||
import { coerceComponent, render } from "util/vue";
|
import { coerceComponent, render } from "util/vue";
|
||||||
|
import { CSSProperties, watch } from "vue";
|
||||||
import { computed, toRef, unref } from "vue";
|
import { computed, toRef, unref } from "vue";
|
||||||
import Game from "./components/Game.vue";
|
import Game from "./components/Game.vue";
|
||||||
import GameOverScreen from "./components/GameOverScreen.vue";
|
import GameOverScreen from "./components/GameOverScreen.vue";
|
||||||
|
@ -23,12 +30,11 @@ import projInfo from "./data/projInfo.json";
|
||||||
import themes from "./data/themes";
|
import themes from "./data/themes";
|
||||||
import settings, { gameComponents } from "./game/settings";
|
import settings, { gameComponents } from "./game/settings";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
import "@fontsource/roboto-mono";
|
|
||||||
import type { CSSProperties } from "vue";
|
|
||||||
|
|
||||||
const useHeader = projInfo.useHeader;
|
const useHeader = projInfo.useHeader;
|
||||||
const theme = computed(() => themes[settings.theme].variables as CSSProperties);
|
const theme = computed(() => themes[settings.theme].variables as CSSProperties);
|
||||||
const showTPS = toRef(settings, "showTPS");
|
const showTPS = toRef(settings, "showTPS");
|
||||||
|
const appErrors = toRef(state, "errors");
|
||||||
|
|
||||||
const gameComponent = computed(() => {
|
const gameComponent = computed(() => {
|
||||||
return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>)));
|
return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>)));
|
||||||
|
@ -51,4 +57,15 @@ const gameComponent = computed(() => {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
background: var(--background);
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container > .error {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
103
src/components/Error.vue
Normal file
103
src/components/Error.vue
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<div class="error">
|
||||||
|
<h1 class="error-title">{{ firstError.name }}: {{ firstError.message }}</h1>
|
||||||
|
<div class="error-details" style="margin-top: -10px">
|
||||||
|
<div v-if="firstError.cause">
|
||||||
|
<div v-for="row in causes[0]" :key="row">{{ row }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="firstError.stack" :style="firstError.cause ? 'margin-top: 10px' : ''">
|
||||||
|
<div v-for="row in stacks[0]" :key="row">{{ row }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="instructions">
|
||||||
|
Check the console for more details, and consider sharing it with the developers on
|
||||||
|
<a :href="projInfo.discordLink || 'https://discord.gg/yJ4fjnjU54'" class="discord-link"
|
||||||
|
>discord</a
|
||||||
|
>!<br />
|
||||||
|
<div v-if="errors.length > 1" style="margin-top: 20px"><h3>Other errors</h3></div>
|
||||||
|
<div v-for="(error, i) in errors.slice(1)" :key="i" style="margin-top: 20px">
|
||||||
|
<details class="error-details">
|
||||||
|
<summary>{{ error.name }}: {{ error.message }}</summary>
|
||||||
|
<div v-if="error.cause" style="margin-top: 10px">
|
||||||
|
<div v-for="row in causes[i + 1]" :key="row">{{ row }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="error.stack" style="margin-top: 10px">
|
||||||
|
<div v-for="row in stacks[i + 1]" :key="row">{{ row }}</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import projInfo from "data/projInfo.json";
|
||||||
|
import player from "game/player";
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
errors: Error[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const firstError = computed(() => props.errors[0]);
|
||||||
|
const stacks = computed(() =>
|
||||||
|
props.errors.map(error => (error.stack == null ? [] : error.stack.split("\n")))
|
||||||
|
);
|
||||||
|
const causes = computed(() =>
|
||||||
|
props.errors.map(error =>
|
||||||
|
error.cause == null
|
||||||
|
? []
|
||||||
|
: (typeof error.cause === "string" ? error.cause : JSON.stringify(error.cause)).split(
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
player.autosave = false;
|
||||||
|
player.devSpeed = 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error {
|
||||||
|
border: solid 10px var(--danger);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: left;
|
||||||
|
min-height: calc(100% - 20px);
|
||||||
|
text-align: left;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--feature-foreground);
|
||||||
|
display: block;
|
||||||
|
margin: -10px 0 10px 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--raised-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discord-link {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="layer-container" :style="{ '--layer-color': unref(color) }">
|
<ErrorVue v-if="errors.length > 0" :errors="errors" />
|
||||||
|
<div class="layer-container" :style="{ '--layer-color': unref(color) }" v-bind="$attrs" v-else>
|
||||||
<button v-if="showGoBack" class="goBack" @click="goBack">❌</button>
|
<button v-if="showGoBack" class="goBack" @click="goBack">❌</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -28,12 +29,12 @@ import type { CoercableComponent } from "features/feature";
|
||||||
import type { FeatureNode } from "game/layers";
|
import type { FeatureNode } from "game/layers";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
||||||
import type { PropType, Ref } from "vue";
|
import { PropType, Ref, computed, defineComponent, onErrorCaptured, ref, toRefs, unref } from "vue";
|
||||||
import { computed, defineComponent, toRefs, unref } from "vue";
|
|
||||||
import Context from "./Context.vue";
|
import Context from "./Context.vue";
|
||||||
|
import ErrorVue from "./Error.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Context },
|
components: { Context, ErrorVue },
|
||||||
props: {
|
props: {
|
||||||
index: {
|
index: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -77,13 +78,23 @@ export default defineComponent({
|
||||||
props.nodes.value = nodes;
|
props.nodes.value = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errors = ref<Error[]>([]);
|
||||||
|
onErrorCaptured((err, instance, info) => {
|
||||||
|
console.warn(`Error caught in "${props.name}" layer`, err, instance, info);
|
||||||
|
errors.value.push(
|
||||||
|
err instanceof Error ? (err as Error) : new Error(JSON.stringify(err))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
minimizedComponent,
|
minimizedComponent,
|
||||||
showGoBack,
|
showGoBack,
|
||||||
updateNodes,
|
updateNodes,
|
||||||
unref,
|
unref,
|
||||||
goBack
|
goBack,
|
||||||
|
errors
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -459,7 +459,7 @@ export function createFormulaPreview(
|
||||||
const processedShowPreview = convertComputable(showPreview);
|
const processedShowPreview = convertComputable(showPreview);
|
||||||
const processedPreviewAmount = convertComputable(previewAmount);
|
const processedPreviewAmount = convertComputable(previewAmount);
|
||||||
if (!formula.hasVariable()) {
|
if (!formula.hasVariable()) {
|
||||||
throw new Error("Cannot create formula preview if the formula does not have a variable");
|
console.error("Cannot create formula preview if the formula does not have a variable");
|
||||||
}
|
}
|
||||||
return jsx(() => {
|
return jsx(() => {
|
||||||
if (unref(processedShowPreview)) {
|
if (unref(processedShowPreview)) {
|
||||||
|
|
|
@ -52,8 +52,6 @@ export interface ChallengeOptions {
|
||||||
reset?: GenericReset;
|
reset?: GenericReset;
|
||||||
/** The requirement(s) to complete this challenge. */
|
/** The requirement(s) to complete this challenge. */
|
||||||
requirements: Requirements;
|
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. */
|
/** The maximum number of times the challenge can be completed. */
|
||||||
completionLimit?: Computable<DecimalSource>;
|
completionLimit?: Computable<DecimalSource>;
|
||||||
/** Shows a marker on the corner of the feature. */
|
/** 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>;
|
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||||
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
||||||
requirements: GetComputableType<T["requirements"]>;
|
requirements: GetComputableType<T["requirements"]>;
|
||||||
maximize: GetComputableType<T["maximize"]>;
|
|
||||||
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
||||||
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
||||||
classes: GetComputableType<T["classes"]>;
|
classes: GetComputableType<T["classes"]>;
|
||||||
|
@ -210,10 +207,7 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
challenge.canComplete = computed(() =>
|
challenge.canComplete = computed(() =>
|
||||||
Decimal.max(
|
maxRequirementsMet((challenge as GenericChallenge).requirements)
|
||||||
maxRequirementsMet((challenge as GenericChallenge).requirements),
|
|
||||||
unref((challenge as GenericChallenge).maximize) ? Decimal.dInf : 1
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
challenge.complete = function (remainInChallenge?: boolean) {
|
challenge.complete = function (remainInChallenge?: boolean) {
|
||||||
const genericChallenge = challenge as GenericChallenge;
|
const genericChallenge = challenge as GenericChallenge;
|
||||||
|
@ -254,7 +248,6 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
|
|
||||||
processComputable(challenge as T, "canStart");
|
processComputable(challenge as T, "canStart");
|
||||||
setDefault(challenge, "canStart", true);
|
setDefault(challenge, "canStart", true);
|
||||||
processComputable(challenge as T, "maximize");
|
|
||||||
processComputable(challenge as T, "completionLimit");
|
processComputable(challenge as T, "completionLimit");
|
||||||
setDefault(challenge, "completionLimit", 1);
|
setDefault(challenge, "completionLimit", 1);
|
||||||
processComputable(challenge as T, "mark");
|
processComputable(challenge as T, "mark");
|
||||||
|
|
|
@ -67,8 +67,6 @@ export interface RepeatableOptions {
|
||||||
mark?: Computable<boolean | string>;
|
mark?: Computable<boolean | string>;
|
||||||
/** Toggles a smaller design for the feature. */
|
/** Toggles a smaller design for the feature. */
|
||||||
small?: Computable<boolean>;
|
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. */
|
/** The display to use for this repeatable. */
|
||||||
display?: Computable<RepeatableDisplay>;
|
display?: Computable<RepeatableDisplay>;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +85,6 @@ export interface BaseRepeatable {
|
||||||
canClick: ProcessedComputable<boolean>;
|
canClick: ProcessedComputable<boolean>;
|
||||||
/**
|
/**
|
||||||
* How much amount can be increased by, or 1 if unclickable.
|
* How much amount can be increased by, or 1 if unclickable.
|
||||||
* Capped at 1 if {@link RepeatableOptions.maximize} is false.
|
|
||||||
**/
|
**/
|
||||||
amountToIncrease: Ref<DecimalSource>;
|
amountToIncrease: Ref<DecimalSource>;
|
||||||
/** A function that gets called when this repeatable is clicked. */
|
/** 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"]>;
|
style: GetComputableType<T["style"]>;
|
||||||
mark: GetComputableType<T["mark"]>;
|
mark: GetComputableType<T["mark"]>;
|
||||||
small: GetComputableType<T["small"]>;
|
small: GetComputableType<T["small"]>;
|
||||||
maximize: GetComputableType<T["maximize"]>;
|
|
||||||
display: Ref<CoercableComponent>;
|
display: Ref<CoercableComponent>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -195,9 +191,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
return currClasses;
|
return currClasses;
|
||||||
});
|
});
|
||||||
repeatable.amountToIncrease = computed(() =>
|
repeatable.amountToIncrease = computed(() =>
|
||||||
unref((repeatable as GenericRepeatable).maximize)
|
Decimal.clampMin(maxRequirementsMet(repeatable.requirements), 1)
|
||||||
? maxRequirementsMet(repeatable.requirements)
|
|
||||||
: 1
|
|
||||||
);
|
);
|
||||||
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
||||||
const onClick = repeatable.onClick;
|
const onClick = repeatable.onClick;
|
||||||
|
@ -274,7 +268,6 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
processComputable(repeatable as T, "style");
|
processComputable(repeatable as T, "style");
|
||||||
processComputable(repeatable as T, "mark");
|
processComputable(repeatable as T, "mark");
|
||||||
processComputable(repeatable as T, "small");
|
processComputable(repeatable as T, "small");
|
||||||
processComputable(repeatable as T, "maximize");
|
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
for (const decorator of decorators) {
|
||||||
decorator.postConstruct?.(repeatable);
|
decorator.postConstruct?.(repeatable);
|
||||||
|
|
|
@ -151,8 +151,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily>
|
optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily>
|
||||||
): TabFamily<T> {
|
): TabFamily<T> {
|
||||||
if (Object.keys(tabs).length === 0) {
|
if (Object.keys(tabs).length === 0) {
|
||||||
console.warn("Cannot create tab family with 0 tabs");
|
console.error("Cannot create tab family with 0 tabs");
|
||||||
throw new Error("Cannot create tab family with 0 tabs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = persistent(Object.keys(tabs)[0], false);
|
const selected = persistent(Object.keys(tabs)[0], false);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Resource } from "features/resources/resource";
|
||||||
import { NonPersistent } from "game/persistence";
|
import { NonPersistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||||
import { Computable, ProcessedComputable, convertComputable } from "util/computed";
|
import { Computable, ProcessedComputable, convertComputable } from "util/computed";
|
||||||
import { ComputedRef, Ref, computed, ref, unref } from "vue";
|
import { Ref, computed, ref, unref } from "vue";
|
||||||
import * as ops from "./operations";
|
import * as ops from "./operations";
|
||||||
import type {
|
import type {
|
||||||
EvaluateFunction,
|
EvaluateFunction,
|
||||||
|
@ -104,7 +104,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
|
|
||||||
private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> {
|
private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> {
|
||||||
if (inputs.length !== 1) {
|
if (inputs.length !== 1) {
|
||||||
throw new Error("Evaluate function is required if inputs is not length 1");
|
console.error("Evaluate function is required if inputs is not length 1");
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
inputs: inputs as T,
|
inputs: inputs as T,
|
||||||
|
@ -250,7 +250,8 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
}
|
}
|
||||||
return lhs.invert(value);
|
return lhs.invert(value);
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [value],
|
inputs: [value],
|
||||||
|
@ -294,7 +295,8 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
!formula.isInvertible() ||
|
!formula.isInvertible() ||
|
||||||
(elseFormula != null && !elseFormula.isInvertible())
|
(elseFormula != null && !elseFormula.isInvertible())
|
||||||
) {
|
) {
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (unref(processedCondition)) {
|
if (unref(processedCondition)) {
|
||||||
return lhs.invert(formula.invert(value));
|
return lhs.invert(formula.invert(value));
|
||||||
|
@ -1260,7 +1262,8 @@ export default class Formula<
|
||||||
} else if (this.inputs.length === 1 && this.hasVariable()) {
|
} else if (this.inputs.length === 1 && this.hasVariable()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
throw new Error("Cannot invert non-invertible formula");
|
console.error("Cannot invert non-invertible formula");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1270,7 +1273,8 @@ export default class Formula<
|
||||||
*/
|
*/
|
||||||
evaluateIntegral(variable?: DecimalSource): DecimalSource {
|
evaluateIntegral(variable?: DecimalSource): DecimalSource {
|
||||||
if (!this.isIntegrable()) {
|
if (!this.isIntegrable()) {
|
||||||
throw new Error("Cannot evaluate integral of formula without integral");
|
console.error("Cannot evaluate integral of formula without integral");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return this.getIntegralFormula().evaluate(variable);
|
return this.getIntegralFormula().evaluate(variable);
|
||||||
}
|
}
|
||||||
|
@ -1282,7 +1286,8 @@ export default class Formula<
|
||||||
*/
|
*/
|
||||||
invertIntegral(value: DecimalSource): DecimalSource {
|
invertIntegral(value: DecimalSource): DecimalSource {
|
||||||
if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
|
if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
|
||||||
throw new Error("Cannot invert integral of formula without invertible integral");
|
console.error("Cannot invert integral of formula without invertible integral");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return (this.getIntegralFormula() as InvertibleFormula).invert(value);
|
return (this.getIntegralFormula() as InvertibleFormula).invert(value);
|
||||||
}
|
}
|
||||||
|
@ -1309,7 +1314,8 @@ export default class Formula<
|
||||||
// We're the complex operation of this formula
|
// We're the complex operation of this formula
|
||||||
stack = [];
|
stack = [];
|
||||||
if (this.internalIntegrate == null) {
|
if (this.internalIntegrate == null) {
|
||||||
throw new Error("Cannot integrate formula with non-integrable operation");
|
console.error("Cannot integrate formula with non-integrable operation");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
let value = this.internalIntegrate.call(this, stack, ...this.inputs);
|
let value = this.internalIntegrate.call(this, stack, ...this.inputs);
|
||||||
stack.forEach(func => (value = func(value)));
|
stack.forEach(func => (value = func(value)));
|
||||||
|
@ -1329,14 +1335,16 @@ export default class Formula<
|
||||||
) {
|
) {
|
||||||
this.integralFormula = this;
|
this.integralFormula = this;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Cannot integrate formula without variable");
|
console.error("Cannot integrate formula without variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.integralFormula;
|
return this.integralFormula;
|
||||||
} else {
|
} else {
|
||||||
// "Inner" part of the formula
|
// "Inner" part of the formula
|
||||||
if (this.applySubstitution == null) {
|
if (this.applySubstitution == null) {
|
||||||
throw new Error("Cannot have two complex operations in an integrable formula");
|
console.error("Cannot have two complex operations in an integrable formula");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
stack.push((variable: GenericFormula) =>
|
stack.push((variable: GenericFormula) =>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
@ -1353,7 +1361,8 @@ export default class Formula<
|
||||||
) {
|
) {
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Cannot integrate formula without variable");
|
console.error("Cannot integrate formula without variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1400,58 +1409,70 @@ 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 formula The formula to use for calculating buy max from
|
||||||
* @param resource The resource used when purchasing (is only read 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 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 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 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(
|
export function calculateMaxAffordable(
|
||||||
formula: InvertibleFormula,
|
formula: GenericFormula,
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
spendResources?: true,
|
cumulativeCost: Computable<boolean> = true,
|
||||||
summedPurchases?: number
|
directSum?: Computable<number>,
|
||||||
): ComputedRef<DecimalSource>;
|
maxBulkAmount: Computable<DecimalSource> = Decimal.dInf
|
||||||
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
|
|
||||||
) {
|
) {
|
||||||
const computedSpendResources = convertComputable(spendResources);
|
const computedCumulativeCost = convertComputable(cumulativeCost);
|
||||||
|
const computedDirectSum = convertComputable(directSum);
|
||||||
|
const computedmaxBulkAmount = convertComputable(maxBulkAmount);
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
let affordable;
|
const maxBulkAmount = unref(computedmaxBulkAmount);
|
||||||
if (unref(computedSpendResources)) {
|
if (Decimal.eq(maxBulkAmount, 1)) {
|
||||||
if (!formula.isIntegrable() || !formula.isIntegralInvertible()) {
|
return Decimal.gte(resource.value, formula.evaluate()) ? Decimal.dOne : Decimal.dZero;
|
||||||
throw new Error(
|
}
|
||||||
"Cannot calculate max affordable of formula with non-invertible integral"
|
|
||||||
);
|
const cumulativeCost = unref(computedCumulativeCost);
|
||||||
}
|
const directSum = unref(computedDirectSum) ?? (cumulativeCost ? 10 : 0);
|
||||||
affordable = Decimal.floor(
|
let affordable: DecimalSource = 0;
|
||||||
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
if (Decimal.gt(maxBulkAmount, directSum)) {
|
||||||
).sub(unref(formula.innermostVariable) ?? 0);
|
|
||||||
if (summedPurchases == null) {
|
|
||||||
summedPurchases = 10;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!formula.isInvertible()) {
|
if (!formula.isInvertible()) {
|
||||||
throw new Error("Cannot calculate max affordable of non-invertible formula");
|
console.error(
|
||||||
|
"Cannot calculate max affordable of non-invertible formula with more maxBulkAmount than directSum"
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
affordable = Decimal.floor(formula.invert(resource.value));
|
if (cumulativeCost) {
|
||||||
if (summedPurchases == null) {
|
if (!formula.isIntegralInvertible()) {
|
||||||
summedPurchases = 0;
|
console.error(
|
||||||
|
"Cannot calculate max affordable of formula with non-invertible integral"
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
affordable = Decimal.floor(
|
||||||
|
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
||||||
|
).sub(unref(formula.innermostVariable) ?? 0);
|
||||||
|
} else {
|
||||||
|
affordable = Decimal.floor(formula.invert(resource.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (summedPurchases > 0 && Decimal.lt(calculateCost(formula, affordable, true, 0), 1e308)) {
|
affordable = Decimal.clampMax(affordable, maxBulkAmount);
|
||||||
affordable = affordable.sub(summedPurchases).clampMin(0);
|
if (directSum > 0) {
|
||||||
let summedCost = calculateCost(formula, affordable, true, 0);
|
const preSumAffordable = affordable;
|
||||||
while (true) {
|
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) &&
|
||||||
|
Decimal.add(preSumAffordable, 1).gte(affordable)
|
||||||
|
) {
|
||||||
const nextCost = formula.evaluate(
|
const nextCost = formula.evaluate(
|
||||||
affordable.add(unref(formula.innermostVariable) ?? 0)
|
affordable.add(unref(formula.innermostVariable) ?? 0)
|
||||||
);
|
);
|
||||||
|
@ -1468,67 +1489,78 @@ 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 formula The formula to use for calculating buy max from
|
||||||
* @param amountToBuy The amount of purchases to calculate the cost for
|
* @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 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 summedPurchases How many purchases to manually sum for improved accuracy. If not specified, defaults to 10 when spending resources and 0 when not
|
* @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(
|
export function calculateCost(
|
||||||
formula: InvertibleFormula,
|
formula: InvertibleFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
spendResources?: true,
|
cumulativeCost?: true,
|
||||||
summedPurchases?: number
|
directSum?: number
|
||||||
): DecimalSource;
|
): DecimalSource;
|
||||||
export function calculateCost(
|
export function calculateCost(
|
||||||
formula: InvertibleIntegralFormula,
|
formula: InvertibleIntegralFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
spendResources: boolean,
|
cumulativeCost: boolean,
|
||||||
summedPurchases?: number
|
directSum?: number
|
||||||
): DecimalSource;
|
): DecimalSource;
|
||||||
export function calculateCost(
|
export function calculateCost(
|
||||||
formula: InvertibleFormula,
|
formula: InvertibleFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
spendResources = true,
|
cumulativeCost = true,
|
||||||
summedPurchases?: number
|
directSum?: number
|
||||||
) {
|
) {
|
||||||
|
// Single purchase
|
||||||
|
if (Decimal.eq(amountToBuy, 1)) {
|
||||||
|
return formula.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
const origValue = unref(formula.innermostVariable) ?? 0;
|
const origValue = unref(formula.innermostVariable) ?? 0;
|
||||||
let newValue = Decimal.add(amountToBuy, origValue);
|
let newValue = Decimal.add(amountToBuy, origValue);
|
||||||
const targetValue = newValue;
|
const targetValue = newValue;
|
||||||
summedPurchases ??= spendResources ? 10 : 0;
|
directSum ??= cumulativeCost ? 10 : 0;
|
||||||
newValue = newValue.sub(summedPurchases).clampMin(origValue);
|
newValue = newValue.sub(directSum).clampMin(origValue);
|
||||||
let cost: DecimalSource = 0;
|
let cost: DecimalSource = 0;
|
||||||
if (spendResources) {
|
|
||||||
if (Decimal.gt(amountToBuy, summedPurchases)) {
|
// Indirect sum
|
||||||
|
if (Decimal.gt(amountToBuy, directSum)) {
|
||||||
|
if (!formula.isInvertible()) {
|
||||||
|
console.error("Cannot calculate cost with indirect sum of non-invertible formula");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (cumulativeCost) {
|
||||||
if (!formula.isIntegrable()) {
|
if (!formula.isIntegrable()) {
|
||||||
throw new Error(
|
console.error(
|
||||||
"Cannot calculate cost with spending resources of non-integrable formula"
|
"Cannot calculate cost with cumulative cost of non-integrable formula"
|
||||||
);
|
);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
|
cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
|
||||||
|
if (targetValue.gt(1e308)) {
|
||||||
|
// 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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cost = formula.evaluate(newValue);
|
||||||
|
newValue = newValue.add(1);
|
||||||
|
if (targetValue.gt(1e308)) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (targetValue.gt(1e308)) {
|
}
|
||||||
// Too large of a number for summedPurchases to make a difference,
|
|
||||||
// just get the cost and multiply by summed purchases
|
// Direct sum
|
||||||
return Decimal.add(
|
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
||||||
cost,
|
cost = Decimal.add(cost, formula.evaluate(i));
|
||||||
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,
|
|
||||||
// just get the cost and multiply by summed purchases
|
|
||||||
return Decimal.sub(targetValue, newValue).add(1).times(cost);
|
|
||||||
}
|
|
||||||
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
|
||||||
cost = Decimal.add(cost, formula.evaluate(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,17 +12,20 @@ export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.neg(value));
|
return lhs.invert(Decimal.neg(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
return Formula.neg(lhs.getIntegralFormula(stack));
|
return Formula.neg(lhs.getIntegralFormula(stack));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionNeg(value: GenericFormula) {
|
export function applySubstitutionNeg(value: GenericFormula) {
|
||||||
|
@ -35,24 +38,28 @@ export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs)));
|
return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs)));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
|
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
|
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateInnerAdd(
|
export function integrateInnerAdd(
|
||||||
|
@ -62,18 +69,21 @@ export function integrateInnerAdd(
|
||||||
) {
|
) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.add(x, rhs);
|
return Formula.add(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.add(x, lhs);
|
return Formula.add(x, lhs);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -82,24 +92,28 @@ export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
|
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
|
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateInnerSub(
|
export function integrateInnerSub(
|
||||||
|
@ -109,18 +123,21 @@ export function integrateInnerSub(
|
||||||
) {
|
) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, rhs);
|
return Formula.sub(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, lhs);
|
return Formula.sub(x, lhs);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -129,24 +146,28 @@ export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs)));
|
return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs)));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.times(x, rhs);
|
return Formula.times(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(x, lhs);
|
return Formula.times(x, lhs);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionMul(
|
export function applySubstitutionMul(
|
||||||
|
@ -159,7 +180,8 @@ export function applySubstitutionMul(
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return Formula.div(value, lhs);
|
return Formula.div(value, lhs);
|
||||||
}
|
}
|
||||||
throw new Error("Could not apply substitution due to no input being a variable");
|
console.error("Could not apply substitution due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -168,24 +190,28 @@ export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.div(x, rhs);
|
return Formula.div(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.div(lhs, x);
|
return Formula.div(lhs, x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionDiv(
|
export function applySubstitutionDiv(
|
||||||
|
@ -198,32 +224,37 @@ export function applySubstitutionDiv(
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return Formula.mul(value, lhs);
|
return Formula.mul(value, lhs);
|
||||||
}
|
}
|
||||||
throw new Error("Could not apply substitution due to no input being a variable");
|
console.error("Could not apply substitution due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
|
export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.recip(value));
|
return lhs.invert(Decimal.recip(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.ln(x);
|
return Formula.ln(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow10(value));
|
return lhs.invert(Decimal.pow10(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog10(lhs: DecimalSource) {
|
function internalIntegrateLog10(lhs: DecimalSource) {
|
||||||
|
@ -235,13 +266,15 @@ function internalInvertIntegralLog10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
const numerator = ln10.times(value);
|
const numerator = ln10.times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -249,7 +282,8 @@ export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLog10
|
invert: internalInvertIntegralLog10
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -258,7 +292,8 @@ export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
|
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
|
||||||
|
@ -270,13 +305,15 @@ function internalInvertIntegralLog(value: DecimalSource, lhs: FormulaSource, rhs
|
||||||
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
|
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack), rhs],
|
inputs: [lhs.getIntegralFormula(stack), rhs],
|
||||||
|
@ -284,14 +321,16 @@ export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs:
|
||||||
invert: internalInvertIntegralLog
|
invert: internalInvertIntegralLog
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow(2, value));
|
return lhs.invert(Decimal.pow(2, value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog2(lhs: DecimalSource) {
|
function internalIntegrateLog2(lhs: DecimalSource) {
|
||||||
|
@ -303,13 +342,15 @@ function internalInvertIntegralLog2(value: DecimalSource, lhs: FormulaSource) {
|
||||||
const numerator = Decimal.ln(2).times(value);
|
const numerator = Decimal.ln(2).times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -317,14 +358,16 @@ export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLog2
|
invert: internalInvertIntegralLog2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLn(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLn(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.exp(value));
|
return lhs.invert(Decimal.exp(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLn(lhs: DecimalSource) {
|
function internalIntegrateLn(lhs: DecimalSource) {
|
||||||
|
@ -335,13 +378,15 @@ function internalInvertIntegralLn(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
|
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -349,7 +394,8 @@ export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLn
|
invert: internalInvertIntegralLn
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -358,70 +404,81 @@ export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs))));
|
return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs))));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
const pow = Formula.add(rhs, 1);
|
const pow = Formula.add(rhs, 1);
|
||||||
return Formula.pow(x, pow).div(pow);
|
return Formula.pow(x, pow).div(pow);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(lhs, x).div(Formula.ln(lhs));
|
return Formula.pow(lhs, x).div(Formula.ln(lhs));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
|
export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.root(value, 10));
|
return lhs.invert(Decimal.root(value, 10));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow10(x).div(Formula.ln(10));
|
return Formula.pow10(x).div(Formula.ln(10));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.ln(value).div(unrefFormulaSource(rhs)));
|
return lhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(rhs))));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(rhs, x).div(Formula.ln(rhs));
|
return Formula.pow(rhs, x).div(Formula.ln(rhs));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
const denominator = Formula.add(lhs, 1);
|
const denominator = Formula.add(lhs, 1);
|
||||||
return Formula.pow(x, denominator).div(denominator);
|
return Formula.pow(x, denominator).div(denominator);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -430,36 +487,42 @@ export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: Formul
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value)));
|
return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value)));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
|
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertExp(value: DecimalSource, lhs: FormulaSource) {
|
export function invertExp(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.ln(value));
|
return lhs.invert(Decimal.ln(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.exp(x);
|
return Formula.exp(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tetrate(
|
export function tetrate(
|
||||||
|
@ -481,7 +544,8 @@ export function invertTetrate(
|
||||||
return base.invert(Decimal.ssqrt(value));
|
return base.invert(Decimal.ssqrt(value));
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function iteratedexp(
|
export function iteratedexp(
|
||||||
|
@ -509,7 +573,8 @@ export function invertIteratedExp(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function iteratedLog(
|
export function iteratedLog(
|
||||||
|
@ -533,7 +598,8 @@ export function invertSlog(value: DecimalSource, lhs: FormulaSource, rhs: Formul
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) {
|
export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) {
|
||||||
|
@ -556,21 +622,24 @@ export function invertLayeradd(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLambertw(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLambertw(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow(Math.E, value).times(value));
|
return lhs.invert(Decimal.pow(Math.E, value).times(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) {
|
export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tetrate(value, 2));
|
return lhs.invert(Decimal.tetrate(value, 2));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) {
|
export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) {
|
||||||
|
@ -582,226 +651,262 @@ export function invertSin(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.asin(value));
|
return lhs.invert(Decimal.asin(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cos(x).neg();
|
return Formula.cos(x).neg();
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertCos(value: DecimalSource, lhs: FormulaSource) {
|
export function invertCos(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.acos(value));
|
return lhs.invert(Decimal.acos(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sin(x);
|
return Formula.sin(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertTan(value: DecimalSource, lhs: FormulaSource) {
|
export function invertTan(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.atan(value));
|
return lhs.invert(Decimal.atan(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cos(x).ln().neg();
|
return Formula.cos(x).ln().neg();
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.sin(value));
|
return lhs.invert(Decimal.sin(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.asin(x)
|
return Formula.asin(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.cos(value));
|
return lhs.invert(Decimal.cos(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.acos(x)
|
return Formula.acos(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tan(value));
|
return lhs.invert(Decimal.tan(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.atan(x)
|
return Formula.atan(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
|
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.asinh(value));
|
return lhs.invert(Decimal.asinh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cosh(x);
|
return Formula.cosh(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.acosh(value));
|
return lhs.invert(Decimal.acosh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sinh(x);
|
return Formula.sinh(x);
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.atanh(value));
|
return lhs.invert(Decimal.atanh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cosh(x).ln();
|
return Formula.cosh(x).ln();
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.sinh(value));
|
return lhs.invert(Decimal.sinh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
|
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.cosh(value));
|
return lhs.invert(Decimal.cosh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.acosh(x)
|
return Formula.acosh(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
|
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tanh(value));
|
return lhs.invert(Decimal.tanh(value));
|
||||||
}
|
}
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
console.error("Could not invert due to no input being a variable");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
throw new Error("Could not integrate due to variable not being integrable");
|
console.error("Could not integrate due to variable not being integrable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.atanh(x)
|
return Formula.atanh(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));
|
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));
|
||||||
}
|
}
|
||||||
throw new Error("Could not integrate due to no input being a variable");
|
console.error("Could not integrate due to no input being a variable");
|
||||||
|
return Formula.constant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPassthroughBinaryFormula(
|
export function createPassthroughBinaryFormula(
|
||||||
|
|
|
@ -225,7 +225,9 @@ export function createLayer<T extends LayerOptions>(
|
||||||
addingLayers[addingLayers.length - 1] == null ||
|
addingLayers[addingLayers.length - 1] == null ||
|
||||||
addingLayers[addingLayers.length - 1] !== id
|
addingLayers[addingLayers.length - 1] !== id
|
||||||
) {
|
) {
|
||||||
throw `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`;
|
throw new Error(
|
||||||
|
`Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
addingLayers.pop();
|
addingLayers.pop();
|
||||||
|
|
||||||
|
|
|
@ -116,12 +116,7 @@ function checkNaNAndWrite<T extends State>(persistent: Persistent<T>, value: T)
|
||||||
state.NaNPath = persistent[SaveDataPath];
|
state.NaNPath = persistent[SaveDataPath];
|
||||||
state.NaNPersistent = persistent as Persistent<DecimalSource>;
|
state.NaNPersistent = persistent as Persistent<DecimalSource>;
|
||||||
}
|
}
|
||||||
console.error(
|
console.error(`Attempted to save NaN value to ${persistent[SaveDataPath]?.join(".")}`);
|
||||||
`Attempted to save NaN value to`,
|
|
||||||
persistent[SaveDataPath]?.join("."),
|
|
||||||
persistent
|
|
||||||
);
|
|
||||||
throw new Error("Attempted to set NaN value. See above for details");
|
|
||||||
}
|
}
|
||||||
persistent[PersistentState].value = value;
|
persistent[PersistentState].value = value;
|
||||||
}
|
}
|
||||||
|
@ -292,8 +287,8 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
"."
|
"."
|
||||||
)}\` when it's already present at \`${value[SaveDataPath].join(
|
)}\` when it's already present at \`${value[SaveDataPath].join(
|
||||||
"."
|
"."
|
||||||
)}\`. This can cause unexpected behavior when loading saves between updates.`,
|
)}\`.`,
|
||||||
value
|
"This can cause unexpected behavior when loading saves between updates."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
value[SaveDataPath] = newPath;
|
value[SaveDataPath] = newPath;
|
||||||
|
@ -368,9 +363,9 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error(
|
console.error(
|
||||||
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`,
|
`Created persistent ref in ${layer.id} without registering it to the layer!`,
|
||||||
persistent,
|
"Make sure to include everything persistent in the returned object.\n\nCreated at:\n" +
|
||||||
"\nCreated at:\n" + persistent[StackTrace]
|
persistent[StackTrace]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
persistentRefs[layer.id].clear();
|
persistentRefs[layer.id].clear();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import { joinJSX, renderJSX } from "util/vue";
|
import { joinJSX, renderJSX } from "util/vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
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";
|
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.
|
* 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}
|
* @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.
|
* Pass-through to {@link Requirement.pay}. May be required for maximizing support.
|
||||||
* @see {@link cost} for restrictions on maximizing support.
|
* @see {@link cost} for restrictions on maximizing support.
|
||||||
|
@ -100,7 +108,7 @@ export type CostRequirement = Replace<
|
||||||
cost: ProcessedComputable<DecimalSource> | GenericFormula;
|
cost: ProcessedComputable<DecimalSource> | GenericFormula;
|
||||||
visibility: ProcessedComputable<Visibility.Visible | Visibility.None | boolean>;
|
visibility: ProcessedComputable<Visibility.Visible | Visibility.None | boolean>;
|
||||||
requiresPay: ProcessedComputable<boolean>;
|
requiresPay: ProcessedComputable<boolean>;
|
||||||
spendResources: ProcessedComputable<boolean>;
|
cumulativeCost: ProcessedComputable<boolean>;
|
||||||
canMaximize: ProcessedComputable<boolean>;
|
canMaximize: ProcessedComputable<boolean>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -126,7 +134,12 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
{displayResource(
|
{displayResource(
|
||||||
req.resource,
|
req.resource,
|
||||||
req.cost instanceof Formula
|
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>)
|
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{req.resource.displayName}
|
{req.resource.displayName}
|
||||||
|
@ -138,7 +151,12 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
{displayResource(
|
{displayResource(
|
||||||
req.resource,
|
req.resource,
|
||||||
req.cost instanceof Formula
|
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>)
|
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{req.resource.displayName}
|
{req.resource.displayName}
|
||||||
|
@ -150,54 +168,62 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
processComputable(req as T, "cost");
|
processComputable(req as T, "cost");
|
||||||
processComputable(req as T, "requiresPay");
|
processComputable(req as T, "requiresPay");
|
||||||
setDefault(req, "requiresPay", true);
|
setDefault(req, "requiresPay", true);
|
||||||
processComputable(req as T, "spendResources");
|
processComputable(req as T, "cumulativeCost");
|
||||||
setDefault(req, "spendResources", true);
|
setDefault(req, "cumulativeCost", true);
|
||||||
|
processComputable(req as T, "maxBulkAmount");
|
||||||
|
setDefault(req, "maxBulkAmount", 1);
|
||||||
|
processComputable(req as T, "directSum");
|
||||||
setDefault(req, "pay", function (amount?: DecimalSource) {
|
setDefault(req, "pay", function (amount?: DecimalSource) {
|
||||||
const cost =
|
const cost =
|
||||||
req.cost instanceof Formula
|
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>);
|
: unref(req.cost as ProcessedComputable<DecimalSource>);
|
||||||
req.resource.value = Decimal.sub(req.resource.value, cost).max(0);
|
req.resource.value = Decimal.sub(req.resource.value, cost).max(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
req.canMaximize = computed(
|
req.canMaximize = computed(() => {
|
||||||
() =>
|
if (!(req.cost instanceof Formula)) {
|
||||||
req.cost instanceof Formula &&
|
return false;
|
||||||
req.cost.isInvertible() &&
|
}
|
||||||
(unref(req.spendResources) === false || req.cost.isIntegrable())
|
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()) {
|
if (req.cost instanceof Formula) {
|
||||||
const maxAffordable = calculateMaxAffordable(
|
req.requirementMet = calculateMaxAffordable(
|
||||||
req.cost,
|
req.cost,
|
||||||
req.resource,
|
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.resource.value,
|
|
||||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
req.requirementMet = computed(() => {
|
req.requirementMet = computed(() =>
|
||||||
if (req.cost instanceof Formula) {
|
Decimal.gte(
|
||||||
return Decimal.gte(req.resource.value, req.cost.evaluate());
|
req.resource.value,
|
||||||
} else {
|
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
return Decimal.gte(
|
)
|
||||||
req.resource.value,
|
);
|
||||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return req as CostRequirement;
|
return req as CostRequirement;
|
||||||
|
@ -328,7 +354,7 @@ export function payByDivision(this: CostRequirement, amount?: DecimalSource) {
|
||||||
? calculateCost(
|
? calculateCost(
|
||||||
this.cost,
|
this.cost,
|
||||||
amount ?? 1,
|
amount ?? 1,
|
||||||
unref(this.spendResources as ProcessedComputable<boolean> | undefined) ?? true
|
unref(this.cumulativeCost as ProcessedComputable<boolean> | undefined) ?? true
|
||||||
)
|
)
|
||||||
: unref(this.cost as ProcessedComputable<DecimalSource>);
|
: unref(this.cost as ProcessedComputable<DecimalSource>);
|
||||||
this.resource.value = Decimal.div(this.resource.value, cost);
|
this.resource.value = Decimal.div(this.resource.value, cost);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import { shallowReactive } from "vue";
|
import { reactive, shallowReactive } from "vue";
|
||||||
import type { Persistent } from "./persistence";
|
import type { Persistent } from "./persistence";
|
||||||
|
|
||||||
/** An object of global data that is not persistent. */
|
/** An object of global data that is not persistent. */
|
||||||
|
@ -12,6 +12,8 @@ export interface Transient {
|
||||||
NaNPath?: string[];
|
NaNPath?: string[];
|
||||||
/** The ref that was being set to NaN. */
|
/** The ref that was being set to NaN. */
|
||||||
NaNPersistent?: Persistent<DecimalSource>;
|
NaNPersistent?: Persistent<DecimalSource>;
|
||||||
|
/** List of errors that have occurred, to show the user. */
|
||||||
|
errors: Error[];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -24,5 +26,6 @@ declare global {
|
||||||
export default window.state = shallowReactive<Transient>({
|
export default window.state = shallowReactive<Transient>({
|
||||||
lastTenTicks: [],
|
lastTenTicks: [],
|
||||||
hasNaN: false,
|
hasNaN: false,
|
||||||
NaNPath: []
|
NaNPath: [],
|
||||||
|
errors: reactive([])
|
||||||
});
|
});
|
||||||
|
|
27
src/main.ts
27
src/main.ts
|
@ -2,6 +2,7 @@ import "@fontsource/material-icons";
|
||||||
import App from "App.vue";
|
import App from "App.vue";
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
|
import state from "game/state";
|
||||||
import { load } from "util/save";
|
import { load } from "util/save";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/vue";
|
import { useRegisterSW } from "virtual:pwa-register/vue";
|
||||||
import type { App as VueApp } from "vue";
|
import type { App as VueApp } from "vue";
|
||||||
|
@ -23,11 +24,30 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const error = console.error;
|
||||||
|
console.error = function (...args) {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
state.errors.push(new Error(args[0], { cause: args[1] }));
|
||||||
|
}
|
||||||
|
error(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onerror = function (event, source, lineno, colno, error) {
|
||||||
|
state.errors.push(error instanceof Error ? error : new Error(JSON.stringify(error)));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
window.onunhandledrejection = function (event) {
|
||||||
|
state.errors.push(
|
||||||
|
event.reason instanceof Error ? event.reason : new Error(JSON.stringify(event.reason))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
document.title = projInfo.title;
|
document.title = projInfo.title;
|
||||||
window.projInfo = projInfo;
|
window.projInfo = projInfo;
|
||||||
if (projInfo.id === "") {
|
if (projInfo.id === "") {
|
||||||
throw new Error(
|
console.error(
|
||||||
"Project ID is empty! Please select a unique ID for this project in /src/data/projInfo.json"
|
"Project ID is empty!",
|
||||||
|
"Please select a unique ID for this project in /src/data/projInfo.json"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +63,9 @@ requestAnimationFrame(async () => {
|
||||||
|
|
||||||
// Create Vue
|
// Create Vue
|
||||||
const vue = (window.vue = createApp(App));
|
const vue = (window.vue = createApp(App));
|
||||||
|
vue.config.errorHandler = function (err, instance, info) {
|
||||||
|
console.error(err, info, instance);
|
||||||
|
};
|
||||||
globalBus.emit("setupVue", vue);
|
globalBus.emit("setupVue", vue);
|
||||||
vue.mount("#app");
|
vue.mount("#app");
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function createLazyProxy<T extends object, S extends T>(
|
||||||
function calculateObj(): T {
|
function calculateObj(): T {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
if (calculating) {
|
if (calculating) {
|
||||||
throw new Error("Cyclical dependency detected. Cannot evaluate lazy proxy.");
|
console.error("Cyclical dependency detected. Cannot evaluate lazy proxy.");
|
||||||
}
|
}
|
||||||
calculating = true;
|
calculating = true;
|
||||||
Object.assign(obj, objectFunc.call(obj, obj));
|
Object.assign(obj, objectFunc.call(obj, obj));
|
||||||
|
|
|
@ -227,14 +227,15 @@ describe("Creating Formulas", () => {
|
||||||
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
test("Invert throws", () => expect(() => formula.invert(25)).toThrow());
|
test("Invert errors", () => expect(() => formula.invert(25)).toLogError());
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
test("Integrate errors", () =>
|
||||||
/* @ts-ignore */
|
|
||||||
test("Integrate throws", () => expect(() => formula.evaluateIntegral()).toThrow());
|
|
||||||
test("Invert integral throws", () =>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
expect(() => formula.invertIntegral(25)).toThrow());
|
expect(() => formula.evaluateIntegral()).toLogError());
|
||||||
|
test("Invert integral errors", () =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
/* @ts-ignore */
|
||||||
|
expect(() => formula.invertIntegral(25)).toLogError());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
testConstant("number", () => Formula.constant(10));
|
testConstant("number", () => Formula.constant(10));
|
||||||
|
@ -256,10 +257,10 @@ describe("Creating Formulas", () => {
|
||||||
// None of these formulas have variables, so they should all behave the same
|
// None of these formulas have variables, so they should all behave the same
|
||||||
test("Is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
test("Is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
||||||
test("Is not invertible", () => expect(formula.isInvertible()).toBe(false));
|
test("Is not invertible", () => expect(formula.isInvertible()).toBe(false));
|
||||||
test(`Formula throws if trying to invert`, () =>
|
test(`Formula errors if trying to invert`, () =>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
expect(() => formula.invert(10)).toThrow());
|
expect(() => formula.invert(10)).toLogError());
|
||||||
test("Is not integrable", () => expect(formula.isIntegrable()).toBe(false));
|
test("Is not integrable", () => expect(formula.isIntegrable()).toBe(false));
|
||||||
test("Has a non-invertible integral", () =>
|
test("Has a non-invertible integral", () =>
|
||||||
expect(formula.isIntegralInvertible()).toBe(false));
|
expect(formula.isIntegralInvertible()).toBe(false));
|
||||||
|
@ -495,12 +496,12 @@ describe("Inverting", () => {
|
||||||
test("Non-invertible constant", () => {
|
test("Non-invertible constant", () => {
|
||||||
const formula = Formula.add(variable, constant.ceil());
|
const formula = Formula.add(variable, constant.ceil());
|
||||||
expect(formula.isInvertible()).toBe(true);
|
expect(formula.isInvertible()).toBe(true);
|
||||||
expect(() => formula.invert(10)).not.toThrow();
|
expect(() => formula.invert(10)).not.toLogError();
|
||||||
});
|
});
|
||||||
test("Non-invertible variable", () => {
|
test("Non-invertible variable", () => {
|
||||||
const formula = Formula.add(variable.ceil(), constant);
|
const formula = Formula.add(variable.ceil(), constant);
|
||||||
expect(formula.isInvertible()).toBe(false);
|
expect(formula.isInvertible()).toBe(false);
|
||||||
expect(() => formula.invert(10)).toThrow();
|
expect(() => formula.invert(10)).toLogError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -624,19 +625,19 @@ describe("Integrating", () => {
|
||||||
|
|
||||||
test("Integrating nested complex formulas", () => {
|
test("Integrating nested complex formulas", () => {
|
||||||
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
||||||
expect(() => formula.evaluateIntegral()).toThrow();
|
expect(() => formula.evaluateIntegral()).toLogError();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Integrating with non-integrable sections", () => {
|
describe("Integrating with non-integrable sections", () => {
|
||||||
test("Non-integrable constant", () => {
|
test("Non-integrable constant", () => {
|
||||||
const formula = Formula.add(variable, constant.ceil());
|
const formula = Formula.add(variable, constant.ceil());
|
||||||
expect(formula.isIntegrable()).toBe(true);
|
expect(formula.isIntegrable()).toBe(true);
|
||||||
expect(() => formula.evaluateIntegral()).not.toThrow();
|
expect(() => formula.evaluateIntegral()).not.toLogError();
|
||||||
});
|
});
|
||||||
test("Non-integrable variable", () => {
|
test("Non-integrable variable", () => {
|
||||||
const formula = Formula.add(variable.ceil(), constant);
|
const formula = Formula.add(variable.ceil(), constant);
|
||||||
expect(formula.isIntegrable()).toBe(false);
|
expect(formula.isIntegrable()).toBe(false);
|
||||||
expect(() => formula.evaluateIntegral()).toThrow();
|
expect(() => formula.evaluateIntegral()).toLogError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -657,7 +658,7 @@ describe("Inverting integrals", () => {
|
||||||
describe("Invertible Integral functions marked as such", () => {
|
describe("Invertible Integral functions marked as such", () => {
|
||||||
function checkFormula(formula: InvertibleIntegralFormula) {
|
function checkFormula(formula: InvertibleIntegralFormula) {
|
||||||
expect(formula.isIntegralInvertible()).toBe(true);
|
expect(formula.isIntegralInvertible()).toBe(true);
|
||||||
expect(() => formula.invertIntegral(10)).to.not.throw();
|
expect(() => formula.invertIntegral(10)).not.toLogError();
|
||||||
}
|
}
|
||||||
invertibleIntegralZeroPramFunctionNames.forEach(name => {
|
invertibleIntegralZeroPramFunctionNames.forEach(name => {
|
||||||
describe(name, () => {
|
describe(name, () => {
|
||||||
|
@ -676,7 +677,7 @@ describe("Inverting integrals", () => {
|
||||||
test(`${name}(var, var) is marked as not having an invertible integral`, () => {
|
test(`${name}(var, var) is marked as not having an invertible integral`, () => {
|
||||||
const formula = Formula[name](variable, variable);
|
const formula = Formula[name](variable, variable);
|
||||||
expect(formula.isIntegralInvertible()).toBe(false);
|
expect(formula.isIntegralInvertible()).toBe(false);
|
||||||
expect(() => formula.invertIntegral(10)).to.throw();
|
expect(() => formula.invertIntegral(10)).toLogError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -732,7 +733,7 @@ describe("Inverting integrals", () => {
|
||||||
|
|
||||||
test("Inverting integral of nested complex formulas", () => {
|
test("Inverting integral of nested complex formulas", () => {
|
||||||
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
||||||
expect(() => formula.invertIntegral(100)).toThrow();
|
expect(() => formula.invertIntegral(100)).toLogError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -765,7 +766,7 @@ describe("Step-wise", () => {
|
||||||
);
|
);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.step(constant, 10, value => Formula.add(value, 10)).evaluateIntegral()
|
Formula.step(constant, 10, value => Formula.add(value, 10)).evaluateIntegral()
|
||||||
).toThrow();
|
).toLogError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula never marked as having an invertible integral", () => {
|
test("Formula never marked as having an invertible integral", () => {
|
||||||
|
@ -774,7 +775,7 @@ describe("Step-wise", () => {
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.step(constant, 10, value => Formula.add(value, 10)).invertIntegral(10)
|
Formula.step(constant, 10, value => Formula.add(value, 10)).invertIntegral(10)
|
||||||
).toThrow();
|
).toLogError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
||||||
|
@ -866,7 +867,7 @@ describe("Conditionals", () => {
|
||||||
);
|
);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.if(constant, true, value => Formula.add(value, 10)).evaluateIntegral()
|
Formula.if(constant, true, value => Formula.add(value, 10)).evaluateIntegral()
|
||||||
).toThrow();
|
).toLogError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula never marked as having an invertible integral", () => {
|
test("Formula never marked as having an invertible integral", () => {
|
||||||
|
@ -875,7 +876,7 @@ describe("Conditionals", () => {
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.if(constant, true, value => Formula.add(value, 10)).invertIntegral(10)
|
Formula.if(constant, true, value => Formula.add(value, 10)).invertIntegral(10)
|
||||||
).toThrow();
|
).toLogError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
||||||
|
@ -976,7 +977,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 6,
|
evaluate: () => 6,
|
||||||
invert: value => value
|
invert: value => value
|
||||||
}).invert(10)
|
}).invert(10)
|
||||||
).toThrow());
|
).toLogError());
|
||||||
test("One input inverts correctly", () =>
|
test("One input inverts correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1003,7 +1004,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 0,
|
evaluate: () => 0,
|
||||||
integrate: stack => variable
|
integrate: stack => variable
|
||||||
}).evaluateIntegral()
|
}).evaluateIntegral()
|
||||||
).toThrow());
|
).toLogError());
|
||||||
test("One input integrates correctly", () =>
|
test("One input integrates correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1030,7 +1031,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 0,
|
evaluate: () => 0,
|
||||||
integrate: stack => variable
|
integrate: stack => variable
|
||||||
}).invertIntegral(20)
|
}).invertIntegral(20)
|
||||||
).toThrow());
|
).toLogError());
|
||||||
test("One input inverts integral correctly", () =>
|
test("One input inverts integral correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1073,12 +1074,19 @@ describe("Buy Max", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
resource = createResource(ref(100000));
|
resource = createResource(ref(100000));
|
||||||
});
|
});
|
||||||
describe("Without spending", () => {
|
describe("Without cumulative cost", () => {
|
||||||
test("Throws on formula with non-invertible integral", () => {
|
test("Errors on calculating max affordable of non-invertible formula", () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
const purchases = ref(1);
|
||||||
/* @ts-ignore */
|
const variable = Formula.variable(purchases);
|
||||||
const maxAffordable = calculateMaxAffordable(Formula.neg(10), resource, false);
|
const formula = Formula.abs(variable);
|
||||||
expect(() => maxAffordable.value).toThrow();
|
const maxAffordable = calculateMaxAffordable(formula, resource, false);
|
||||||
|
expect(() => maxAffordable.value).toLogError();
|
||||||
|
});
|
||||||
|
test("Errors 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)).toLogError();
|
||||||
});
|
});
|
||||||
test("Calculates max affordable and cost correctly", () => {
|
test("Calculates max affordable and cost correctly", () => {
|
||||||
const variable = Formula.variable(0);
|
const variable = Formula.variable(0);
|
||||||
|
@ -1089,7 +1097,7 @@ describe("Buy Max", () => {
|
||||||
Decimal.pow(1.05, 141).times(100)
|
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 variable = Formula.variable(0);
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
const formula = Formula.pow(1.05, variable).times(100);
|
||||||
const maxAffordable = calculateMaxAffordable(formula, resource, false, 4);
|
const maxAffordable = calculateMaxAffordable(formula, resource, false, 4);
|
||||||
|
@ -1102,10 +1110,19 @@ describe("Buy Max", () => {
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
expect(calculatedCost).compare_tolerance(actualCost);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("With spending", () => {
|
describe("With cumulative cost", () => {
|
||||||
test("Throws on calculating max affordable of non-invertible formula", () => {
|
test("Errors on calculating max affordable of non-invertible formula", () => {
|
||||||
const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource);
|
const purchases = ref(1);
|
||||||
expect(() => maxAffordable.value).toThrow();
|
const variable = Formula.variable(purchases);
|
||||||
|
const formula = Formula.abs(variable);
|
||||||
|
const maxAffordable = calculateMaxAffordable(formula, resource, true);
|
||||||
|
expect(() => maxAffordable.value).toLogError();
|
||||||
|
});
|
||||||
|
test("Errors 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)).toLogError();
|
||||||
});
|
});
|
||||||
test("Estimates max affordable and cost correctly with 0 purchases", () => {
|
test("Estimates max affordable and cost correctly with 0 purchases", () => {
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
|
@ -1163,7 +1180,7 @@ describe("Buy Max", () => {
|
||||||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||||
).toBeLessThan(0.1);
|
).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 purchases = ref(1);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
const formula = Formula.pow(1.05, variable).times(100);
|
||||||
|
@ -1190,7 +1207,7 @@ describe("Buy Max", () => {
|
||||||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||||
).toBeLessThan(0.02);
|
).toBeLessThan(0.02);
|
||||||
});
|
});
|
||||||
test("Handles summing purchases when making few purchases", () => {
|
test("Handles direct sum when making few purchases", () => {
|
||||||
const purchases = ref(90);
|
const purchases = ref(90);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
const formula = Formula.pow(1.05, variable).times(100);
|
||||||
|
@ -1218,7 +1235,7 @@ describe("Buy Max", () => {
|
||||||
// Since we're summing all the purchases this should be equivalent
|
// Since we're summing all the purchases this should be equivalent
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
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 purchases = ref(0);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = variable.add(1);
|
const formula = variable.add(1);
|
||||||
|
@ -1236,7 +1253,7 @@ describe("Buy Max", () => {
|
||||||
// Since we're summing all the purchases this should be equivalent
|
// Since we're summing all the purchases this should be equivalent
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
expect(calculatedCost).compare_tolerance(actualCost);
|
||||||
});
|
});
|
||||||
test("Handles summing purchases when over e308 purchases", () => {
|
test("Handles direct sum when over e308 purchases", () => {
|
||||||
resource.value = "1ee308";
|
resource.value = "1ee308";
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
|
@ -1247,10 +1264,10 @@ describe("Buy Max", () => {
|
||||||
expect(Decimal.isFinite(calculatedCost)).toBe(true);
|
expect(Decimal.isFinite(calculatedCost)).toBe(true);
|
||||||
resource.value = 100000;
|
resource.value = 100000;
|
||||||
});
|
});
|
||||||
test("Handles summing purchases of non-integrable formula", () => {
|
test("Handles direct sum of non-integrable formula", () => {
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
const formula = Formula.variable(purchases).abs();
|
const formula = Formula.variable(purchases).abs();
|
||||||
expect(() => calculateCost(formula, 10)).not.toThrow();
|
expect(() => calculateCost(formula, 10)).not.toLogError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
Requirement,
|
Requirement,
|
||||||
requirementsMet
|
requirementsMet
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
|
import Decimal from "util/bignum";
|
||||||
import { beforeAll, describe, expect, test } from "vitest";
|
import { beforeAll, describe, expect, test } from "vitest";
|
||||||
import { isRef, ref, unref } from "vue";
|
import { isRef, ref, unref } from "vue";
|
||||||
import "../utils";
|
import "../utils";
|
||||||
|
@ -26,8 +27,7 @@ describe("Creating cost requirement", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
requirement = createCostRequirement(() => ({
|
requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10
|
||||||
spendResources: false
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ describe("Creating cost requirement", () => {
|
||||||
});
|
});
|
||||||
test("is visible", () => expect(requirement.visibility).toBe(Visibility.Visible));
|
test("is visible", () => expect(requirement.visibility).toBe(Visibility.Visible));
|
||||||
test("requires pay", () => expect(requirement.requiresPay).toBe(true));
|
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));
|
test("cannot maximize", () => expect(unref(requirement.canMaximize)).toBe(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,8 +56,9 @@ describe("Creating cost requirement", () => {
|
||||||
cost: Formula.variable(resource).times(10),
|
cost: Formula.variable(resource).times(10),
|
||||||
visibility: Visibility.None,
|
visibility: Visibility.None,
|
||||||
requiresPay: false,
|
requiresPay: false,
|
||||||
maximize: true,
|
cumulativeCost: false,
|
||||||
spendResources: true,
|
maxBulkAmount: Decimal.dInf,
|
||||||
|
directSum: 5,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
pay() {}
|
pay() {}
|
||||||
}));
|
}));
|
||||||
|
@ -69,15 +70,18 @@ describe("Creating cost requirement", () => {
|
||||||
requirement.pay.length === 1);
|
requirement.pay.length === 1);
|
||||||
test("is not visible", () => expect(requirement.visibility).toBe(Visibility.None));
|
test("is not visible", () => expect(requirement.visibility).toBe(Visibility.None));
|
||||||
test("does not require pay", () => expect(requirement.requiresPay).toBe(false));
|
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("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", () => {
|
test("Requirement met when meeting the cost", () => {
|
||||||
const requirement = createCostRequirement(() => ({
|
const requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
spendResources: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -86,13 +90,23 @@ describe("Creating cost requirement", () => {
|
||||||
const requirement = createCostRequirement(() => ({
|
const requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 100,
|
cost: 100,
|
||||||
spendResources: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("canMaximize works correctly", () => {
|
describe("canMaximize works correctly", () => {
|
||||||
test("Cost function cannot maximize", () =>
|
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(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
|
@ -101,82 +115,104 @@ describe("Creating cost requirement", () => {
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).toBe(false));
|
||||||
test("Non-invertible formula cannot maximize", () =>
|
test("Non-invertible formula cannot maximize when max bulk amount is above direct sum", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).abs()
|
cost: Formula.variable(resource).abs(),
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).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(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).lambertw(),
|
cost: Formula.variable(resource).lambertw(),
|
||||||
spendResources: false
|
cumulativeCost: false,
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
test("Invertible formula cannot maximize if spendResources is true", () =>
|
test("Invertible formula cannot maximize if cumulativeCost is true", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).lambertw(),
|
cost: Formula.variable(resource).lambertw(),
|
||||||
spendResources: true
|
cumulativeCost: true,
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).toBe(false));
|
||||||
test("Integrable formula can maximize if spendResources is false", () =>
|
test("Integrable formula can maximize if cumulativeCost is false", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).pow(2),
|
cost: Formula.variable(resource).pow(2),
|
||||||
spendResources: false
|
cumulativeCost: false,
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
test("Integrable formula can maximize if spendResources is true", () =>
|
test("Integrable formula can maximize if cumulativeCost is true", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).pow(2),
|
cost: Formula.variable(resource).pow(2),
|
||||||
spendResources: true
|
cumulativeCost: true,
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Requirements met capped by maxBulkAmount", () =>
|
||||||
|
expect(
|
||||||
|
unref(
|
||||||
|
createCostRequirement(() => ({
|
||||||
|
resource,
|
||||||
|
cost: Formula.variable(resource).times(0.0001),
|
||||||
|
maxBulkAmount: 10,
|
||||||
|
cumulativeCost: false
|
||||||
|
})).requirementMet
|
||||||
|
)
|
||||||
|
).compare_tolerance(10));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Creating visibility requirement", () => {
|
test("Creating visibility requirement", () => {
|
||||||
test("Requirement met when visible", () => {
|
const visibility = ref<Visibility.None | Visibility.Visible | boolean>(Visibility.Visible);
|
||||||
const requirement = createVisibilityRequirement({ visibility: Visibility.Visible });
|
const requirement = createVisibilityRequirement({ visibility });
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
});
|
visibility.value = true;
|
||||||
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
test("Requirement not met when not visible", () => {
|
visibility.value = Visibility.None;
|
||||||
let requirement = createVisibilityRequirement({ visibility: Visibility.None });
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
visibility.value = false;
|
||||||
requirement = createVisibilityRequirement({ visibility: false });
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Creating boolean requirement", () => {
|
test("Creating boolean requirement", () => {
|
||||||
test("Requirement met when true", () => {
|
const req = ref(true);
|
||||||
const requirement = createBooleanRequirement(ref(true));
|
const requirement = createBooleanRequirement(req);
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
});
|
req.value = false;
|
||||||
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
test("Requirement not met when false", () => {
|
|
||||||
const requirement = createBooleanRequirement(ref(false));
|
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Checking all requirements met", () => {
|
describe("Checking all requirements met", () => {
|
||||||
|
@ -208,7 +244,7 @@ describe("Checking maximum levels of requirements met", () => {
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource: createResource(ref(10)),
|
resource: createResource(ref(10)),
|
||||||
cost: Formula.variable(0),
|
cost: Formula.variable(0),
|
||||||
spendResources: false
|
cumulativeCost: false
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
expect(maxRequirementsMet(requirements)).compare_tolerance(0);
|
expect(maxRequirementsMet(requirements)).compare_tolerance(0);
|
||||||
|
@ -220,7 +256,8 @@ describe("Checking maximum levels of requirements met", () => {
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource: createResource(ref(10)),
|
resource: createResource(ref(10)),
|
||||||
cost: Formula.variable(0),
|
cost: Formula.variable(0),
|
||||||
spendResources: false
|
cumulativeCost: false,
|
||||||
|
maxBulkAmount: Decimal.dInf
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
expect(maxRequirementsMet(requirements)).compare_tolerance(10);
|
expect(maxRequirementsMet(requirements)).compare_tolerance(10);
|
||||||
|
@ -233,12 +270,12 @@ test("Paying requirements", () => {
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
requiresPay: false,
|
requiresPay: false,
|
||||||
spendResources: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
const payment = createCostRequirement(() => ({
|
const payment = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
spendResources: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
payRequirements([noPayment, payment]);
|
payRequirements([noPayment, payment]);
|
||||||
expect(resource.value).compare_tolerance(90);
|
expect(resource.value).compare_tolerance(90);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||||
import { expect } from "vitest";
|
import { Mock, expect, vi } from "vitest";
|
||||||
|
|
||||||
interface CustomMatchers<R = unknown> {
|
interface CustomMatchers<R = unknown> {
|
||||||
compare_tolerance(expected: DecimalSource, tolerance?: number): R;
|
compare_tolerance(expected: DecimalSource, tolerance?: number): R;
|
||||||
|
toLogError(): R;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -36,5 +37,25 @@ expect.extend({
|
||||||
expected: format(expected),
|
expected: format(expected),
|
||||||
actual: format(received)
|
actual: format(received)
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
toLogError(received: () => unknown) {
|
||||||
|
const { isNot } = this;
|
||||||
|
console.error = vi.fn();
|
||||||
|
received();
|
||||||
|
const calls = (
|
||||||
|
console.error as unknown as Mock<
|
||||||
|
Parameters<typeof console.error>,
|
||||||
|
ReturnType<typeof console.error>
|
||||||
|
>
|
||||||
|
).mock.calls.length;
|
||||||
|
const pass = calls >= 1;
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
`Expected ${received} to ${(isNot as boolean) ? " not" : ""} log an error`,
|
||||||
|
expected: "1+",
|
||||||
|
actual: calls
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue