// 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. // // All five layouts are verified against the rulebook's Set-Up Diagram // (research/images/6e-rules-setup-diagram.jpg): 2p crossed pairs, 3p stair // with the Aisle Warp arc, 4p and 6p straight-across, 5p plus with corner // warps at every notch. board.test.ts pins each pairing. 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, 3, 4, 5, 6] 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 === 3) { // The rulebook's stair/L: one sector on the top row (offset right), two // on the bottom row. The two openings facing the concave notch join via // the AUTO WARP; the convex openings pair as below (interpretation of // the diagram's letters — geometrically matched pairs). const picks = pick(3); const placements: SectorPlacement[] = [ { boardId: picks[0]!.id, origin: { x: 5, y: 0 }, rotation: picks[0]!.rotation }, { boardId: picks[1]!.id, origin: { x: 0, y: 5 }, rotation: picks[1]!.rotation }, { boardId: picks[2]!.id, origin: { x: 5, y: 5 }, rotation: picks[2]!.rotation }, ]; const board = assembleBoard(placements, { warpPairs: [ [{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // AUTO WARP corner [{ sector: 0, side: "N" }, { sector: 2, side: "S" }], [{ sector: 1, side: "W" }, { sector: 2, side: "E" }], [{ sector: 0, side: "E" }, { sector: 1, side: "S" }], ], }); 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 }; } if (playerCount === 5) { // The plus/cross: four arms around a center. Opposite arm tips wrap to // each other; the notch-facing side openings warp around the corners. const picks = pick(5); const placements: SectorPlacement[] = [ { boardId: picks[0]!.id, origin: { x: 5, y: 0 }, rotation: picks[0]!.rotation }, // N arm { boardId: picks[1]!.id, origin: { x: 0, y: 5 }, rotation: picks[1]!.rotation }, // W arm { boardId: picks[2]!.id, origin: { x: 5, y: 5 }, rotation: picks[2]!.rotation }, // center { boardId: picks[3]!.id, origin: { x: 10, y: 5 }, rotation: picks[3]!.rotation }, // E arm { boardId: picks[4]!.id, origin: { x: 5, y: 10 }, rotation: picks[4]!.rotation }, // S arm ]; const board = assembleBoard(placements, { warpPairs: [ [{ sector: 0, side: "N" }, { sector: 4, side: "S" }], // tip to tip [{ sector: 1, side: "W" }, { sector: 3, side: "E" }], [{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // aisle-warp corners [{ sector: 0, side: "E" }, { sector: 3, side: "N" }, "aisle"], [{ sector: 4, side: "W" }, { sector: 1, side: "S" }, "aisle"], [{ sector: 4, side: "E" }, { sector: 3, side: "S" }, "aisle"], ], }); return { board, rng: rngN }; } if (playerCount === 6) { // 3x2 rectangle: straight-across wrap (the default pairing). const picks = pick(6); 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: 10, y: 0 }, rotation: picks[2]!.rotation }, { boardId: picks[3]!.id, origin: { x: 0, y: 5 }, rotation: picks[3]!.rotation }, { boardId: picks[4]!.id, origin: { x: 5, y: 5 }, rotation: picks[4]!.rotation }, { boardId: picks[5]!.id, origin: { x: 10, y: 5 }, rotation: picks[5]!.rotation }, ]; const board = assembleBoard(placements); return { board, rng: rngN }; } throw new Error(`unsupported player count: ${playerCount} (supported: 2-6)`); }