Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9caa47a830 |
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
*node_modules
|
node_modules
|
||||||
*dist
|
dist
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
Generated
-39
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@incgame/engine",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "@incgame/engine",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"break_eternity.js": "^2.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.6.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/break_eternity.js": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/break_eternity.js/-/break_eternity.js-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-4tg4j0wc0lhaYAnOHubN5mAyHbhMfUI7adQLO8l/loKqtylZ/kHWp8WYqG2EC0TinSesKvpCi3XeVFcKRUBJsQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.9.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@incgame/engine",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"type": "module",
|
|
||||||
"main": "src/index.ts",
|
|
||||||
"types": "src/index.ts",
|
|
||||||
"scripts": {
|
|
||||||
"typecheck": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"break_eternity.js": "^2.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.6.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import Decimal, { type DecimalSource } from "break_eternity.js";
|
|
||||||
import type { BehaviourDisplayInfo, BehaviourLink, BehaviourSocket, Resource, Resources, SerializedBehaviour } from "../types";
|
|
||||||
|
|
||||||
export abstract class Behaviour {
|
|
||||||
public readonly id: string;
|
|
||||||
public readonly kind: string;
|
|
||||||
public readonly inputLinks: BehaviourLink[];
|
|
||||||
|
|
||||||
constructor(source: SerializedBehaviour) {
|
|
||||||
this.id = source.id;
|
|
||||||
this.kind = source.kind;
|
|
||||||
this.inputLinks = source.inputLinks.pins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayInfo(): BehaviourDisplayInfo {
|
|
||||||
return {
|
|
||||||
storages: {},
|
|
||||||
sockets: [
|
|
||||||
...this.inputs.map((input, index) => ({
|
|
||||||
type: 'input',
|
|
||||||
index,
|
|
||||||
resource: input,
|
|
||||||
} satisfies BehaviourSocket)),
|
|
||||||
...this.outputs.map((output, index) => ({
|
|
||||||
type: 'output',
|
|
||||||
index,
|
|
||||||
resource: output,
|
|
||||||
} satisfies BehaviourSocket)),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract get inputs(): Resource[];
|
|
||||||
public abstract get outputs(): Resource[];
|
|
||||||
|
|
||||||
/** Withdraws up to `amount` of `resource` from this behaviour's storage, returning how much was actually taken. */
|
|
||||||
public abstract take(resource: Resource, amount: DecimalSource): Decimal;
|
|
||||||
|
|
||||||
protected abstract give(resource: Resource, amount: DecimalSource): void;
|
|
||||||
|
|
||||||
/** Snapshot of everything currently held, keyed by resource. */
|
|
||||||
public abstract getStorage(): Partial<Resources>;
|
|
||||||
|
|
||||||
abstract reset(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances this behaviour by `deltaTime`. Must pull whatever inputs it needs (via `resolve`)
|
|
||||||
* before advancing its own cycle, so that a cycle only completes once enough input was actually
|
|
||||||
* available — otherwise it stalls and tries again on a later tick.
|
|
||||||
*
|
|
||||||
* `fairShare(sourceId, resource)` caps how much of `resource` may be pulled from `sourceId` this
|
|
||||||
* tick: when several behaviours are linked to the same source for the same resource, each is
|
|
||||||
* capped to an equal share of what that source held right after its own tick, regardless of
|
|
||||||
* pull order, instead of whoever asks first draining it.
|
|
||||||
*/
|
|
||||||
abstract tick(
|
|
||||||
deltaTime: number,
|
|
||||||
resolve: (id: string) => Behaviour | undefined,
|
|
||||||
fairShare: (sourceId: string, resource: Resource) => Decimal,
|
|
||||||
): void;
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import Decimal, { type DecimalSource } from "break_eternity.js";
|
|
||||||
import type { BehaviourDisplayInfo, Resource, Resources, SerializedBehaviour, SerializedConverter } from "../types";
|
|
||||||
import { Behaviour } from "./base";
|
|
||||||
|
|
||||||
export class Converter extends Behaviour {
|
|
||||||
protected readonly storage: Partial<Resources>;
|
|
||||||
public cycleTime: Decimal;
|
|
||||||
public readonly cycleLength: Decimal;
|
|
||||||
public readonly inputRates: Partial<Resources>;
|
|
||||||
public readonly outputRates: Partial<Resources>;
|
|
||||||
|
|
||||||
constructor(source: SerializedBehaviour & SerializedConverter) {
|
|
||||||
if (source.kind !== 'converter')
|
|
||||||
throw new Error('Invalid source');
|
|
||||||
super(source);
|
|
||||||
|
|
||||||
this.cycleTime = new Decimal(source.cycleTime);
|
|
||||||
this.cycleLength = new Decimal(source.rules.cycleLength);
|
|
||||||
|
|
||||||
this.storage = {};
|
|
||||||
for (const [res, amount] of Object.entries(source.storage ?? {}))
|
|
||||||
this.storage[res as Resource] = new Decimal(amount);
|
|
||||||
|
|
||||||
this.inputRates = {};
|
|
||||||
for (const [res, rate] of Object.entries(source.rules.inputRates ?? {}))
|
|
||||||
this.inputRates[res as Resource] = new Decimal(rate);
|
|
||||||
|
|
||||||
this.outputRates = {};
|
|
||||||
for (const [res, rate] of Object.entries(source.rules.outputRates ?? {}))
|
|
||||||
this.outputRates[res as Resource] = new Decimal(rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get inputs(): Resource[] {
|
|
||||||
return Object.keys(this.inputRates) as Resource[];
|
|
||||||
}
|
|
||||||
|
|
||||||
public get outputs(): Resource[] {
|
|
||||||
return Object.keys(this.outputRates) as Resource[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A converter grabs a full batch of each input the moment enough is available — reserving
|
|
||||||
* ahead for however many cycles (including the one currently underway) the elapsed time could
|
|
||||||
* reach, rather than trickling resources in proportionally as time passes. If the source can't
|
|
||||||
* supply the full batch, `cycleTime` only advances as far as what was actually secured
|
|
||||||
* justifies: a converter can't be "cooking" a batch it doesn't have the ingredients for, so it
|
|
||||||
* stalls proportionally to whatever is actually missing rather than free-running on elapsed
|
|
||||||
* time alone.
|
|
||||||
*/
|
|
||||||
public tick(
|
|
||||||
deltaTime: number,
|
|
||||||
resolve: (id: string) => Behaviour | undefined,
|
|
||||||
fairShare: (sourceId: string, resource: Resource) => Decimal,
|
|
||||||
): void {
|
|
||||||
const idealCycleTime = this.cycleTime.add(deltaTime);
|
|
||||||
const reserveCycles = idealCycleTime.div(this.cycleLength).ceil();
|
|
||||||
let newCycleTime = idealCycleTime;
|
|
||||||
|
|
||||||
for (const [res, rate] of Object.entries(this.inputRates) as [Resource, Decimal][]) {
|
|
||||||
if (rate.eq(0))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const target = rate.mul(reserveCycles);
|
|
||||||
const have = this.storage[res] ?? Decimal.dZero;
|
|
||||||
const shortfall = target.sub(have).max(0);
|
|
||||||
if (shortfall.gt(0)) {
|
|
||||||
const link = this.inputLinks.find((l) => l.resource === res);
|
|
||||||
if (link) {
|
|
||||||
const source = resolve(link.target);
|
|
||||||
if (source) {
|
|
||||||
const request = shortfall.min(fairShare(link.target, link.resource));
|
|
||||||
if (request.gt(0))
|
|
||||||
this.give(res, source.take(link.resource, request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const haveNow = this.storage[res] ?? Decimal.dZero;
|
|
||||||
newCycleTime = newCycleTime.min(haveNow.mul(this.cycleLength).div(rate));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cycleTime = newCycleTime;
|
|
||||||
const affordableCycles = this.cycleTime.div(this.cycleLength).floor();
|
|
||||||
|
|
||||||
if (affordableCycles.gt(0)) {
|
|
||||||
for (const [res, rate] of Object.entries(this.inputRates) as [Resource, Decimal][])
|
|
||||||
this.take(res, rate.mul(affordableCycles));
|
|
||||||
for (const [res, rate] of Object.entries(this.outputRates) as [Resource, Decimal][])
|
|
||||||
this.give(res, rate.mul(affordableCycles));
|
|
||||||
this.cycleTime = this.cycleTime.sub(affordableCycles.mul(this.cycleLength));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public take(resource: Resource, amount: DecimalSource): Decimal {
|
|
||||||
const available = this.storage[resource] ?? Decimal.dZero;
|
|
||||||
const taken = available.min(amount);
|
|
||||||
this.storage[resource] = available.sub(taken);
|
|
||||||
return taken;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected give(resource: Resource, amount: DecimalSource): void {
|
|
||||||
this.storage[resource] = (this.storage[resource] ?? Decimal.dZero).add(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getStorage(): Partial<Resources> {
|
|
||||||
return { ...this.storage };
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayInfo(): BehaviourDisplayInfo {
|
|
||||||
const info = super.getDisplayInfo();
|
|
||||||
info.storages['Буфер'] = { ...this.storage } as Resources;
|
|
||||||
return {
|
|
||||||
...info,
|
|
||||||
raw: {
|
|
||||||
inputRates: this.inputRates,
|
|
||||||
outputRates: this.outputRates,
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
for (const res of Object.keys(this.storage))
|
|
||||||
delete (this.storage[res as Resource]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './base';
|
|
||||||
export * from './converter';
|
|
||||||
export * from './storage';
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import Decimal, { type DecimalSource } from "break_eternity.js";
|
|
||||||
import type { BehaviourDisplayInfo, Resource, Resources, SerializedBehaviour, SerializedStorage } from "../types";
|
|
||||||
import { Behaviour } from "./base";
|
|
||||||
|
|
||||||
export class Storage extends Behaviour {
|
|
||||||
public readonly capacity: Decimal | undefined;
|
|
||||||
public readonly acceptsResources: Resource[];
|
|
||||||
public readonly items: Partial<Resources>;
|
|
||||||
|
|
||||||
constructor(source: SerializedBehaviour & SerializedStorage) {
|
|
||||||
if (source.kind !== 'storage')
|
|
||||||
throw new Error('Invalid source');
|
|
||||||
super(source);
|
|
||||||
|
|
||||||
this.capacity = source.capacity ? new Decimal(source.capacity) : undefined;
|
|
||||||
this.acceptsResources = source.acceptsResources;
|
|
||||||
this.items = {};
|
|
||||||
for (const [res, amount] of Object.entries(source.items ?? {}))
|
|
||||||
this.items[res as Resource] = new Decimal(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get inputs(): Resource[] {
|
|
||||||
return this.acceptsResources;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get outputs(): Resource[] {
|
|
||||||
return this.acceptsResources;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A storage has no rate of its own — it pulls as much as its linked source is willing to give
|
|
||||||
* (up to whatever room its own capacity leaves), and later hands out as much as whoever calls
|
|
||||||
* `take` asks for (up to whatever it actually has). The speed in either direction is entirely
|
|
||||||
* up to the counterpart, not this behaviour.
|
|
||||||
*/
|
|
||||||
public tick(
|
|
||||||
_deltaTime: number,
|
|
||||||
resolve: (id: string) => Behaviour | undefined,
|
|
||||||
fairShare: (sourceId: string, resource: Resource) => Decimal,
|
|
||||||
): void {
|
|
||||||
for (const resource of this.acceptsResources) {
|
|
||||||
const link = this.inputLinks.find((l) => l.resource === resource);
|
|
||||||
if (!link)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const source = resolve(link.target);
|
|
||||||
if (!source)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const have = this.items[resource] ?? Decimal.dZero;
|
|
||||||
const room = this.capacity ? this.capacity.sub(have).max(0) : Decimal.dInf;
|
|
||||||
const request = room.min(fairShare(link.target, link.resource));
|
|
||||||
if (request.gt(0))
|
|
||||||
this.give(resource, source.take(link.resource, request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public take(resource: Resource, amount: DecimalSource): Decimal {
|
|
||||||
const available = this.items[resource] ?? Decimal.dZero;
|
|
||||||
const taken = available.min(amount);
|
|
||||||
this.items[resource] = available.sub(taken);
|
|
||||||
return taken;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected give(resource: Resource, amount: DecimalSource): void {
|
|
||||||
this.items[resource] = (this.items[resource] ?? Decimal.dZero).add(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getStorage(): Partial<Resources> {
|
|
||||||
return { ...this.items };
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayInfo(): BehaviourDisplayInfo {
|
|
||||||
const info = super.getDisplayInfo();
|
|
||||||
info.storages['Хранилище'] = { ...this.items } as Resources;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
for (const res of Object.keys(this.items))
|
|
||||||
delete (this.items[res as Resource]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
export * from './types';
|
|
||||||
export * from './behaviours';
|
|
||||||
export * from './game';
|
|
||||||
export * from './serialization';
|
|
||||||
export * from './utils';
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { type Behaviour, Converter, Storage } from "../behaviours";
|
|
||||||
import type { SerializedBehaviour, SerializedConverter, SerializedStorage } from "../types";
|
|
||||||
|
|
||||||
export function deserializeBehaviour(value: SerializedBehaviour): Behaviour {
|
|
||||||
switch (value.kind) {
|
|
||||||
case 'converter':
|
|
||||||
return new Converter(value);
|
|
||||||
case 'storage':
|
|
||||||
return new Storage(value);
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown behaviour');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeBehaviour(behaviour: Behaviour): SerializedBehaviour {
|
|
||||||
const base = {
|
|
||||||
id: behaviour.id,
|
|
||||||
kind: behaviour.kind,
|
|
||||||
inputLinks: { pins: behaviour.inputLinks },
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (behaviour.kind) {
|
|
||||||
case 'converter': {
|
|
||||||
const converter = behaviour as Converter;
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
cycleTime: converter.cycleTime,
|
|
||||||
storage: converter.getStorage(),
|
|
||||||
rules: {
|
|
||||||
cycleLength: converter.cycleLength,
|
|
||||||
inputRates: converter.inputRates,
|
|
||||||
outputRates: converter.outputRates,
|
|
||||||
}
|
|
||||||
} as (SerializedBehaviour & SerializedConverter);;
|
|
||||||
}
|
|
||||||
case 'storage':
|
|
||||||
const storage = behaviour as Storage;
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
capacity: storage.capacity,
|
|
||||||
acceptsResources: storage.acceptsResources,
|
|
||||||
items: storage.getStorage(),
|
|
||||||
} as (SerializedBehaviour & SerializedStorage);
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown behaviour');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { Game } from "../game";
|
|
||||||
import type { SerializedGame } from "../types";
|
|
||||||
import { serializeBehaviour } from "./behaviour";
|
|
||||||
|
|
||||||
export function serializeGame(game: Game): SerializedGame {
|
|
||||||
return {
|
|
||||||
time: game.time,
|
|
||||||
nodes: game.nodes.map((node) => ({
|
|
||||||
id: node.id,
|
|
||||||
name: node.name,
|
|
||||||
behaviour: serializeBehaviour(node.behaviour),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deserializeGame(value: SerializedGame): Game {
|
|
||||||
return new Game(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeGameToString(game: Game): string {
|
|
||||||
return JSON.stringify(serializeGame(game));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deserializeGameFromString(value: string): Game {
|
|
||||||
return deserializeGame(JSON.parse(value));
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './behaviour';
|
|
||||||
export * from './game';
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import type { Resource, Resources } from "./resource";
|
|
||||||
|
|
||||||
export type BehaviourResourcePin = {
|
|
||||||
resource: Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BehaviourSocket = {
|
|
||||||
type: 'input' | 'output';
|
|
||||||
index: number; // within its type
|
|
||||||
resource: Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BehaviourLink = {
|
|
||||||
target: string,
|
|
||||||
resource: Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BehaviourDisplayInfo = {
|
|
||||||
storages: Record<string, Resources>;
|
|
||||||
sockets: BehaviourSocket[];
|
|
||||||
|
|
||||||
[x: string]: unknown;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './behaviour';
|
|
||||||
export * from './unit';
|
|
||||||
export * from './resource';
|
|
||||||
export * from './serialization';
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import type Decimal from "break_eternity.js";
|
|
||||||
|
|
||||||
export type Resource = 'tree' | 'wood' | 'stone';
|
|
||||||
|
|
||||||
export type Resources = Record<Resource, Decimal>;
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import type { Unit } from './unit';
|
|
||||||
import type { DecimalSource } from "break_eternity.js";
|
|
||||||
import type { Resource } from "./resource";
|
|
||||||
|
|
||||||
export type SerializedInputLink = {
|
|
||||||
target: SerializedBehaviour['id'],
|
|
||||||
resource: Resource,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SerializedInputLinks = {
|
|
||||||
pins: SerializedInputLink[];
|
|
||||||
}
|
|
||||||
|
|
||||||
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',
|
|
||||||
capacity?: DecimalSource;
|
|
||||||
acceptsResources: Resource[];
|
|
||||||
items: Partial<Record<Resource, DecimalSource>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SerializedBehaviour = {
|
|
||||||
id: string;
|
|
||||||
inputLinks: SerializedInputLinks;
|
|
||||||
} & (SerializedConverter | SerializedStorage);
|
|
||||||
|
|
||||||
export type SerializedNode = {
|
|
||||||
id: Unit['id'],
|
|
||||||
name: Unit['name'],
|
|
||||||
behaviour: SerializedBehaviour,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SerializedGame = {
|
|
||||||
time: DecimalSource;
|
|
||||||
nodes: SerializedNode[];
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { Behaviour } from "../behaviours";
|
|
||||||
|
|
||||||
export type Unit = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
behaviour: Behaviour;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import type { Behaviour } from "../behaviours";
|
|
||||||
|
|
||||||
/** Orders behaviours so that whatever a behaviour's inputs link to comes before it (a dependency cycle just falls back to encounter order). */
|
|
||||||
export 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 inputLink of behaviour.inputLinks) {
|
|
||||||
const dependency = byId.get(inputLink.target);
|
|
||||||
if (dependency)
|
|
||||||
visit(dependency);
|
|
||||||
}
|
|
||||||
order.push(behaviour);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const behaviour of behaviours)
|
|
||||||
visit(behaviour);
|
|
||||||
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"lib": ["ES2020"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
*.tsbuildinfo
|
|
||||||
.DS_Store
|
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
import { defaultGame } from "./prefabs/game";
|
import { defaultGame } from "./prefabs/game";
|
||||||
import { Game, serializeGameToString, deserializeGame, deserializeGameFromString } from '@incgame/engine';
|
import { Game, serializeGameToString, deserializeGame, deserializeGameFromString } from '@incgame/engine';
|
||||||
|
|
||||||
const KEY_GAME = 'incgame';
|
export const saveParams = {
|
||||||
|
path: 'default',
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_KEY_GAME = 'incgame';
|
||||||
|
|
||||||
|
function getKeyGame(): string {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
return params.get('game') || DEFAULT_KEY_GAME;
|
||||||
|
}
|
||||||
|
|
||||||
export function save(game: Game) {
|
export function save(game: Game) {
|
||||||
localStorage.setItem(KEY_GAME, serializeGameToString(game));
|
localStorage.setItem(`${saveParams.path}_${getKeyGame()}`, serializeGameToString(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function load(): Game {
|
export function load(): Game {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(KEY_GAME);
|
const raw = localStorage.getItem(`${saveParams.path}_${getKeyGame()}`);
|
||||||
if (raw)
|
if (raw)
|
||||||
return deserializeGameFromString(raw);
|
return deserializeGameFromString(raw);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Reference in New Issue
Block a user