126 lines
3.5 KiB
TypeScript
126 lines
3.5 KiB
TypeScript
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<Resource, Decimal>;
|
|
|
|
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<string>();
|
|
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<Record<Resource, DecimalSource>>;
|
|
rules: {
|
|
cycleLength: DecimalSource;
|
|
inputRates?: Partial<Record<Resource, DecimalSource>>;
|
|
outputRates?: Partial<Record<Resource, DecimalSource>>;
|
|
}
|
|
}
|
|
|
|
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[];
|
|
} |