Solver for Nonogram puzzles.
If you are new to Nonogram puzzle, see the various iOS / Google apps, e.g. See for example iOS or Android apps.
This solver is currently text-mode only. Input is text file describing the puzzle, output is each of the steps (and some intermediate board positions) as the solver progresses:
$ cat 3x3game
3
1 1
2
2
1 1
2
2
$ ./run.py 3x3game
R1 [1, 1] , 1 possibles
R2 [2] , 2 possibles
R3 [2] , 2 possibles
C1 [1, 1] , 1 possibles
C2 [2] , 2 possibles
C3 [2] , 2 possibles
Applying solution YxY to R1
Applying solution YxY to C1
Remaining: 4/9
R1 : YxY
R2 : x..
R3 : Y..
reduced possibles by 4
Applying solution xYY to R2
Applying solution YYx to R3
Applying solution xYY to C2
Applying solution YYx to C3
Remaining: 0/9
R1 : YxY
R2 : xYY
R3 : YYx
$
input file is text, in 3 sections. All lines are lists of integers, there is currently no comment syntax or any other formatting:
- 1 line containing a single integer for the size of the puzzle grid (call it
n
, 3 in the above example). Currently, only square grids are supported (3x3), but expanding to rectangular should not be hard. n
lines of row clues, 1 line per row.n
lines of column clues, 1 line per column- There is currently no support for inputting already-solved pieces of the puzzle, solver assumes initial grid is empty.
- Some minor sanity-checking is done on the clue lines, see
RowColBase.find_possible()
.
I create solver programs for puzzles as a way of exploring the puzzles and thinking about how to solve them. Alas, unlike Sudoku, Nonogram puzzles are very basic and a single, simple algorithm is sufficient to solve all puzzles I have encountered so far with no guessing or back-tracking. This was something of a disappointment.
The basic algorithm is as follows:
- Read the puzzle in
- Set each cell in the grid to "unknown", represented by '.'
- For each row & column in the grid, generate a set of possible solutions for that row, based only on the clue. This is the most algorithmically interesting part of the problem, see
RowColBase._find_possible()
and the unit tests intests/test_Row.py
. - While the puzzle is not solved:
- For any row or column with only 1 remaining possible solution, apply that and mark all the cells in that row as solved with a state of "Y" or "x" (
RowColbase.apply_solution()
) - For each row & column, check each possible solution and discard any possible solution that has been disqualified by any cell that has been solved (i.e. possible solution has 'Y' but cell has 'x', and vice-versa). (
RowColBase.reduce_possibles()
) - For each row and column:
- check each unsolved cell in that row. If all remaining possible solutions for that row have the same value for that cell, set that cell as solved with that value. (
RowColbase.common_cells()
)
- check each unsolved cell in that row. If all remaining possible solutions for that row have the same value for that cell, set that cell as solved with that value. (
- For any row or column with only 1 remaining possible solution, apply that and mark all the cells in that row as solved with a state of "Y" or "x" (
- If the puzzle is not solvable (probably because the input file is not correct), an
Infeasible
exception will be raised.
That's it.
The solver is implemented in python 3, but should easily be made to work on python2 if required (consider print()
and super()
).
Actual solver is in the nonogram
module.
Unit tests in the tests/
directory are built using the standard unittest
library, they can be run by
python3 -m unittest discover -s tests
Some sample puzzles are in the puzzles/
directory.