TinyGames · how it works
Numbers down the side, numbers across the top, and one picture that satisfies both. Every puzzle is solvable by deduction alone — a generator that cannot prove it is never shipped.
Draft. It is built, compiled and tested like every other game, and it is
not on the hub: "draft": true in build/games.json keeps it off the cards,
out of the sitemap and without a readme page until the rules are real. Take the
flag off when it is worth playing.
picross.ts is the rules and touches nothing - no canvas, no engine, no
storage. nonogram.ts is everything that draws or listens. The split is what
makes the hard half testable: node test-picross.js runs against the shipped,
minified JavaScript rather than the source, which is how a minification fault
gets caught.
Every screen except the run itself belongs to the shared shell, so this game
has one scene of its own, play. The menu, the pause card, the game-over
screen and the high-score table come from src/shared/scenes.ts and look and
behave the same here as in the other twenty.
Every puzzle can be finished by reasoning, and never by guessing. Keeping that needs two pieces, and the second is the one usually skipped.
The line solver is complete: given a row's clues and what is known of it, it finds every cell whose value is forced, not just the ones the familiar overlap trick catches. It does that by enumerating the arrangements still consistent with the line and keeping the cells they agree on - slow in theory, instant at ten wide, and correct by construction rather than by a list of named techniques that is incomplete in ways nobody notices.
The generator throws puzzles away. Deriving clues from a random picture is easy and most results need a guess somewhere, so every candidate goes to the solver and is discarded unless the solver finishes it by deduction alone. A puzzle it has not proved is never shipped.
Uniqueness comes free: a puzzle the deduction solver completes has exactly one answer, because at no point was there a choice to make.
test-picross.js checks this against an independent reference - a
brute-force enumerator written in the test file, sharing no code with the
solver - over four thousand random lines including impossible ones. Its
negative control is the smallest ambiguous nonogram there is: two cells on a
diagonal, clues of one everywhere, two valid answers. The solver must refuse
it, because that refusal is the judgement the generator depends on.
Tap to fill, and drag to fill a run - deduction produces cells in runs, and
tapping fifteen of them individually turns reasoning into clerical work. The
mode button (or X, or the right mouse button) switches to marking blanks,
which is how you keep your reasoning on the board instead of in your head.
The score is the time taken, so lower is better. Filling a cell that turns out to be blank is counted and shown, never punished - the puzzle never required a guess, so a wrong cell is information, not a penalty.
generate() already takes its random source for this"draft": true off the entry in build/games.json, and update the
hub copy that says twenty games in four placesAdvertisement