import { describe, expect, it } from "vitest"; import { assembleBoard, edgeState, hasLineOfSight, layoutIds, stepTarget, type SectorPlacement, } from "../src/board"; import { buildDeck } from "../src/cards"; describe("card data", () => { it("basic deck is exactly 125 cards (official 6e rulebook)", () => { expect(buildDeck(["basic"]).length).toBe(125); }); it("basic + expansion1 is exactly 200 cards (125 + 75)", () => { expect(buildDeck(["basic", "expansion1"]).length).toBe(200); }); it("number cards follow the official distribution", () => { const deck = buildDeck(["basic"]); const count = (id: string) => deck.filter((c) => c.cardId === id).length; expect(count("number-2")).toBe(12); expect(count("number-3")).toBe(10); expect(count("number-4")).toBe(7); expect(count("number-5")).toBe(4); expect(count("number-6")).toBe(1); }); }); describe("board assembly", () => { it("knows all six verified layouts", () => { expect(layoutIds().sort()).toEqual( ["board-a", "board-b", "board-c", "board-d", "board-e", "board-f"].sort(), ); }); const twoSector: SectorPlacement[] = [ { boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 }, { boardId: "board-b", origin: { x: 0, y: 5 }, rotation: 0 }, ]; it("merges the junction between stacked sectors into a single wall with a centered corridor", () => { const board = assembleBoard(twoSector); // Junction edges y=4/5: wall everywhere except the centered opening x=2. expect(edgeState(board, { x: 0, y: 4 }, "S")).toBe("wall"); expect(edgeState(board, { x: 1, y: 4 }, "S")).toBe("wall"); expect(edgeState(board, { x: 2, y: 4 }, "S")).toBe("open"); expect(edgeState(board, { x: 3, y: 4 }, "S")).toBe("wall"); expect(edgeState(board, { x: 4, y: 4 }, "S")).toBe("wall"); const step = stepTarget(board, { x: 2, y: 4 }, "S"); expect(step).toEqual({ kind: "step", to: { x: 2, y: 5 } }); }); it("wraps the vertical openings top-to-bottom by default", () => { const board = assembleBoard(twoSector); const up = stepTarget(board, { x: 2, y: 0 }, "N"); expect(up).toEqual({ kind: "warp", to: { x: 2, y: 9 } }); const down = stepTarget(board, { x: 2, y: 9 }, "S"); expect(down).toEqual({ kind: "warp", to: { x: 2, y: 0 } }); }); it("honors explicit crossed pairings (2-player diagram)", () => { const board = assembleBoard(twoSector, { 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" }], ], }); // Leaving the top sector's west opening arrives at the bottom sector's east. expect(stepTarget(board, { x: 0, y: 2 }, "W")).toEqual({ kind: "warp", to: { x: 4, y: 7 } }); expect(stepTarget(board, { x: 4, y: 7 }, "E")).toEqual({ kind: "warp", to: { x: 0, y: 2 } }); }); it("blocks steps through walls and doors", () => { const board = assembleBoard([{ boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 }]); // Layout A wall V (2,1)|(2,2): 1-indexed row 2 col 1 east = 0-indexed (0,1) E. expect(stepTarget(board, { x: 0, y: 1 }, "E")).toEqual({ kind: "blocked", by: "wall" }); // Layout A door H (3,2)-(4,2): 0-indexed (1,2) S. expect(stepTarget(board, { x: 1, y: 2 }, "S")).toEqual({ kind: "blocked", by: "door" }); }); it("rotating a sector 180 degrees preserves its wall count", () => { const flat = assembleBoard([{ boardId: "board-c", origin: { x: 0, y: 0 }, rotation: 0 }]); const rotated = assembleBoard([{ boardId: "board-c", origin: { x: 0, y: 0 }, rotation: 180 }]); const countWalls = (b: typeof flat) => Object.values(b.edges).filter((e) => e === "wall").length; const countDoors = (b: typeof flat) => Object.values(b.edges).filter((e) => e === "door").length; expect(countWalls(rotated)).toBe(countWalls(flat)); expect(countDoors(rotated)).toBe(countDoors(flat)); // Home stays centered under rotation. expect(rotated.homes[0]).toEqual({ x: 2, y: 2 }); }); it("computes line of sight blocked by walls", () => { const board = assembleBoard([{ boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 }]); // Straight down col 0 from (0,0): wall H (3,1)|(4,1) = 0-indexed (0,2) S blocks. expect(hasLineOfSight(board, { x: 0, y: 0 }, { x: 0, y: 1 })).toBe(true); expect(hasLineOfSight(board, { x: 0, y: 0 }, { x: 0, y: 4 })).toBe(false); // Home (2,2) sees one cell up (open edge), but the wall between rows 1-2 // of the home column blocks sight to the top row. expect(hasLineOfSight(board, { x: 2, y: 2 }, { x: 2, y: 1 })).toBe(true); expect(hasLineOfSight(board, { x: 2, y: 2 }, { x: 2, y: 0 })).toBe(false); }); });