Implement engine core: board assembly, movement, turns, combat, victory
Pure deterministic game core in @wizwar/engine: seeded RNG (mulberry32, state in GameState so seed+commands replays identically), sector assembly with rotation, junction merging, and wraparound warps (configurable pairings; the 2p diagram crosses its side openings), movement (3 + one number card), geometric line of sight, deck building from the verified card data (asserts 125/200 totals), and the command-to-event reducer: setup with TRAP! redraw and die-roll first player, punching (no combat round 1, no self-attack, once per turn), damage/death with killer-takes-cards and forced discard, treasure stealing with both victory conditions, pick-up-ends-turn, and end-of-turn draw. Events carry full spatial detail for future replay rendering; private card knowledge rides on visibleTo events with a redaction helper. 21 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
11209cdc5d
commit
a8884592a4
@@ -0,0 +1,72 @@
|
||||
// Board configurations from the 6e rulebook's setup diagram. Sectors are
|
||||
// shuffled and randomly rotated, then laid out per player count; each player
|
||||
// gets the sector containing their home star.
|
||||
//
|
||||
// Implemented: 2 players (1x2 column, crossed side pairings per the diagram)
|
||||
// and 4 players (2x2 square, straight-across pairings). 3 players (L-shape
|
||||
// with the AUTO WARP corner connector) and 5+ (two sets) are TODO.
|
||||
|
||||
import {
|
||||
assembleBoard,
|
||||
type AssembledBoard,
|
||||
type Rotation,
|
||||
type SectorPlacement,
|
||||
} from "./board";
|
||||
import { layoutIds } from "./board";
|
||||
import { shuffle, nextInt, type RngState } from "./rng";
|
||||
|
||||
export const SUPPORTED_PLAYER_COUNTS = [2, 4] as const;
|
||||
|
||||
export interface SetupResult {
|
||||
board: AssembledBoard;
|
||||
rng: RngState;
|
||||
}
|
||||
|
||||
export function setupBoard(playerCount: number, rng: RngState): SetupResult {
|
||||
const [shuffled, rng1] = shuffle(rng, layoutIds());
|
||||
let rngN = rng1;
|
||||
const pick = (n: number): { id: string; rotation: Rotation }[] => {
|
||||
const chosen: { id: string; rotation: Rotation }[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const [r, next] = nextInt(rngN, 4);
|
||||
rngN = next;
|
||||
chosen.push({ id: shuffled[i]!, rotation: (r * 90) as Rotation });
|
||||
}
|
||||
return chosen;
|
||||
};
|
||||
|
||||
if (playerCount === 2) {
|
||||
const picks = pick(2);
|
||||
const placements: SectorPlacement[] = [
|
||||
{ boardId: picks[0]!.id, origin: { x: 0, y: 0 }, rotation: picks[0]!.rotation },
|
||||
{ boardId: picks[1]!.id, origin: { x: 0, y: 5 }, rotation: picks[1]!.rotation },
|
||||
];
|
||||
// Diagram: top {top A, left C, right B}, bottom {left B, right C, bottom A}
|
||||
// — A wraps vertically; the side openings pair CROSSED (top-left to
|
||||
// bottom-right and top-right to bottom-left).
|
||||
const board = assembleBoard(placements, {
|
||||
warpPairs: [
|
||||
[{ sector: 0, side: "N" }, { sector: 1, side: "S" }],
|
||||
[{ sector: 0, side: "W" }, { sector: 1, side: "E" }],
|
||||
[{ sector: 0, side: "E" }, { sector: 1, side: "W" }],
|
||||
],
|
||||
});
|
||||
return { board, rng: rngN };
|
||||
}
|
||||
|
||||
if (playerCount === 4) {
|
||||
const picks = pick(4);
|
||||
const placements: SectorPlacement[] = [
|
||||
{ boardId: picks[0]!.id, origin: { x: 0, y: 0 }, rotation: picks[0]!.rotation },
|
||||
{ boardId: picks[1]!.id, origin: { x: 5, y: 0 }, rotation: picks[1]!.rotation },
|
||||
{ boardId: picks[2]!.id, origin: { x: 0, y: 5 }, rotation: picks[2]!.rotation },
|
||||
{ boardId: picks[3]!.id, origin: { x: 5, y: 5 }, rotation: picks[3]!.rotation },
|
||||
];
|
||||
// 4p square wraps straight across (left openings to right openings on the
|
||||
// same rows, top to bottom on the same columns) — the default pairing.
|
||||
const board = assembleBoard(placements);
|
||||
return { board, rng: rngN };
|
||||
}
|
||||
|
||||
throw new Error(`unsupported player count: ${playerCount} (supported: 2, 4)`);
|
||||
}
|
||||
Reference in New Issue
Block a user