feat: module 01 handles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1453a00b9c
commit
78dc09b303
32 files changed
+2561
No files matched your search
@@ -0,0 +1,21 @@
|
||||
// Card definitions as the engine sees them. The definitions themselves live in `content`.
|
||||
|
||||
/** What the engine needs to know about a card. Module 1: only its name. */
|
||||
export interface CardDef {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** Every card the game can create, looked up by name. */
|
||||
export class CardLibrary {
|
||||
private readonly cards = new Map<string, CardDef>();
|
||||
|
||||
constructor(defs: CardDef[]) {
|
||||
for (const def of defs) this.cards.set(def.name, def);
|
||||
}
|
||||
|
||||
get(name: string): CardDef {
|
||||
const def = this.cards.get(name);
|
||||
if (!def) throw new Error(`unknown card "${name}": define it in src/content`);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Engine core: generic rules machinery. Never name a specific card here.
|
||||
//
|
||||
// Module 1: every card in the game is an entity addressed by an id handle.
|
||||
// The engine knows which zone each entity is in, who owns it, and when it
|
||||
// entered play.
|
||||
|
||||
export { CardLibrary, type CardDef } from "./card";
|
||||
export { World, type EntityId, type PlayerId, type Zone } from "./world";
|
||||
@@ -0,0 +1,81 @@
|
||||
// Entities, zones and order of play.
|
||||
|
||||
import type { CardLibrary } from "./card";
|
||||
|
||||
/** A handle to an entity. Ids are never reused and stay valid when the entity changes zone. */
|
||||
export type EntityId = number;
|
||||
export type PlayerId = 0 | 1;
|
||||
export type Zone = "deck" | "hand" | "board" | "graveyard";
|
||||
|
||||
interface Entity {
|
||||
card: string;
|
||||
owner: PlayerId;
|
||||
zone: Zone;
|
||||
/** Stamped each time the entity enters the board; undefined until it first does. */
|
||||
orderOfPlay?: number;
|
||||
}
|
||||
|
||||
/** One player's zones. Each is an ordered list of ids (the board is left to right). */
|
||||
type Side = Record<Zone, EntityId[]>;
|
||||
|
||||
const emptySide = (): Side => ({ deck: [], hand: [], board: [], graveyard: [] });
|
||||
|
||||
export class World {
|
||||
/** Indexed by EntityId. */
|
||||
private readonly entities: Entity[] = [];
|
||||
private readonly sides: [Side, Side] = [emptySide(), emptySide()];
|
||||
private nextOrderOfPlay = 0;
|
||||
|
||||
constructor(private readonly library: CardLibrary) {}
|
||||
|
||||
/** Create an entity of `card` for `owner` at the end of `zone` (the right end for the board). */
|
||||
create(card: string, owner: PlayerId, zone: Zone): EntityId {
|
||||
if (owner !== 0 && owner !== 1) throw new Error(`no player ${owner}`);
|
||||
const name = this.library.get(card).name;
|
||||
const id = this.entities.length;
|
||||
this.entities.push({ card: name, owner, zone });
|
||||
this.enter(id, zone);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Move an entity to the end of `zone` on its owner's side, keeping its id. */
|
||||
moveTo(id: EntityId, zone: Zone): void {
|
||||
const { owner, zone: from } = this.entity(id);
|
||||
const list = this.sides[owner][from];
|
||||
list.splice(list.indexOf(id), 1);
|
||||
this.enter(id, zone);
|
||||
}
|
||||
|
||||
/** The single place an entity arrives in a zone. Entering the board starts a new order of play. */
|
||||
private enter(id: EntityId, zone: Zone): void {
|
||||
const entity = this.entity(id);
|
||||
this.sides[entity.owner][zone].push(id);
|
||||
entity.zone = zone;
|
||||
if (zone === "board") entity.orderOfPlay = ++this.nextOrderOfPlay;
|
||||
}
|
||||
|
||||
private entity(id: EntityId): Entity {
|
||||
const entity = this.entities[id];
|
||||
if (!entity) throw new Error(`unknown entity id ${id}`);
|
||||
return entity;
|
||||
}
|
||||
|
||||
zoneOf(id: EntityId): Zone {
|
||||
return this.entity(id).zone;
|
||||
}
|
||||
|
||||
cardName(id: EntityId): string {
|
||||
return this.entity(id).card;
|
||||
}
|
||||
|
||||
/** When the entity last entered the board (larger is later); undefined if it never has. */
|
||||
orderOfPlay(id: EntityId): number | undefined {
|
||||
return this.entity(id).orderOfPlay;
|
||||
}
|
||||
|
||||
/** The ids in one player's zone, in zone order (left to right for the board). */
|
||||
zone(owner: PlayerId, zone: Zone): readonly EntityId[] {
|
||||
if (owner !== 0 && owner !== 1) throw new Error(`no player ${owner}`);
|
||||
return this.sides[owner][zone];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user