diff --git a/engine/src/game/ResourceSet.ts b/engine/src/game/ResourceSet.ts index f23aa5d..9fcfa19 100644 --- a/engine/src/game/ResourceSet.ts +++ b/engine/src/game/ResourceSet.ts @@ -1,11 +1,12 @@ +import Decimal from "break_eternity.js"; import { ResourceNames, type PartialResources, type Resource } from "../types"; export class ResourceSet { - private items = new Map(); + private items = new Map(); public static from(source: PartialResources): ResourceSet { const inst = new ResourceSet(); - inst.items = new Map(Object.entries(source) as [Resource, number][]); + inst.items = new Map(Object.entries(source) as [Resource, Decimal][]); return inst; } @@ -13,11 +14,11 @@ export class ResourceSet { return ResourceSet.from(this.toObject()); } - public get(key: Resource): number { - return this.items.get(key) ?? 0; + public get(key: Resource): Decimal { + return this.items.get(key) ?? Decimal.dZero; } - public set(key: Resource, value: number): void { + public set(key: Resource, value: Decimal): void { this.items.set(key, value); } @@ -26,31 +27,31 @@ export class ResourceSet { } public gte(arg: ResourceSet): boolean { - return this.predicate((res) => this.get(res) >= arg.get(res)); + return this.predicate((res) => this.get(res).gte(arg.get(res))); } - public static transform(target: ResourceSet, predicate: (res: Resource) => number): void { + public static transform(target: ResourceSet, predicate: (res: Resource) => Decimal): void { for (const res of ResourceNames) target.set(res, predicate(res)); } public sum(arg: ResourceSet): ResourceSet { const inst = new ResourceSet(); - ResourceSet.transform(inst, (res) => this.get(res) + arg.get(res)); + ResourceSet.transform(inst, (res) => this.get(res).plus(arg.get(res))); return inst; } public add(arg: ResourceSet): void { - ResourceSet.transform(this, (res) => this.get(res) + arg.get(res)); + ResourceSet.transform(this, (res) => this.get(res).plus(arg.get(res))); } public remove(arg: ResourceSet): void { - ResourceSet.transform(this, (res) => this.get(res) - arg.get(res)); + ResourceSet.transform(this, (res) => this.get(res).minus(arg.get(res))); } public times(factor: number): ResourceSet { const inst = new ResourceSet(); - ResourceSet.transform(inst, (res) => this.get(res) * factor); + ResourceSet.transform(inst, (res) => this.get(res).multiply(factor)); return inst; } diff --git a/engine/src/index.ts b/engine/src/index.ts index 907e09c..c438895 100644 --- a/engine/src/index.ts +++ b/engine/src/index.ts @@ -1,6 +1,7 @@ +import Decimal from 'break_eternity.js'; import { Game } from './game'; import type { ResourceGeneratorRule } from './rules'; -import type { PartialResources, Resource } from './types'; +import type { PartialResources, Resource, Resources } from './types'; export * from './types'; export * from './rules'; @@ -33,9 +34,9 @@ function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule { Object.entries(p) .map(([res, rp]) => [ res, - (rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0), + new Decimal((rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0)), ]) - ); + ) as Resources; } return { @@ -60,7 +61,7 @@ export function makeGame(): Game { generation: { RED: { offset: 3, power: 1.5, factor: 1 } }, openPrice: { RED: { offset: 10, power: 2.1, factor: 10 } } }), - ...['orange', 'yellow', 'green', 'cyan', 'blue', 'violet', ...Array(20)] + ...['orange', 'yellow', 'green', 'cyan', 'blue', 'violet'] .map((color, idx) => simpleResGen({ name: color, period: idx + 2, diff --git a/engine/src/types/Resource.ts b/engine/src/types/Resource.ts index 3bde413..cdf0f38 100644 --- a/engine/src/types/Resource.ts +++ b/engine/src/types/Resource.ts @@ -1,5 +1,7 @@ +import Decimal from "break_eternity.js"; + export const ResourceNames = ['RED'] as const; export type Resource = (typeof ResourceNames)[number]; -export type Resources = Record; -export type PartialResources = Partial>; +export type Resources = Record; +export type PartialResources = Partial; diff --git a/package-lock.json b/package-lock.json index a4a1fed..dbc43ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2023,6 +2023,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2424,6 +2431,15 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2576,6 +2592,29 @@ "dev": true, "license": "MIT" }, + "node_modules/command-line-args": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz", + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "find-replace": "^5.0.2", + "lodash.camelcase": "^4.3.0", + "typical": "^7.2.0" + }, + "engines": { + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2904,6 +2943,16 @@ "node": ">=0.10.0" } }, + "node_modules/eternal_notations": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eternal_notations/-/eternal_notations-1.1.1.tgz", + "integrity": "sha512-o4Pw4SyYh8j+vcPEID6B1kwo9T5qEbE4ld//297g8Rli0ZBVBGa93JWt3G5RetLL7nUO3x/u3oaZC1hDM13mgA==", + "license": "MIT", + "dependencies": { + "break_eternity.js": "^2.1.0", + "lru_cache": "^1.0.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2956,6 +3005,23 @@ "node": ">=16.0.0" } }, + "node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3527,6 +3593,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3534,6 +3606,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lru_cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lru_cache/-/lru_cache-1.0.2.tgz", + "integrity": "sha512-gssVsWiGr3hdoJtlpY21bVrN+zDDzqIIi0Fdktn6mzw2IaKENO8gyTiwawLuGWu/ecRUnxBot4ilr0Uxtk6Stw==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4145,6 +4223,15 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -4354,7 +4441,11 @@ "license": "ISC", "dependencies": { "@formatjs/intl-durationformat": "^0.10.1", - "@idle-economy/engine": "*" + "@idle-economy/engine": "*", + "command-line-args": "^6.0.1" + }, + "devDependencies": { + "@types/command-line-args": "^5.2.3" } }, "simulator/node_modules/@formatjs/ecma402-abstract": { @@ -4400,6 +4491,7 @@ "version": "0.0.0", "dependencies": { "@idle-economy/engine": "*", + "eternal_notations": "^1.1.1", "mobx": "^6.15.0", "mobx-react-lite": "^4.1.1", "react": "^19.2.0", diff --git a/simulator/package.json b/simulator/package.json index 17b2105..13a73b8 100644 --- a/simulator/package.json +++ b/simulator/package.json @@ -12,7 +12,11 @@ "license": "ISC", "type": "commonjs", "dependencies": { + "@formatjs/intl-durationformat": "^0.10.1", "@idle-economy/engine": "*", - "@formatjs/intl-durationformat": "^0.10.1" + "command-line-args": "^6.0.1" + }, + "devDependencies": { + "@types/command-line-args": "^5.2.3" } } diff --git a/simulator/src/Simulator.ts b/simulator/src/Simulator.ts index f207546..5f5661b 100644 --- a/simulator/src/Simulator.ts +++ b/simulator/src/Simulator.ts @@ -6,6 +6,7 @@ const durationFormat = new DurationFormat("en", { style: "short" }); export type SimulationStats = { actions: Action[], + finalTime: number; finalSnapshot: GameSnapshot, } @@ -50,6 +51,7 @@ export class Simulator { time += deltaTime; } + stats.finalTime = time; stats.finalSnapshot = this.makeGameSnapshot(); this.log(time, "done"); @@ -82,8 +84,8 @@ export class Simulator { lastTime = action.time; maxDeltaTime = Math.max(maxDeltaTime, deltaTime); } - this.log(0, `max action wait fime is ${maxDeltaTime}`) - this.log(0, `final data:: ${JSON.stringify(stats.finalSnapshot, undefined, 2)}`); + this.log(stats.finalTime, `max action wait fime is ${maxDeltaTime}`) + this.log(stats.finalTime, `final data: ${JSON.stringify(stats.finalSnapshot, undefined, 2)}`); } private logResources(time: number, resources: ResourceSet): void { diff --git a/simulator/src/index.ts b/simulator/src/index.ts index 18ee51c..76dbc44 100644 --- a/simulator/src/index.ts +++ b/simulator/src/index.ts @@ -1,16 +1,21 @@ import { makeGame } from '@idle-economy/engine'; import { Simulator } from './Simulator' import { Player } from './player/Player'; +import commandLineArgs from 'command-line-args' process.on('SIGTERM', () => { // Clean up resources and exit process.exit(0); }); +const optionDefinitions: commandLineArgs.OptionDefinition[] = [ + { name: 'time', alias: 't', type: Number, defaultValue: 60 }, +]; +const options = commandLineArgs(optionDefinitions); const game = makeGame(); const player = new Player(game); const simulator = new Simulator(player); -simulator.simulate(3600); \ No newline at end of file +simulator.simulate(options.time); diff --git a/web/package.json b/web/package.json index 80bf434..fd25aa4 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@idle-economy/engine": "*", + "eternal_notations": "^1.1.1", "mobx": "^6.15.0", "mobx-react-lite": "^4.1.1", "react": "^19.2.0", diff --git a/web/src/App.tsx b/web/src/App.tsx index 9d7e7d5..562a737 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,7 @@ import { root } from './state/Root'; import { Page } from './Page'; import './App.scss' +import type { ChangeEvent } from 'react'; function now(): number { return (performance.now() + performance.timeOrigin) / 1000; @@ -33,7 +34,11 @@ gameLoop(); export const App = observer(function () { function handleTickClick(): void { - root.tick(1/60); + root.tick(1 / 60); + } + + function handleNumberNotationChange(event: ChangeEvent): void { + root.setNumberNotationName(event.target.value); } // useEffect(() => { @@ -42,6 +47,11 @@ export const App = observer(function () { return ( <> +
Notation:  + +
{/* */} diff --git a/web/src/ResourcesView.tsx b/web/src/ResourcesView.tsx index b3dda17..532c853 100644 --- a/web/src/ResourcesView.tsx +++ b/web/src/ResourcesView.tsx @@ -10,7 +10,9 @@ export const ResourcesView = function (props: Props) { <> { - Object.entries(props.resources).map(([res, value]) => {formatNumber(value)}) + Object.entries(props.resources).map( + ([res, value]) => {formatNumber(value)} + ) } diff --git a/web/src/state/Root.ts b/web/src/state/Root.ts index 117eca5..8675283 100644 --- a/web/src/state/Root.ts +++ b/web/src/state/Root.ts @@ -1,5 +1,6 @@ import { makeGame, ResourceGenerator, ResourceSet } from "@idle-economy/engine"; import { makeAutoObservable } from "mobx"; +import { Notation, Presets } from 'eternal_notations'; export class Root { @@ -8,8 +9,13 @@ export class Root { public resources: ResourceSet = new ResourceSet(); public generators: ResourceGenerator[] = []; + public readonly availableNotationNames: string[] = Object.keys(Presets).filter((n: any) => !!(Presets as any)[n].name); + public numberNotationName: keyof typeof Presets = 'Standard'; + public numberNotation: Notation = Presets.Standard; + constructor() { this.copyGame(); + this.setNumberNotation(); makeAutoObservable(this); } @@ -23,6 +29,16 @@ export class Root { this.resources = this.game.resources; this.generators = this.game.generators; } + + public setNumberNotationName(value: string | keyof typeof Presets): void { + this.numberNotationName = value as keyof typeof Presets; + this.setNumberNotation(); + this.numberNotation.name + } + + private setNumberNotation() { + this.numberNotation = Presets[this.numberNotationName] as Notation; + } } export const root = new Root(); diff --git a/web/src/tools/format.ts b/web/src/tools/format.ts index 586b3f4..da91cd4 100644 --- a/web/src/tools/format.ts +++ b/web/src/tools/format.ts @@ -1,3 +1,6 @@ -export function formatNumber(value: number): string { - return (Math.round(value * 10 ** 2) / 10 ** 2).toPrecision(2); +import type Decimal from 'break_eternity.js'; +import { root } from '../state/Root'; + +export function formatNumber(value: Decimal): string { + return root.numberNotation.format(value); } \ No newline at end of file