Grids and Coords
Almost every Nikoli + Sudoku-variant puzzle lives on a square grid; the only outlier worth treating specially is hex. So the standard layout is a square cell grid with three companion sub-grids over the same coordinate system:
| Layer | Dimensions | Used by |
|---|---|---|
| Cells | N × M | Sudoku, Akari, Nurikabe, Heyawake, Hitori, Masyu |
| Edges | (N+1) × M horizontals + N × (M+1) verticals | Slitherlink, fence rules |
| Corners | (N+1) × (M+1) | rare; Masyu pearls have corner-flavored constraints |
A single puzzle can host constraints across any mixture of sub-grids. Most genres use one; Slitherlink uses cells (for clues) and both edge grids (for the loop). The four sub-grids and a universal coordinate type are library-defined:
/// Reference enum for *talking about* layers — used by
/// `Region::AllOf(Layer)`, `Coord::layer()`, `Substrate::grid(layer)`.
pub enum Layer {
/// `N × M` cell grid. Default for almost every puzzle.
Cells,
/// `(N+1) × M` horizontal edges (between vertically-adjacent cells).
EdgesH,
/// `N × (M+1)` vertical edges (between horizontally-adjacent cells).
EdgesV,
/// `(N+1) × (M+1)` corners.
Corners,
}
/// A position in any sub-grid. The library's universal coordinate
/// type — every Nikoli/Sudoku-variant genre shares it. Hex puzzles
/// and other non-square layouts live behind a separate `HexPuzzle`
/// trait family with their own coord type.
pub enum Coord {
Cell { r: usize, c: usize },
EdgeH { r: usize, c: usize },
EdgeV { r: usize, c: usize },
Corner { r: usize, c: usize },
}
Layer is the discriminator that constraints and regions use to say
which sub-grid they’re talking about; Coord is the value-level
position they resolve to. A Slitherlink puzzle can carry cell clues
alongside edge state without ambiguity because every constraint
ultimately bottoms out at Coord values.
Coordinates. Internal: (row, col) zero-indexed from the top-
left, so (0, 0) is the upper-left cell — matches array indexing
and how every renderer naturally walks.
External (URLs, shared puzzle strings, REPL output) is layer-prefixed
and chess-up-numbered: <layer>:<col><row>, where the column is a
bijective base-26 letter sequence (a, b, …, z, aa, ab, …, az, ba,
…) and the row is a 1-indexed integer counted from the bottom up (so
cell:a1 is bottom-left, matching chess and Sudoku convention). The
bijective base-26 scheme handles arbitrarily wide boards — a
30-column Akari grid uses a..z then aa..ad; the chess 8-column
ceiling does not apply.
Each layer has its own grid dimensions and is addressed within that layer’s own coordinate space:
| Prefix | Layer | Grid | Example |
|---|---|---|---|
cell: | Cells | N × M | cell:a1 = bottom-left cell |
edgeh: | EdgesH | (N+1) × M | edgeh:a1 = bottommost horizontal edge of column a |
edgev: | EdgesV | N × (M+1) | edgev:a1 = leftmost vertical edge of row 1 |
corner: | Corners | (N+1) × (M+1) | corner:a1 = bottom-left corner |
The cell: prefix may be omitted (cells are the default layer), so
a1 and cell:a1 parse identically. The other prefixes are required.
A human-friendlier alternate form references neighboring cells —
edgeh(a1,a2) for the horizontal edge between cells a1 and a2,
edgev(a1,b1) for the vertical edge between a1 and b1,
corner(a1,b1,a2,b2) for the corner shared by those four cells.
Both forms parse to the same Coord; the canonical
<layer>:<col><row> is what the wire format uses, and the alternate
form is sugar for hand-typed input.
Grid storage
Layer contents — substrate Material markings, per-coord candidate
bitmasks, render glyph caches — all share one storage type: a flat
row-major Grid<T>, in core::grid.
pub struct Grid<T> {
rows: usize,
cols: usize,
data: Vec<T>,
}
impl<T: Default + Copy> Grid<T> {
pub fn new(rows: usize, cols: usize) -> Self; // filled with default
pub fn get(&self, r: usize, c: usize) -> T;
pub fn set(&mut self, r: usize, c: usize, v: T);
pub fn iter(&self) -> impl Iterator<Item = ((usize, usize), T)>;
pub fn data(&self) -> &[T];
}
Flat storage gives O(1) index, no hash overhead. The Default + Copy bound covers every layer payload the catalog reaches for —
Material and Mark are both small Copy enums that fit it
trivially.