full generation and upgrade rules revamp
This commit is contained in:
parent
c9c9f03526
commit
52f75c74c0
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
this._progress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public manualGenerate(): boolean {
|
||||||
|
if (this.rule.manualGeneration && !this.isFull)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isOpen && this.canAffordUpgrade) {
|
this._progress = 0;
|
||||||
console.log('ok to upgrade');
|
this.generate(1);
|
||||||
this.game.resources.remove(this.upgradePrice);
|
return true;
|
||||||
this.increaseLevel();
|
}
|
||||||
|
|
||||||
if (this.rule.manualProgressReset)
|
private generate(times: number): void {
|
||||||
this.game.resources.add(this.generation);
|
const gain = this.getGain();
|
||||||
|
this.rule.onGenerate(this.level, this.game, gain.mul(times));
|
||||||
|
}
|
||||||
|
|
||||||
if (this.rule.resetProgressOnUpgrade)
|
public upgrade(): void {
|
||||||
this._progress = 0;
|
this.rule.onUpgrade?.(this.level, this.game);
|
||||||
return true;
|
this.increaseLevel();
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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[],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
@ -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]) => {
|
||||||
res,
|
const value = rp
|
||||||
new Decimal((rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0)),
|
? new Decimal((rp.offset ?? 0) + Math.pow(level, rp.power ?? 1) * (rp.factor ?? 0))
|
||||||
])
|
: Decimal.dZero;
|
||||||
) as Resources;
|
return [
|
||||||
|
res,
|
||||||
|
value,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
// }
|
|
||||||
|
|
@ -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:
|
<div>Notation:
|
||||||
<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>)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>)
|
</>)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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',
|
|
||||||
}}
|
}}
|
||||||
> </div>
|
> </div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
</>)
|
||||||
|
});
|
||||||
|
|
@ -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);
|
||||||
<>
|
|
||||||
<span className="resources">
|
return (<>
|
||||||
{
|
<span className="resources">
|
||||||
Object.entries(props.resources).map(
|
{
|
||||||
([res, value]) => <span key={res} style={{ color: res }}>{formatNumber(value)}</span>
|
list.length
|
||||||
)
|
? list.map(([res, value]) => <span key={res} style={{ color: res }}>{res} {formatNumber(value)}</span>)
|
||||||
}
|
: 'none'
|
||||||
</span>
|
}
|
||||||
</>
|
</span>
|
||||||
);
|
</>);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,24 +77,68 @@ 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 {
|
||||||
display: none;
|
font-size: bold;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
& .upgrade {
|
||||||
box-shadow: 0px 0px 8px #ffffff40;
|
padding: 0.5em;
|
||||||
// border-width: 1px;
|
background: transparent;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 8px #ffffff40;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.red {
|
&.red {
|
||||||
|
|
@ -123,4 +175,19 @@ button:focus-visible {
|
||||||
background-color: #ff00ff30;
|
background-color: #ff00ff30;
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -19,6 +22,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));
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function now(): number {
|
||||||
|
return (performance.now() + performance.timeOrigin) / 1000;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue