A local desktop Nonogram (Picross) solver with a Tkinter GUI. Enter row and column clues, solve using logic propagation plus backtracking, and save/load puzzles as JSON. Designed for Python 3.10+ with no third-party dependencies.
The vast majority of the code was generated by GPT.
- Board-first workspace with a dedicated header, live sidebar, and activity trail
- GUI for editing row/column clues and visualizing the grid
- Scrollable board area so larger puzzles do not crush the layout
- Live puzzle summary with dimensions, clue balance, and solve telemetry
- Left-click cycles a cell through unknown/filled/empty, right-click sets empty
- Logic propagation (constraint intersection) + DFS backtracking
- Detects no-solution / unique-solution / multiple-solution cases
- Step mode: one propagation pass for incremental solving
- Stop button to cancel long solves (threaded, UI stays responsive)
- JSON open/save with current grid state
- Python 3.10+
- Tkinter (ships with standard Python on most platforms)
python main.pyOptional: open the sample puzzle at examples/sample_10x10.json.
The interface is organized into three regions:
-
Header bar for project actions, solver actions, and zoom control
-
Left sidebar for puzzle stats, legend, and live solver telemetry
-
Main workspace for the board, status banner, and activity trail
-
New: create a new puzzle size
-
Open/Save: load or persist a JSON puzzle file
-
Solve: full solve (propagation + DFS), reports status
-
Step: one propagation pass (no backtracking)
-
Stop: cancel a running solve
-
Reset: clear the grid to all unknown
- Double-click a row/column hint area to edit that line.
- Use
.(dot) as the separator when typing clues, for example:3.1.2means blocks of 3, 1, and 2- empty input means no filled cells
- Dot separators are used to make numeric keypad input faster.
- Display formatting uses spaces (rows) or line breaks (columns); dots are input-only.
- Left-click cycles unknown (-1) -> filled (1) -> empty (0)
- Right-click sets empty (0)
- Mouse wheel scrolls the board when needed
Ctrl+ mouse wheel adjusts board zoom
The solver works in two phases:
-
Logic propagation
- For each row/column, generate all line patterns that match the clue.
- Filter patterns against current known cells.
- Intersect all valid patterns to determine forced cells.
-
Backtracking (DFS)
- If propagation stalls, choose the row/column with the fewest candidates.
- Branch on candidate patterns, continue propagation.
- Stop after finding two solutions to report "multiple solutions".
Solve can be interrupted at any time with Stop.
-
Step runs exactly one propagation pass: it generates valid line patterns for all rows and columns, intersects them, and applies any forced cells.
-
Solve repeats propagation until it cannot make further changes, then switches to backtracking search. It chooses a row/column with the fewest valid candidates, branches on a candidate, and continues propagation in each branch. The search stops after 2 solutions to report "multiple solutions".
{
"width": 10,
"height": 10,
"row_clues": [[3], [1,1], [], ...],
"col_clues": [[2], [1,1], [], ...],
"grid": [[-1, -1, 1, 0, ...], ...]
}Grid values:
-1= unknown0= empty1= filled
nonogram/
__init__.py
model.py # data model + JSON IO
solver.py # propagation + backtracking solver
gui.py # Tkinter UI
main.py # app entry point
README.md
examples/
sample_10x10.json
- Typical 5x5 to 25x25 puzzles solve quickly.
- Hard puzzles may require deeper search; use Stop to cancel.
This project does not include a built-in PNG export. You can:
- Use a screenshot tool, or
- Add Pillow yourself and export from the canvas
- If Solve reports "no solution", verify the clues and current grid state.
- If Solve reports "multiple solutions", the puzzle is ambiguous.
- If it is slow, try Step to see propagation progress or Stop to cancel.
