// Labyrinth — static data. No Phaser, no game state: just the maze tile // vocabulary, the fixed-tile skeleton, the movable-tile bag, the treasure // catalog, player home corners, and spritesheet frame helpers. Everything // dynamic (the shuffled board, spare tile, pawns, targets) lives in // LabyrinthLogic.js. // 7×7 board. The 49 cells hold 48 movable + the fixed skeleton; one extra // "spare" tile is always held off-board and pushed in each turn. export const GRID = 7; // ── Directions ──────────────────────────────────────────────────────────────── // Side indices around a tile, clockwise from the top. A clockwise rotation by // `r` quarter-turns maps side s → (s + r) % 4. export const N = 0, E = 1, S = 2, W = 3; export const DIRS = [N, E, S, W]; export const OPPOSITE = { [N]: S, [E]: W, [S]: N, [W]: E }; // Row/col delta when stepping out of a tile through a given side. export const DELTA = { [N]: { dr: -1, dc: 0 }, [E]: { dr: 0, dc: 1 }, [S]: { dr: 1, dc: 0 }, [W]: { dr: 0, dc: -1 }, }; // Base (unrotated) open sides per tile shape. // I (straight) — corridor N↔S // L (corner) — corridor N↔E // T (junction) — open E,S,W (closed N) export const BASE_SIDES = { I: [N, S], L: [N, E], T: [E, S, W], }; // The set of open sides for a tile of `type` rotated `rot` quarter-turns. export function openSides(type, rot) { return BASE_SIDES[type].map((s) => (s + rot) % 4); } // Does a tile of (type,rot) have an opening on `side`? export function isOpen(type, rot, side) { return openSides(type, rot).includes(side); } // ── Treasures ──────────────────────────────────────────────────────────────── // 24 treasures. The array index IS the spritesheet frame for both the on-tile // overlay (labyrinth-treasures, 100×100) and the held card (labyrinth-cards, // 270×390). 12 creatures (0–11) then 12 objects (12–23). export const TREASURES = [ { key: 'dragon', name: 'Dragon' }, { key: 'genie', name: 'Genie' }, { key: 'ghost', name: 'Ghost' }, { key: 'goblin', name: 'Goblin' }, { key: 'witch', name: 'Witch' }, { key: 'skeleton', name: 'Skeleton' }, { key: 'bat', name: 'Bat' }, { key: 'owl', name: 'Owl' }, { key: 'spider', name: 'Spider' }, { key: 'lizard', name: 'Lizard' }, { key: 'beetle', name: 'Beetle' }, { key: 'rat', name: 'Rat' }, { key: 'crown', name: 'Crown' }, { key: 'ring', name: 'Ring' }, { key: 'keys', name: 'Keys' }, { key: 'chest', name: 'Treasure Chest' }, { key: 'gem', name: 'Gem' }, { key: 'coins', name: 'Gold Coins' }, { key: 'candelabra', name: 'Candelabra' }, { key: 'grimoire', name: 'Grimoire' }, { key: 'sword', name: 'Sword' }, { key: 'helmet', name: 'Helmet' }, { key: 'map', name: 'Map Scroll' }, { key: 'chalice', name: 'Chalice' }, ]; export const TREASURE_COUNT = TREASURES.length; // 24 // ── Fixed skeleton ────────────────────────────────────────────────────────── // The 16 tiles anchored at even row/col never move. Corners are L-tiles opening // inward; the rest are T-junctions whose closed (flat) side faces the board // edge. The 12 non-corner fixed tiles each carry a treasure (frames 0–11). export const FIXED = [ // four corners (no treasure) { r: 0, c: 0, type: 'L', rot: 1 }, // opens E,S { r: 0, c: 6, type: 'L', rot: 2 }, // opens S,W { r: 6, c: 0, type: 'L', rot: 0 }, // opens N,E { r: 6, c: 6, type: 'L', rot: 3 }, // opens N,W // top edge — closed N { r: 0, c: 2, type: 'T', rot: 0, treasure: 0 }, { r: 0, c: 4, type: 'T', rot: 0, treasure: 1 }, // bottom edge — closed S { r: 6, c: 2, type: 'T', rot: 2, treasure: 2 }, { r: 6, c: 4, type: 'T', rot: 2, treasure: 3 }, // left edge — closed W { r: 2, c: 0, type: 'T', rot: 3, treasure: 4 }, { r: 4, c: 0, type: 'T', rot: 3, treasure: 5 }, // right edge — closed E { r: 2, c: 6, type: 'T', rot: 1, treasure: 6 }, { r: 4, c: 6, type: 'T', rot: 1, treasure: 7 }, // inner four — point inward { r: 2, c: 2, type: 'T', rot: 3, treasure: 8 }, // open N,E,S { r: 2, c: 4, type: 'T', rot: 1, treasure: 9 }, // open N,S,W { r: 4, c: 2, type: 'T', rot: 3, treasure: 10 }, { r: 4, c: 4, type: 'T', rot: 1, treasure: 11 }, ]; export function isFixed(r, c) { return r % 2 === 0 && c % 2 === 0; } // ── Movable tile bag ────────────────────────────────────────────────────────── // 34 tiles (33 fill the open cells, 1 is the starting spare). Treasures 12–23 // ride on twelve of them (the six T's + six L's, matching the classic deck); // straights carry none. Rotations are randomized at deal time in the logic. export function buildMovableBag() { const bag = []; const push = (type, treasure = null) => bag.push({ type, treasure }); // 6 T-junctions, all treasured (12–17) for (let i = 0; i < 6; i++) push('T', 12 + i); // 16 corners — six treasured (18–23), ten plain for (let i = 0; i < 6; i++) push('L', 18 + i); for (let i = 0; i < 10; i++) push('L', null); // 12 straights, no treasure for (let i = 0; i < 12; i++) push('I', null); return bag; // length 34 } // ── Players ──────────────────────────────────────────────────────────────── // Home corners in seat order. Two-player games use opposite corners (seats 0 & // 1 are diagonal), three/four fill in the remaining corners. export const HOME_CORNERS = [ { r: 0, c: 0 }, { r: 6, c: 6 }, { r: 0, c: 6 }, { r: 6, c: 0 }, ]; export const PLAYER_COLORS = [0xd0473a, 0x4a90d9, 0x49a25a, 0xe2b53c]; export const PLAYER_COLOR_HEX = ['#d0473a', '#4a90d9', '#49a25a', '#e2b53c']; // ── Insertion slots ────────────────────────────────────────────────────────── // The 12 places the spare can be pushed in: the shiftable rows/cols (1,3,5) on // each of the four edges. `side` names where the spare enters; the row/col it // affects is `index`. Pushing into a slot ejects the tile at the far end. export const SHIFT_LINES = [1, 3, 5]; export const SLOTS = (() => { const out = []; for (const i of SHIFT_LINES) out.push({ id: `T${i}`, side: 'top', index: i }); for (const i of SHIFT_LINES) out.push({ id: `B${i}`, side: 'bottom', index: i }); for (const i of SHIFT_LINES) out.push({ id: `L${i}`, side: 'left', index: i }); for (const i of SHIFT_LINES) out.push({ id: `R${i}`, side: 'right', index: i }); return out; })(); export const OPPOSITE_SIDE = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }; // The slot whose push would directly reverse the given one (same line, opposite // edge). Returns a slot id or null. export function reverseSlotId(slot) { if (!slot) return null; const side = OPPOSITE_SIDE[slot.side]; return `${side === 'top' ? 'T' : side === 'bottom' ? 'B' : side === 'left' ? 'L' : 'R'}${slot.index}`; } // ── Spritesheet frame helpers ────────────────────────────────────────────── // Tile background: frame 0 = movable, 1 = fixed/anchored. export function tileFrame(fixed) { return fixed ? 1 : 0; } // Treasure overlay & card share the treasure index as their frame. export function treasureFrame(idx) { return idx; } export function cardFrame(idx) { return idx; }