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";
export class ResourceSet {
private items = new Map<Resource, number>();
private items = new Map<Resource, Decimal>();
public static from(source: PartialResources): 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;
}
@ -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;
}

View File

@ -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,

View File

@ -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<Resource, number>;
export type PartialResources = Partial<Record<Resource, number>>;
export type Resources = Record<Resource, Decimal>;
export type PartialResources = Partial<Resources>;

94
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -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 {

View File

@ -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);
simulator.simulate(options.time);

View File

@ -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",

View File

@ -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;
@ -36,12 +37,21 @@ export const App = observer(function () {
root.tick(1 / 60);
}
function handleNumberNotationChange(event: ChangeEvent<HTMLSelectElement, HTMLSelectElement>): void {
root.setNumberNotationName(event.target.value);
}
// useEffect(() => {
// gameLoop();
// }, []);
return (
<>
<div>Notation:&nbsp;
<select onChange={handleNumberNotationChange} value={root.numberNotationName}>
{root.availableNotationNames.map((n) => <option key={n} value={n}>{n}</option>)}
</select>
</div>
<Page />
{/* <button onClick={handleTickClick}>tick</button> */}
</>

View File

@ -10,7 +10,9 @@ export const ResourcesView = function (props: Props) {
<>
<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>
</>

View File

@ -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();

View File

@ -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);
}