converter now has cycleLength. storage save load fix
This commit is contained in:
@@ -73,7 +73,7 @@ const NodeCard = forwardRef<HTMLDivElement, Props>(function NodeCard({ node2d, s
|
|||||||
info.links.map((link) => <div key={`${link.target}/${link.resource}`}>→{link.target}/{link.resource}</div>)
|
info.links.map((link) => <div key={`${link.target}/${link.resource}`}>→{link.target}/{link.resource}</div>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="node__debug" style={{fontSize: '50%', whiteSpace: 'pre'}}>{JSON.stringify(info, undefined, 4)}</div>
|
<div className="node__debug" style={{fontSize: '8px', whiteSpace: 'pre'}}>{JSON.stringify(info.raw, undefined, 4)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
+109
-62
@@ -35,6 +35,8 @@ export type BehaviourDisplayInfo = {
|
|||||||
storages: Record<string, Resources>;
|
storages: Record<string, Resources>;
|
||||||
sockets: BehaviourSocket[];
|
sockets: BehaviourSocket[];
|
||||||
links: BehaviourLink[];
|
links: BehaviourLink[];
|
||||||
|
|
||||||
|
[x: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Behaviour {
|
export abstract class Behaviour {
|
||||||
@@ -42,7 +44,6 @@ export abstract class Behaviour {
|
|||||||
public readonly kind: string;
|
public readonly kind: string;
|
||||||
public readonly inputs: BehaviourInputs;
|
public readonly inputs: BehaviourInputs;
|
||||||
public readonly outputs: BehaviourOutputs;
|
public readonly outputs: BehaviourOutputs;
|
||||||
protected readonly storage: Partial<Resources> = {};
|
|
||||||
|
|
||||||
constructor(source: SerializedBehaviour) {
|
constructor(source: SerializedBehaviour) {
|
||||||
this.id = source.id;
|
this.id = source.id;
|
||||||
@@ -60,69 +61,9 @@ export abstract class Behaviour {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset() {
|
|
||||||
for (const res of Object.keys(this.storage))
|
|
||||||
delete (this.storage[res as Resource]);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getDisplayInfo(): BehaviourDisplayInfo;
|
|
||||||
|
|
||||||
/** Withdraws up to `amount` of `resource` from this behaviour's storage, returning how much was actually taken. */
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract produce(deltaTime: number): void;
|
|
||||||
|
|
||||||
abstract pull(deltaTime: number, resolve: (id: string) => Behaviour | undefined): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Converter extends Behaviour {
|
|
||||||
public readonly inputRates: Partial<Resources>;
|
|
||||||
public readonly outputRates: Partial<Resources>;
|
|
||||||
|
|
||||||
constructor(source: SerializedNodeBehaviour & SerializedConverter) {
|
|
||||||
if (source.kind !== 'converter')
|
|
||||||
throw new Error('Invalid source');
|
|
||||||
super(source);
|
|
||||||
|
|
||||||
this.inputRates = {};
|
|
||||||
for (const pin of source.inputs.pins)
|
|
||||||
this.inputRates[pin.resource] = new Decimal(pin.rate);
|
|
||||||
|
|
||||||
this.outputRates = {};
|
|
||||||
for (const pin of source.outputs.pins)
|
|
||||||
this.outputRates[pin.resource] = new Decimal(pin.rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fills this converter's own storage from its outputs' production rates. */
|
|
||||||
public produce(deltaTime: number): void {
|
|
||||||
for (const output of this.outputs.pins)
|
|
||||||
this.give(output.resource, (this.outputRates[output.resource] ?? Decimal.dZero).mul(deltaTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Pulls resources into this converter's storage from whatever each input pin is linked to. */
|
|
||||||
public pull(deltaTime: number, resolve: (id: string) => Behaviour | undefined): void {
|
|
||||||
for (const input of this.inputs.pins) {
|
|
||||||
const source = resolve(input.link.target);
|
|
||||||
if (!source)
|
|
||||||
continue;
|
|
||||||
const rate = this.inputRates[input.resource] ?? Decimal.dZero;
|
|
||||||
const taken = source.take(input.link.resource, rate.mul(deltaTime));
|
|
||||||
this.give(input.resource, taken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayInfo(): BehaviourDisplayInfo {
|
public getDisplayInfo(): BehaviourDisplayInfo {
|
||||||
return {
|
return {
|
||||||
storages: { 'Буфер': { ...this.storage } as Resources },
|
storages: {},
|
||||||
links: this.inputs.pins.map((input) => input.link),
|
links: this.inputs.pins.map((input) => input.link),
|
||||||
sockets: [
|
sockets: [
|
||||||
...this.inputs.pins.map((input, index) => ({
|
...this.inputs.pins.map((input, index) => ({
|
||||||
@@ -136,8 +77,114 @@ export class Converter extends Behaviour {
|
|||||||
resource: output.resource,
|
resource: output.resource,
|
||||||
} satisfies BehaviourSocket)),
|
} satisfies BehaviourSocket)),
|
||||||
],
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
abstract produce(times: DecimalSource): void;
|
||||||
|
|
||||||
|
/** Pulls into this behaviour's own storage the same number of cycles this tick's `tick()` call just completed. */
|
||||||
|
abstract pull(resolve: (id: string) => Behaviour | undefined): void;
|
||||||
|
|
||||||
|
abstract reset(): void;
|
||||||
|
|
||||||
|
abstract tick(deltaTime: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>;
|
||||||
|
/** Number of full cycles completed by the most recent `tick()` call, consumed by `pull()`. */
|
||||||
|
private pendingCycles: Decimal = Decimal.dZero;
|
||||||
|
|
||||||
|
constructor(source: SerializedNodeBehaviour & 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 tick(deltaTime: number): void {
|
||||||
|
this.cycleTime = this.cycleTime.add(deltaTime);
|
||||||
|
const fullCycles = this.cycleTime.div(this.cycleLength).floor();
|
||||||
|
this.cycleTime = this.cycleTime.sub(fullCycles.mul(this.cycleLength));
|
||||||
|
|
||||||
|
this.pendingCycles = fullCycles;
|
||||||
|
this.produce(fullCycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fills this converter's own storage from its outputs' production rates. */
|
||||||
|
public produce(times: DecimalSource): void {
|
||||||
|
for (const output of this.outputs.pins)
|
||||||
|
this.give(output.resource, (this.outputRates[output.resource] ?? Decimal.dZero).mul(times));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pulls resources into this converter's storage from whatever each input pin is linked to, scaled by the cycles this tick's `tick()` just completed. */
|
||||||
|
public pull(resolve: (id: string) => Behaviour | undefined): void {
|
||||||
|
for (const input of this.inputs.pins) {
|
||||||
|
const source = resolve(input.link.target);
|
||||||
|
if (!source)
|
||||||
|
continue;
|
||||||
|
const rate = this.inputRates[input.resource] ?? Decimal.dZero;
|
||||||
|
const taken = source.take(input.link.resource, rate.mul(this.pendingCycles));
|
||||||
|
this.give(input.resource, taken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
|
cycleTime: this.cycleTime,
|
||||||
|
cycleLength: this.cycleLength,
|
||||||
|
inputRates: this.inputRates,
|
||||||
|
outputRates: this.outputRates,
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset() {
|
||||||
|
for (const res of Object.keys(this.storage))
|
||||||
|
delete (this.storage[res as Resource]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// export class Storage extends Behaviour {
|
// export class Storage extends Behaviour {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export function serializeBehaviour(behaviour: Behaviour): SerializedNodeBehaviou
|
|||||||
return {
|
return {
|
||||||
id: converter.id,
|
id: converter.id,
|
||||||
kind: 'converter',
|
kind: 'converter',
|
||||||
|
cycleTime: converter.cycleTime,
|
||||||
|
storage: converter.getStorage(),
|
||||||
inputs: {
|
inputs: {
|
||||||
pins: converter.inputs.pins.map((pin) => ({
|
pins: converter.inputs.pins.map((pin) => ({
|
||||||
...pin,
|
...pin,
|
||||||
@@ -21,6 +23,11 @@ export function serializeBehaviour(behaviour: Behaviour): SerializedNodeBehaviou
|
|||||||
rate: converter.outputRates[pin.resource] ?? 0,
|
rate: converter.outputRates[pin.resource] ?? 0,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
cycleLength: converter.cycleLength,
|
||||||
|
inputRates: converter.inputRates,
|
||||||
|
outputRates: converter.outputRates,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
+13
-5
@@ -16,14 +16,16 @@ export const defaultGame: () => SerializedGame = () => ({
|
|||||||
pins: [
|
pins: [
|
||||||
{
|
{
|
||||||
resource: 'wood',
|
resource: 'wood',
|
||||||
rate: 1,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
resource: 'stone',
|
resource: 'stone',
|
||||||
rate: 0.1,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
cycleLength: 1,
|
||||||
|
outputRates: { wood: 1, stone: 0.1 },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -36,7 +38,6 @@ export const defaultGame: () => SerializedGame = () => ({
|
|||||||
pins: [
|
pins: [
|
||||||
{
|
{
|
||||||
resource: 'wood',
|
resource: 'wood',
|
||||||
rate: 0.5,
|
|
||||||
link: { target: 'forest.1', resource: 'wood' },
|
link: { target: 'forest.1', resource: 'wood' },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -45,10 +46,14 @@ export const defaultGame: () => SerializedGame = () => ({
|
|||||||
pins: [
|
pins: [
|
||||||
{
|
{
|
||||||
resource: 'plank',
|
resource: 'plank',
|
||||||
rate: 1,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
cycleLength: 1.8,
|
||||||
|
inputRates: { wood: 1 },
|
||||||
|
outputRates: { plank: 2 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -61,7 +66,6 @@ export const defaultGame: () => SerializedGame = () => ({
|
|||||||
pins: [
|
pins: [
|
||||||
{
|
{
|
||||||
resource: 'plank',
|
resource: 'plank',
|
||||||
rate: 1,
|
|
||||||
link: { target: 'sawmill.1', resource: 'plank' },
|
link: { target: 'sawmill.1', resource: 'plank' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -69,6 +73,10 @@ export const defaultGame: () => SerializedGame = () => ({
|
|||||||
outputs: {
|
outputs: {
|
||||||
pins: [],
|
pins: [],
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
cycleLength: 1,
|
||||||
|
inputRates: { plank: 1 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+18
-13
@@ -31,14 +31,14 @@ export class Game {
|
|||||||
const behavioursById = new Map(this.nodes.map((node) => [node.behaviour.id, node.behaviour]));
|
const behavioursById = new Map(this.nodes.map((node) => [node.behaviour.id, node.behaviour]));
|
||||||
|
|
||||||
for (const node of this.nodes)
|
for (const node of this.nodes)
|
||||||
node.behaviour.produce(deltaTime);
|
node.behaviour.tick(deltaTime);
|
||||||
for (const node of this.nodes)
|
for (const node of this.nodes)
|
||||||
node.behaviour.pull(deltaTime, (id) => behavioursById.get(id));
|
node.behaviour.pull((id) => behavioursById.get(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset() {
|
public reset() {
|
||||||
this.time = Decimal.dZero;
|
this.time = Decimal.dZero;
|
||||||
for (const node of this.nodes)
|
for (const node of this.nodes)
|
||||||
node.behaviour.reset();
|
node.behaviour.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,30 +48,35 @@ export class Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedConverterInput = {
|
export type SerializedInput = {
|
||||||
resource: Resource;
|
resource: Resource;
|
||||||
rate: DecimalSource;
|
|
||||||
link: {
|
link: {
|
||||||
target: SerializedNodeBehaviour['id'],
|
target: SerializedNodeBehaviour['id'],
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedConverterOutput = {
|
export type SerializedOutput = {
|
||||||
resource: Resource;
|
resource: Resource;
|
||||||
rate: DecimalSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedConverterInputs = {
|
export type SerializedInputs = {
|
||||||
pins: SerializedConverterInput[];
|
pins: SerializedInput[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedConverterOutputs = {
|
export type SerializedOutputs = {
|
||||||
pins: SerializedConverterOutput[];
|
pins: SerializedOutput[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedConverter = {
|
export type SerializedConverter = {
|
||||||
kind: 'converter';
|
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 = {
|
export type SerializedStorage = {
|
||||||
@@ -80,8 +85,8 @@ export type SerializedStorage = {
|
|||||||
|
|
||||||
export type SerializedNodeBehaviour = {
|
export type SerializedNodeBehaviour = {
|
||||||
id: string;
|
id: string;
|
||||||
inputs: SerializedConverterInputs;
|
inputs: SerializedInputs;
|
||||||
outputs: SerializedConverterOutputs;
|
outputs: SerializedOutputs;
|
||||||
} & (SerializedConverter | SerializedStorage);
|
} & (SerializedConverter | SerializedStorage);
|
||||||
|
|
||||||
export type SerializedNode = {
|
export type SerializedNode = {
|
||||||
|
|||||||
Reference in New Issue
Block a user