full generation and upgrade rules revamp

This commit is contained in:
azykov@mail.ru 2026-02-14 13:37:17 +03:00
parent c9c9f03526
commit 52f75c74c0
19 changed files with 374 additions and 204 deletions

View File

@ -15,10 +15,15 @@ export class Game {
) { ) {
this.rules = rules; this.rules = rules;
this.initResources();
this.initGenerators(); this.initGenerators();
} }
private initGenerators(): void { public initResources(): void {
this.resources.setMany(ResourceSet.from(this.rules.startResources));
}
public initGenerators(): void {
this.generators.splice(0); this.generators.splice(0);
this.generators.push(...this.rules.generators.map((rule) => new ResourceGenerator(this, rule))); this.generators.push(...this.rules.generators.map((rule) => new ResourceGenerator(this, rule)));
} }

View File

@ -5,8 +5,8 @@ import { ResourceSet } from "./ResourceSet";
export class ResourceGenerator { export class ResourceGenerator {
private _level: number; private _level!: number;
private _progress: number; private _progress!: number;
public readonly game: Game; public readonly game: Game;
public readonly rule: ResourceGeneratorRule; public readonly rule: ResourceGeneratorRule;
@ -21,80 +21,91 @@ export class ResourceGenerator {
) { ) {
this.game = game; this.game = game;
this.rule = rule; this.rule = rule;
this._level = this.rule.startingLevel; this.reset();
this._progress = 0;
} }
public tick(time: number): void { public tick(time: number): void {
if (this.period === 0) { const period = this.getPeriod();
if (period === 0) {
this._progress = 0; this._progress = 0;
return; return;
} }
if (this.rule.manualProgressReset) { let progress = this._progress + time;
this._progress = Math.min(this._progress + time, this.period);
} if (this.rule.manualGeneration)
this._progress = Math.min(progress, period);
else { else {
this._progress = (this._progress + time) % this.period; this._progress = progress % period;
this.game.resources.add(this.generation.times(time / this.period)); const times = Math.floor(progress / period);
if (times > 0)
this.generate(times);
} }
} }
public upgrade(): boolean { public reset(): void {
if (this.rule.manualProgressReset && this._progress < this.period) { this._level = this.rule.startingLevel;
return false;
}
if (this.isOpen && this.canAffordUpgrade) {
console.log('ok to upgrade');
this.game.resources.remove(this.upgradePrice);
this.increaseLevel();
if (this.rule.manualProgressReset)
this.game.resources.add(this.generation);
if (this.rule.resetProgressOnUpgrade)
this._progress = 0; this._progress = 0;
}
public manualGenerate(): boolean {
if (this.rule.manualGeneration && !this.isFull)
return false;
this._progress = 0;
this.generate(1);
return true; return true;
} }
return false; private generate(times: number): void {
const gain = this.getGain();
this.rule.onGenerate(this.level, this.game, gain.mul(times));
}
public upgrade(): void {
this.rule.onUpgrade?.(this.level, this.game);
this.increaseLevel();
} }
private increaseLevel() { private increaseLevel() {
this._level++; this._level++;
} }
public get period(): number { public get isFull(): boolean {
return this.rule.period(this.level); const period = this.getPeriod();
return period === 0 || this._progress === period;
} }
public get generation(): ResourceSet { public getPeriod(level?: number): number {
return ResourceSet.from(this.rule.generation(this.level)); return this.rule.period(level ?? this.level, this.game);
}
public get visibilityPrice(): ResourceSet {
return ResourceSet.from(this.rule.visibilityPrice(this.level));
}
public get openPrice(): ResourceSet {
return ResourceSet.from(this.rule.openPrice(this.level));
}
public get upgradePrice(): ResourceSet {
return ResourceSet.from(this.rule.upgradePrice(this.level));
} }
public get isVisible(): boolean { public get isVisible(): boolean {
return this.game.resources.gte(this.visibilityPrice); return this.rule.isVisible(this.level, this.game);
} }
public get isOpen(): boolean { public getUpgradePrice(level?: number): ResourceSet {
return this.game.resources.gte(this.openPrice); return this.rule.upgradePrice(level ?? this.level, this.game);
} }
public get canAffordUpgrade(): boolean { public upgradableToLevel(limit: number = 100000): number {
return this.game.resources.gte(this.upgradePrice);
for (let idx = 0; idx < limit; idx++) {
const level = this.level + idx;
if (!this.rule.isUpgradable(level, this.game, this.getUpgradePrice(level)))
return level;
}
return -1;
}
public get isUpgradable(): boolean {
return this.rule.isUpgradable(this.level, this.game, this.getUpgradePrice());
}
public getGain(level?: number): ResourceSet {
return this.rule.generationGain(level ?? this.level, this.game);
} }
public snapshot(): ResourceGeneratorSnapshot { public snapshot(): ResourceGeneratorSnapshot {

View File

@ -1,57 +1,81 @@
import Decimal from "break_eternity.js"; import Decimal from "break_eternity.js";
import { ResourceNames, type PartialResources, type Resource } from "../types"; import { type PartialResources } from "../types";
export class ResourceSet { export class ResourceSet {
private items = new Map<Resource, Decimal>(); private items = new Map<string, Decimal>();
public static get zero(): ResourceSet {
return new ResourceSet();
}
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, Decimal>(Object.entries(source) as [Resource, Decimal][]); inst.items = new Map<string, Decimal>(Object.entries(source) as [string, Decimal][]);
return inst; return inst;
} }
public clear(): void {
this.items.clear();
}
public clone(): ResourceSet { public clone(): ResourceSet {
return ResourceSet.from(this.toObject()); return ResourceSet.from(this.toObject());
} }
public get(key: Resource): Decimal { public keys(mergeWith?: ResourceSet): string[] {
const keys = Array.from(this.items.keys());
const mergeWithKeys = mergeWith ? mergeWith.keys() : [];
return [...keys, ...mergeWithKeys.filter((mk) => !keys.includes(mk))];
}
public get(key: string): Decimal {
return this.items.get(key) ?? Decimal.dZero; return this.items.get(key) ?? Decimal.dZero;
} }
public set(key: Resource, value: Decimal): void { public set(key: string, value: Decimal): void {
this.items.set(key, value); this.items.set(key, value);
} }
public predicate(predicate: (res: Resource) => boolean): boolean { public setMany(values: ResourceSet): void {
return ResourceNames.every((res) => predicate(res)); this.items.clear();
[...values.items.entries()].forEach(([k, v]) => this.items.set(k, v));
}
public get isZero(): boolean {
return this.predicate(this.keys(), (res) => this.get(res).eq(0));
}
public predicate(keys: string[], predicate: (res: string) => boolean): boolean {
return keys.every((res) => predicate(res));
} }
public gte(arg: ResourceSet): boolean { public gte(arg: ResourceSet): boolean {
return this.predicate((res) => this.get(res).gte(arg.get(res))); return this.predicate(this.keys(arg), (res) => this.get(res).gte(arg.get(res)));
} }
public static transform(target: ResourceSet, predicate: (res: Resource) => Decimal): void { public static transform(keys: string[], target: ResourceSet, predicate: (res: string) => Decimal): void {
for (const res of ResourceNames) for (const res of keys)
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).plus(arg.get(res))); ResourceSet.transform(this.keys(arg), 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).plus(arg.get(res))); ResourceSet.transform(this.keys(arg), 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).minus(arg.get(res))); ResourceSet.transform(this.keys(arg), this, (res) => this.get(res).minus(arg.get(res)));
} }
public times(factor: number): ResourceSet { public mul(factor: number): ResourceSet {
const inst = new ResourceSet(); const inst = new ResourceSet();
ResourceSet.transform(inst, (res) => this.get(res).multiply(factor)); ResourceSet.transform(this.keys(), inst, (res) => this.get(res).multiply(factor));
return inst; return inst;
} }

View File

@ -1,5 +1,7 @@
import type { Resources } from "../types";
import type { ResourceGeneratorRule } from "./ResourceGeneratorRule"; import type { ResourceGeneratorRule } from "./ResourceGeneratorRule";
export type GameRules = { export type GameRules = {
startResources: Resources,
generators: ResourceGeneratorRule[], generators: ResourceGeneratorRule[],
} }

View File

@ -1,13 +1,15 @@
import type { PartialResources } from "../types"; import type { Game, ResourceSet } from "../game";
export type ResourceGeneratorRule = { export type ResourceGeneratorRule = {
name: string, name: string,
startingLevel: number, // usually zero startingLevel: number, // usually zero
period: (level: number) => number, period: (level: number, game: Game) => number,
generation: (level: number) => PartialResources, // per period isVisible: (level: number, game: Game) => boolean;
visibilityPrice: (level: number) => PartialResources; upgradePrice: (level: number, game: Game) => ResourceSet;
openPrice: (level: number) => PartialResources; isUpgradable: (level: number, game: Game, upgradePrice: ResourceSet) => boolean;
upgradePrice: (level: number) => PartialResources; generationGain: (level: number, game: Game) => ResourceSet,
onGenerate: (level: number, game: Game, gain: ResourceSet) => void,
onUpgrade?: (level: number, game: Game) => void,
resetProgressOnUpgrade: boolean; resetProgressOnUpgrade: boolean;
manualProgressReset: boolean; manualGeneration: boolean;
}; };

View File

@ -1,33 +1,63 @@
import Decimal from "break_eternity.js";
import type { GameRules } from "../rules/GameRules"; import type { GameRules } from "../rules/GameRules";
import { simpleResGen, resourceGen } from './utils'; import { simpleResGen, resourceGen } from './utils';
import { ResourceSet } from "../game";
export const rules: GameRules = { export const rules: GameRules = {
startResources: { prestigeMul: new Decimal(1) },
generators: [ generators: [
{ {
name: 'white', name: 'white',
startingLevel: 1, startingLevel: 1,
resetProgressOnUpgrade: true, resetProgressOnUpgrade: true,
manualProgressReset: true, manualGeneration: true,
period: (level) => level ? 0.5 : 0, period: (level) => level ? 3 : 0,
generation: resourceGen({ RED: { offset: 14, power: 2, factor: 1 } }), isVisible: () => true,
visibilityPrice: () => ({}), upgradePrice: () => ResourceSet.zero,
openPrice: () => ({}), isUpgradable: () => false,
upgradePrice: () => ({}), generationGain: (level, game) => resourceGen({ RED: { offset: 14, power: 2, factor: 1 } }, level),
onGenerate: (level, game, gain) => game.resources.add(gain),
}, },
simpleResGen({ simpleResGen({
name: 'red', name: 'red',
startingLevel: 1, startingLevel: 1,
period: 1, period: 1,
generation: { RED: { offset: 3, power: 1.5, factor: 1 } }, upgradePrice: { RED: { offset: 3, power: 2.1, factor: 10 } },
openPrice: { RED: { offset: 3, power: 2.1, factor: 10 } } generatesResources: { RED: { offset: 3, power: 1.5, factor: 1 } },
}), }),
...['orange', 'yellow', 'green', 'cyan', 'blue', 'violet'] ...['orange', 'yellow', 'green', 'cyan', 'blue', 'violet']
.map((color, idx) => simpleResGen({ .map((color, idx) => simpleResGen({
name: color, name: color,
period: idx + 2, period: idx + 2,
generation: { RED: { power: 1.5, factor: (idx + 2) ** 2 } }, generatesResources: { RED: { power: 1.5, factor: (idx + 2) ** 2 } },
// visibilityPrice: { RED: { offset: 5 * 10 ** (idx + 1) } }, // visibilityPrice: { RED: { offset: 5 * 10 ** (idx + 1) } },
openPrice: { RED: { offset: 10 ** (idx + 2), power: 2.1, factor: 10 * 5 ** (idx + 1) } } upgradePrice: { RED: { offset: 10 ** (idx + 2), power: 2.1, factor: 10 * 5 ** (idx + 1) } }
})), })),
// {
// name: 'prestige',
// startingLevel: 0,
// period: () => 0,
// // generation: resourceGen({ prestigeMul: { offset: 0, power: 0.2, factor: 1 } }),
// openPrice: (level) => ({ RED: new Decimal(50) }),
// upgradePrice: () => ({}),
// visibilityPrice: () => ({}),
// manualProgressReset: true,
// resetProgressOnUpgrade: true,
// generation: (level, game, dryRun) => {
// if (!dryRun) {
// console.log('prestige ' + level);
// game.generators.filter((gen) => gen.rule.name !== 'prestige').forEach((gen) => gen.reset());
// ResourceSet.transform(
// game.resources.keys().filter((res) => res != 'prestigeMul'),
// game.resources,
// () => Decimal.dZero,
// );
// }
// return resourceGen({ prestigeMul: { offset: 0, power: 1, factor: 1 } })(level, game);
// // game.resources.clear();
// }
// }
], ],
} }

View File

@ -1,6 +1,7 @@
import Decimal from "break_eternity.js"; import Decimal from "break_eternity.js";
import type { PartialResources, Resource, Resources } from "../types"; import type { PartialResources, Resources } from "../types";
import type { ResourceGeneratorRule } from "../rules/ResourceGeneratorRule"; import type { ResourceGeneratorRule } from "../rules/ResourceGeneratorRule";
import { ResourceSet, type Game } from "../game";
type ResourceGenParams = { type ResourceGenParams = {
offset?: number, offset?: number,
@ -12,23 +13,49 @@ type SimpleResGenParams = {
name: string, name: string,
startingLevel?: number, startingLevel?: number,
period: number, period: number,
generation: Partial<Record<Resource, ResourceGenParams>>, visibilityPrice?: Partial<Record<string, ResourceGenParams>>,
visibilityPrice?: Partial<Record<Resource, ResourceGenParams>>, // openPrice: Partial<Record<string, ResourceGenParams>>,
openPrice: Partial<Record<Resource, ResourceGenParams>>, upgradePrice: Partial<Record<string, ResourceGenParams>>,
upgradePrice?: Partial<Record<Resource, ResourceGenParams>>, generatesResources: Partial<Record<string, ResourceGenParams>>,
} }
export function resourceGen(p?: Partial<Record<Resource, ResourceGenParams>>): (lvl: number) => PartialResources { // export function resourceGen(p?: Partial<Record<string, ResourceGenParams>>, multiplyByResource?: string): (lvl: number, game: Game) => PartialResources {
if (!p) // if (!p)
return () => ({}); // return () => ({});
return (lvl) => Object.fromEntries( // return (lvl, game) => Object.fromEntries(
// Object.entries(p)
// .map(([res, rp]) => {
// const value = rp
// ? new Decimal((rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0))
// : Decimal.dZero;
// return [
// res,
// multiplyByResource
// ? value.mul(game.resources.get(multiplyByResource))
// : value,
// ];
// })
// ) as Resources;
// }
export function resourceGen(p: Partial<Record<string, ResourceGenParams>> | undefined, level: number): ResourceSet {
if (!p)
return ResourceSet.zero;
return ResourceSet.from(Object.fromEntries(
Object.entries(p) Object.entries(p)
.map(([res, rp]) => [ .map(([res, rp]) => {
const value = rp
? new Decimal((rp.offset ?? 0) + Math.pow(level, rp.power ?? 1) * (rp.factor ?? 0))
: Decimal.dZero;
return [
res, res,
new Decimal((rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0)), value,
]) ];
) as Resources; })
));
} }
export function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule { export function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule {
@ -37,11 +64,12 @@ export function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule
name: params.name, name: params.name,
startingLevel: params.startingLevel ?? 0, startingLevel: params.startingLevel ?? 0,
resetProgressOnUpgrade: true, resetProgressOnUpgrade: true,
manualProgressReset: false, manualGeneration: false,
period: (level) => level ? params.period : 0, period: (level) => level ? params.period : 0,
generation: resourceGen(params.generation), isVisible: (level, game) => game.resources.gte(resourceGen(params.visibilityPrice, level)),
visibilityPrice: resourceGen(params.visibilityPrice), upgradePrice: (level, game) => resourceGen(params.upgradePrice, level),
openPrice: resourceGen(params.openPrice), isUpgradable: (level, game, upgradePrice) => game.resources.gte(upgradePrice),
upgradePrice: resourceGen(params.upgradePrice), generationGain: (level, game) => resourceGen(params.generatesResources, level),
onGenerate: (level, game, gain) => game.resources.add(gain),
} }
} }

View File

@ -1,7 +1,7 @@
import Decimal from "break_eternity.js"; 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, Decimal>; export type Resources = Record<string, Decimal>;
export type PartialResources = Partial<Resources>; export type PartialResources = Partial<Resources>;

View File

@ -24,6 +24,7 @@ export class Simulator {
public simulate(duration: number) { public simulate(duration: number) {
const stats: SimulationStats = { const stats: SimulationStats = {
actions: [], actions: [],
finalTime: 0,
finalSnapshot: { resources: {}, generators: [] }, finalSnapshot: { resources: {}, generators: [] },
}; };

View File

@ -1,42 +0,0 @@
// #root {
// max-width: 1280px;
// margin: 0 auto;
// padding: 2rem;
// text-align: center;
// }
// .logo {
// height: 6em;
// padding: 1.5em;
// will-change: filter;
// transition: filter 300ms;
// }
// .logo:hover {
// filter: drop-shadow(0 0 2em #646cffaa);
// }
// .logo.react:hover {
// filter: drop-shadow(0 0 2em #61dafbaa);
// }
// @keyframes logo-spin {
// from {
// transform: rotate(0deg);
// }
// to {
// transform: rotate(360deg);
// }
// }
// @media (prefers-reduced-motion: no-preference) {
// a:nth-of-type(2) .logo {
// animation: logo-spin infinite 20s linear;
// }
// }
// .card {
// padding: 2em;
// }
// .read-the-docs {
// color: #888;
// }

View File

@ -3,21 +3,16 @@ import { observer } from 'mobx-react-lite';
import { root } from './state/Root'; import { root } from './state/Root';
import { Page } from './Page'; import { Page } from './Page';
import './App.scss'
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { now } from './tools/time';
function now(): number {
return (performance.now() + performance.timeOrigin) / 1000;
}
const gameLoop = function () { const gameLoop = function () {
let lastTime = now();
function loop() { function loop() {
const nowTime = now(); const nowTime = now();
const deltaTime = nowTime - lastTime; const deltaTime = nowTime - root.lastGameTime;
lastTime = now(); root.setLastGameTime(now());
// console.log('loop tick ' + deltaTime); // console.log('loop tick ' + deltaTime);
root.tick(deltaTime); root.tick(deltaTime);
@ -33,10 +28,6 @@ gameLoop();
export const App = observer(function () { export const App = observer(function () {
function handleTickClick(): void {
root.tick(1 / 60);
}
function handleNumberNotationChange(event: ChangeEvent<HTMLSelectElement, HTMLSelectElement>): void { function handleNumberNotationChange(event: ChangeEvent<HTMLSelectElement, HTMLSelectElement>): void {
root.setNumberNotationName(event.target.value); root.setNumberNotationName(event.target.value);
} }
@ -47,6 +38,7 @@ export const App = observer(function () {
return ( return (
<> <>
<div>{root.lastGameTime}</div>
<div>Notation:&nbsp; <div>Notation:&nbsp;
<select onChange={handleNumberNotationChange} value={root.numberNotationName}> <select onChange={handleNumberNotationChange} value={root.numberNotationName}>
{root.availableNotationNames.map((n) => <option key={n} value={n}>{n}</option>)} {root.availableNotationNames.map((n) => <option key={n} value={n}>{n}</option>)}

View File

@ -1,35 +1,27 @@
import { type ResourceGenerator } from "@idle-economy/engine";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { root } from "./state/Root"; import { root } from "./state/Root";
import { ResourcesView } from "./ResourcesView"; import { ResourcesView } from "./ResourcesView";
import { ResourceGeneratorView } from "./ResourceGeneratorView";
import { ProgressView } from "./ProgressView"; import { ProgressView } from "./ProgressView";
export const Page = observer(function () { export const Page = observer(function () {
function handleGeneratorUpgradeClick(generator: ResourceGenerator): void {
generator.upgrade();
}
return (<> return (<>
<div>Resources: <ResourcesView resources={root.resources.toObject()} /></div> <div>Resources: <ResourcesView resources={root.resources.toObject()} /></div>
<div> <div className="generators">
{ {
root.generators.filter((gen) => gen.isVisible).map((gen) => (<button className={`generator ${gen.rule.name}`} key={gen.rule.name} disabled={!gen.isOpen} onClick={() => handleGeneratorUpgradeClick(gen)}> root.generators
<div className="name">{gen.rule.name}</div> // .filter((gen) => gen.isVisible)
<div>LEVEL {gen.level}</div> .map((gen) => <ResourceGeneratorView generator={gen} key={gen.rule.name} />)
<div>+<ResourcesView resources={gen.generation.toObject()} /></div>
{/* <div>+<ResourcesView resources={gen.generation.toObject()} /> / {gen.period} sec</div> */}
<div><ProgressView period={gen.period} progress={gen.progress} /></div>
{
true
? <div>{gen.level} {gen.level + 1} for <ResourcesView resources={gen.openPrice.toObject()} /></div>
: <></>
}
</button>))
} }
</div> </div>
<div className="progresses">
{
root.generators
// .filter((gen) => gen.isVisible)
.map((gen) => <ProgressView period={gen.getPeriod()} progress={gen.progress} name={gen.rule.name} />)
}
</div>
</>) </>)
}); });

View File

@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
type Props = { type Props = {
name?: string,
progress: number, progress: number,
period: number, period: number,
} }
@ -10,15 +11,10 @@ export const ProgressView = observer(function (props: Props) {
<> <>
{/* <div>{formatNumber(props.progress)} / {formatNumber(props.period)}</div> */} {/* <div>{formatNumber(props.progress)} / {formatNumber(props.period)}</div> */}
<div <div
className="progressBar" className={`progressBar`}
style={{ style={{
backgroundColor: '#ffffff', backgroundColor: props.name ? props.name : '#ffffff',
width: `${props.period === 0 ? 0 : props.progress / props.period * 100}%`, width: `${props.period === 0 ? 0 : props.progress / props.period * 100}%`,
fontSize: '0.1px',
margin: '8px 0px',
minHeight: '0.25rem',
borderRadius: '4px',
boxSizing: 'border-box',
}} }}
>&nbsp;</div> >&nbsp;</div>
</> </>

View File

@ -0,0 +1,52 @@
import { type ResourceGenerator } from "@idle-economy/engine";
import { observer } from "mobx-react-lite";
import { ResourcesView } from "./ResourcesView";
import { root } from "./state/Root";
type Props = {
generator: ResourceGenerator,
}
export const ResourceGeneratorView = observer(function (props: Props) {
let gen = props.generator;
let showName = gen.rule.name === 'prestige';
function handleGeneratorClick(): void {
if (props.generator.rule.manualGeneration)
props.generator.manualGenerate();
}
function handleGeneratorUpgradeClick(): void {
while (props.generator.isUpgradable)
props.generator.upgrade();
}
const upgradeLevelAvailable = gen.upgradableToLevel();
return (<>
<div
className={['generator', gen.rule.name].join(' ')}
data-lasttime={root.lastGameTime}
>
<div
className={['body', gen.rule.manualGeneration ? 'interactive' : ''].join(' ')}
onClick={handleGeneratorClick}
>
<div className={`name ${showName ? '' : 'hidden'}`}>{gen.rule.name}</div>
<div>LEVEL {gen.level}</div>
<div>+<ResourcesView resources={gen.getGain().toObject()} /> / {gen.getPeriod()} sec</div>
{/* <div><ProgressView period={gen.getPeriod()} progress={gen.progress} /></div> */}
{/* <div>{gen.isVisible ? 'visible' : 'not visible'}</div>
<div>{gen.isUpgradable ? 'upgradable' : 'not upgradable'}</div>
<div>{gen.isFull ? 'full' : 'not full'}</div> */}
</div>
<button onClick={handleGeneratorUpgradeClick} disabled={!gen.isUpgradable} className="upgrade">
<div>UPGRADE {gen.level} {upgradeLevelAvailable}</div>
<div>at: <ResourcesView resources={gen.getUpgradePrice(upgradeLevelAvailable).toObject()} /></div>
<div>cost: </div>
<div>+<ResourcesView resources={gen.getGain(upgradeLevelAvailable).toObject()} /> / {gen.getPeriod(upgradeLevelAvailable)} sec</div>
</button>
</div>
</>)
});

View File

@ -6,15 +6,15 @@ type Props = {
} }
export const ResourcesView = function (props: Props) { export const ResourcesView = function (props: Props) {
return ( const list = Object.entries(props.resources);
<>
return (<>
<span className="resources"> <span className="resources">
{ {
Object.entries(props.resources).map( list.length
([res, value]) => <span key={res} style={{ color: res }}>{formatNumber(value)}</span> ? list.map(([res, value]) => <span key={res} style={{ color: res }}>{res} {formatNumber(value)}</span>)
) : 'none'
} }
</span> </span>
</> </>);
);
}; };

View File

@ -14,6 +14,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
* {
-webkit-user-select: none;
-webkit-touch-callout: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* /*
a { a {
font-weight: 500; font-weight: 500;
@ -69,16 +77,60 @@ button:focus-visible {
} }
} */ } */
.generator { button,
margin: 1rem; .interactive {
padding: 0.5rem 1rem; cursor: pointer;
background-color: #1a1a1a; }
border: 1px solid transparent;
.progresses {
& .progressBar {
min-height: 2rem;
}
}
.progressBar {
background-color: white;
font-size: 0.1px;
margin: 8px 0px;
min-height: 0.25rem;
border-radius: 4px; border-radius: 4px;
box-sizing: border-box;
}
.generator {
margin: 1em;
padding: 0.5em;
background-color: #ffffff40;
border: 1px solid #ffffff60;
color: white;
border-radius: 4px;
text-align: center;
&.interactive {
&:hover {
box-shadow: 0px 0px 8px #ffffff40;
}
}
& .name { & .name {
font-size: bold;
&.hidden {
display: none; display: none;
} }
}
&:disabled {
opacity: 0.5;
}
& .upgrade {
padding: 0.5em;
background: transparent;
border: 1px solid white;
border-radius: 4px;
color: white;
&:disabled { &:disabled {
opacity: 0.5; opacity: 0.5;
@ -86,7 +138,7 @@ button:focus-visible {
&:hover { &:hover {
box-shadow: 0px 0px 8px #ffffff40; box-shadow: 0px 0px 8px #ffffff40;
// border-width: 1px; }
} }
&.red { &.red {
@ -124,3 +176,18 @@ button:focus-visible {
border-color: #ff00ff80; border-color: #ff00ff80;
} }
} }
.generators {
display: flex;
flex-wrap: wrap;
}
.resources>span {
// min-width: 6em;
display: inline-block;
// border: 1px solid red;
&+span {
margin-left: 1em;
}
}

View File

@ -1,6 +1,7 @@
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'; import { Notation, Presets } from 'eternal_notations';
import { now } from "../tools/time";
export class Root { export class Root {
@ -9,6 +10,8 @@ export class Root {
public resources: ResourceSet = new ResourceSet(); public resources: ResourceSet = new ResourceSet();
public generators: ResourceGenerator[] = []; public generators: ResourceGenerator[] = [];
public lastGameTime = now();
public readonly availableNotationNames: string[] = Object.keys(Presets).filter((n: any) => !!(Presets as any)[n].name).sort(); public readonly availableNotationNames: string[] = Object.keys(Presets).filter((n: any) => !!(Presets as any)[n].name).sort();
public numberNotationName: keyof typeof Presets = 'Standard'; public numberNotationName: keyof typeof Presets = 'Standard';
public numberNotation: Notation = Presets.Standard; public numberNotation: Notation = Presets.Standard;
@ -20,6 +23,10 @@ export class Root {
makeAutoObservable(this); makeAutoObservable(this);
} }
public setLastGameTime(value: number) {
this.lastGameTime = value;
}
public tick(deltaTime: number): void { public tick(deltaTime: number): void {
Array.from(this.game.tick(deltaTime)); Array.from(this.game.tick(deltaTime));
this.copyGame(); this.copyGame();

View File

@ -1,6 +1,6 @@
import type Decimal from 'break_eternity.js'; import type Decimal from 'break_eternity.js';
import { root } from '../state/Root'; import { root } from '../state/Root';
export function formatNumber(value: Decimal): string { export function formatNumber(value: Decimal | undefined): string {
return root.numberNotation.format(value); return root.numberNotation.format(value ?? 0);
} }

3
web/src/tools/time.ts Normal file
View File

@ -0,0 +1,3 @@
export function now(): number {
return (performance.now() + performance.timeOrigin) / 1000;
}