Genres, Puzzles, Games
Now that the vocabulary’s in place — Substrate, Layer, Coord,
Mark, Region, Constraint, Move — we can talk about how
Penmark actually represents puzzles.
A genre is a runtime value: a
&'static Genrefn-pointer table. A puzzle is aPuzzle— aSubstrateplus aVec<Constraint>, tagged with its genre handle. A game isMarkson a puzzle.
A genre is a value, not a type. The Genre struct
(core/src/genre.rs) is a table of fn pointers plus identity
constants (name, display_name, mark_render, layers,
build_constraints, cell_clue, …). One &'static Genre literal
ships per genre (AKARI, SLITHER, SUDOKU, TAKUZU) and they
all live in the GENRES slice. The per-genre logic itself is
authored as an impl GenreTrait for AkariGenre block on a
zero-sized marker — the macro genre_static! then coerces those
monomorphized items into the struct’s fn-pointer slots. Authors
think in trait methods; callers dispatch through the runtime handle.
A puzzle is a value of Puzzle — a single non-generic struct
that carries its genre as a field:
pub struct Puzzle {
pub genre: &'static Genre,
rows: usize,
cols: usize,
substrate: Substrate,
constraints: Vec<Constraint>,
meta: PuzzleMeta,
}
Per-genre type aliases still exist (pub type Sudoku = Puzzle;) as
shorthand, but they’re vestigial — Sudoku and Puzzle are the
same type. Constructors live as associated functions on the marker
struct: SudokuGenre::new(box_w, box_h, givens) returns a
Puzzle whose .genre is set to crate::genre::SUDOKU.
A puzzle carries a Substrate (which positions exist, where walls
and holes are) and a Vec<Constraint> (every rule the solution must
satisfy, with givens included as Pin-shaped constraints). There’s
no separate “clue map” or “wall set” field — those live as
Material::Wall / Material::Hole in the substrate and as
constraint entries in the rule list.
Editing a puzzle (add_clue, clear_clue, set_wall, set_hole,
set_floor, set_material) goes through methods on Puzzle that
mutate the substrate and rebuild the constraint list by dispatching
through puzzle.genre.build_constraints(substrate, clues). Rebuilds
are microseconds at human-interaction sizes, so we always rebuild
rather than apply surgical updates.
A game is someone’s in-progress play — which marks have been
committed, which candidates eliminated, where the propagator has
narrowed things. A game is a value of Game<'p>, holding Marks
(per-coord candidate sets) plus a borrow back to the static
Puzzle. The borrow lifetime makes “this game is bound to this
puzzle” a compile-time guarantee.
In prose, lowercase puzzle keeps its colloquial sense — “this
Sudoku puzzle is hard.” Capital Puzzle always refers to the
struct.