break_eternity and eternal_notations. cmd line option for time

This commit is contained in:
azykov@mail.ru 2026-02-13 15:39:26 +03:00
parent 03d4948f64
commit a89f9b8141
12 changed files with 165 additions and 26 deletions

View File

@ -1,11 +1,12 @@
import Decimal from "break_eternity.js";
import { ResourceNames, type PartialResources, type Resource } from "../types"; import { ResourceNames, type PartialResources, type Resource } from "../types";
export class ResourceSet { export class ResourceSet {
private items = new Map<Resource, number>(); private items = new Map<Resource, Decimal>();
public static from(source: PartialResources): ResourceSet { public static from(source: PartialResources): ResourceSet {
const inst = new ResourceSet(); const inst = new ResourceSet();
inst.items = new Map<Resource, number>(Object.entries(source) as [Resource, number][]); inst.items = new Map<Resource, Decimal>(Object.entries(source) as [Resource, Decimal][]);
return inst; return inst;
} }
@ -13,11 +14,11 @@ export class ResourceSet {
return ResourceSet.from(this.toObject()); return ResourceSet.from(this.toObject());
} }
public get(key: Resource): number { public get(key: Resource): Decimal {
return this.items.get(key) ?? 0; 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); this.items.set(key, value);
} }
@ -26,31 +27,31 @@ export class ResourceSet {
} }
public gte(arg: ResourceSet): boolean { 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) for (const res of ResourceNames)
target.set(res, predicate(res)); target.set(res, predicate(res));
} }
public sum(arg: ResourceSet): ResourceSet { public sum(arg: ResourceSet): ResourceSet {
const inst = new 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; return inst;
} }
public add(arg: ResourceSet): void { 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 { 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 { public times(factor: number): ResourceSet {
const inst = new ResourceSet(); const inst = new ResourceSet();
ResourceSet.transform(inst, (res) => this.get(res) * factor); ResourceSet.transform(inst, (res) => this.get(res).multiply(factor));
return inst; return inst;
} }

View File

@ -1,6 +1,7 @@
import Decimal from 'break_eternity.js';
import { Game } from './game'; import { Game } from './game';
import type { ResourceGeneratorRule } from './rules'; import type { ResourceGeneratorRule } from './rules';
import type { PartialResources, Resource } from './types'; import type { PartialResources, Resource, Resources } from './types';
export * from './types'; export * from './types';
export * from './rules'; export * from './rules';
@ -33,9 +34,9 @@ function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule {
Object.entries(p) Object.entries(p)
.map(([res, rp]) => [ .map(([res, rp]) => [
res, 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 { return {
@ -60,7 +61,7 @@ export function makeGame(): Game {
generation: { RED: { offset: 3, power: 1.5, factor: 1 } }, generation: { RED: { offset: 3, power: 1.5, factor: 1 } },
openPrice: { RED: { offset: 10, power: 2.1, factor: 10 } } 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({ .map((color, idx) => simpleResGen({
name: color, name: color,
period: idx + 2, period: idx + 2,

View File

@ -1,5 +1,7 @@
import Decimal from "break_eternity.js";
export const ResourceNames = ['RED'] as const; export const ResourceNames = ['RED'] as const;
export type Resource = (typeof ResourceNames)[number]; export type Resource = (typeof ResourceNames)[number];
export type Resources = Record<Resource, number>; export type Resources = Record<Resource, Decimal>;
export type PartialResources = Partial<Record<Resource, number>>; export type PartialResources = Partial<Resources>;

94
package-lock.json generated
View File

@ -2023,6 +2023,13 @@
"@babel/types": "^7.28.2" "@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": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@ -2424,6 +2431,15 @@
"dev": true, "dev": true,
"license": "Python-2.0" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2576,6 +2592,29 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -2904,6 +2943,16 @@
"node": ">=0.10.0" "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": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -2956,6 +3005,23 @@
"node": ">=16.0.0" "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": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@ -3527,6 +3593,12 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -3534,6 +3606,12 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -4145,6 +4223,15 @@
"typescript": ">=4.8.4 <6.0.0" "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": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@ -4354,7 +4441,11 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@formatjs/intl-durationformat": "^0.10.1", "@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": { "simulator/node_modules/@formatjs/ecma402-abstract": {
@ -4400,6 +4491,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@idle-economy/engine": "*", "@idle-economy/engine": "*",
"eternal_notations": "^1.1.1",
"mobx": "^6.15.0", "mobx": "^6.15.0",
"mobx-react-lite": "^4.1.1", "mobx-react-lite": "^4.1.1",
"react": "^19.2.0", "react": "^19.2.0",

View File

@ -12,7 +12,11 @@
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
"dependencies": { "dependencies": {
"@formatjs/intl-durationformat": "^0.10.1",
"@idle-economy/engine": "*", "@idle-economy/engine": "*",
"@formatjs/intl-durationformat": "^0.10.1" "command-line-args": "^6.0.1"
},
"devDependencies": {
"@types/command-line-args": "^5.2.3"
} }
} }

View File

@ -6,6 +6,7 @@ const durationFormat = new DurationFormat("en", { style: "short" });
export type SimulationStats = { export type SimulationStats = {
actions: Action[], actions: Action[],
finalTime: number;
finalSnapshot: GameSnapshot, finalSnapshot: GameSnapshot,
} }
@ -50,6 +51,7 @@ export class Simulator {
time += deltaTime; time += deltaTime;
} }
stats.finalTime = time;
stats.finalSnapshot = this.makeGameSnapshot(); stats.finalSnapshot = this.makeGameSnapshot();
this.log(time, "done"); this.log(time, "done");
@ -82,8 +84,8 @@ export class Simulator {
lastTime = action.time; lastTime = action.time;
maxDeltaTime = Math.max(maxDeltaTime, deltaTime); maxDeltaTime = Math.max(maxDeltaTime, deltaTime);
} }
this.log(0, `max action wait fime is ${maxDeltaTime}`) this.log(stats.finalTime, `max action wait fime is ${maxDeltaTime}`)
this.log(0, `final data:: ${JSON.stringify(stats.finalSnapshot, undefined, 2)}`); this.log(stats.finalTime, `final data: ${JSON.stringify(stats.finalSnapshot, undefined, 2)}`);
} }
private logResources(time: number, resources: ResourceSet): void { private logResources(time: number, resources: ResourceSet): void {

View File

@ -1,16 +1,21 @@
import { makeGame } from '@idle-economy/engine'; import { makeGame } from '@idle-economy/engine';
import { Simulator } from './Simulator' import { Simulator } from './Simulator'
import { Player } from './player/Player'; import { Player } from './player/Player';
import commandLineArgs from 'command-line-args'
process.on('SIGTERM', () => { process.on('SIGTERM', () => {
// Clean up resources and exit // Clean up resources and exit
process.exit(0); process.exit(0);
}); });
const optionDefinitions: commandLineArgs.OptionDefinition[] = [
{ name: 'time', alias: 't', type: Number, defaultValue: 60 },
];
const options = commandLineArgs(optionDefinitions);
const game = makeGame(); const game = makeGame();
const player = new Player(game); const player = new Player(game);
const simulator = new Simulator(player); const simulator = new Simulator(player);
simulator.simulate(3600); simulator.simulate(options.time);

View File

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@idle-economy/engine": "*", "@idle-economy/engine": "*",
"eternal_notations": "^1.1.1",
"mobx": "^6.15.0", "mobx": "^6.15.0",
"mobx-react-lite": "^4.1.1", "mobx-react-lite": "^4.1.1",
"react": "^19.2.0", "react": "^19.2.0",

View File

@ -4,6 +4,7 @@ import { root } from './state/Root';
import { Page } from './Page'; import { Page } from './Page';
import './App.scss' import './App.scss'
import type { ChangeEvent } from 'react';
function now(): number { function now(): number {
return (performance.now() + performance.timeOrigin) / 1000; return (performance.now() + performance.timeOrigin) / 1000;
@ -36,12 +37,21 @@ export const App = observer(function () {
root.tick(1 / 60); root.tick(1 / 60);
} }
function handleNumberNotationChange(event: ChangeEvent<HTMLSelectElement, HTMLSelectElement>): void {
root.setNumberNotationName(event.target.value);
}
// useEffect(() => { // useEffect(() => {
// gameLoop(); // gameLoop();
// }, []); // }, []);
return ( return (
<> <>
<div>Notation:&nbsp;
<select onChange={handleNumberNotationChange} value={root.numberNotationName}>
{root.availableNotationNames.map((n) => <option key={n} value={n}>{n}</option>)}
</select>
</div>
<Page /> <Page />
{/* <button onClick={handleTickClick}>tick</button> */} {/* <button onClick={handleTickClick}>tick</button> */}
</> </>

View File

@ -10,7 +10,9 @@ export const ResourcesView = function (props: Props) {
<> <>
<span className="resources"> <span className="resources">
{ {
Object.entries(props.resources).map(([res, value]) => <span style={{ color: res }}>{formatNumber(value)}</span>) Object.entries(props.resources).map(
([res, value]) => <span key={res} style={{ color: res }}>{formatNumber(value)}</span>
)
} }
</span> </span>
</> </>

View File

@ -1,5 +1,6 @@
import { makeGame, ResourceGenerator, ResourceSet } from "@idle-economy/engine"; import { makeGame, ResourceGenerator, ResourceSet } from "@idle-economy/engine";
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
import { Notation, Presets } from 'eternal_notations';
export class Root { export class Root {
@ -8,8 +9,13 @@ export class Root {
public resources: ResourceSet = new ResourceSet(); public resources: ResourceSet = new ResourceSet();
public generators: ResourceGenerator[] = []; 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() { constructor() {
this.copyGame(); this.copyGame();
this.setNumberNotation();
makeAutoObservable(this); makeAutoObservable(this);
} }
@ -23,6 +29,16 @@ export class Root {
this.resources = this.game.resources; this.resources = this.game.resources;
this.generators = this.game.generators; 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(); export const root = new Root();

View File

@ -1,3 +1,6 @@
export function formatNumber(value: number): string { import type Decimal from 'break_eternity.js';
return (Math.round(value * 10 ** 2) / 10 ** 2).toPrecision(2); import { root } from '../state/Root';
export function formatNumber(value: Decimal): string {
return root.numberNotation.format(value);
} }