basic logic
This commit is contained in:
+4
-10
@@ -15,13 +15,8 @@ export default function App() {
|
||||
// useEffect(() => saveTickCount(tickCount), [tickCount]);
|
||||
|
||||
function handleTick() {
|
||||
game.tick(1.0 / 60);
|
||||
// const nextTopology = cloneJSON(game);
|
||||
// const nextBuffers = cloneJSON(buffers);
|
||||
// tick(nextTopology, nextBuffers);
|
||||
// setGame(nextTopology);
|
||||
// setBuffers(nextBuffers);
|
||||
// setTickCount((n) => n + 1);
|
||||
game.tick(1.0 / 10);
|
||||
setGame(game.clone());
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
@@ -31,8 +26,8 @@ export default function App() {
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
game.time = 0;
|
||||
save(game);
|
||||
game.reset();
|
||||
setGame(game.clone());
|
||||
}
|
||||
|
||||
function handleInitialize() {
|
||||
@@ -43,7 +38,6 @@ export default function App() {
|
||||
return;
|
||||
|
||||
setGame(deserialize(defaultGame()));
|
||||
save(game);
|
||||
}
|
||||
|
||||
// function handleAddNode(kind: NodeKind) {
|
||||
|
||||
@@ -59,7 +59,7 @@ const NodeCard = forwardRef<HTMLDivElement, Props>(function NodeCard({ node2d, s
|
||||
<div className="node__kind">{node.behaviour.kind}</div>
|
||||
{
|
||||
Object.entries(info.storages).map(([name, storage]) => (
|
||||
<div className="node__resources">
|
||||
<div className="node__resources" key={name}>
|
||||
<div>{name}</div>
|
||||
{
|
||||
Object.entries(storage)
|
||||
@@ -73,6 +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>)
|
||||
}
|
||||
</div>
|
||||
<div className="node__debug" style={{fontSize: '50%', whiteSpace: 'pre'}}>{JSON.stringify(info, undefined, 4)}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Resource, Resources, SerializedConverter, SerializedNodeBehaviour as SerializedBehaviour, SerializedNodeBehaviour, SerializedConverterOutput, SerializedConverterInput } from "../../types";
|
||||
import { Resource, Resources, SerializedConverter, SerializedNodeBehaviour as SerializedBehaviour, SerializedNodeBehaviour } from "../../types";
|
||||
|
||||
export type BehaviourResourcePin = {
|
||||
resource: Resource;
|
||||
@@ -16,6 +16,7 @@ export type BehaviourLink = {
|
||||
}
|
||||
|
||||
export type BehaviourInput = BehaviourResourcePin & {
|
||||
rate: number; // per second of tick deltaTime
|
||||
link: BehaviourLink;
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ export type BehaviourInputs = {
|
||||
}
|
||||
|
||||
export type BehaviourOutput = BehaviourResourcePin & {
|
||||
rate: number; // per second of tick deltaTime
|
||||
}
|
||||
|
||||
export type BehaviourOutputs = {
|
||||
@@ -41,51 +43,73 @@ export abstract class Behaviour {
|
||||
public readonly kind: string;
|
||||
public readonly inputs: BehaviourInputs;
|
||||
public readonly outputs: BehaviourOutputs;
|
||||
protected readonly storage: Partial<Resources> = {};
|
||||
|
||||
constructor(source: SerializedBehaviour) {
|
||||
this.id = source.id;
|
||||
this.kind = source.kind;
|
||||
this.outputs = {
|
||||
pins: source.outputs.pins.map((input) => ({
|
||||
resource: input.resource,
|
||||
pins: source.outputs.pins.map((output) => ({
|
||||
resource: output.resource,
|
||||
rate: output.rate,
|
||||
})),
|
||||
}
|
||||
this.inputs = {
|
||||
pins: source.inputs.pins.map((input) => ({
|
||||
resource: input.resource,
|
||||
rate: input.rate,
|
||||
link: input.link,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
public reset() {
|
||||
for (const res of Object.keys(this.storage))
|
||||
delete (this.storage[res as Resource]);
|
||||
}
|
||||
|
||||
abstract getDisplayInfo(): BehaviourDisplayInfo;
|
||||
|
||||
abstract tick(deltaTime: number): void;
|
||||
}
|
||||
/** Withdraws up to `amount` of `resource` from this behaviour's storage, returning how much was actually taken. */
|
||||
protected take(resource: Resource, amount: number): number {
|
||||
const available = this.storage[resource] ?? 0;
|
||||
const taken = Math.min(available, amount);
|
||||
this.storage[resource] = available - taken;
|
||||
return taken;
|
||||
}
|
||||
|
||||
export type ResourceUsageRule = {
|
||||
resource: Resource;
|
||||
speed: number; // per second
|
||||
protected give(resource: Resource, amount: number): void {
|
||||
this.storage[resource] = (this.storage[resource] ?? 0) + amount;
|
||||
}
|
||||
|
||||
/** Fills this behaviour's own storage from its outputs' production rates. */
|
||||
public produce(deltaTime: number): void {
|
||||
for (const output of this.outputs.pins)
|
||||
this.give(output.resource, output.rate * deltaTime);
|
||||
}
|
||||
|
||||
/** Pulls resources into this behaviour'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 taken = source.take(input.link.resource, input.rate * deltaTime);
|
||||
this.give(input.resource, taken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Converter extends Behaviour {
|
||||
public outputRules: ResourceUsageRule[] = [];
|
||||
rules: { inputs: SerializedConverterInput[]; outputs: SerializedConverterOutput[]; };
|
||||
|
||||
constructor(source: SerializedNodeBehaviour & SerializedConverter) {
|
||||
if (source.kind !== 'converter')
|
||||
throw new Error('Invalid source');
|
||||
super(source);
|
||||
|
||||
this.rules = {
|
||||
inputs: source.inputs.pins,
|
||||
outputs: source.outputs.pins,
|
||||
};
|
||||
}
|
||||
|
||||
public getDisplayInfo(): BehaviourDisplayInfo {
|
||||
return {
|
||||
storages: {},
|
||||
storages: { 'Буфер': { ...this.storage } as Resources },
|
||||
links: this.inputs.pins.map((input) => input.link),
|
||||
sockets: [
|
||||
...this.inputs.pins.map((input, index) => ({
|
||||
@@ -101,10 +125,6 @@ export class Converter extends Behaviour {
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
tick(deltaTime: number): void {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// export class Storage extends Behaviour {
|
||||
|
||||
@@ -9,16 +9,10 @@ export function serializeBehaviour(behaviour: Behaviour): SerializedNodeBehaviou
|
||||
id: behaviour.id,
|
||||
kind: 'converter',
|
||||
inputs: {
|
||||
pins: behaviour.inputs.pins.map((pin) => ({
|
||||
...pin,
|
||||
rate: 11,
|
||||
})),
|
||||
pins: behaviour.inputs.pins.map((pin) => ({ ...pin })),
|
||||
},
|
||||
outputs: {
|
||||
pins: behaviour.outputs.pins.map((pin) => ({
|
||||
...pin,
|
||||
rate: 11,
|
||||
})),
|
||||
pins: behaviour.outputs.pins.map((pin) => ({ ...pin })),
|
||||
},
|
||||
};
|
||||
default:
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { Topology, Buffers } from './engine';
|
||||
import { defaultTopology, resetBuffers } from './engine';
|
||||
|
||||
const KEY_TOPOLOGY = 'incsim.topology.v1';
|
||||
const KEY_BUFFERS = 'incsim.buffers.v1';
|
||||
const KEY_TICKS = 'incsim.tickcount.v1';
|
||||
|
||||
export function loadTopology(): Topology {
|
||||
try {
|
||||
const raw = localStorage.getItem(KEY_TOPOLOGY);
|
||||
if (raw) return JSON.parse(raw) as Topology;
|
||||
} catch (e) {
|
||||
console.warn('Не удалось прочитать игру из localStorage', e);
|
||||
}
|
||||
const fresh = defaultTopology();
|
||||
saveTopology(fresh);
|
||||
return fresh;
|
||||
}
|
||||
|
||||
export function saveTopology(topology: Topology): void {
|
||||
localStorage.setItem(KEY_TOPOLOGY, JSON.stringify(topology));
|
||||
}
|
||||
|
||||
export function loadBuffers(topology: Topology): Buffers {
|
||||
try {
|
||||
const raw = localStorage.getItem(KEY_BUFFERS);
|
||||
if (raw) return JSON.parse(raw) as Buffers;
|
||||
} catch (e) {
|
||||
console.warn('Не удалось прочитать буферы из localStorage', e);
|
||||
}
|
||||
const fresh = resetBuffers(topology);
|
||||
saveBuffers(fresh);
|
||||
return fresh;
|
||||
}
|
||||
|
||||
export function saveBuffers(buffers: Buffers): void {
|
||||
localStorage.setItem(KEY_BUFFERS, JSON.stringify(buffers));
|
||||
}
|
||||
|
||||
export function loadTickCount(): number {
|
||||
const raw = localStorage.getItem(KEY_TICKS);
|
||||
return raw ? Number(raw) || 0 : 0;
|
||||
}
|
||||
|
||||
export function saveTickCount(n: number): void {
|
||||
localStorage.setItem(KEY_TICKS, String(n));
|
||||
}
|
||||
+17
-1
@@ -26,8 +26,24 @@ export class Game {
|
||||
|
||||
public tick(deltaTime: number) {
|
||||
this.time += deltaTime;
|
||||
|
||||
const behavioursById = new Map(this.nodes.map((node) => [node.behaviour.id, node.behaviour]));
|
||||
|
||||
for (const node of this.nodes)
|
||||
node.behaviour.tick(deltaTime);
|
||||
node.behaviour.produce(deltaTime);
|
||||
for (const node of this.nodes)
|
||||
node.behaviour.pull(deltaTime, (id) => behavioursById.get(id));
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.time = 0;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user