Domains and Marks
Marks are what the player puts on the board — a Bulb here,
the Sudoku digit 7 there, this edge Drawn. Each move either
commits a mark at a coord or eliminates one from the set still
possible there. The puzzle is solved when every coord’s set
has narrowed to a single mark and every rule still holds.
The set of marks still possible at a coord at a given moment is
its candidate set; the domain is the candidate set a fresh
game starts with — every mark the genre permits at that position.
This chapter covers both — the type of the values themselves
(Mark) and how a candidate set narrows during play.
What’s at a coord
Every Floor coord starts a game with a full domain. For Sudoku,
that’s 1..=9 per cell; for Akari, {Bulb, Empty} per floor cell;
for Slitherlink, {Drawn, NotDrawn} per edge. Walls and holes
have no domain — they aren’t decision targets.
Play narrows the candidate set. Each move eliminates a mark from a
coord, or commits to a single one, and the propagator cascades the
elimination through constraints: a Sudoku row’s Distinct rule
removes the same digit from the other eight cells the moment one
is committed; an Akari bulb removes Bulb from every cell its
rays reach.
A coord ends up in one of three states:
- Open — candidate set has more than one mark left. Play continues.
- Solved — candidate set narrowed to a singleton. The remaining mark is the answer at this coord.
- Contradicted — candidate set is empty. No legal mark; the engine reports a violation and the puzzle (or the current branch of the search) is unsolvable.
A puzzle is solved when every Floor coord is Solved and
every Goal constraint is Satisfied (covered in the next
chapter).
Mark
Every decision in the catalog is either binary (Akari bulb / empty, Slitherlink drawn / not-drawn, Nurikabe shaded / unshaded) or N-state numeric (Sudoku digits, Skyscrapers, Hashiwokakero bridge counts).
pub enum Mark {
/// Binary mark — `false` / `true`. Per-puzzle prose labels
/// ("Bulb" / "Empty", "Drawn" / "NotDrawn") are render concerns,
/// not type concerns.
Binary(bool),
/// N-state numeric mark. The valid range is whatever the puzzle's
/// `full_domain(coord)` emits — there's no library-fixed
/// convention. Sudoku uses `1..=9` (matches the printed glyph);
/// Hashiwokakero bridge slots use `0..=2` (zero bridges is a
/// real value). The `u8` carries the value as displayed.
Numeric(u8),
}
Keeping Binary distinct from Numeric(u8) is deliberate.
Collapsing both into a single u8 would force binary genres to pick
a convention (is bulb 0 or 1?) and propagator code to
match-and-decode at every boundary. With the split, LOS sweeps and
count constraints over Binary marks dispatch on the type itself,
and Sudoku arithmetic stays unambiguous.
Numeric(u8) carries the displayed value — a Sudoku 5 is
Numeric(5), not Numeric(4) zero-indexed. The u8 is wide enough
for every digit any genre prints and narrow enough that a per-coord
candidate set still fits in a small bitmask, which the fast solver
relies on.