Board System

engine/board.ts provides coordinates, rectangular grids with walls, distance metrics, neighbour/reachability search (for legal-move highlighting), and a looping/clamping track for race games.

Coordinates & cells

interface Coord { x: number; y: number; }   // x→right, y→down
cellId({ x: 3, y: 5 })  // "3,5"  (use as object keys / occupancy maps)
parseCell("3,5")        // { x: 3, y: 5 }

Grids & walls

import { Board } from './engine';

const grid = Board.makeGrid(7, 7, ['3,3', '2,4']); // width, height, wall cells
Board.inBounds(grid, c);     // within the rectangle?
Board.isPassable(grid, c);   // in bounds AND not a wall

Distances

Board.manhattan(a, b);  // orthogonal step count
Board.chebyshev(a, b);  // king-move count (diagonals cost 1)

Neighbours & reachability

Board.neighbors(grid, c);              // passable orthogonal neighbours
Board.neighbors(grid, c, true);        // include diagonals
Board.reachable(grid, start, maxSteps, { diagonal?, blocked? });

reachable is a breadth-first flood fill returning every cell within maxSteps, honouring walls and dynamic blocked cells (e.g. other pawns). It excludes the start cell. This is exactly what powers Lantern Hunt’s highlighted legal moves:

const targets = Board.reachable(grid, from, die, { blocked: otherPawns });

Tracks (race games)

const track = { cells: [...coords], loop: true };
Board.advanceOnTrack(track, index, steps); // loops or clamps the index

Tokens & pieces

Tokens are game-state — usually a Record<cellId, ...> or arrays of cell ids (Lantern Hunt stores lanterns as string[] of cell ids and pawns as Record<PlayerId, cellId>). The renderer (Pawn, Token in ui/assets.tsx) draws them; the engine never assumes a specific token shape.

See Lantern Hunt (src/games/lantern-hunt.ts) for grid + dice + tokens + legal-move highlighting + scoring, and Relic Run for a looping track.