Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Genre fn-pointer table. A puzzle is a Puzzle — a Substrate plus a Vec<Constraint>, tagged with its genre handle. A game is Marks on 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.