Most of the source material I have read on constraint programming stops at the paper. It scores which propagation scheme clears a board without branching, sorts difficulty bands, and is done. The thing I missed until recently is how much work sits between “the model solves it” and “someone actually wants to play it”. A researcher who wrote the paper did the homework to find out, and turned the same constraint models into nine playable puzzle games you can load in a browser. The transformation between those two states is bigger than I expected, and most of it is the same kind of code pointed in a different direction.
For context, the researcher’s academic paper generated a corpus of 434,201 puzzles across five different board sizes, from 6×6 up to 36×36. The experiments measured how much information each board needs to crack, and how far a constraint solver can get before it has to start guessing. That side was settled and published. The non-academic side started when he decided to play a few of his own instances. That impulse cost him nine games.
The current set includes sudoku, nonogram, queens, zip, loopy, tents, patches, wend, plus a swedish version called swend. They live on a single games page. Each game on the page comes with a short minizinc model (a constraint-modelling language where you describe rules and a back-end solver does the search) that explains the part of the generator the researcher thought was worth highlighting. The models are writeups more than they are programs. The actual generator and solver rely on gecode 6.4.0, with helpers around it for import, image conversion, exact-cover puzzles, and final pack assembly.
Every generator in the collection starts from a known answer or a source picture. It nudges, swaps, or removes information until the puzzle has exactly one answer, and throws out anything it cannot salvage. The same reasoning it uses to make the puzzles also classifies their difficulty and powers hint systems. Generation and uniqueness checks happen offline. The browser only loads finished puzzles with stored answers, no live solver running in the page. That call has two consequences worth naming. Puzzles load instantly, and the difficulty tag belongs to the pipeline rather than to whatever solver happens to be on the visitor’s machine.
Difficulty labels are derived from propagation runs, search nodes, and deterministic deduction measurements. They give a mechanically derived ordering inside one pack and nothing else. They do not predict how a human will fare, because that depends on the human. Use them as a relative sort, not as a star rating.
The sudoku setup
Of the nine games on the page, sudoku has the longest paper trail, so it is the obvious place to look first. The researcher built a base collection of 32,000 uniquely solvable boards at each of five sizes. A generator fills out a full grid, then strips clues one at a time while checking the puzzle still has exactly one solution. Gecode then classifies what remains.
The classification borrows from helmut simonis’s 2005 paper. It walks through an ordered list of propagation configurations. The three base configurations cover value, bounds, and domain propagation; two more add bounds or domain shaving (tighter reasoning about where each variable’s values can land). The weakest configuration that still solves the board without guessing becomes the puzzle’s hardness tag. If none of them finishes, the tag is search.
The sudoku model itself fits on a screen. Box dimensions are data, so the same model handles 6×6 boards with 2×3 boxes and 9×9 boards with 3×3 boxes. The researcher kept the model portable across sizes instead of maintaining one file per board. The playable pack is a fixed-seed sample of 500 boards from a pinned corpus revision, restricted to the two smallest sizes so the page stays light.
Why a published sudoku looks symmetrical and a generated one does not
A freshly generated board does not automatically have the visual symmetry people expect from a published one. Rather than ship a second collection for aesthetics, the researcher wrote a symmetrification pass that runs after selection. Two freedoms are useful. Rows and columns can be permuted while preserving the box structure, and any missing rotational partner can be filled in from the stored answer. The second freedom only adds information, so it cannot introduce a second solution, although it can move the hardness tag.
For each puzzle, the pass lists every distinct rotational pairing achievable with box-preserving layouts and ranks them by how many clues 180-degree symmetry would take. It tries the three best-ranked layouts first. If a symmetrification would shift the hardness tag, it adds partner clues one at a time and only keeps additions that let gecode reproduce the original tag. The pass then picks the layout with the fewest asymmetric cells.
After that pass, 228 of 500 selected boards are exactly rotationally symmetric, and the total number of asymmetric cells drops from 6,172 to 1,928. That level of polish is invisible in a paper and is the kind of thing that decides whether a puzzle looks worth picking up.
Nonogram and a constraint from twenty years ago
Nonogram is the second game on the page, and it closes a loop going back to 2005, when the researcher wrote the original gecode example using gilles pesant’s regular constraint. The constraint describes a row or column with a regular expression that says things like “zero or more empty cells, then three filled cells, then at least one empty cell, then two filled cells, and so on”. Gecode converts the expression to a finite-state machine and forces every row and column to match it. Jan wolter included that example in a wide survey of paint-by-number solvers, and found the small demo held its own against much larger specialised ones.
The minizinc version of the model builds the same automata directly. Run clues are lists, so rows and columns can hold different numbers of runs without padding. The function that builds the expression takes each list and stitches the strings together; minizinc compiles the result to a finite-state machine and applies it. The full model fits on a screen.
Queens and zip
The remaining games on the page are mostly additional case studies. Queens grows orthogonally connected regions around a no-touch queen placement and perturbs the boundaries; a second valid solution makes a candidate invalid, so the generator keeps nudging until exactly one survives. Zip models the path as a gecode circuit (a constraint that says every cell points to exactly one successor and the whole thing forms a single loop), uses an extra return node to close the loop back to clue 1, and lets the circuit constraint rule out disconnected subtours (smaller loops that would satisfy individual clues but leave the board fragmented).
The pattern across all nine games is the same. Generate from a known answer. Perturb until the answer is unique. Sort difficulty by what propagation can clear without guessing. Ship a static pack to the browser. The minizinc models are reading material. The actual generator and classifier are gecode and a small offline pipeline.
Trade-offs
An offline-only pipeline is not cheap to maintain. Every new game pulls its own generator, its own classifier configuration, and its own decision about which difficulty labels even apply. Loopy, tents, patches, wend, and swend each carry separate code even though the constraint programming core is shared. The researcher is shipping nine small projects rather than one big one, which is the right trade for a personal project but a real cost if anyone wanted to copy the approach across multiple genres.
Symmetrification is the trade I would push on hardest. Adding clues to reach 180-degree symmetry can shift a puzzle’s hardness tag, which means the label on a symmetrified board is one classifier run looser than the label on the bare board. The researcher handles this by validating each added clue against the original tag, but the symmetrified boards skew easier than the unprocessed selection. For a publication where the difficulty label is part of the product, that is a real cost. For a casual page, it is invisible.
Difficulty labels are also propagation-mechanical and do not predict how a human will struggle. The researcher says so himself. Anyone who actually plays the page will rank the puzzles differently than the labels suggest, especially in the smaller sizes where propagation clears almost everything. Treat the labels as a relative order and expect to disagree with them in the middle of the pack.
The puzzles are static. There is no live solver in the browser, no on-demand regeneration, no “give me a harder one” button. That call is what keeps the page fast and what makes offline classification possible. The cost is that every pack is a snapshot rather than a stream.
What I would tell past me
The hardest call to make in a project like this is when to stop adding games. Nine is a lot, and the public page makes it sound like the list will keep growing. For anyone thinking about starting a similar side project, the lessons that aged well are these.
- Write models in minizinc even when the generator runs in gecode. The two-line regular constraint in nonogram is far more readable than the gecode equivalent and is the version of the model that survives in the writeup.
- Run the difficulty classifier on every candidate before selection. Classification is cheap once a generator is in place, and it lets you ship a labeled pack without re-running anything later.
- Validate symmetrification clue-by-clue against the original tag. Bulk symmetrification moves difficulty labels, and the per-clue check is the only way to keep the labels honest.
- Ship puzzles as a static pack. A browser-side solver would let the page regenerate on demand, but it would also make every puzzle slower than the page itself and would put the difficulty label in the player’s browser rather than in the pipeline.
The lift from “the paper works” to “the paper works and nine people have played the games” is mostly the same skill set pointed at the player. The researcher’s side project is a useful template for anyone who has ever wanted to turn a research output into something a person could actually use.