логика выделена в отдельный набор классов

This commit is contained in:
azykov@mail.ru
2026-09-05 10:24:24 +03:00
parent 2f57140899
commit 10041d9593
16 changed files with 1294 additions and 469 deletions
+75
View File
@@ -0,0 +1,75 @@
import { makeAutoObservable } from 'mobx';
import { LOGS_PER_TREE, TWIGS_PER_BUSH, TWIGS_PER_TREE } from '../constants';
import { randomId } from '../id';
import type { NodeKind, NodeResourceType, ResourceNodeData, Vec2 } from '../types';
/** A node yields its logs before its twigs. */
const RESOURCE_ORDER: NodeResourceType[] = ['log', 'twig'];
/** A choppable tree or bush. Framework-free — no React/DOM dependency. */
export class ResourceNode {
readonly id: string;
readonly kind: NodeKind;
x: number;
y: number;
/** Remaining yield per resource type; the node disappears once every count reaches 0. */
remaining: Record<NodeResourceType, number>;
constructor(kind: NodeKind, x: number, y: number, remaining: Record<NodeResourceType, number>, id = randomId(kind)) {
this.id = id;
this.kind = kind;
this.x = x;
this.y = y;
this.remaining = remaining;
makeAutoObservable(this, {}, { autoBind: true });
}
/** Builds a fresh tree or bush at `pos`, with its kind's full starting yield. */
static spawnNew(kind: NodeKind, pos: Vec2): ResourceNode {
const remaining = kind === 'tree'
? { log: LOGS_PER_TREE, twig: TWIGS_PER_TREE }
: { log: 0, twig: TWIGS_PER_BUSH };
return new ResourceNode(kind, pos.x, pos.y, remaining);
}
/** Reconstructs a node from saved data (a straight deep copy — every field has always been
* required, so no defensive fallback is needed here). */
static restore(data: ResourceNodeData): ResourceNode {
return new ResourceNode(data.kind, data.x, data.y, { ...data.remaining }, data.id);
}
/** The nearest node to `pos`, optionally restricted to one kind, or null if there are none. */
static findNearestToPosition(nodes: ResourceNode[], pos: Vec2, kind?: NodeKind): ResourceNode | null {
let nearest: ResourceNode | null = null;
let nearestDistance = Infinity;
for (const node of nodes) {
if (kind && node.kind !== kind)
continue;
const distance = Math.hypot(node.x - pos.x, node.y - pos.y);
if (distance < nearestDistance) {
nearestDistance = distance;
nearest = node;
}
}
return nearest;
}
get isDepleted(): boolean {
return RESOURCE_ORDER.every((resource) => this.remaining[resource] <= 0);
}
/** The next resource this node should yield when chopped, or null once fully depleted. */
get nextResource(): NodeResourceType | null {
return RESOURCE_ORDER.find((resource) => this.remaining[resource] > 0) ?? null;
}
/** Removes one unit of its next resource. Returns which resource was taken, or null if already
* depleted. */
chopOnce(): NodeResourceType | null {
const resource = this.nextResource;
if (!resource)
return null;
this.remaining[resource] -= 1;
return resource;
}
}