full generation and upgrade rules revamp
This commit is contained in:
@@ -15,10 +15,15 @@ export class Game {
|
||||
) {
|
||||
this.rules = rules;
|
||||
|
||||
this.initResources();
|
||||
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.push(...this.rules.generators.map((rule) => new ResourceGenerator(this, rule)));
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ResourceSet } from "./ResourceSet";
|
||||
|
||||
export class ResourceGenerator {
|
||||
|
||||
private _level: number;
|
||||
private _progress: number;
|
||||
private _level!: number;
|
||||
private _progress!: number;
|
||||
|
||||
public readonly game: Game;
|
||||
public readonly rule: ResourceGeneratorRule;
|
||||
@@ -21,80 +21,91 @@ export class ResourceGenerator {
|
||||
) {
|
||||
this.game = game;
|
||||
this.rule = rule;
|
||||
this._level = this.rule.startingLevel;
|
||||
this._progress = 0;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public tick(time: number): void {
|
||||
if (this.period === 0) {
|
||||
const period = this.getPeriod();
|
||||
|
||||
if (period === 0) {
|
||||
this._progress = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.rule.manualProgressReset) {
|
||||
this._progress = Math.min(this._progress + time, this.period);
|
||||
}
|
||||
let progress = this._progress + time;
|
||||
|
||||
if (this.rule.manualGeneration)
|
||||
this._progress = Math.min(progress, period);
|
||||
else {
|
||||
this._progress = (this._progress + time) % this.period;
|
||||
this.game.resources.add(this.generation.times(time / this.period));
|
||||
this._progress = progress % period;
|
||||
const times = Math.floor(progress / period);
|
||||
if (times > 0)
|
||||
this.generate(times);
|
||||
}
|
||||
}
|
||||
|
||||
public upgrade(): boolean {
|
||||
if (this.rule.manualProgressReset && this._progress < this.period) {
|
||||
public reset(): void {
|
||||
this._level = this.rule.startingLevel;
|
||||
this._progress = 0;
|
||||
}
|
||||
|
||||
public manualGenerate(): boolean {
|
||||
if (this.rule.manualGeneration && !this.isFull)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isOpen && this.canAffordUpgrade) {
|
||||
console.log('ok to upgrade');
|
||||
this.game.resources.remove(this.upgradePrice);
|
||||
this.increaseLevel();
|
||||
this._progress = 0;
|
||||
this.generate(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rule.manualProgressReset)
|
||||
this.game.resources.add(this.generation);
|
||||
private generate(times: number): void {
|
||||
const gain = this.getGain();
|
||||
this.rule.onGenerate(this.level, this.game, gain.mul(times));
|
||||
}
|
||||
|
||||
if (this.rule.resetProgressOnUpgrade)
|
||||
this._progress = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
public upgrade(): void {
|
||||
this.rule.onUpgrade?.(this.level, this.game);
|
||||
this.increaseLevel();
|
||||
}
|
||||
|
||||
private increaseLevel() {
|
||||
this._level++;
|
||||
}
|
||||
|
||||
public get period(): number {
|
||||
return this.rule.period(this.level);
|
||||
public get isFull(): boolean {
|
||||
const period = this.getPeriod();
|
||||
return period === 0 || this._progress === period;
|
||||
}
|
||||
|
||||
public get generation(): ResourceSet {
|
||||
return ResourceSet.from(this.rule.generation(this.level));
|
||||
}
|
||||
|
||||
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 getPeriod(level?: number): number {
|
||||
return this.rule.period(level ?? this.level, this.game);
|
||||
}
|
||||
|
||||
public get isVisible(): boolean {
|
||||
return this.game.resources.gte(this.visibilityPrice);
|
||||
return this.rule.isVisible(this.level, this.game);
|
||||
}
|
||||
|
||||
public get isOpen(): boolean {
|
||||
return this.game.resources.gte(this.openPrice);
|
||||
public getUpgradePrice(level?: number): ResourceSet {
|
||||
return this.rule.upgradePrice(level ?? this.level, this.game);
|
||||
}
|
||||
|
||||
public get canAffordUpgrade(): boolean {
|
||||
return this.game.resources.gte(this.upgradePrice);
|
||||
public upgradableToLevel(limit: number = 100000): number {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -1,57 +1,81 @@
|
||||
import Decimal from "break_eternity.js";
|
||||
import { ResourceNames, type PartialResources, type Resource } from "../types";
|
||||
import { type PartialResources } from "../types";
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.items.clear();
|
||||
}
|
||||
|
||||
public clone(): ResourceSet {
|
||||
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;
|
||||
}
|
||||
|
||||
public set(key: Resource, value: Decimal): void {
|
||||
public set(key: string, value: Decimal): void {
|
||||
this.items.set(key, value);
|
||||
}
|
||||
|
||||
public predicate(predicate: (res: Resource) => boolean): boolean {
|
||||
return ResourceNames.every((res) => predicate(res));
|
||||
public setMany(values: ResourceSet): void {
|
||||
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 {
|
||||
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 {
|
||||
for (const res of ResourceNames)
|
||||
public static transform(keys: string[], target: ResourceSet, predicate: (res: string) => Decimal): void {
|
||||
for (const res of keys)
|
||||
target.set(res, predicate(res));
|
||||
}
|
||||
|
||||
public sum(arg: ResourceSet): 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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();
|
||||
ResourceSet.transform(inst, (res) => this.get(res).multiply(factor));
|
||||
ResourceSet.transform(this.keys(), inst, (res) => this.get(res).multiply(factor));
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Resources } from "../types";
|
||||
import type { ResourceGeneratorRule } from "./ResourceGeneratorRule";
|
||||
|
||||
export type GameRules = {
|
||||
startResources: Resources,
|
||||
generators: ResourceGeneratorRule[],
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { PartialResources } from "../types";
|
||||
import type { Game, ResourceSet } from "../game";
|
||||
|
||||
export type ResourceGeneratorRule = {
|
||||
name: string,
|
||||
startingLevel: number, // usually zero
|
||||
period: (level: number) => number,
|
||||
generation: (level: number) => PartialResources, // per period
|
||||
visibilityPrice: (level: number) => PartialResources;
|
||||
openPrice: (level: number) => PartialResources;
|
||||
upgradePrice: (level: number) => PartialResources;
|
||||
period: (level: number, game: Game) => number,
|
||||
isVisible: (level: number, game: Game) => boolean;
|
||||
upgradePrice: (level: number, game: Game) => ResourceSet;
|
||||
isUpgradable: (level: number, game: Game, upgradePrice: ResourceSet) => boolean;
|
||||
generationGain: (level: number, game: Game) => ResourceSet,
|
||||
onGenerate: (level: number, game: Game, gain: ResourceSet) => void,
|
||||
onUpgrade?: (level: number, game: Game) => void,
|
||||
resetProgressOnUpgrade: boolean;
|
||||
manualProgressReset: boolean;
|
||||
manualGeneration: boolean;
|
||||
};
|
||||
|
||||
@@ -1,33 +1,63 @@
|
||||
import Decimal from "break_eternity.js";
|
||||
import type { GameRules } from "../rules/GameRules";
|
||||
import { simpleResGen, resourceGen } from './utils';
|
||||
import { ResourceSet } from "../game";
|
||||
|
||||
export const rules: GameRules = {
|
||||
startResources: { prestigeMul: new Decimal(1) },
|
||||
generators: [
|
||||
{
|
||||
name: 'white',
|
||||
startingLevel: 1,
|
||||
resetProgressOnUpgrade: true,
|
||||
manualProgressReset: true,
|
||||
period: (level) => level ? 0.5 : 0,
|
||||
generation: resourceGen({ RED: { offset: 14, power: 2, factor: 1 } }),
|
||||
visibilityPrice: () => ({}),
|
||||
openPrice: () => ({}),
|
||||
upgradePrice: () => ({}),
|
||||
manualGeneration: true,
|
||||
period: (level) => level ? 3 : 0,
|
||||
isVisible: () => true,
|
||||
upgradePrice: () => ResourceSet.zero,
|
||||
isUpgradable: () => false,
|
||||
generationGain: (level, game) => resourceGen({ RED: { offset: 14, power: 2, factor: 1 } }, level),
|
||||
onGenerate: (level, game, gain) => game.resources.add(gain),
|
||||
},
|
||||
simpleResGen({
|
||||
name: 'red',
|
||||
startingLevel: 1,
|
||||
period: 1,
|
||||
generation: { RED: { offset: 3, power: 1.5, factor: 1 } },
|
||||
openPrice: { RED: { offset: 3, power: 2.1, factor: 10 } }
|
||||
upgradePrice: { RED: { offset: 3, power: 2.1, factor: 10 } },
|
||||
generatesResources: { RED: { offset: 3, power: 1.5, factor: 1 } },
|
||||
}),
|
||||
...['orange', 'yellow', 'green', 'cyan', 'blue', 'violet']
|
||||
.map((color, idx) => simpleResGen({
|
||||
name: color,
|
||||
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) } },
|
||||
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 type { PartialResources, Resource, Resources } from "../types";
|
||||
import type { PartialResources, Resources } from "../types";
|
||||
import type { ResourceGeneratorRule } from "../rules/ResourceGeneratorRule";
|
||||
import { ResourceSet, type Game } from "../game";
|
||||
|
||||
type ResourceGenParams = {
|
||||
offset?: number,
|
||||
@@ -12,23 +13,49 @@ type SimpleResGenParams = {
|
||||
name: string,
|
||||
startingLevel?: number,
|
||||
period: number,
|
||||
generation: Partial<Record<Resource, ResourceGenParams>>,
|
||||
visibilityPrice?: Partial<Record<Resource, ResourceGenParams>>,
|
||||
openPrice: Partial<Record<Resource, ResourceGenParams>>,
|
||||
upgradePrice?: Partial<Record<Resource, ResourceGenParams>>,
|
||||
visibilityPrice?: Partial<Record<string, ResourceGenParams>>,
|
||||
// openPrice: Partial<Record<string, ResourceGenParams>>,
|
||||
upgradePrice: Partial<Record<string, ResourceGenParams>>,
|
||||
generatesResources: Partial<Record<string, ResourceGenParams>>,
|
||||
}
|
||||
|
||||
export function resourceGen(p?: Partial<Record<Resource, ResourceGenParams>>): (lvl: number) => PartialResources {
|
||||
if (!p)
|
||||
return () => ({});
|
||||
// export function resourceGen(p?: Partial<Record<string, ResourceGenParams>>, multiplyByResource?: string): (lvl: number, game: Game) => PartialResources {
|
||||
// if (!p)
|
||||
// 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)
|
||||
.map(([res, rp]) => [
|
||||
res,
|
||||
new Decimal((rp.offset ?? 0) + Math.pow(lvl, rp.power ?? 1) * (rp.factor ?? 0)),
|
||||
])
|
||||
) as Resources;
|
||||
.map(([res, rp]) => {
|
||||
const value = rp
|
||||
? new Decimal((rp.offset ?? 0) + Math.pow(level, rp.power ?? 1) * (rp.factor ?? 0))
|
||||
: Decimal.dZero;
|
||||
return [
|
||||
res,
|
||||
value,
|
||||
];
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
export function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule {
|
||||
@@ -37,11 +64,12 @@ export function simpleResGen(params: SimpleResGenParams): ResourceGeneratorRule
|
||||
name: params.name,
|
||||
startingLevel: params.startingLevel ?? 0,
|
||||
resetProgressOnUpgrade: true,
|
||||
manualProgressReset: false,
|
||||
manualGeneration: false,
|
||||
period: (level) => level ? params.period : 0,
|
||||
generation: resourceGen(params.generation),
|
||||
visibilityPrice: resourceGen(params.visibilityPrice),
|
||||
openPrice: resourceGen(params.openPrice),
|
||||
upgradePrice: resourceGen(params.upgradePrice),
|
||||
isVisible: (level, game) => game.resources.gte(resourceGen(params.visibilityPrice, level)),
|
||||
upgradePrice: (level, game) => resourceGen(params.upgradePrice, level),
|
||||
isUpgradable: (level, game, upgradePrice) => game.resources.gte(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";
|
||||
|
||||
export const ResourceNames = ['RED'] as const;
|
||||
export type Resource = (typeof ResourceNames)[number];
|
||||
// export const ResourceNames = ['RED'] as const;
|
||||
// export type Resource = (typeof ResourceNames)[number];
|
||||
|
||||
export type Resources = Record<Resource, Decimal>;
|
||||
export type Resources = Record<string, Decimal>;
|
||||
export type PartialResources = Partial<Resources>;
|
||||
|
||||
Reference in New Issue
Block a user