import Decimal, { DecimalSource } from "break_eternity.js"; import { Behaviour } from "../engine/functions"; import { deserializeBehaviour } from "../engine/saveLoad"; export type Resource = 'wood' | 'plank' | 'stone'; export type Resources = Record; export type Node = { id: string; name: string; behaviour: Behaviour; } /** Orders behaviours so that whatever a behaviour's inputs link to comes before it (a dependency cycle just falls back to encounter order). */ function orderByDependency(behaviours: Behaviour[]): Behaviour[] { const byId = new Map(behaviours.map((behaviour) => [behaviour.id, behaviour])); const visited = new Set(); const order: Behaviour[] = []; function visit(behaviour: Behaviour) { if (visited.has(behaviour.id)) return; visited.add(behaviour.id); for (const input of behaviour.inputs.pins) { const dependency = byId.get(input.link.target); if (dependency) visit(dependency); } order.push(behaviour); } for (const behaviour of behaviours) visit(behaviour); return order; } export class Game { public time: Decimal; public readonly nodes: Node[]; constructor(source: SerializedGame) { this.time = new Decimal(source.time); this.nodes = source.nodes.map((node) => ({ id: node.id, name: node.name, behaviour: deserializeBehaviour(node.behaviour), })); } public tick(deltaTime: number) { this.time = this.time.add(deltaTime); const behavioursById = new Map(this.nodes.map((node) => [node.behaviour.id, node.behaviour])); const resolve = (id: string) => behavioursById.get(id); // Producers must tick (and so fill their storage) before whatever pulls from them, // so resources can flow all the way down a chain within a single tick. for (const behaviour of orderByDependency(this.nodes.map((node) => node.behaviour))) behaviour.tick(deltaTime, resolve); } public reset() { this.time = Decimal.dZero; for (const node of this.nodes) node.behaviour.reset(); } /** Shallow copy with a new identity, so React sees a change after in-place mutations like `tick`. */ public clone(): Game { return Object.assign(Object.create(Game.prototype), this); } } export type SerializedInput = { resource: Resource; link: { target: SerializedNodeBehaviour['id'], resource: Resource, } } export type SerializedOutput = { resource: Resource; } export type SerializedInputs = { pins: SerializedInput[]; } export type SerializedOutputs = { pins: SerializedOutput[]; } export type SerializedConverter = { kind: 'converter'; cycleTime?: DecimalSource; storage?: Partial>; rules: { cycleLength: DecimalSource; inputRates?: Partial>; outputRates?: Partial>; } } export type SerializedStorage = { kind: 'storage', } export type SerializedNodeBehaviour = { id: string; inputs: SerializedInputs; outputs: SerializedOutputs; } & (SerializedConverter | SerializedStorage); export type SerializedNode = { id: Node['id'], name: Node['name'], behaviour: SerializedNodeBehaviour, } export type SerializedGame = { time: DecimalSource; nodes: SerializedNode[]; }