multi-puzzle-solver 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multi-puzzle-solver might be problematic. Click here for more details.
- multi_puzzle_solver-0.1.0/PKG-INFO +1897 -0
- multi_puzzle_solver-0.1.0/README.md +1871 -0
- multi_puzzle_solver-0.1.0/pyproject.toml +49 -0
- multi_puzzle_solver-0.1.0/setup.cfg +4 -0
- multi_puzzle_solver-0.1.0/src/multi_puzzle_solver.egg-info/PKG-INFO +1897 -0
- multi_puzzle_solver-0.1.0/src/multi_puzzle_solver.egg-info/SOURCES.txt +56 -0
- multi_puzzle_solver-0.1.0/src/multi_puzzle_solver.egg-info/dependency_links.txt +1 -0
- multi_puzzle_solver-0.1.0/src/multi_puzzle_solver.egg-info/requires.txt +6 -0
- multi_puzzle_solver-0.1.0/src/multi_puzzle_solver.egg-info/top_level.txt +1 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/__init__.py +26 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/core/utils.py +127 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/core/utils_ortools.py +78 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/bridges/bridges.py +106 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/dominosa/dominosa.py +136 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/filling/filling.py +192 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/guess/guess.py +231 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/inertia/inertia.py +122 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/inertia/parse_map/parse_map.py +204 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/inertia/tsp.py +398 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/keen/keen.py +99 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/light_up/light_up.py +95 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/magnets/magnets.py +117 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/map/map.py +56 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/minesweeper/minesweeper.py +110 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/mosaic/mosaic.py +48 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/nonograms/nonograms.py +126 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/pearl/pearl.py +151 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/range/range.py +154 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/signpost/signpost.py +95 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/singles/singles.py +116 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/sudoku/sudoku.py +90 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/tents/tents.py +110 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/towers/towers.py +139 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/tracks/tracks.py +170 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/undead/undead.py +168 -0
- multi_puzzle_solver-0.1.0/src/puzzle_solver/puzzles/unruly/unruly.py +86 -0
- multi_puzzle_solver-0.1.0/tests/test_bridges.py +53 -0
- multi_puzzle_solver-0.1.0/tests/test_dominosa.py +44 -0
- multi_puzzle_solver-0.1.0/tests/test_filling.py +106 -0
- multi_puzzle_solver-0.1.0/tests/test_guess.py +87 -0
- multi_puzzle_solver-0.1.0/tests/test_inertia.py +87 -0
- multi_puzzle_solver-0.1.0/tests/test_keen.py +77 -0
- multi_puzzle_solver-0.1.0/tests/test_light_up.py +45 -0
- multi_puzzle_solver-0.1.0/tests/test_magnets.py +59 -0
- multi_puzzle_solver-0.1.0/tests/test_map.py +102 -0
- multi_puzzle_solver-0.1.0/tests/test_minesweeper.py +37 -0
- multi_puzzle_solver-0.1.0/tests/test_mosaic.py +53 -0
- multi_puzzle_solver-0.1.0/tests/test_nonograms.py +81 -0
- multi_puzzle_solver-0.1.0/tests/test_pearl.py +39 -0
- multi_puzzle_solver-0.1.0/tests/test_range.py +45 -0
- multi_puzzle_solver-0.1.0/tests/test_signpost.py +49 -0
- multi_puzzle_solver-0.1.0/tests/test_singles.py +47 -0
- multi_puzzle_solver-0.1.0/tests/test_sudoku.py +59 -0
- multi_puzzle_solver-0.1.0/tests/test_tents.py +56 -0
- multi_puzzle_solver-0.1.0/tests/test_towers.py +49 -0
- multi_puzzle_solver-0.1.0/tests/test_tracks.py +71 -0
- multi_puzzle_solver-0.1.0/tests/test_undead.py +44 -0
- multi_puzzle_solver-0.1.0/tests/test_unruly.py +52 -0
|
@@ -0,0 +1,1897 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multi-puzzle-solver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Efficient solvers for numerous popular and esoteric logic puzzles using CP-SAT
|
|
5
|
+
Author: Ar-Kareem
|
|
6
|
+
Project-URL: Homepage, https://github.com/Ar-Kareem/puzzle_solver
|
|
7
|
+
Project-URL: Repository, https://github.com/Ar-Kareem/puzzle_solver
|
|
8
|
+
Project-URL: Issues, https://github.com/Ar-Kareem/puzzle_solver/issues
|
|
9
|
+
Keywords: puzzle,solver,sat,cp-sat,google-or-tools,efficient,logic,puzzle-solver,puzzle-solver-python,sudoku,nonograms,minesweeper,dominosa,lightup,map,inertia,tents,towers,tracks,undead,unruly,bridges,pearl,range,signpost,singles,magnets
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: ortools
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# Python Puzzle Solver
|
|
28
|
+
|
|
29
|
+
Solve classic logic puzzles automatically in Python.
|
|
30
|
+
|
|
31
|
+
The aim of this repo is to provide very efficient solvers (i.e. not brute force solvers) for many popular pencil logic puzzles like Nonograms, Sudoku, Minesweeper, and many more lesser known ones.
|
|
32
|
+
|
|
33
|
+
If you happen to have a puzzle similar to the ones listed below and want to solve it (or see how many potential solutions a partially covered board has), then this repo is perfect for you.
|
|
34
|
+
|
|
35
|
+
The simple use-case of this repo is if you want to solve a puzzle given the state of the board. But other use-cases exist such as checking if removing a clue would still result in a unique solution or would make the puzzle ambiguous and have multiple solutions.
|
|
36
|
+
|
|
37
|
+
**Why?** There are countless python packages that can solve popular puzzles like the ones below, so a valid question to ask is **why would I want to use this package and why did you create it?**. That is a fair question and the answer is that there are multiple problems with most of those packages that this package solves.
|
|
38
|
+
|
|
39
|
+
1. **Sophisticated solvers:** A lot of available online solvers are incredibly inefficient as they implement naive algorithms that brute force and backtrack through all possible solutions. This package solves that issue as all the solvers included here never use naive algorithms and instead use a very efficient CP-SAT solver which is a more sophisticated solver than any one person could possibly write.
|
|
40
|
+
2. **Numerous puzzles:** Most of the available python solvers are only designed for a single type of puzzle and each one requires a different way to encode the input and extract the solution. This package solves both those issues as this package provides solvers for many puzzles all with a similar interface that encodes the input and extracts the solution in a similar way.
|
|
41
|
+
3. **Esoteric puzzles:** Most packages you can find online are only designed for popular puzzles. This package partially solves this issue by providing solvers for many puzzles. I'm open to suggestions for implementing solvers for more puzzles.
|
|
42
|
+
4. **All possible solutions:** The available solvers often lack uniqueness checks and simply stop at the first possible solution without verifying uniqueness or completeness. This package supports checking whether puzzles are uniquely solvable, ambiguous, or unsolvable for all the puzzles.
|
|
43
|
+
|
|
44
|
+
Play the original puzzles online: https://www.chiark.greenend.org.uk/~sgtatham/puzzles
|
|
45
|
+
|
|
46
|
+
Almost all the solvers in this repo use the CP-SAT solver from Google OR-Tools.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Table of Contents
|
|
51
|
+
|
|
52
|
+
- [Python Puzzle Solver](#python-puzzle-solver)
|
|
53
|
+
- [Table of Contents](#table-of-contents)
|
|
54
|
+
- [Puzzles](#puzzles)
|
|
55
|
+
- [Nonograms (Puzzle Type #1)](#nonograms-puzzle-type-1)
|
|
56
|
+
- [Sudoku (Puzzle Type #2)](#sudoku-puzzle-type-2)
|
|
57
|
+
- [Minesweeper (Puzzle Type #3)](#minesweeper-puzzle-type-3)
|
|
58
|
+
- [Dominosa (Puzzle Type #4)](#dominosa-puzzle-type-4)
|
|
59
|
+
- [Light Up (Puzzle Type #5)](#light-up-puzzle-type-5)
|
|
60
|
+
- [Tents (Puzzle Type #6)](#tents-puzzle-type-6)
|
|
61
|
+
- [Filling (Puzzle Type #7)](#filling-puzzle-type-7)
|
|
62
|
+
- [Keen (Puzzle Type #8)](#keen-puzzle-type-8)
|
|
63
|
+
- [Towers (Puzzle Type #9)](#towers-puzzle-type-9)
|
|
64
|
+
- [Singles (Puzzle Type #10)](#singles-puzzle-type-10)
|
|
65
|
+
- [Magnets (Puzzle Type #11)](#magnets-puzzle-type-11)
|
|
66
|
+
- [Signpost (Puzzle Type #12)](#signpost-puzzle-type-12)
|
|
67
|
+
- [Range (Puzzle Type #13)](#range-puzzle-type-13)
|
|
68
|
+
- [UnDead (Puzzle Type #14)](#undead-puzzle-type-14)
|
|
69
|
+
- [Unruly (Puzzle Type #15)](#unruly-puzzle-type-15)
|
|
70
|
+
- [Tracks (Puzzle Type #16)](#tracks-puzzle-type-16)
|
|
71
|
+
- [Mosaic (Puzzle Type #17)](#mosaic-puzzle-type-17)
|
|
72
|
+
- [Map (Puzzle Type #18)](#map-puzzle-type-18)
|
|
73
|
+
- [Pearl (Puzzle Type #19)](#pearl-puzzle-type-19)
|
|
74
|
+
- [Bridges (Puzzle Type #20)](#bridges-puzzle-type-20)
|
|
75
|
+
- [Inertia (Puzzle Type #21)](#inertia-puzzle-type-21)
|
|
76
|
+
- [Guess (Puzzle Type #22)](#guess-puzzle-type-22)
|
|
77
|
+
- [Quick Start](#quick-start)
|
|
78
|
+
- [1) Install Python deps](#1-install-python-deps)
|
|
79
|
+
- [2) Explore a puzzle](#2-explore-a-puzzle)
|
|
80
|
+
- [Why SAT / CP-SAT?](#why-sat--cp-sat)
|
|
81
|
+
- [What’s Inside](#whats-inside)
|
|
82
|
+
- [Testing](#testing)
|
|
83
|
+
- [Contributing](#contributing)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
# Puzzles
|
|
88
|
+
|
|
89
|
+
The puzzles that have solvers implemented are listed below. Each puzzle has a simple example input board followed by the code to utilize this package and solve the puzzle, followed by the scripts output, and finally the solved puzzle.
|
|
90
|
+
|
|
91
|
+
## Nonograms (Puzzle Type #1)
|
|
92
|
+
|
|
93
|
+
Called "Pattern" in the website.
|
|
94
|
+
|
|
95
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/pattern.html)
|
|
96
|
+
|
|
97
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/pattern.html#pattern)
|
|
98
|
+
|
|
99
|
+
* [**Solver Code**][1]
|
|
100
|
+
|
|
101
|
+
<details>
|
|
102
|
+
<summary><strong>Rules</strong></summary>
|
|
103
|
+
You have a grid of squares, which must all be filled in either black or white. Beside each row of the grid are listed, in order, the lengths of the runs of black squares on that row; above each column are listed, in order, the lengths of the runs of black squares in that column. Your aim is to fill in the entire grid black or white.
|
|
104
|
+
</details>
|
|
105
|
+
|
|
106
|
+
**Unsolved puzzle**
|
|
107
|
+
|
|
108
|
+
<img src="./images/nonogram_unsolved.png" alt="Nonogram unsolved" width="500">
|
|
109
|
+
|
|
110
|
+
Code to utilize this package and solve the puzzle:
|
|
111
|
+
```python
|
|
112
|
+
from puzzle_solver import nonograms_solver as solver
|
|
113
|
+
top_numbers = [
|
|
114
|
+
[8, 2],
|
|
115
|
+
[5, 4],
|
|
116
|
+
[2, 1, 4],
|
|
117
|
+
[2, 4],
|
|
118
|
+
[2, 1, 4],
|
|
119
|
+
[2, 5],
|
|
120
|
+
[2, 8],
|
|
121
|
+
[3, 2],
|
|
122
|
+
[1, 6],
|
|
123
|
+
[1, 9],
|
|
124
|
+
[1, 6, 1],
|
|
125
|
+
[1, 5, 3],
|
|
126
|
+
[3, 2, 1],
|
|
127
|
+
[4, 2],
|
|
128
|
+
[1, 5],
|
|
129
|
+
]
|
|
130
|
+
side_numbers = [
|
|
131
|
+
[7, 3],
|
|
132
|
+
[7, 1, 1],
|
|
133
|
+
[2, 3],
|
|
134
|
+
[2, 3],
|
|
135
|
+
[3, 2],
|
|
136
|
+
[1, 1, 1, 1, 2],
|
|
137
|
+
[1, 6, 1],
|
|
138
|
+
[1, 9],
|
|
139
|
+
[9],
|
|
140
|
+
[2, 4],
|
|
141
|
+
[8],
|
|
142
|
+
[11],
|
|
143
|
+
[7, 1, 1],
|
|
144
|
+
[4, 3],
|
|
145
|
+
[3, 2],
|
|
146
|
+
]
|
|
147
|
+
binst = solver.Board(top=top_numbers, side=side_numbers)
|
|
148
|
+
solutions = binst.solve_and_print()
|
|
149
|
+
```
|
|
150
|
+
**Script Output**
|
|
151
|
+
```
|
|
152
|
+
Solution found
|
|
153
|
+
B B B B B B B . B B B . . . .
|
|
154
|
+
B B B B B B B . . . . . B . B
|
|
155
|
+
B B . . . . . . . . . B B B .
|
|
156
|
+
B B . . . . . . . . . . B B B
|
|
157
|
+
B B B . . . . . . . . . . B B
|
|
158
|
+
B . . . B . B . . B . . . B B
|
|
159
|
+
B . . . . . B B B B B B . . B
|
|
160
|
+
B . . . . . B B B B B B B B B
|
|
161
|
+
. . . . . B B B B B B B B B .
|
|
162
|
+
. . . . . B B . B B B B . . .
|
|
163
|
+
. . . . B B B B B B B B . . .
|
|
164
|
+
B B B B B B B B B B B . . . .
|
|
165
|
+
B B B B B B B . . B . B . . .
|
|
166
|
+
. B B B B . . . . B B B . . .
|
|
167
|
+
. B B B . . . . . . . B B . .
|
|
168
|
+
Solutions found: 1
|
|
169
|
+
status: OPTIMAL
|
|
170
|
+
Time taken: 0.04 seconds
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Solved puzzle**
|
|
174
|
+
|
|
175
|
+
<img src="./images/nonogram_solved.png" alt="Nonogram solved" width="500">
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Sudoku (Puzzle Type #2)
|
|
180
|
+
|
|
181
|
+
Called "Solo" in the website.
|
|
182
|
+
|
|
183
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/solo.html)
|
|
184
|
+
|
|
185
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/solo.html#solo)
|
|
186
|
+
|
|
187
|
+
* [**Solver Code**][2]
|
|
188
|
+
|
|
189
|
+
<details>
|
|
190
|
+
<summary><strong>Rules</strong></summary>
|
|
191
|
+
You have a square grid, which is divided into as many equally sized sub-blocks as the grid has rows. Each square must be filled in with a digit from 1 to the size of the grid, in such a way that
|
|
192
|
+
|
|
193
|
+
- every row contains only one occurrence of each digit
|
|
194
|
+
- every column contains only one occurrence of each digit
|
|
195
|
+
- every block contains only one occurrence of each digit.
|
|
196
|
+
|
|
197
|
+
You are given some of the numbers as clues; your aim is to place the rest of the numbers correctly.
|
|
198
|
+
</details>
|
|
199
|
+
|
|
200
|
+
**Unsolved puzzle**
|
|
201
|
+
|
|
202
|
+
<img src="./images/sudoku_unsolved.png" alt="Sudoku unsolved" width="500">
|
|
203
|
+
|
|
204
|
+
Code to utilize this package and solve the puzzle:
|
|
205
|
+
```python
|
|
206
|
+
import numpy as np
|
|
207
|
+
from puzzle_solver import sudoku_solver as solver
|
|
208
|
+
bor = np.array([
|
|
209
|
+
['*', '7', '5', '4', '9', '1', 'c', 'e', 'd', 'f', '*', '*', '2', '*', '3', '*'],
|
|
210
|
+
['*', '*', '*', '*', 'f', 'a', '*', '*', '*', '6', '*', 'c', '*', '*', '8', 'b'],
|
|
211
|
+
['*', '*', '1', '*', '*', '6', '*', '*', '*', '9', '*', '*', '*', 'g', '*', 'd'],
|
|
212
|
+
['*', '6', '*', '*', '*', '*', '*', '*', '*', '*', '5', 'g', 'c', '7', '*', '*'],
|
|
213
|
+
|
|
214
|
+
['4', 'a', '*', '*', '*', '*', '*', '*', '*', '*', '*', '9', '*', '*', '*', '*'],
|
|
215
|
+
['*', 'g', 'f', '*', 'e', '*', '*', '5', '4', '*', '*', '1', '*', '9', '*', '8'],
|
|
216
|
+
['*', '*', '*', '*', 'a', '3', 'b', '7', 'c', 'g', '*', '6', '*', '*', '*', '4'],
|
|
217
|
+
['*', 'b', '*', '7', '*', '*', '*', '*', 'f', '*', '3', '*', '*', 'a', '*', '6'],
|
|
218
|
+
|
|
219
|
+
['2', '*', 'a', '*', '*', 'c', '*', '1', '*', '*', '*', '*', '7', '*', '6', '*'],
|
|
220
|
+
['8', '*', '*', '*', '3', '*', 'e', 'f', '7', '5', 'c', 'd', '*', '*', '*', '*'],
|
|
221
|
+
['9', '*', '3', '*', '7', '*', '*', 'a', '6', '*', '*', '2', '*', 'b', '1', '*'],
|
|
222
|
+
['*', '*', '*', '*', '4', '*', '*', '*', '*', '*', '*', '*', '*', '*', 'e', 'f'],
|
|
223
|
+
|
|
224
|
+
['*', '*', 'g', 'd', '2', '9', '*', '*', '*', '*', '*', '*', '*', '*', '4', '*'],
|
|
225
|
+
['a', '*', 'b', '*', '*', '*', '5', '*', '*', '*', 'd', '*', '*', '8', '*', '*'],
|
|
226
|
+
['e', '8', '*', '*', '1', '*', '4', '*', '*', '*', '6', '7', '*', '*', '*', '*'],
|
|
227
|
+
['*', '3', '*', '9', '*', '*', 'f', '8', 'a', 'e', 'g', '5', 'b', 'c', 'd', '*'],
|
|
228
|
+
])
|
|
229
|
+
binst = solver.Board(board=bor)
|
|
230
|
+
solutions = binst.solve_and_print()
|
|
231
|
+
assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
|
|
232
|
+
```
|
|
233
|
+
**Script Output**
|
|
234
|
+
```
|
|
235
|
+
Solution found
|
|
236
|
+
[['g' '7' '5' '4' '9' '1' 'c' 'e' 'd' 'f' 'b' '8' '2' '6' '3' 'a']
|
|
237
|
+
['3' '9' 'd' 'e' 'f' 'a' '7' 'g' '2' '6' '4' 'c' '5' '1' '8' 'b']
|
|
238
|
+
['b' 'c' '1' '8' '5' '6' '3' '2' 'e' '9' '7' 'a' '4' 'g' 'f' 'd']
|
|
239
|
+
['f' '6' '2' 'a' 'b' '8' 'd' '4' '1' '3' '5' 'g' 'c' '7' '9' 'e']
|
|
240
|
+
['4' 'a' 'e' '3' '8' 'f' '1' '6' '5' 'b' '2' '9' 'g' 'd' 'c' '7']
|
|
241
|
+
['6' 'g' 'f' 'c' 'e' 'd' '2' '5' '4' '7' 'a' '1' '3' '9' 'b' '8']
|
|
242
|
+
['d' '1' '9' '2' 'a' '3' 'b' '7' 'c' 'g' '8' '6' 'e' 'f' '5' '4']
|
|
243
|
+
['5' 'b' '8' '7' 'g' '4' '9' 'c' 'f' 'd' '3' 'e' '1' 'a' '2' '6']
|
|
244
|
+
['2' 'e' 'a' 'b' 'd' 'c' 'g' '1' '3' '8' '9' 'f' '7' '4' '6' '5']
|
|
245
|
+
['8' '4' '6' '1' '3' 'b' 'e' 'f' '7' '5' 'c' 'd' 'a' '2' 'g' '9']
|
|
246
|
+
['9' 'f' '3' 'g' '7' '5' '8' 'a' '6' '4' 'e' '2' 'd' 'b' '1' 'c']
|
|
247
|
+
['c' 'd' '7' '5' '4' '2' '6' '9' 'g' 'a' '1' 'b' '8' '3' 'e' 'f']
|
|
248
|
+
['7' '5' 'g' 'd' '2' '9' 'a' 'b' '8' 'c' 'f' '3' '6' 'e' '4' '1']
|
|
249
|
+
['a' '2' 'b' '6' 'c' 'e' '5' '3' '9' '1' 'd' '4' 'f' '8' '7' 'g']
|
|
250
|
+
['e' '8' 'c' 'f' '1' 'g' '4' 'd' 'b' '2' '6' '7' '9' '5' 'a' '3']
|
|
251
|
+
['1' '3' '4' '9' '6' '7' 'f' '8' 'a' 'e' 'g' '5' 'b' 'c' 'd' '2']]
|
|
252
|
+
Solutions found: 1
|
|
253
|
+
status: OPTIMAL
|
|
254
|
+
Time taken: 0.04 seconds
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Solved puzzle**
|
|
258
|
+
|
|
259
|
+
<img src="./images/sudoku_solved.png" alt="Sudoku solved" width="500">
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Minesweeper (Puzzle Type #3)
|
|
264
|
+
|
|
265
|
+
This Minesweeper solver is a bit different from the other solvers in this repo because in Minesweeper is a uniquely different type of puzzle.
|
|
266
|
+
|
|
267
|
+
In Minesweeper, you don't solve the puzzle in one go. You need to partially solve the puzzle and get new information to continue. Thus the solver is designed to take the state of the board at any timestep and always gives the most amount of garunteed next steps to take (i.e. garunteed safe positions, garunteed mine positions, and even warns you if you placed a flag in a potentially wrong position).
|
|
268
|
+
|
|
269
|
+
Then obviously, once the you act upon the guesses and get the new information, you simply put that new info back into the solver and repeat the process until the puzzle is fully solved.
|
|
270
|
+
|
|
271
|
+
Below is an example of how to utilize the solver while in the middle of a puzzle. (notice how there's an intentionally placed incorrect flag in the example and the solver will warn you about it)
|
|
272
|
+
|
|
273
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/mines.html)
|
|
274
|
+
|
|
275
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/mines.html#mines)
|
|
276
|
+
|
|
277
|
+
* [**Solver Code**][3]
|
|
278
|
+
|
|
279
|
+
<details>
|
|
280
|
+
<summary><strong>Rules</strong></summary>
|
|
281
|
+
You have a grid of covered squares, some of which contain mines, but you don't know which. Your job is to uncover every square which does not contain a mine. If you uncover a square containing a mine, you lose. If you uncover a square which does not contain a mine, you are told how many mines are contained within the eight surrounding squares.
|
|
282
|
+
|
|
283
|
+
This game needs no introduction; popularised by Windows, it is perhaps the single best known desktop puzzle game in existence.
|
|
284
|
+
|
|
285
|
+
This version of it has an unusual property. By default, it will generate its mine positions in such a way as to ensure that you never need to guess where a mine is: you will always be able to deduce it somehow. So you will never, as can happen in other versions, get to the last four squares and discover that there are two mines left but you have no way of knowing for sure where they are.
|
|
286
|
+
</details>
|
|
287
|
+
|
|
288
|
+
**Partially solved puzzle**
|
|
289
|
+
|
|
290
|
+
<img src="./images/minesweeper_pre.png" alt="Minesweeper partially solved" width="500">
|
|
291
|
+
|
|
292
|
+
Code to utilize this package and solve the puzzle:
|
|
293
|
+
```python
|
|
294
|
+
import numpy as np
|
|
295
|
+
from puzzle_solver import minesweeper_solver as solver
|
|
296
|
+
bor = np.array([
|
|
297
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '1', '1', '1', '3', 'F', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
298
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '2', '2', '1', 'F', '4', 'F', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
299
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'F', '2', '1', '3', 'F', '5', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
300
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '2', '4', 'F', '3', '0', '3', 'F', 'F', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
301
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '3', '4', 'F', '3', '0', '2', 'F', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
302
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'F', '4', 'F', '2', '0', '2', '3', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
303
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'F', '4', '1', '1', '0', '1', 'F', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
304
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'F', '4', '2', '1', '1', '2', '2', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
305
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
306
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
307
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
308
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
309
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
310
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
311
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
312
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
313
|
+
])
|
|
314
|
+
mine_count = 30
|
|
315
|
+
safe_positions, new_garuneed_mine_positions, wrong_flag_positions = solver.give_next_guess(board=bor, mine_count=mine_count)
|
|
316
|
+
```
|
|
317
|
+
**Script Output**
|
|
318
|
+
```
|
|
319
|
+
Found 8 new guaranteed safe positions
|
|
320
|
+
{Pos(x=9, y=0), Pos(x=15, y=8), Pos(x=15, y=7), Pos(x=9, y=2), Pos(x=15, y=6), Pos(x=7, y=2), Pos(x=9, y=1), Pos(x=12, y=8)}
|
|
321
|
+
----------
|
|
322
|
+
Found 4 new guaranteed mine positions
|
|
323
|
+
{Pos(x=8, y=2), Pos(x=7, y=5), Pos(x=10, y=0), Pos(x=9, y=8)}
|
|
324
|
+
----------
|
|
325
|
+
WARNING | WARNING | WARNING | WARNING | WARNING
|
|
326
|
+
Found 1 wrong flag positions
|
|
327
|
+
{Pos(x=15, y=3)}
|
|
328
|
+
----------
|
|
329
|
+
Time taken: 0.92 seconds
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Progressed puzzle**
|
|
333
|
+
|
|
334
|
+
<img src="./images/minesweeper_post.png" alt="Minesweeper progressed" width="500">
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Dominosa (Puzzle Type #4)
|
|
339
|
+
|
|
340
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/dominosa.html)
|
|
341
|
+
|
|
342
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/dominosa.html#dominosa)
|
|
343
|
+
|
|
344
|
+
* [**Solver Code**][4]
|
|
345
|
+
|
|
346
|
+
<details>
|
|
347
|
+
<summary><strong>Rules</strong></summary>
|
|
348
|
+
A normal set of dominoes – that is, one instance of every (unordered) pair of numbers from 0 to N – has been arranged irregularly into a rectangle; then the number in each square has been written down and the dominoes themselves removed.
|
|
349
|
+
|
|
350
|
+
Your task is to reconstruct the pattern by arranging the set of dominoes to match the provided array of numbers.
|
|
351
|
+
</details>
|
|
352
|
+
|
|
353
|
+
**Unsolved puzzle**
|
|
354
|
+
|
|
355
|
+
<img src="./images/dominosa_unsolved.png" alt="Dominosa unsolved" width="500">
|
|
356
|
+
|
|
357
|
+
Code to utilize this package and solve the puzzle:
|
|
358
|
+
```python
|
|
359
|
+
import numpy as np
|
|
360
|
+
from puzzle_solver import dominosa_solver as solver
|
|
361
|
+
bor = np.array([
|
|
362
|
+
[6, 8, 2, 7, 1, 3, 3, 4, 6, 6, 0],
|
|
363
|
+
[4, 9, 5, 6, 1, 0, 6, 1, 2, 2, 4],
|
|
364
|
+
[8, 2, 8, 9, 1, 9, 3, 3, 8, 8, 5],
|
|
365
|
+
[1, 1, 7, 3, 4, 7, 0, 8, 7, 7, 7],
|
|
366
|
+
[4, 5, 3, 9, 9, 3, 0, 1, 6, 1, 5],
|
|
367
|
+
[6, 9, 5, 8, 9, 2, 1, 2, 6, 7, 9],
|
|
368
|
+
[2, 7, 4, 3, 5, 5, 9, 6, 4, 0, 9],
|
|
369
|
+
[0, 7, 8, 0, 5, 4, 2, 7, 6, 7, 3],
|
|
370
|
+
[0, 4, 5, 2, 8, 6, 1, 0, 9, 0, 4],
|
|
371
|
+
[0, 8, 8, 3, 2, 1, 3, 2, 5, 5, 4],
|
|
372
|
+
])
|
|
373
|
+
binst = solver.Board(board=bor)
|
|
374
|
+
solutions = binst.solve_and_print()
|
|
375
|
+
assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
|
|
376
|
+
```
|
|
377
|
+
**Script Output**
|
|
378
|
+
```
|
|
379
|
+
Solution found
|
|
380
|
+
[['R' 'L' 'R' 'L' 'D' 'R' 'L' 'R' 'L' 'R' 'L']
|
|
381
|
+
['D' 'D' 'R' 'L' 'U' 'D' 'D' 'D' 'R' 'L' 'D']
|
|
382
|
+
['U' 'U' 'D' 'R' 'L' 'U' 'U' 'U' 'R' 'L' 'U']
|
|
383
|
+
['D' 'D' 'U' 'D' 'D' 'R' 'L' 'D' 'R' 'L' 'D']
|
|
384
|
+
['U' 'U' 'D' 'U' 'U' 'R' 'L' 'U' 'D' 'D' 'U']
|
|
385
|
+
['D' 'D' 'U' 'R' 'L' 'D' 'R' 'L' 'U' 'U' 'D']
|
|
386
|
+
['U' 'U' 'R' 'L' 'D' 'U' 'R' 'L' 'R' 'L' 'U']
|
|
387
|
+
['D' 'D' 'D' 'D' 'U' 'R' 'L' 'R' 'L' 'R' 'L']
|
|
388
|
+
['U' 'U' 'U' 'U' 'D' 'D' 'R' 'L' 'D' 'D' 'D']
|
|
389
|
+
['R' 'L' 'R' 'L' 'U' 'U' 'R' 'L' 'U' 'U' 'U']]
|
|
390
|
+
Solutions found: 1
|
|
391
|
+
status: OPTIMAL
|
|
392
|
+
Time taken: 0.02 seconds
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Solved puzzle**
|
|
396
|
+
|
|
397
|
+
<img src="./images/dominosa_solved.png" alt="Dominosa solved" width="500">
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Light Up (Puzzle Type #5)
|
|
402
|
+
|
|
403
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/lightup.html)
|
|
404
|
+
|
|
405
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/lightup.html#lightup)
|
|
406
|
+
|
|
407
|
+
* [**Solver Code**][5]
|
|
408
|
+
|
|
409
|
+
<details>
|
|
410
|
+
<summary><strong>Rules</strong></summary>
|
|
411
|
+
You have a grid of squares. Some are filled in black; some of the black squares are numbered. Your aim is to ‘light up’ all the empty squares by placing light bulbs in some of them.
|
|
412
|
+
|
|
413
|
+
Each light bulb illuminates the square it is on, plus all squares in line with it horizontally or vertically unless a black square is blocking the way.
|
|
414
|
+
|
|
415
|
+
To win the game, you must satisfy the following conditions:
|
|
416
|
+
|
|
417
|
+
- All non-black squares are lit.
|
|
418
|
+
- No light is lit by another light.
|
|
419
|
+
- All numbered black squares have exactly that number of lights adjacent to them (in the four squares above, below, and to the side).
|
|
420
|
+
|
|
421
|
+
Non-numbered black squares may have any number of lights adjacent to them.
|
|
422
|
+
</details>
|
|
423
|
+
|
|
424
|
+
**Unsolved puzzle**
|
|
425
|
+
|
|
426
|
+
<img src="./images/lightup_unsolved.png" alt="Light Up unsolved" width="500">
|
|
427
|
+
|
|
428
|
+
Code to utilize this package and solve the puzzle:
|
|
429
|
+
```python
|
|
430
|
+
import numpy as np
|
|
431
|
+
from puzzle_solver import light_up_solver as solver
|
|
432
|
+
bor = np.array([
|
|
433
|
+
['*', '0', '*', '*', '*', '*', 'W', '*', '*', '*'],
|
|
434
|
+
['*', '*', '*', '0', '*', '*', '*', '*', '*', '1'],
|
|
435
|
+
['W', '*', 'W', '*', '*', 'W', '*', '*', '0', '*'],
|
|
436
|
+
['0', '*', '*', '*', '3', '*', 'W', '*', '0', '*'],
|
|
437
|
+
['*', '*', '*', '*', 'W', '*', '2', '*', 'W', '*'],
|
|
438
|
+
['*', '1', '*', 'W', '*', '2', '*', '*', '*', '*'],
|
|
439
|
+
['*', '0', '*', 'W', '*', 'W', '*', '*', '*', 'W'],
|
|
440
|
+
['*', '0', '*', '*', '1', '*', '*', '2', '*', 'W'],
|
|
441
|
+
['0', '*', '*', '*', '*', '*', '1', '*', '*', '*'],
|
|
442
|
+
['*', '*', '*', '2', '*', '*', '*', '*', 'W', '*'],
|
|
443
|
+
]) # W is wall, * is space, # is number
|
|
444
|
+
|
|
445
|
+
binst = solver.Board(board=bor)
|
|
446
|
+
solutions = binst.solve_and_print()
|
|
447
|
+
```
|
|
448
|
+
**Script Output**
|
|
449
|
+
```
|
|
450
|
+
Solution found
|
|
451
|
+
[[' ' '0' ' ' ' ' ' ' 'L' 'W' ' ' ' ' 'L']
|
|
452
|
+
['L' ' ' ' ' '0' ' ' ' ' 'L' ' ' ' ' '1']
|
|
453
|
+
['W' 'L' 'W' ' ' 'L' 'W' ' ' ' ' '0' ' ']
|
|
454
|
+
['0' ' ' ' ' 'L' '3' 'L' 'W' ' ' '0' ' ']
|
|
455
|
+
[' ' ' ' 'L' ' ' 'W' ' ' '2' 'L' 'W' 'L']
|
|
456
|
+
['L' '1' ' ' 'W' 'L' '2' 'L' ' ' ' ' ' ']
|
|
457
|
+
[' ' '0' ' ' 'W' ' ' 'W' ' ' ' ' ' ' 'W']
|
|
458
|
+
[' ' '0' ' ' ' ' '1' 'L' ' ' '2' 'L' 'W']
|
|
459
|
+
['0' ' ' ' ' 'L' ' ' ' ' '1' 'L' ' ' ' ']
|
|
460
|
+
[' ' 'L' ' ' '2' 'L' ' ' ' ' ' ' 'W' 'L']]
|
|
461
|
+
Solutions found: 1
|
|
462
|
+
status: OPTIMAL
|
|
463
|
+
Time taken: 0.01 seconds
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Solved puzzle**
|
|
467
|
+
|
|
468
|
+
Which exactly matches the true solutions (Remember, the goal of the puzzle is to find where to place the lights, marked as 'L' in the solution above):
|
|
469
|
+
|
|
470
|
+
<img src="./images/lightup_solved.png" alt="Light Up solved" width="500">
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Tents (Puzzle Type #6)
|
|
475
|
+
|
|
476
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/tents.html)
|
|
477
|
+
|
|
478
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/tents.html#tents)
|
|
479
|
+
|
|
480
|
+
* [**Solver Code**][6]
|
|
481
|
+
|
|
482
|
+
<details>
|
|
483
|
+
<summary><strong>Rules</strong></summary>
|
|
484
|
+
You have a grid of squares, some of which contain trees. Your aim is to place tents in some of the remaining squares, in such a way that the following conditions are met:
|
|
485
|
+
|
|
486
|
+
- There are exactly as many tents as trees.
|
|
487
|
+
- The tents and trees can be matched up in such a way that each tent is directly adjacent (horizontally or vertically, but not diagonally) to its own tree. However, a tent may be adjacent to other trees as well as its own.
|
|
488
|
+
- No two tents are adjacent horizontally, vertically or diagonally.
|
|
489
|
+
- The number of tents in each row, and in each column, matches the numbers given round the sides of the grid.
|
|
490
|
+
</details>
|
|
491
|
+
|
|
492
|
+
**Unsolved puzzle**
|
|
493
|
+
|
|
494
|
+
<img src="./images/tents_unsolved.png" alt="Tents unsolved" width="500">
|
|
495
|
+
|
|
496
|
+
Code to utilize this package and solve the puzzle:
|
|
497
|
+
```python
|
|
498
|
+
import numpy as np
|
|
499
|
+
from puzzle_solver import tents_solver as solver
|
|
500
|
+
bor = np.array([
|
|
501
|
+
['*', 'T', '*', '*', '*', '*', '*', '*', 'T', '*', 'T', '*', 'T', '*', '*'],
|
|
502
|
+
['*', '*', '*', '*', 'T', '*', '*', 'T', '*', 'T', '*', '*', 'T', '*', '*'],
|
|
503
|
+
['*', 'T', '*', 'T', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
504
|
+
['*', '*', '*', '*', '*', '*', '*', '*', 'T', '*', '*', '*', 'T', '*', 'T'],
|
|
505
|
+
['*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
506
|
+
['*', 'T', '*', '*', 'T', '*', 'T', '*', '*', 'T', '*', '*', 'T', 'T', '*'],
|
|
507
|
+
['*', 'T', '*', '*', 'T', '*', '*', '*', 'T', '*', '*', '*', '*', '*', '*'],
|
|
508
|
+
['*', '*', '*', '*', '*', '*', 'T', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
509
|
+
['*', '*', '*', '*', '*', 'T', '*', '*', '*', '*', 'T', '*', '*', 'T', '*'],
|
|
510
|
+
['*', '*', '*', 'T', '*', '*', '*', '*', '*', '*', '*', '*', 'T', '*', 'T'],
|
|
511
|
+
['T', '*', '*', '*', '*', '*', '*', 'T', '*', '*', '*', 'T', '*', '*', '*'],
|
|
512
|
+
['T', '*', '*', '*', 'T', '*', 'T', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
513
|
+
['*', '*', '*', '*', '*', '*', '*', '*', 'T', '*', 'T', '*', '*', '*', 'T'],
|
|
514
|
+
['*', 'T', '*', '*', '*', 'T', '*', '*', '*', '*', '*', '*', 'T', '*', '*'],
|
|
515
|
+
['*', 'T', '*', '*', 'T', '*', '*', '*', '*', 'T', '*', 'T', '*', '*', '*'],
|
|
516
|
+
])
|
|
517
|
+
side = np.array([4, 1, 6, 0, 5, 2, 3, 1, 5, 2, 3, 2, 4, 3, 4])
|
|
518
|
+
top = np.array([4, 2, 4, 1, 3, 3, 3, 3, 3, 3, 2, 2, 6, 2, 4])
|
|
519
|
+
|
|
520
|
+
binst = solver.Board(board=bor, sides={'top': top, 'side': side})
|
|
521
|
+
solutions = binst.solve_and_print()
|
|
522
|
+
```
|
|
523
|
+
**Script Output**
|
|
524
|
+
```
|
|
525
|
+
Solution found
|
|
526
|
+
[[' ' 'T' 'E' ' ' ' ' ' ' ' ' 'E' 'T' ' ' 'T' 'E' 'T' 'E' ' ']
|
|
527
|
+
[' ' ' ' ' ' ' ' 'T' 'E' ' ' 'T' ' ' 'T' ' ' ' ' 'T' ' ' ' ']
|
|
528
|
+
['E' 'T' 'E' 'T' ' ' ' ' ' ' 'E' ' ' 'E' ' ' ' ' 'E' ' ' 'E']
|
|
529
|
+
[' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 'T' ' ' ' ' ' ' 'T' ' ' 'T']
|
|
530
|
+
[' ' 'E' ' ' ' ' 'E' ' ' 'E' ' ' 'E' ' ' ' ' ' ' 'E' ' ' ' ']
|
|
531
|
+
[' ' 'T' ' ' ' ' 'T' ' ' 'T' ' ' ' ' 'T' 'E' ' ' 'T' 'T' 'E']
|
|
532
|
+
[' ' 'T' ' ' ' ' 'T' 'E' ' ' 'E' 'T' ' ' ' ' ' ' 'E' ' ' ' ']
|
|
533
|
+
[' ' 'E' ' ' ' ' ' ' ' ' 'T' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ']
|
|
534
|
+
[' ' ' ' ' ' ' ' 'E' 'T' 'E' ' ' ' ' 'E' 'T' ' ' 'E' 'T' 'E']
|
|
535
|
+
['E' ' ' 'E' 'T' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 'T' ' ' 'T']
|
|
536
|
+
['T' ' ' ' ' ' ' ' ' ' ' ' ' 'T' 'E' ' ' ' ' 'T' 'E' ' ' 'E']
|
|
537
|
+
['T' ' ' ' ' 'E' 'T' 'E' 'T' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ']
|
|
538
|
+
['E' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 'T' 'E' 'T' 'E' ' ' 'E' 'T']
|
|
539
|
+
[' ' 'T' 'E' ' ' 'E' 'T' 'E' ' ' ' ' ' ' ' ' ' ' 'T' ' ' ' ']
|
|
540
|
+
['E' 'T' ' ' ' ' 'T' ' ' ' ' ' ' 'E' 'T' 'E' 'T' 'E' ' ' ' ']]
|
|
541
|
+
Solutions found: 1
|
|
542
|
+
status: OPTIMAL
|
|
543
|
+
Time taken: 0.02 seconds
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Solved puzzle**
|
|
547
|
+
|
|
548
|
+
<img src="./images/tents_solved.png" alt="Tents solved" width="500">
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Filling (Puzzle Type #7)
|
|
553
|
+
|
|
554
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/filling.html)
|
|
555
|
+
|
|
556
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/filling.html#filling)
|
|
557
|
+
|
|
558
|
+
* [**Solver Code**][7]
|
|
559
|
+
|
|
560
|
+
<details>
|
|
561
|
+
<summary><strong>Rules</strong></summary>
|
|
562
|
+
You have a grid of squares, some of which contain digits, and the rest of which are empty. Your job is to fill in digits in the empty squares, in such a way that each connected region of squares all containing the same digit has an area equal to that digit.
|
|
563
|
+
|
|
564
|
+
(‘Connected region’, for the purposes of this game, does not count diagonally separated squares as adjacent.)
|
|
565
|
+
|
|
566
|
+
For example, it follows that no square can contain a zero, and that two adjacent squares can not both contain a one. No region has an area greater than 9 (because then its area would not be a single digit).
|
|
567
|
+
</details>
|
|
568
|
+
|
|
569
|
+
(Note: The solver for this puzzle is the only extremely slow solver in this repo and will take a minute to solve a simple 6x7 puzzle)
|
|
570
|
+
|
|
571
|
+
**Unsolved puzzle**
|
|
572
|
+
|
|
573
|
+
<img src="./images/filling_unsolved.png" alt="Filling unsolved" width="500">
|
|
574
|
+
|
|
575
|
+
Code to utilize this package and solve the puzzle:
|
|
576
|
+
```python
|
|
577
|
+
import numpy as np
|
|
578
|
+
from puzzle_solver import filling_solver as solver
|
|
579
|
+
bor = np.array([
|
|
580
|
+
['*', '4', '2', '*', '*', '2', '*'],
|
|
581
|
+
['*', '*', '7', '*', '*', '3', '*'],
|
|
582
|
+
['*', '*', '*', '*', '4', '*', '3'],
|
|
583
|
+
['*', '6', '6', '*', '3', '*', '*'],
|
|
584
|
+
['*', '7', '*', '6', '4', '5', '*'],
|
|
585
|
+
['*', '6', '*', '*', '*', '*', '4'],
|
|
586
|
+
])
|
|
587
|
+
binst = solver.Board(board=bor)
|
|
588
|
+
solutions = binst.solve_and_print()
|
|
589
|
+
assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
|
|
590
|
+
```
|
|
591
|
+
**Script Output**
|
|
592
|
+
```
|
|
593
|
+
Solution found
|
|
594
|
+
[[4 4 2 2 4 2 2]
|
|
595
|
+
[4 4 7 4 4 3 3]
|
|
596
|
+
[7 7 7 3 4 5 3]
|
|
597
|
+
[7 6 6 3 3 5 5]
|
|
598
|
+
[7 7 6 6 4 5 5]
|
|
599
|
+
[1 6 6 1 4 4 4]]
|
|
600
|
+
Solutions found: 1
|
|
601
|
+
status: OPTIMAL
|
|
602
|
+
Time taken: 46.27 seconds
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Solved puzzle**
|
|
606
|
+
|
|
607
|
+
<img src="./images/filling_solved.png" alt="Filling solved" width="500">
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Keen (Puzzle Type #8)
|
|
612
|
+
|
|
613
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/keen.html)
|
|
614
|
+
|
|
615
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/keen.html#keen)
|
|
616
|
+
|
|
617
|
+
* [**Solver Code**][8]
|
|
618
|
+
|
|
619
|
+
<details>
|
|
620
|
+
<summary><strong>Rules</strong></summary>
|
|
621
|
+
You have a square grid; each square may contain a digit from 1 to the size of the grid. The grid is divided into blocks of varying shape and size, with arithmetic clues written in them. Your aim is to fully populate the grid with digits such that:
|
|
622
|
+
|
|
623
|
+
- Each row contains only one occurrence of each digit
|
|
624
|
+
- Each column contains only one occurrence of each digit
|
|
625
|
+
- The digits in each block can be combined to form the number stated in the clue, using the arithmetic operation given in the clue. That is:
|
|
626
|
+
- An addition clue means that the sum of the digits in the block must be the given number. For example, ‘15+’ means the contents of the block adds up to fifteen.
|
|
627
|
+
- A multiplication clue (e.g. ‘60×’), similarly, means that the product of the digits in the block must be the given number.
|
|
628
|
+
- A subtraction clue will always be written in a block of size two, and it means that one of the digits in the block is greater than the other by the given amount. For example, ‘2−’ means that one of the digits in the block is 2 more than the other, or equivalently that one digit minus the other one is 2. The two digits could be either way round, though.
|
|
629
|
+
- A division clue (e.g. ‘3÷’), similarly, is always in a block of size two and means that one digit divided by the other is equal to the given amount.
|
|
630
|
+
|
|
631
|
+
Note that a block may contain the same digit more than once (provided the identical ones are not in the same row and column).
|
|
632
|
+
</details>
|
|
633
|
+
|
|
634
|
+
**Unsolved puzzle**
|
|
635
|
+
|
|
636
|
+
<img src="./images/keen_unsolved.png" alt="Keen unsolved" width="500">
|
|
637
|
+
|
|
638
|
+
Code to utilize this package and solve the puzzle:
|
|
639
|
+
```python
|
|
640
|
+
import numpy as np
|
|
641
|
+
from puzzle_solver import keen_solver as solver
|
|
642
|
+
# tells the api the shape of the blocks in the board
|
|
643
|
+
bor = np.array([
|
|
644
|
+
['d01', 'd01', 'd03', 'd03', 'd05', 'd05', 'd08', 'd08', 'd10'],
|
|
645
|
+
['d02', 'd02', 'd03', 'd04', 'd06', 'd06', 'd09', 'd09', 'd10'],
|
|
646
|
+
['d12', 'd13', 'd14', 'd04', 'd07', 'd07', 'd07', 'd11', 'd11'],
|
|
647
|
+
['d12', 'd13', 'd14', 'd14', 'd15', 'd16', 'd11', 'd11', 'd18'],
|
|
648
|
+
['d19', 'd20', 'd24', 'd26', 'd15', 'd16', 'd16', 'd17', 'd18'],
|
|
649
|
+
['d19', 'd20', 'd24', 'd26', 'd28', 'd28', 'd29', 'd17', 'd33'],
|
|
650
|
+
['d21', 'd21', 'd24', 'd27', 'd30', 'd30', 'd29', 'd33', 'd33'],
|
|
651
|
+
['d22', 'd23', 'd25', 'd27', 'd31', 'd32', 'd34', 'd34', 'd36'],
|
|
652
|
+
['d22', 'd23', 'd25', 'd25', 'd31', 'd32', 'd35', 'd35', 'd36'],
|
|
653
|
+
])
|
|
654
|
+
# tells the api the operation and the result for each block
|
|
655
|
+
block_results = {
|
|
656
|
+
'd01': ('-', 1), 'd02': ('-', 1), 'd03': ('*', 378), 'd04': ('/', 4), 'd05': ('/', 2),
|
|
657
|
+
'd06': ('-', 2), 'd07': ('*', 6), 'd08': ('+', 9), 'd09': ('/', 2), 'd10': ('+', 9),
|
|
658
|
+
'd11': ('+', 22), 'd12': ('-', 1), 'd13': ('*', 30), 'd14': ('+', 12), 'd15': ('-', 1),
|
|
659
|
+
'd16': ('*', 196), 'd17': ('*', 63), 'd18': ('-', 1), 'd19': ('/', 3), 'd20': ('/', 3),
|
|
660
|
+
'd21': ('*', 21), 'd22': ('/', 4), 'd23': ('-', 7), 'd24': ('*', 64), 'd25': ('+', 15),
|
|
661
|
+
'd26': ('-', 1), 'd27': ('+', 11), 'd28': ('-', 4), 'd29': ('/', 4), 'd30': ('*', 54),
|
|
662
|
+
'd31': ('+', 11), 'd32': ('/', 4), 'd33': ('+', 16), 'd34': ('+', 15), 'd35': ('*', 30),
|
|
663
|
+
'd36': ('-', 7),
|
|
664
|
+
}
|
|
665
|
+
binst = solver.Board(board=bor, block_results=block_results)
|
|
666
|
+
solutions = binst.solve_and_print()
|
|
667
|
+
```
|
|
668
|
+
**Script Output**
|
|
669
|
+
```
|
|
670
|
+
Solution found
|
|
671
|
+
[[5 4 7 9 3 6 8 1 2]
|
|
672
|
+
[9 8 6 1 5 3 2 4 7]
|
|
673
|
+
[7 5 9 4 2 1 3 8 6]
|
|
674
|
+
[8 6 1 2 9 7 5 3 4]
|
|
675
|
+
[6 1 2 5 8 4 7 9 3]
|
|
676
|
+
[2 3 8 6 1 5 4 7 9]
|
|
677
|
+
[3 7 4 8 6 9 1 2 5]
|
|
678
|
+
[4 2 5 3 7 8 9 6 1]
|
|
679
|
+
[1 9 3 7 4 2 6 5 8]]
|
|
680
|
+
Solutions found: 1
|
|
681
|
+
status: OPTIMAL
|
|
682
|
+
Time taken: 0.02 seconds
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Solved puzzle**
|
|
686
|
+
|
|
687
|
+
<img src="./images/keen_solved.png" alt="Keen solved" width="500">
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## Towers (Puzzle Type #9)
|
|
692
|
+
|
|
693
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/towers.html)
|
|
694
|
+
|
|
695
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/towers.html#towers)
|
|
696
|
+
|
|
697
|
+
* [**Solver Code**][9]
|
|
698
|
+
|
|
699
|
+
<details>
|
|
700
|
+
<summary><strong>Rules</strong></summary>
|
|
701
|
+
You have a square grid. On each square of the grid you can build a tower, with its height ranging from 1 to the size of the grid. Around the edge of the grid are some numeric clues.
|
|
702
|
+
|
|
703
|
+
Your task is to build a tower on every square, in such a way that:
|
|
704
|
+
|
|
705
|
+
- Each row contains every possible height of tower once
|
|
706
|
+
- Each column contains every possible height of tower once
|
|
707
|
+
- Each numeric clue describes the number of towers that can be seen if you look into the square from that direction, assuming that shorter towers are hidden behind taller ones. For example, in a 5×5 grid, a clue marked ‘5’ indicates that the five tower heights must appear in increasing order (otherwise you would not be able to see all five towers), whereas a clue marked ‘1’ indicates that the tallest tower (the one marked 5) must come first.
|
|
708
|
+
|
|
709
|
+
In harder or larger puzzles, some towers will be specified for you as well as the clues round the edge, and some edge clues may be missing.
|
|
710
|
+
</details>
|
|
711
|
+
|
|
712
|
+
**Unsolved puzzle**
|
|
713
|
+
|
|
714
|
+
<img src="./images/towers_unsolved.png" alt="Towers unsolved" width="500">
|
|
715
|
+
|
|
716
|
+
Code to utilize this package and solve the puzzle:
|
|
717
|
+
```python
|
|
718
|
+
import numpy as np
|
|
719
|
+
from puzzle_solver import towers_solver as solver
|
|
720
|
+
bor = np.array([
|
|
721
|
+
['*', '*', '*', '*', '*', '*'],
|
|
722
|
+
['*', '*', '*', '*', '*', '*'],
|
|
723
|
+
['*', '*', '3', '*', '*', '*'],
|
|
724
|
+
['*', '*', '*', '*', '*', '*'],
|
|
725
|
+
['*', '*', '*', '*', '*', '*'],
|
|
726
|
+
['*', '*', '*', '*', '*', '*'],
|
|
727
|
+
])
|
|
728
|
+
t = np.array([2, -1, 2, 2, 2, 3])
|
|
729
|
+
b = np.array([2, 4, -1, 4, -1, -1])
|
|
730
|
+
r = np.array([3, -1, 2, -1, -1, -1])
|
|
731
|
+
l = np.array([-1, -1, -1, 2, -1, 4])
|
|
732
|
+
binst = solver.Board(board=bor, sides={'top': t, 'bottom': b, 'right': r, 'left': l})
|
|
733
|
+
solutions = binst.solve_and_print()
|
|
734
|
+
```
|
|
735
|
+
**Script Output**
|
|
736
|
+
```
|
|
737
|
+
Solution found
|
|
738
|
+
[[5 6 4 1 2 3]
|
|
739
|
+
[3 4 2 6 1 5]
|
|
740
|
+
[4 5 3 2 6 1]
|
|
741
|
+
[2 1 6 5 3 4]
|
|
742
|
+
[6 3 1 4 5 2]
|
|
743
|
+
[1 2 5 3 4 6]]
|
|
744
|
+
Solutions found: 1
|
|
745
|
+
status: OPTIMAL
|
|
746
|
+
Time taken: 0.03 seconds
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
**Solved puzzle**
|
|
750
|
+
|
|
751
|
+
<img src="./images/towers_solved.png" alt="Towers solved" width="500">
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
## Singles (Puzzle Type #10)
|
|
756
|
+
|
|
757
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/singles.html)
|
|
758
|
+
|
|
759
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/singles.html#singles)
|
|
760
|
+
|
|
761
|
+
* [**Solver Code**][10]
|
|
762
|
+
|
|
763
|
+
<details>
|
|
764
|
+
<summary><strong>Rules</strong></summary>
|
|
765
|
+
You have a grid of white squares, all of which contain numbers. Your task is to colour some of the squares black (removing the number) so as to satisfy all of the following conditions:
|
|
766
|
+
|
|
767
|
+
- No number occurs more than once in any row or column.
|
|
768
|
+
- No black square is horizontally or vertically adjacent to any other black square.
|
|
769
|
+
- The remaining white squares must all form one contiguous region (connected by edges, not just touching at corners).
|
|
770
|
+
</details>
|
|
771
|
+
|
|
772
|
+
**Unsolved puzzle**
|
|
773
|
+
|
|
774
|
+
<img src="./images/singles_unsolved.png" alt="Singles unsolved" width="500">
|
|
775
|
+
|
|
776
|
+
Code to utilize this package and solve the puzzle:
|
|
777
|
+
```python
|
|
778
|
+
import numpy as np
|
|
779
|
+
from puzzle_solver import singles_solver as solver
|
|
780
|
+
bor = np.array([
|
|
781
|
+
[1, 6, 5, 4, 9, 8, 9, 3, 5, 1, 3, 7],
|
|
782
|
+
[2, 8, 5, 7, 1, 1, 4, 3, 6, 3, 10, 7],
|
|
783
|
+
[6, 7, 7, 11, 2, 6, 3, 10, 10, 2, 3, 3],
|
|
784
|
+
[11, 9, 4, 3, 6, 1, 2, 5, 3, 10, 7, 8],
|
|
785
|
+
[5, 5, 4, 9, 7, 9, 6, 6, 11, 5, 4, 11],
|
|
786
|
+
[1, 3, 7, 9, 12, 5, 4, 2, 9, 6, 12, 4],
|
|
787
|
+
[6, 11, 1, 3, 6, 4, 11, 2, 2, 10, 8, 10],
|
|
788
|
+
[3, 11, 12, 6, 2, 9, 9, 1, 4, 8, 12, 5],
|
|
789
|
+
[4, 8, 8, 5, 11, 3, 3, 6, 5, 9, 1, 4],
|
|
790
|
+
[2, 4, 6, 2, 1, 10, 1, 10, 8, 5, 4, 6],
|
|
791
|
+
[5, 1, 6, 10, 9, 4, 8, 4, 8, 3, 2, 12],
|
|
792
|
+
[11, 2, 12, 10, 8, 3, 5, 4, 10, 4, 8, 11],
|
|
793
|
+
])
|
|
794
|
+
binst = solver.Board(board=bor)
|
|
795
|
+
solutions = binst.solve_and_print()
|
|
796
|
+
```
|
|
797
|
+
**Script Output**
|
|
798
|
+
```
|
|
799
|
+
Solution found
|
|
800
|
+
[['B' ' ' 'B' ' ' 'B' ' ' ' ' 'B' ' ' ' ' ' ' ' ']
|
|
801
|
+
[' ' ' ' ' ' ' ' ' ' 'B' ' ' ' ' ' ' 'B' ' ' 'B']
|
|
802
|
+
['B' ' ' 'B' ' ' 'B' ' ' 'B' ' ' 'B' ' ' 'B' ' ']
|
|
803
|
+
[' ' ' ' ' ' 'B' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ']
|
|
804
|
+
['B' ' ' 'B' ' ' ' ' 'B' ' ' 'B' ' ' 'B' ' ' 'B']
|
|
805
|
+
[' ' ' ' ' ' 'B' ' ' ' ' 'B' ' ' ' ' ' ' 'B' ' ']
|
|
806
|
+
[' ' 'B' ' ' ' ' 'B' ' ' ' ' 'B' ' ' 'B' ' ' ' ']
|
|
807
|
+
[' ' ' ' 'B' ' ' ' ' ' ' 'B' ' ' ' ' ' ' ' ' ' ']
|
|
808
|
+
[' ' 'B' ' ' ' ' ' ' 'B' ' ' ' ' 'B' ' ' ' ' 'B']
|
|
809
|
+
['B' ' ' 'B' ' ' 'B' ' ' ' ' 'B' ' ' ' ' 'B' ' ']
|
|
810
|
+
[' ' ' ' ' ' ' ' ' ' 'B' ' ' ' ' 'B' ' ' ' ' ' ']
|
|
811
|
+
['B' ' ' ' ' 'B' ' ' ' ' ' ' 'B' ' ' ' ' 'B' ' ']]
|
|
812
|
+
Solutions found: 1
|
|
813
|
+
status: OPTIMAL
|
|
814
|
+
Time taken: 2.14 seconds
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Solved puzzle**
|
|
818
|
+
|
|
819
|
+
<img src="./images/singles_solved.png" alt="Singles solved" width="500">
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## Magnets (Puzzle Type #11)
|
|
824
|
+
|
|
825
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/magnets.html)
|
|
826
|
+
|
|
827
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/magnets.html#magnets)
|
|
828
|
+
|
|
829
|
+
* [**Solver Code**][11]
|
|
830
|
+
|
|
831
|
+
<details>
|
|
832
|
+
<summary><strong>Rules</strong></summary>
|
|
833
|
+
A rectangular grid has been filled with a mixture of magnets (that is, dominoes with one positive end and one negative end) and blank dominoes (that is, dominoes with two neutral poles). These dominoes are initially only seen in silhouette. Around the grid are placed a number of clues indicating the number of positive and negative poles contained in certain columns and rows.
|
|
834
|
+
|
|
835
|
+
Your aim is to correctly place the magnets and blank dominoes such that all the clues are satisfied, with the additional constraint that no two similar magnetic poles may be orthogonally adjacent (since they repel). Neutral poles do not repel, and can be adjacent to any other pole.
|
|
836
|
+
</details>
|
|
837
|
+
|
|
838
|
+
**Unsolved puzzle**
|
|
839
|
+
|
|
840
|
+
<img src="./images/magnets_unsolved.png" alt="Magnets unsolved" width="500">
|
|
841
|
+
|
|
842
|
+
Code to utilize this package and solve the puzzle:
|
|
843
|
+
```python
|
|
844
|
+
import numpy as np
|
|
845
|
+
from puzzle_solver import magnets_solver as solver
|
|
846
|
+
bor = np.array([
|
|
847
|
+
['H', 'H', 'H', 'H', 'V', 'V', 'V', 'V', 'H', 'H'],
|
|
848
|
+
['H', 'H', 'H', 'H', 'V', 'V', 'V', 'V', 'V', 'V'],
|
|
849
|
+
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'V', 'V'],
|
|
850
|
+
['V', 'V', 'V', 'H', 'H', 'H', 'H', 'H', 'H', 'V'],
|
|
851
|
+
['V', 'V', 'V', 'V', 'V', 'V', 'V', 'H', 'H', 'V'],
|
|
852
|
+
['V', 'H', 'H', 'V', 'V', 'V', 'V', 'V', 'V', 'V'],
|
|
853
|
+
['V', 'V', 'V', 'V', 'V', 'H', 'H', 'V', 'V', 'V'],
|
|
854
|
+
['V', 'V', 'V', 'V', 'V', 'V', 'H', 'H', 'H', 'H'],
|
|
855
|
+
['V', 'H', 'H', 'H', 'H', 'V', 'H', 'H', 'H', 'H'],
|
|
856
|
+
])
|
|
857
|
+
pos_v = np.array([-1, -1, 3, 5, 3, 3, -1, 3, -1, 4])
|
|
858
|
+
neg_v = np.array([-1, 2, 3, 4, -1, 3, 4, 3, 4, 4])
|
|
859
|
+
pos_h = np.array([5, -1, -1, -1, 5, -1, 3, 1, -1])
|
|
860
|
+
neg_h = np.array([4, -1, 4, -1, 5, 4, -1, 2, -1])
|
|
861
|
+
|
|
862
|
+
binst = solver.Board(board=bor, sides={'pos_v': pos_v, 'neg_v': neg_v, 'pos_h': pos_h, 'neg_h': neg_h})
|
|
863
|
+
solutions = binst.solve_and_print()
|
|
864
|
+
```
|
|
865
|
+
**Script Output**
|
|
866
|
+
```
|
|
867
|
+
Solution found
|
|
868
|
+
[['-' '+' '-' '+' ' ' '+' '-' '+' '-' '+']
|
|
869
|
+
[' ' ' ' '+' '-' ' ' '-' '+' '-' '+' '-']
|
|
870
|
+
['-' '+' '-' '+' ' ' ' ' '-' '+' '-' '+']
|
|
871
|
+
['+' '-' '+' '-' '+' '-' '+' '-' '+' '-']
|
|
872
|
+
['-' '+' '-' '+' '-' '+' '-' '+' '-' '+']
|
|
873
|
+
[' ' '-' '+' '-' '+' '-' '+' ' ' '+' '-']
|
|
874
|
+
[' ' ' ' ' ' '+' '-' '+' '-' ' ' '-' '+']
|
|
875
|
+
['-' ' ' ' ' '-' '+' ' ' ' ' ' ' ' ' ' ']
|
|
876
|
+
['+' ' ' ' ' '+' '-' ' ' '+' '-' '+' '-']]
|
|
877
|
+
Solutions found: 1
|
|
878
|
+
status: OPTIMAL
|
|
879
|
+
Time taken: 0.02 seconds
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
**Solved puzzle**
|
|
883
|
+
|
|
884
|
+
<img src="./images/magnets_solved.png" alt="Magnets solved" width="500">
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
## Signpost (Puzzle Type #12)
|
|
890
|
+
|
|
891
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/signpost.html)
|
|
892
|
+
|
|
893
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/signpost.html#signpost)
|
|
894
|
+
|
|
895
|
+
* [**Solver Code**][12]
|
|
896
|
+
|
|
897
|
+
<details>
|
|
898
|
+
<summary><strong>Rules</strong></summary>
|
|
899
|
+
You have a grid of squares; each square (except the last one) contains an arrow, and some squares also contain numbers. Your job is to connect the squares to form a continuous list of numbers starting at 1 and linked in the direction of the arrows – so the arrow inside the square with the number 1 will point to the square containing the number 2, which will point to the square containing the number 3, etc. Each square can be any distance away from the previous one, as long as it is somewhere in the direction of the arrow.
|
|
900
|
+
|
|
901
|
+
By convention the first and last numbers are shown; one or more interim numbers may also appear at the beginning.
|
|
902
|
+
</details>
|
|
903
|
+
|
|
904
|
+
**Unsolved puzzle**
|
|
905
|
+
|
|
906
|
+
<img src="./images/signpost_unsolved.png" alt="Signpost unsolved" width="500">
|
|
907
|
+
|
|
908
|
+
Code to utilize this package and solve the puzzle:
|
|
909
|
+
```python
|
|
910
|
+
import numpy as np
|
|
911
|
+
from puzzle_solver import signpost_solver as solver
|
|
912
|
+
# Q = up-left, W = up, E = up-right, A = left, D = right, Z = down-left, X = down, C = down-right
|
|
913
|
+
bor1 = np.array([
|
|
914
|
+
['C', 'D', 'D', 'X', 'D', 'Z', 'X'],
|
|
915
|
+
['D', 'C', 'D', 'X', 'X', 'A', 'A'],
|
|
916
|
+
['X', 'X', 'D', 'Q', 'Z', 'W', 'A'],
|
|
917
|
+
['W', 'D', 'W', 'W', 'X', 'Z', 'X'],
|
|
918
|
+
['X', 'A', 'Q', 'Q', 'A', 'Q', 'X'],
|
|
919
|
+
['D', 'W', 'W', 'A', 'E', 'A', 'Z'],
|
|
920
|
+
['D', 'E', 'D', 'E', 'D', 'A', ' '],
|
|
921
|
+
])
|
|
922
|
+
bor2 = np.array([
|
|
923
|
+
[ 1, 0, 23, 0, 0, 0, 0],
|
|
924
|
+
[30, 32, 0, 0, 0, 0, 0],
|
|
925
|
+
[ 0, 0, 2, 0, 0, 0, 0],
|
|
926
|
+
[ 0, 0, 0, 0, 0, 0, 0],
|
|
927
|
+
[ 0, 45, 0, 0, 33, 0, 0],
|
|
928
|
+
[ 0, 0, 22, 8, 39, 10, 0],
|
|
929
|
+
[ 0, 0, 0, 0, 0, 20, 49],
|
|
930
|
+
])
|
|
931
|
+
|
|
932
|
+
binst = solver.Board(board=bor1, values=bor2)
|
|
933
|
+
solutions = binst.solve_and_print()
|
|
934
|
+
```
|
|
935
|
+
**Script Output**
|
|
936
|
+
```
|
|
937
|
+
Solution found
|
|
938
|
+
[[1 42 23 7 43 44 24]
|
|
939
|
+
[30 32 36 5 37 4 31]
|
|
940
|
+
[28 12 2 41 26 3 25]
|
|
941
|
+
[29 13 35 6 38 14 17]
|
|
942
|
+
[46 45 27 34 33 40 18]
|
|
943
|
+
[9 11 22 8 39 10 19]
|
|
944
|
+
[47 21 15 16 48 20 49]]
|
|
945
|
+
Solutions found: 1
|
|
946
|
+
status: OPTIMAL
|
|
947
|
+
Time taken: 0.03 seconds
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
**Solved puzzle**
|
|
951
|
+
|
|
952
|
+
<img src="./images/signpost_solved.png" alt="Signpost solved" width="500">
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Range (Puzzle Type #13)
|
|
958
|
+
|
|
959
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/range.html)
|
|
960
|
+
|
|
961
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/range.html#range)
|
|
962
|
+
|
|
963
|
+
* [**Solver Code**][13]
|
|
964
|
+
|
|
965
|
+
<details>
|
|
966
|
+
<summary><strong>Rules</strong></summary>
|
|
967
|
+
You have a grid of squares; some squares contain numbers. Your job is to colour some of the squares black, such that several criteria are satisfied:
|
|
968
|
+
|
|
969
|
+
- no square with a number is coloured black.
|
|
970
|
+
- no two black squares are adjacent (horizontally or vertically).
|
|
971
|
+
- for any two white squares, there is a path between them using only white squares.
|
|
972
|
+
- for each square with a number, that number denotes the total number of white squares reachable from that square going in a straight line in any horizontal or vertical direction until hitting a wall or a black square; the square with the number is included in the total (once).
|
|
973
|
+
|
|
974
|
+
For instance, a square containing the number one must have four black squares as its neighbours by the last criterion; but then it's impossible for it to be connected to any outside white square, which violates the second to last criterion. So no square will contain the number one.
|
|
975
|
+
</details>
|
|
976
|
+
|
|
977
|
+
(Note: The solver for this puzzle is slightly slower and could take several seconds to solve a 16x11 puzzle)
|
|
978
|
+
|
|
979
|
+
**Unsolved puzzle**
|
|
980
|
+
|
|
981
|
+
<img src="./images/range_unsolved.png" alt="Range unsolved" width="500">
|
|
982
|
+
|
|
983
|
+
Code to utilize this package and solve the puzzle:
|
|
984
|
+
```python
|
|
985
|
+
import numpy as np
|
|
986
|
+
from puzzle_solver import range_solver as solver
|
|
987
|
+
clues = np.array([
|
|
988
|
+
[-1, 4, 2, -1, -1, 3, -1, -1, -1, 8, -1, -1, -1, -1, 6, -1],
|
|
989
|
+
[-1, -1, -1, -1, -1, 13, -1, 18, -1, -1, 14, -1, -1, 22, -1, -1],
|
|
990
|
+
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, -1, -1, -1, -1],
|
|
991
|
+
[-1, -1, -1, -1, 12, -1, 11, -1, -1, -1, 9, -1, -1, -1, -1, -1],
|
|
992
|
+
[7, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1],
|
|
993
|
+
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
|
|
994
|
+
[-1, -1, -1, -1, -1, -1, -1, -1, -1, 12, -1, -1, -1, -1, -1, 5],
|
|
995
|
+
[-1, -1, -1, -1, -1, 9, -1, -1, -1, 9, -1, 4, -1, -1, -1, -1],
|
|
996
|
+
[-1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
|
|
997
|
+
[-1, -1, 10, -1, -1, 7, -1, -1, 13, -1, 10, -1, -1, -1, -1, -1],
|
|
998
|
+
[-1, 7, -1, -1, -1, -1, 6, -1, -1, -1, 6, -1, -1, 13, 5, -1],
|
|
999
|
+
])
|
|
1000
|
+
binst = solver.Board(clues)
|
|
1001
|
+
solutions = binst.solve_and_print()
|
|
1002
|
+
```
|
|
1003
|
+
**Script Output**
|
|
1004
|
+
```
|
|
1005
|
+
Solution:
|
|
1006
|
+
B . . B . . B . B . B . B . . .
|
|
1007
|
+
. . B . . . . . . . . . . . . B
|
|
1008
|
+
B . . . . B . . . . . . . . . .
|
|
1009
|
+
. B . B . . . . . . . B . . . .
|
|
1010
|
+
. . . . . B . . B . B . . . B .
|
|
1011
|
+
. . B . . . . . . . . B . . . B
|
|
1012
|
+
B . . . B . B . . . . . B . . .
|
|
1013
|
+
. . . . . . . B . . B . . . B .
|
|
1014
|
+
. B . . . B . . . B . B . . . .
|
|
1015
|
+
. . . . . . B . . . . . . . . B
|
|
1016
|
+
B . . . . . . B . . . . B . . .
|
|
1017
|
+
Solutions found: 1
|
|
1018
|
+
status: OPTIMAL
|
|
1019
|
+
Time taken: 3.32 seconds
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
**Solved puzzle**
|
|
1023
|
+
|
|
1024
|
+
<img src="./images/range_solved.png" alt="Range solved" width="500">
|
|
1025
|
+
|
|
1026
|
+
---
|
|
1027
|
+
|
|
1028
|
+
## UnDead (Puzzle Type #14)
|
|
1029
|
+
|
|
1030
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/undead.html)
|
|
1031
|
+
|
|
1032
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/undead.html#undead)
|
|
1033
|
+
|
|
1034
|
+
* [**Solver Code**][14]
|
|
1035
|
+
|
|
1036
|
+
<details>
|
|
1037
|
+
<summary><strong>Rules</strong></summary>
|
|
1038
|
+
You are given a grid of squares, some of which contain diagonal mirrors. Every square which is not a mirror must be filled with one of three types of undead monster: a ghost, a vampire, or a zombie.
|
|
1039
|
+
|
|
1040
|
+
Vampires can be seen directly, but are invisible when reflected in mirrors. Ghosts are the opposite way round: they can be seen in mirrors, but are invisible when looked at directly. Zombies are visible by any means.
|
|
1041
|
+
|
|
1042
|
+
You are also told the total number of each type of monster in the grid. Also around the edge of the grid are written numbers, which indicate how many monsters can be seen if you look into the grid along a row or column starting from that position. (The diagonal mirrors are reflective on both sides. If your reflected line of sight crosses the same monster more than once, the number will count it each time it is visible, not just once.)
|
|
1043
|
+
</details>
|
|
1044
|
+
|
|
1045
|
+
**Unsolved puzzle**
|
|
1046
|
+
|
|
1047
|
+
<img src="./images/undead_unsolved.png" alt="UnDead unsolved" width="500">
|
|
1048
|
+
|
|
1049
|
+
Code to utilize this package and solve the puzzle:
|
|
1050
|
+
```python
|
|
1051
|
+
import numpy as np
|
|
1052
|
+
from puzzle_solver import undead_solver as solver
|
|
1053
|
+
bor = np.array([
|
|
1054
|
+
['**', '//', '**', '**', '**', '**', '\\'],
|
|
1055
|
+
['**', '**', '**', '//', '**', '**', '**'],
|
|
1056
|
+
['**', '//', '//', '**', '**', '\\', '//'],
|
|
1057
|
+
['//', '\\', '//', '**', '//', '\\', '**'],
|
|
1058
|
+
['//', '**', '//', '\\', '**', '//', '//'],
|
|
1059
|
+
['**', '\\', '\\', '\\', '**', '**', '**'],
|
|
1060
|
+
['**', '//', '**', '**', '**', '**', '**'],
|
|
1061
|
+
])
|
|
1062
|
+
t = np.array([3, 0, 3, 0, 5, 6, 0])
|
|
1063
|
+
b = np.array([5, 2, 1, 3, 8, 2, 0])
|
|
1064
|
+
r = np.array([0, 8, 0, 4, 2, 2, 4])
|
|
1065
|
+
l = np.array([1, 4, 8, 0, 0, 2, 2])
|
|
1066
|
+
counts = {Monster.GHOST: 5, Monster.VAMPIRE: 12, Monster.ZOMBIE: 11}
|
|
1067
|
+
|
|
1068
|
+
# create board and solve
|
|
1069
|
+
binst = solver.Board(board=bor, sides={'top': t, 'bottom': b, 'right': r, 'left': l}, monster_count=counts)
|
|
1070
|
+
solutions = binst.solve_and_print()
|
|
1071
|
+
```
|
|
1072
|
+
**Script Output**
|
|
1073
|
+
```
|
|
1074
|
+
Solution found
|
|
1075
|
+
[['VA' '//' 'GH' 'GH' 'ZO' 'GH' '\\']
|
|
1076
|
+
['VA' 'VA' 'VA' '//' 'ZO' 'ZO' 'ZO']
|
|
1077
|
+
['VA' '//' '//' 'ZO' 'ZO' '\\' '//']
|
|
1078
|
+
['//' '\\' '//' 'VA' '//' '\\' 'VA']
|
|
1079
|
+
['//' 'VA' '//' '\\' 'ZO' '//' '//']
|
|
1080
|
+
['ZO' '\\' '\\' '\\' 'ZO' 'VA' 'GH']
|
|
1081
|
+
['ZO' '//' 'VA' 'VA' 'ZO' 'VA' 'GH']]
|
|
1082
|
+
Solutions found: 1
|
|
1083
|
+
status: OPTIMAL
|
|
1084
|
+
Time taken: 0.01 seconds
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**Solved puzzle**
|
|
1088
|
+
|
|
1089
|
+
<img src="./images/undead_solved.png" alt="UnDead solved" width="500">
|
|
1090
|
+
|
|
1091
|
+
---
|
|
1092
|
+
|
|
1093
|
+
## Unruly (Puzzle Type #15)
|
|
1094
|
+
|
|
1095
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/unruly.html)
|
|
1096
|
+
|
|
1097
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/unruly.html#unruly)
|
|
1098
|
+
|
|
1099
|
+
* [**Solver Code**][15]
|
|
1100
|
+
|
|
1101
|
+
<details>
|
|
1102
|
+
<summary><strong>Rules</strong></summary>
|
|
1103
|
+
You are given a grid of squares, which you must colour either black or white. Some squares are provided as clues; the rest are left for you to fill in. Each row and column must contain the same number of black and white squares, and no row or column may contain three consecutive squares of the same colour.
|
|
1104
|
+
</details>
|
|
1105
|
+
|
|
1106
|
+
**Unsolved puzzle**
|
|
1107
|
+
|
|
1108
|
+
<img src="./images/unruly_unsolved.png" alt="Unruly unsolved" width="500">
|
|
1109
|
+
|
|
1110
|
+
Code to utilize this package and solve the puzzle:
|
|
1111
|
+
```python
|
|
1112
|
+
import numpy as np
|
|
1113
|
+
from puzzle_solver import unruly_solver as solver
|
|
1114
|
+
bor = np.array([
|
|
1115
|
+
['W', 'W', '*', 'B', '*', '*', '*', '*', 'B', '*', '*', '*', '*', '*'],
|
|
1116
|
+
['*', '*', '*', '*', '*', '*', '*', 'W', '*', '*', '*', '*', '*', 'W'],
|
|
1117
|
+
['*', '*', '*', '*', '*', 'B', '*', 'W', '*', '*', 'B', '*', '*', '*'],
|
|
1118
|
+
['*', '*', 'W', '*', '*', '*', '*', '*', '*', 'W', '*', 'W', '*', '*'],
|
|
1119
|
+
['B', '*', '*', 'W', '*', '*', '*', '*', 'B', '*', '*', '*', '*', '*'],
|
|
1120
|
+
['*', '*', '*', '*', '*', 'B', '*', '*', '*', 'B', '*', 'B', '*', '*'],
|
|
1121
|
+
['*', 'B', 'B', '*', '*', 'B', '*', '*', '*', '*', '*', 'B', '*', '*'],
|
|
1122
|
+
['*', '*', 'B', '*', '*', '*', '*', 'W', '*', 'B', 'B', '*', '*', 'W'],
|
|
1123
|
+
['*', '*', '*', '*', '*', '*', '*', 'W', '*', '*', '*', '*', '*', 'W'],
|
|
1124
|
+
['*', '*', '*', 'B', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
1125
|
+
['*', '*', 'W', '*', '*', '*', 'W', '*', '*', 'W', '*', 'W', '*', '*'],
|
|
1126
|
+
['*', 'W', '*', 'W', '*', '*', '*', '*', '*', '*', '*', '*', '*', 'B'],
|
|
1127
|
+
['*', '*', '*', '*', 'B', '*', '*', '*', '*', '*', '*', '*', '*', '*'],
|
|
1128
|
+
['W', '*', '*', '*', 'W', '*', '*', '*', 'B', '*', 'W', '*', 'B', '*'],
|
|
1129
|
+
])
|
|
1130
|
+
binst = solver.Board(board=bor)
|
|
1131
|
+
solutions = binst.solve_and_print()
|
|
1132
|
+
```
|
|
1133
|
+
**Script Output**
|
|
1134
|
+
```
|
|
1135
|
+
Solution found
|
|
1136
|
+
[['W' 'W' 'B' 'B' 'W' 'B' 'W' 'B' 'B' 'W' 'B' 'W' 'W' 'B']
|
|
1137
|
+
['B' 'B' 'W' 'W' 'B' 'W' 'B' 'W' 'W' 'B' 'W' 'B' 'B' 'W']
|
|
1138
|
+
['W' 'W' 'B' 'W' 'W' 'B' 'B' 'W' 'B' 'W' 'B' 'B' 'W' 'B']
|
|
1139
|
+
['W' 'B' 'W' 'B' 'B' 'W' 'W' 'B' 'W' 'W' 'B' 'W' 'B' 'B']
|
|
1140
|
+
['B' 'W' 'B' 'W' 'B' 'W' 'B' 'W' 'B' 'B' 'W' 'W' 'B' 'W']
|
|
1141
|
+
['B' 'W' 'W' 'B' 'W' 'B' 'B' 'W' 'B' 'B' 'W' 'B' 'W' 'W']
|
|
1142
|
+
['W' 'B' 'B' 'W' 'W' 'B' 'W' 'B' 'W' 'W' 'B' 'B' 'W' 'B']
|
|
1143
|
+
['B' 'W' 'B' 'W' 'B' 'W' 'B' 'W' 'W' 'B' 'B' 'W' 'B' 'W']
|
|
1144
|
+
['B' 'B' 'W' 'B' 'B' 'W' 'B' 'W' 'B' 'W' 'W' 'B' 'W' 'W']
|
|
1145
|
+
['W' 'W' 'B' 'B' 'W' 'B' 'W' 'B' 'W' 'B' 'W' 'W' 'B' 'B']
|
|
1146
|
+
['B' 'B' 'W' 'W' 'B' 'W' 'W' 'B' 'B' 'W' 'B' 'W' 'B' 'W']
|
|
1147
|
+
['B' 'W' 'B' 'W' 'W' 'B' 'B' 'W' 'W' 'B' 'W' 'B' 'W' 'B']
|
|
1148
|
+
['W' 'B' 'W' 'B' 'B' 'W' 'W' 'B' 'W' 'B' 'B' 'W' 'W' 'B']
|
|
1149
|
+
['W' 'B' 'W' 'B' 'W' 'B' 'W' 'B' 'B' 'W' 'W' 'B' 'B' 'W']]
|
|
1150
|
+
Solutions found: 1
|
|
1151
|
+
status: OPTIMAL
|
|
1152
|
+
Time taken: 0.01 seconds
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
**Solved puzzle**
|
|
1156
|
+
|
|
1157
|
+
<img src="./images/unruly_solved.png" alt="Unruly solved" width="500">
|
|
1158
|
+
|
|
1159
|
+
---
|
|
1160
|
+
|
|
1161
|
+
## Tracks (Puzzle Type #16)
|
|
1162
|
+
|
|
1163
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/tracks.html)
|
|
1164
|
+
|
|
1165
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/tracks.html#tracks)
|
|
1166
|
+
|
|
1167
|
+
* [**Solver Code**][16]
|
|
1168
|
+
|
|
1169
|
+
<details>
|
|
1170
|
+
<summary><strong>Rules</strong></summary>
|
|
1171
|
+
Complete the track from A to B so that the rows and columns contain the same number of track segments as are indicated in the clues to the top and right of the grid. There are only straight and 90-degree curved rail sections, and the track may not cross itself.
|
|
1172
|
+
|
|
1173
|
+
</details>
|
|
1174
|
+
|
|
1175
|
+
(Note: The solver for this puzzle is slightly slower and could take several seconds to solve a large 15x15 puzzle)
|
|
1176
|
+
|
|
1177
|
+
**Unsolved puzzle**
|
|
1178
|
+
|
|
1179
|
+
<img src="./images/tracks_unsolved.png" alt="Tracks unsolved" width="500">
|
|
1180
|
+
|
|
1181
|
+
Code to utilize this package and solve the puzzle:
|
|
1182
|
+
```python
|
|
1183
|
+
import numpy as np
|
|
1184
|
+
from puzzle_solver import tracks_solver as solver
|
|
1185
|
+
bor = np.array([
|
|
1186
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LD', ' ', ' ', ],
|
|
1187
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LD', ' ', ' ', ' ', ' ', ],
|
|
1188
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1189
|
+
[' ', ' ', ' ', ' ', ' ', 'LD', 'UD', 'DR', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1190
|
+
['DR', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1191
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'DR', ' ', ' ', ],
|
|
1192
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'DR', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1193
|
+
[' ', ' ', 'UL', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1194
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LR', ' ', ' ', ' ', ' ', ],
|
|
1195
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LD', ' ', ' ', ' ', 'UD', ],
|
|
1196
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'UR', ' ', ' ', ' ', ' ', 'UD', 'UD', ],
|
|
1197
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LR', ' ', ' ', ' ', ' ', ' ', ],
|
|
1198
|
+
['UL', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'LR', 'LR', ' ', ' ', ' ', ],
|
|
1199
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ],
|
|
1200
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'DR', ' ', ],
|
|
1201
|
+
])
|
|
1202
|
+
side = np.array([9, 7, 7, 7, 11, 10, 9, 8, 9, 10, 7, 9, 9, 2, 2])
|
|
1203
|
+
top = np.array([6, 5, 7, 3, 3, 2, 7, 8, 13, 8, 9, 8, 10, 13, 14])
|
|
1204
|
+
binst = solver.Board(board=bor, top=top, side=side)
|
|
1205
|
+
solutions = binst.solve_and_print()
|
|
1206
|
+
```
|
|
1207
|
+
**Script Output**
|
|
1208
|
+
```
|
|
1209
|
+
[[' ' ' ' ' ' ' ' ' ' ' ' '┏━' '━━' '━━' '━━' '━━' '━━' '━┒' '┏━' '━┒']
|
|
1210
|
+
[' ' ' ' ' ' ' ' ' ' ' ' '┃ ' ' ' '┏━' '━━' '━┒' ' ' '┗━' '━┛' '┃ ']
|
|
1211
|
+
[' ' ' ' ' ' ' ' ' ' ' ' '┃ ' ' ' '┃ ' ' ' '┗━' '━━' '━━' '━━' '━┛']
|
|
1212
|
+
[' ' ' ' '┏━' '━┒' '┏━' '━┒' '┃ ' '┏━' '━┛' ' ' ' ' ' ' ' ' ' ' ' ']
|
|
1213
|
+
['┏━' '━━' '━┛' '┃ ' '┃ ' '┗━' '━┛' '┗━' '━┒' ' ' ' ' ' ' ' ' '┏━' '━┒']
|
|
1214
|
+
['┗━' '━━' '━┒' '┗━' '━┛' ' ' ' ' '┏━' '━┛' ' ' ' ' ' ' '┏━' '━┛' '┃ ']
|
|
1215
|
+
[' ' ' ' '┃ ' ' ' ' ' ' ' '┏━' '━┛' '┏━' '━━' '━━' '━━' '━┛' ' ' '┃ ']
|
|
1216
|
+
[' ' '┏━' '━┛' ' ' ' ' ' ' '┗━' '━━' '━┛' ' ' ' ' ' ' '┏━' '━━' '━┛']
|
|
1217
|
+
[' ' '┗━' '━┒' ' ' ' ' ' ' ' ' ' ' '┏━' '━━' '━━' '━┒' '┃ ' '┏━' '━┒']
|
|
1218
|
+
['┏━' '━━' '━┛' ' ' ' ' ' ' ' ' ' ' '┃ ' '┏━' '━┒' '┗━' '━┛' '┃ ' '┃ ']
|
|
1219
|
+
['┃ ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '┗━' '━┛' '┗━' '━┒' ' ' '┃ ' '┃ ']
|
|
1220
|
+
['┃ ' ' ' ' ' ' ' ' ' ' ' ' ' '┏━' '━━' '━━' '━━' '━┛' '┏━' '━┛' '┃ ']
|
|
1221
|
+
['━┛' ' ' ' ' ' ' ' ' ' ' ' ' '┗━' '━━' '━━' '━━' '━━' '━┛' '┏━' '━┛']
|
|
1222
|
+
[' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '┗━' '━┒']
|
|
1223
|
+
[' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '┏━' '━┛']]
|
|
1224
|
+
Solutions found: 1
|
|
1225
|
+
status: OPTIMAL
|
|
1226
|
+
Time taken: 9.42 seconds
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
**Solved puzzle**
|
|
1230
|
+
|
|
1231
|
+
<img src="./images/tracks_solved.png" alt="Tracks solved" width="500">
|
|
1232
|
+
|
|
1233
|
+
---
|
|
1234
|
+
|
|
1235
|
+
## Mosaic (Puzzle Type #17)
|
|
1236
|
+
|
|
1237
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/mosaic.html)
|
|
1238
|
+
|
|
1239
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/mosaic.html#mosaic)
|
|
1240
|
+
|
|
1241
|
+
* [**Solver Code**][17]
|
|
1242
|
+
|
|
1243
|
+
<details>
|
|
1244
|
+
<summary><strong>Rules</strong></summary>
|
|
1245
|
+
You are given a grid of squares, which you must colour either black or white.
|
|
1246
|
+
|
|
1247
|
+
Some squares contain clue numbers. Each clue tells you the number of black squares in the 3×3 region surrounding the clue – including the clue square itself.
|
|
1248
|
+
</details>
|
|
1249
|
+
|
|
1250
|
+
**Unsolved puzzle**
|
|
1251
|
+
|
|
1252
|
+
<img src="./images/mosaic_unsolved.png" alt="Mosaic unsolved" width="500">
|
|
1253
|
+
|
|
1254
|
+
Code to utilize this package and solve the puzzle:
|
|
1255
|
+
```python
|
|
1256
|
+
import numpy as np
|
|
1257
|
+
from puzzle_solver import mosaic_solver as solver
|
|
1258
|
+
bor = np.array([
|
|
1259
|
+
['*', '*', '2', '1', '*', '*', '*', '3', '*', '4', '2', '2', '*', '*', '4'],
|
|
1260
|
+
['3', '*', '*', '*', '4', '*', '*', '*', '*', '*', '4', '*', '2', '*', '*'],
|
|
1261
|
+
['4', '*', '*', '5', '*', '5', '*', '*', '5', '*', '3', '3', '2', '5', '*'],
|
|
1262
|
+
['*', '*', '7', '*', '4', '*', '*', '5', '*', '*', '*', '*', '*', '5', '*'],
|
|
1263
|
+
['*', '6', '7', '*', '*', '4', '*', '7', '*', '*', '*', '*', '7', '7', '*'],
|
|
1264
|
+
['3', '*', '*', '3', '*', '5', '7', '7', '6', '4', '*', '4', '*', '5', '*'],
|
|
1265
|
+
['*', '*', '4', '*', '5', '7', '8', '*', '5', '*', '1', '3', '4', '5', '*'],
|
|
1266
|
+
['*', '5', '*', '4', '3', '*', '*', '*', '7', '*', '3', '*', '3', '*', '*'],
|
|
1267
|
+
['3', '*', '*', '*', '*', '*', '*', '5', '*', '6', '*', '*', '*', '*', '*'],
|
|
1268
|
+
['4', '*', '7', '*', '5', '*', '*', '4', '6', '7', '*', '3', '*', '3', '*'],
|
|
1269
|
+
['5', '*', '*', '*', '*', '*', '*', '*', '6', '*', '*', '3', '5', '*', '*'],
|
|
1270
|
+
['*', '*', '*', '5', '4', '5', '3', '*', '7', '*', '*', '5', '6', '6', '*'],
|
|
1271
|
+
['2', '*', '*', '*', '3', '4', '*', '*', '*', '7', '*', '*', '7', '*', '3'],
|
|
1272
|
+
['1', '*', '*', '5', '*', '*', '*', '5', '*', '*', '*', '6', '*', '6', '*'],
|
|
1273
|
+
['*', '*', '3', '*', '2', '*', '3', '*', '2', '*', '*', '*', '*', '*', '*']
|
|
1274
|
+
])
|
|
1275
|
+
binst = solver.Board(board=bor)
|
|
1276
|
+
solutions = binst.solve_and_print()
|
|
1277
|
+
```
|
|
1278
|
+
**Script Output**
|
|
1279
|
+
```
|
|
1280
|
+
Solution found
|
|
1281
|
+
[[' ' 'B' ' ' ' ' ' ' ' ' ' ' ' ' 'B' ' ' 'B' ' ' ' ' 'B' 'B']
|
|
1282
|
+
[' ' 'B' ' ' ' ' 'B' 'B' ' ' 'B' 'B' ' ' 'B' ' ' ' ' 'B' 'B']
|
|
1283
|
+
[' ' 'B' 'B' ' ' 'B' 'B' ' ' ' ' ' ' 'B' 'B' ' ' ' ' ' ' 'B']
|
|
1284
|
+
['B' 'B' 'B' 'B' ' ' ' ' 'B' 'B' 'B' ' ' ' ' ' ' 'B' ' ' 'B']
|
|
1285
|
+
[' ' 'B' 'B' ' ' ' ' 'B' ' ' 'B' 'B' 'B' ' ' 'B' 'B' 'B' ' ']
|
|
1286
|
+
[' ' 'B' ' ' 'B' ' ' 'B' 'B' ' ' 'B' ' ' ' ' 'B' 'B' 'B' 'B']
|
|
1287
|
+
['B' ' ' 'B' ' ' ' ' 'B' 'B' 'B' 'B' ' ' ' ' ' ' ' ' ' ' ' ']
|
|
1288
|
+
[' ' ' ' 'B' ' ' 'B' 'B' 'B' 'B' 'B' ' ' ' ' ' ' 'B' ' ' 'B']
|
|
1289
|
+
[' ' 'B' 'B' ' ' ' ' ' ' ' ' 'B' 'B' 'B' 'B' 'B' ' ' 'B' ' ']
|
|
1290
|
+
['B' 'B' 'B' 'B' 'B' 'B' ' ' ' ' ' ' 'B' 'B' ' ' ' ' 'B' ' ']
|
|
1291
|
+
[' ' 'B' ' ' 'B' 'B' ' ' 'B' ' ' 'B' 'B' ' ' ' ' ' ' 'B' ' ']
|
|
1292
|
+
['B' 'B' ' ' ' ' 'B' ' ' ' ' 'B' 'B' 'B' ' ' 'B' 'B' 'B' 'B']
|
|
1293
|
+
[' ' ' ' 'B' ' ' 'B' ' ' 'B' ' ' 'B' 'B' 'B' 'B' 'B' ' ' 'B']
|
|
1294
|
+
[' ' ' ' 'B' 'B' ' ' ' ' 'B' ' ' 'B' 'B' ' ' 'B' 'B' ' ' ' ']
|
|
1295
|
+
['B' ' ' 'B' ' ' ' ' 'B' 'B' ' ' ' ' ' ' ' ' ' ' 'B' 'B' 'B']]
|
|
1296
|
+
Solutions found: 1
|
|
1297
|
+
status: OPTIMAL
|
|
1298
|
+
Time taken: 0.01 seconds
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
**Solved puzzle**
|
|
1302
|
+
|
|
1303
|
+
<img src="./images/mosaic_solved.png" alt="Mosaic solved" width="500">
|
|
1304
|
+
|
|
1305
|
+
---
|
|
1306
|
+
|
|
1307
|
+
## Map (Puzzle Type #18)
|
|
1308
|
+
|
|
1309
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/map.html)
|
|
1310
|
+
|
|
1311
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/map.html#map)
|
|
1312
|
+
|
|
1313
|
+
* [**Solver Code**][18]
|
|
1314
|
+
|
|
1315
|
+
<details>
|
|
1316
|
+
<summary><strong>Rules</strong></summary>
|
|
1317
|
+
You are given a map consisting of a number of regions. Your task is to colour each region with one of four colours, in such a way that no two regions sharing a boundary have the same colour. You are provided with some regions already coloured, sufficient to make the remainder of the solution unique, and these cannot be changed.
|
|
1318
|
+
|
|
1319
|
+
Only regions which share a length of border are required to be different colours. Two regions which meet at only one point (i.e. are diagonally separated) may be the same colour.
|
|
1320
|
+
</details>
|
|
1321
|
+
|
|
1322
|
+
**Unsolved puzzle**
|
|
1323
|
+
|
|
1324
|
+
<img src="./images/map_unsolved.png" alt="Map unsolved" width="500">
|
|
1325
|
+
|
|
1326
|
+
Code to utilize this package and solve the puzzle:
|
|
1327
|
+
```python
|
|
1328
|
+
from puzzle_solver import map_solver as solver
|
|
1329
|
+
regions = {
|
|
1330
|
+
0: {1, 11, 12, 27},
|
|
1331
|
+
1: {11, 12, 13, 6, 2},
|
|
1332
|
+
2: {3, 4, 6, 7, 9, 10},
|
|
1333
|
+
# ...
|
|
1334
|
+
# ...
|
|
1335
|
+
37: {38, 46, 49, 51, 54, 59, 60, 61},
|
|
1336
|
+
38: {44, 45, 49, 51, 53, 58, 59},
|
|
1337
|
+
39: {40, 46},
|
|
1338
|
+
40: {55, 56},
|
|
1339
|
+
41: {42, 47},
|
|
1340
|
+
42: {48},
|
|
1341
|
+
# ...
|
|
1342
|
+
# ...
|
|
1343
|
+
# ommited for brevity ; this was a pain to type out by hand
|
|
1344
|
+
}
|
|
1345
|
+
fixed_colors = {
|
|
1346
|
+
0: 'Y', 3: 'R', 7: 'Y', 14: 'Y', 15: 'R', 16: 'Y', 20: 'G', 32: 'B', 33: 'Y', 34: 'R', 35: 'G',
|
|
1347
|
+
36: 'B', 39: 'G', 43: 'G', 47: 'R', 55: 'B', 60: 'R', 64: 'G', 66: 'Y', 67: 'G', 73: 'G', 74: 'G',
|
|
1348
|
+
}
|
|
1349
|
+
binst = solver.Board(regions=regions, fixed_colors=fixed_colors)
|
|
1350
|
+
solutions = binst.solve_and_print()
|
|
1351
|
+
```
|
|
1352
|
+
**Script Output**
|
|
1353
|
+
```
|
|
1354
|
+
Solution found
|
|
1355
|
+
{0: 'Y', 1: 'R', 2: 'G', 3: 'R', 4: 'B', 5: 'G', 6: 'B', 7: 'Y', 8: 'R', 9: 'Y', 10: 'B', 11: 'G', 12: 'B', 13: 'G', 14: 'Y', 15: 'R', 16: 'Y', 17: 'R', 18: 'G', 19: 'B', 20: 'G', 21: 'Y', 22: 'R', 23: 'Y', 24: 'Y', 25: 'B', 26: 'R', 27: 'G', 28: 'G', 29: 'B', 30: 'B', 31: 'R', 32: 'B', 33: 'Y', 34: 'R', 35: 'G', 36: 'B', 37: 'G', 38: 'B', 39: 'G', 40: 'Y', 41: 'Y', 42: 'R', 43: 'G', 44: 'R', 45: 'Y', 46: 'Y', 47: 'R', 48: 'Y', 49: 'Y', 50: 'G', 51: 'R', 52: 'R', 53: 'Y', 54: 'B', 55: 'B', 56: 'G', 57: 'B', 58: 'R', 59: 'Y', 60: 'R', 61: 'B', 62: 'B', 63: 'Y', 64: 'G', 65: 'R', 66: 'Y', 67: 'G', 68: 'B', 69: 'R', 70: 'Y', 71: 'R', 72: 'B', 73: 'G', 74: 'G'}
|
|
1356
|
+
Solutions found: 1
|
|
1357
|
+
status: OPTIMAL
|
|
1358
|
+
Time taken: 0.01 seconds
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
**Solved puzzle**
|
|
1362
|
+
|
|
1363
|
+
<img src="./images/map_solved.png" alt="Map solved" width="500">
|
|
1364
|
+
|
|
1365
|
+
---
|
|
1366
|
+
|
|
1367
|
+
## Pearl (Puzzle Type #19)
|
|
1368
|
+
|
|
1369
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/pearl.html)
|
|
1370
|
+
|
|
1371
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/pearl.html#pearl)
|
|
1372
|
+
|
|
1373
|
+
* [**Solver Code**][19]
|
|
1374
|
+
|
|
1375
|
+
<details>
|
|
1376
|
+
<summary><strong>Rules</strong></summary>
|
|
1377
|
+
You have a grid of squares. Your job is to draw lines between the centres of horizontally or vertically adjacent squares, so that the lines form a single closed loop. In the resulting grid, some of the squares that the loop passes through will contain corners, and some will be straight horizontal or vertical lines. (And some squares can be completely empty – the loop doesn't have to pass through every square.)
|
|
1378
|
+
|
|
1379
|
+
Some of the squares contain black and white circles, which are clues that the loop must satisfy.
|
|
1380
|
+
|
|
1381
|
+
A black circle in a square indicates that that square is a corner, but neither of the squares adjacent to it in the loop is also a corner.
|
|
1382
|
+
|
|
1383
|
+
A white circle indicates that the square is a straight edge, but at least one of the squares adjacent to it in the loop is a corner.
|
|
1384
|
+
|
|
1385
|
+
(In both cases, the clue only constrains the two squares adjacent in the loop, that is, the squares that the loop passes into after leaving the clue square. The squares that are only adjacent in the grid are not constrained.)
|
|
1386
|
+
</details>
|
|
1387
|
+
|
|
1388
|
+
**Unsolved puzzle**
|
|
1389
|
+
|
|
1390
|
+
<img src="./images/pearl_unsolved.png" alt="Pearl unsolved" width="500">
|
|
1391
|
+
|
|
1392
|
+
Code to utilize this package and solve the puzzle:
|
|
1393
|
+
```python
|
|
1394
|
+
import numpy as np
|
|
1395
|
+
from puzzle_solver import pearl_solver as solver
|
|
1396
|
+
bor = np.array([
|
|
1397
|
+
['B', ' ', ' ', 'W', ' ', ' ', 'W', ' ', 'B', ' ', ' ', 'B'],
|
|
1398
|
+
[' ', ' ', ' ', 'B', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1399
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'B', ' ', ' ', ' '],
|
|
1400
|
+
[' ', 'B', ' ', 'B', ' ', 'W', ' ', 'B', ' ', 'B', 'W', ' '],
|
|
1401
|
+
[' ', ' ', 'B', ' ', 'B', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1402
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', 'W', 'W', ' ', ' ', 'B'],
|
|
1403
|
+
[' ', ' ', 'B', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1404
|
+
['B', ' ', ' ', ' ', ' ', 'B', 'B', ' ', ' ', ' ', ' ', 'B'],
|
|
1405
|
+
])
|
|
1406
|
+
binst = solver.Board(bor)
|
|
1407
|
+
solutions = binst.solve_and_print()
|
|
1408
|
+
```
|
|
1409
|
+
**Script Output**
|
|
1410
|
+
```
|
|
1411
|
+
Solution found
|
|
1412
|
+
[['┏━' '━━' '━━' '━━' '━┒' '┏━' '━━' '━┒' '┏━' '━━' '━━' '━┒']
|
|
1413
|
+
['┃ ' '┏━' '━━' '━┒' '┗━' '━┛' '┏━' '━┛' '┃ ' '┏━' '━┒' '┃ ']
|
|
1414
|
+
['┗━' '━┛' ' ' '┃ ' '┏━' '━┒' '┗━' '━━' '━┛' '┃ ' '┃ ' '┃ ']
|
|
1415
|
+
[' ' '┏━' '━━' '━┛' '┃ ' '┃ ' ' ' '┏━' '━━' '━┛' '┃ ' '┃ ']
|
|
1416
|
+
[' ' '┃ ' '┏━' '━━' '━┛' '┗━' '━┒' '┃ ' '┏━' '━┒' '┗━' '━┛']
|
|
1417
|
+
['┏━' '━┛' '┃ ' ' ' '┏━' '━┒' '┃ ' '┃ ' '┃ ' '┗━' '━━' '━┒']
|
|
1418
|
+
['┃ ' ' ' '┗━' '━━' '━┛' '┃ ' '┃ ' '┗━' '━┛' ' ' ' ' '┃ ']
|
|
1419
|
+
['┗━' '━━' '━━' '━━' '━━' '━┛' '┗━' '━━' '━━' '━━' '━━' '━┛']]
|
|
1420
|
+
Solutions found: 1
|
|
1421
|
+
status: OPTIMAL
|
|
1422
|
+
Time taken: 0.98 seconds
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
**Solved puzzle**
|
|
1426
|
+
|
|
1427
|
+
<img src="./images/pearl_solved.png" alt="Pearl solved" width="500">
|
|
1428
|
+
|
|
1429
|
+
---
|
|
1430
|
+
|
|
1431
|
+
## Bridges (Puzzle Type #20)
|
|
1432
|
+
|
|
1433
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/bridges.html)
|
|
1434
|
+
|
|
1435
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/bridges.html#bridges)
|
|
1436
|
+
|
|
1437
|
+
* [**Solver Code**][20]
|
|
1438
|
+
|
|
1439
|
+
<details>
|
|
1440
|
+
<summary><strong>Rules</strong></summary>
|
|
1441
|
+
You have a set of islands distributed across the playing area. Each island contains a number. Your aim is to connect the islands together with bridges, in such a way that:
|
|
1442
|
+
|
|
1443
|
+
- Bridges run horizontally or vertically.
|
|
1444
|
+
- The number of bridges terminating at any island is equal to the number written in that island.
|
|
1445
|
+
- Two bridges may run in parallel between the same two islands, but no more than two may do so.
|
|
1446
|
+
- No bridge crosses another bridge.
|
|
1447
|
+
- All the islands are connected together.
|
|
1448
|
+
|
|
1449
|
+
There are some configurable alternative modes, which involve changing the parallel-bridge limit to something other than 2
|
|
1450
|
+
</details>
|
|
1451
|
+
|
|
1452
|
+
**Unsolved puzzle**
|
|
1453
|
+
|
|
1454
|
+
<img src="./images/bridges_unsolved.png" alt="Bridges unsolved" width="500">
|
|
1455
|
+
|
|
1456
|
+
Code to utilize this package and solve the puzzle:
|
|
1457
|
+
```python
|
|
1458
|
+
import numpy as np
|
|
1459
|
+
from puzzle_solver import bridges_solver as solver
|
|
1460
|
+
bor = np.array([
|
|
1461
|
+
[' ', ' ', ' ', ' ', ' ', '1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '3'],
|
|
1462
|
+
['2', ' ', ' ', ' ', ' ', ' ', ' ', '4', ' ', ' ', '4', ' ', ' ', '2', ' '],
|
|
1463
|
+
[' ', ' ', ' ', '2', ' ', '4', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1464
|
+
[' ', '1', ' ', ' ', ' ', ' ', ' ', ' ', '1', ' ', '2', ' ', ' ', ' ', '4'],
|
|
1465
|
+
[' ', '2', ' ', '3', ' ', '6', ' ', '4', ' ', ' ', '3', ' ', '1', ' ', ' '],
|
|
1466
|
+
['2', ' ', ' ', ' ', '2', ' ', ' ', ' ', '1', ' ', ' ', '2', ' ', ' ', ' '],
|
|
1467
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1468
|
+
['2', ' ', ' ', ' ', ' ', ' ', '5', ' ', ' ', '3', ' ', '4', ' ', ' ', ' '],
|
|
1469
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1470
|
+
[' ', '3', ' ', ' ', ' ', '3', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1471
|
+
[' ', ' ', '2', ' ', '2', ' ', ' ', ' ', ' ', ' ', ' ', '5', ' ', ' ', '4'],
|
|
1472
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1473
|
+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
|
1474
|
+
[' ', ' ', '1', ' ', ' ', '2', ' ', ' ', ' ', '1', ' ', '2', ' ', ' ', ' '],
|
|
1475
|
+
[' ', '4', ' ', ' ', '4', ' ', '3', ' ', ' ', ' ', '4', ' ', ' ', ' ', '4'],
|
|
1476
|
+
])
|
|
1477
|
+
binst = solver.Board(bor)
|
|
1478
|
+
solutions = binst.solve_and_print()
|
|
1479
|
+
```
|
|
1480
|
+
**Script Output**
|
|
1481
|
+
|
|
1482
|
+
Note that the four numbers indicate how many bridges in the 4 directions (right, left, down, up) respectively.
|
|
1483
|
+
```
|
|
1484
|
+
Solution found
|
|
1485
|
+
| | | | | |1000| | | | | | | | |0120|
|
|
1486
|
+
|
|
1487
|
+
|1010| | | | | | |2110| | |2200| | |0200| |
|
|
1488
|
+
|
|
1489
|
+
| | | |2000| |0220| | | | | | | | | |
|
|
1490
|
+
|
|
1491
|
+
| |0010| | | | | | |1000| |1100| | | |0112|
|
|
1492
|
+
|
|
1493
|
+
| |1001| |2100| |2202| |1201| | |1110| |0100| | |
|
|
1494
|
+
|
|
1495
|
+
|1001| | | |1100| | | |0100| | |0020| | | |
|
|
1496
|
+
|
|
1497
|
+
| | | | | | | | | | | | | | | |
|
|
1498
|
+
|
|
1499
|
+
|2000| | | | | |2210| | |0210| |0022| | | |
|
|
1500
|
+
|
|
1501
|
+
| | | | | | | | | | | | | | | |
|
|
1502
|
+
|
|
1503
|
+
| |1020| | | |0120| | | | | | | | | |
|
|
1504
|
+
|
|
1505
|
+
| | |1010| |0110| | | | | | |1022| | |0121|
|
|
1506
|
+
|
|
1507
|
+
| | | | | | | | | | | | | | | |
|
|
1508
|
+
|
|
1509
|
+
| | | | | | | | | | | | | | | |
|
|
1510
|
+
|
|
1511
|
+
| | |0001| | |0002| | | |0001| |0002| | | |
|
|
1512
|
+
|
|
1513
|
+
| |2002| | |1201| |1101| | | |2101| | | |0202|
|
|
1514
|
+
|
|
1515
|
+
Solutions found: 1
|
|
1516
|
+
status: OPTIMAL
|
|
1517
|
+
Time taken: 0.01 seconds
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
**Solved puzzle**
|
|
1521
|
+
|
|
1522
|
+
<img src="./images/bridges_solved.png" alt="Bridges solved" width="500">
|
|
1523
|
+
|
|
1524
|
+
---
|
|
1525
|
+
|
|
1526
|
+
## Inertia (Puzzle Type #21)
|
|
1527
|
+
|
|
1528
|
+
This solver is a bit different from the other solvers in this repo because this game does not have a unique solution (you simply move the ball to collect all the gems).
|
|
1529
|
+
|
|
1530
|
+
Thus the solver was developed with the additional much harder goal of collecting all the gems with the least number of moves.
|
|
1531
|
+
|
|
1532
|
+
It does so using the following high level steps:
|
|
1533
|
+
|
|
1534
|
+
1. Model the board as a directed graph where the cells are nodes and legal moves as directed edges with unit cost. Each gem has to a group of edges where traversing any one of them collects that gem.
|
|
1535
|
+
2. Model step (1) as a [Generalized Traveling Salesman Problem (GTSP)](https://en.wikipedia.org/wiki/Set_TSP_problem), where each gem’s edge group forms a cluster.
|
|
1536
|
+
3. Apply the [Noon–Bean transformation](https://deepblue.lib.umich.edu/bitstream/handle/2027.42/6834/ban3102.0001.001.pdf?sequence=5) **(Noon & Bean, 1991)** to convert the GTSP from step (2) into an equivalent Asymmetric TSP (ATSP) that can be solved with OR-Tools’ routing solver. (Noon-Bean transformation is mentioned but not described in the [TSP wikipedia page](https://en.wikipedia.org/wiki/Travelling_salesman_problem).)
|
|
1537
|
+
4. Use a [Vehicle Routing Problem (VRP)](https://en.wikipedia.org/wiki/Vehicle_routing_problem) solver using the [OR-Tools VRP solver](https://developers.google.com/optimization/routing/routing_tasks) to solve the ATSP.
|
|
1538
|
+
|
|
1539
|
+
This achieves a final sequence of moves that is empirically always faster than the website's solution.
|
|
1540
|
+
|
|
1541
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/inertia.html)
|
|
1542
|
+
|
|
1543
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/inertia.html#inertia)
|
|
1544
|
+
|
|
1545
|
+
* [**Solver Code**][21]
|
|
1546
|
+
|
|
1547
|
+
<details>
|
|
1548
|
+
<summary><strong>Rules</strong></summary>
|
|
1549
|
+
You are a small green ball sitting in a grid full of obstacles. Your aim is to collect all the gems without running into any mines.
|
|
1550
|
+
|
|
1551
|
+
You can move the ball in any orthogonal or diagonal direction. Once the ball starts moving, it will continue until something stops it. A wall directly in its path will stop it (but if it is moving diagonally, it will move through a diagonal gap between two other walls without stopping). Also, some of the squares are ‘stops’; when the ball moves on to a stop, it will stop moving no matter what direction it was going in. Gems do not stop the ball; it picks them up and keeps on going.
|
|
1552
|
+
|
|
1553
|
+
Running into a mine is fatal. Even if you picked up the last gem in the same move which then hit a mine, the game will count you as dead rather than victorious.
|
|
1554
|
+
</details>
|
|
1555
|
+
|
|
1556
|
+
**Unsolved puzzle**
|
|
1557
|
+
|
|
1558
|
+
<img src="./images/inertia_unsolved.png" alt="Inertia unsolved" width="500">
|
|
1559
|
+
|
|
1560
|
+
Code to utilize this package and solve the puzzle:
|
|
1561
|
+
|
|
1562
|
+
(Note: there is a script that parses a screenshot of the board and outputs the below array that the solver uses. The script uses classical computer vision techniques and is called `parse_map.py`)
|
|
1563
|
+
```python
|
|
1564
|
+
import numpy as np
|
|
1565
|
+
from puzzle_solver import inertia_solver as solver
|
|
1566
|
+
bor = np.array([
|
|
1567
|
+
["O", "O", "M", " ", "G", "O", "G", "O", " ", " ", "M", " ", " ", "O", "G", "G", "W", "O", "O", "O"],
|
|
1568
|
+
["O", " ", "W", " ", "W", "O", "G", "M", " ", " ", " ", "G", "M", "O", "W", "G", " ", "M", "M", "O"],
|
|
1569
|
+
["O", "M", "O", "O", " ", "M", " ", "W", "W", "M", "G", "W", " ", " ", "G", " ", "W", "G", "O", "G"],
|
|
1570
|
+
["O", " ", "O", "M", "G", "O", "W", "G", "M", "O", " ", " ", "G", "G", "G", " ", "M", "W", "M", "O"],
|
|
1571
|
+
["M", "M", "O", "G", " ", "W", " ", " ", "O", "G", " ", "M", "M", " ", "W", "W", " ", "W", "W", "O"],
|
|
1572
|
+
["G", " ", "G", "W", "M", "W", "W", " ", "G", "G", "W", "M", "G", "G", " ", "G", "O", "O", "M", "M"],
|
|
1573
|
+
["M", " ", "M", " ", "W", "W", "M", "M", "M", "O", "M", "G", "O", "M", "M", "W", "B", "O", "W", "M"],
|
|
1574
|
+
["G", "G", " ", "W", "M", "M", "W", "O", "W", "G", "W", "O", "O", "M", " ", "W", "W", "G", "G", "M"],
|
|
1575
|
+
[" ", "M", "M", " ", " ", " ", "G", "G", "M", "O", "M", "O", "M", "G", "W", "M", "W", " ", "O", " "],
|
|
1576
|
+
["G", " ", "M", " ", " ", " ", "W", "O", "W", "W", "M", "M", "G", "W", " ", " ", "W", "M", "G", "W"],
|
|
1577
|
+
["G", "O", "M", "M", "G", "M", "W", "O", "O", "G", "W", "M", "M", "G", "G", " ", "O", " ", "W", "W"],
|
|
1578
|
+
["G", "G", "W", "G", "M", " ", "G", "W", "W", " ", "G", " ", "O", "W", "G", "G", "O", " ", "M", "M"],
|
|
1579
|
+
["W", "M", "O", " ", "W", "O", "O", "M", "M", "O", "G", "W", " ", "G", "O", "G", "G", "O", "O", "W"],
|
|
1580
|
+
["W", "W", "W", " ", "W", "O", "W", "M", "O", "M", "G", "O", "O", " ", " ", "W", "W", "G", "W", "W"],
|
|
1581
|
+
["O", "W", "O", "M", "O", "G", " ", "O", "O", "M", "O", " ", "M", "M", "O", "G", "W", "G", "M", " "],
|
|
1582
|
+
["M", "G", "O", "G", "O", "G", "O", "G", " ", "W", "W", "G", "O", " ", "W", "M", "G", " ", "W", " "]
|
|
1583
|
+
])
|
|
1584
|
+
start_pos, edges, edges_to_direction, gems_to_edges = solver.parse_nodes_and_edges(bor)
|
|
1585
|
+
optimal_walk = solver.solve_optimal_walk(start_pos, edges, gems_to_edges)
|
|
1586
|
+
moves = solver.get_moves_from_walk(optimal_walk, edges_to_direction, verbose=True)
|
|
1587
|
+
```
|
|
1588
|
+
**Script Output**
|
|
1589
|
+
|
|
1590
|
+
Note that the output is the sequence of moves to collect all the gems. This particular solution is 106 moves, which is 15 moves better than the website's solution.
|
|
1591
|
+
```
|
|
1592
|
+
number of moves 106
|
|
1593
|
+
↗ ↖ ↖ ↙ ↙ ↖ ↖ ↙ → ↘
|
|
1594
|
+
↙ → ↖ → ↙ ↓ → ↘ ↗ ↓
|
|
1595
|
+
↘ → ↘ ↓ ↗ ↓ ↑ → ↗ ↖
|
|
1596
|
+
↑ ↗ ↑ ↗ → ↓ ← ↙ ↖ ↗
|
|
1597
|
+
↓ ↙ ↙ ↑ ← ↘ ↙ ↓ → ↘
|
|
1598
|
+
↘ ↙ ↖ ↙ ↗ ↘ ↗ ↘ ↑ ↘
|
|
1599
|
+
↖ ↑ ↗ → → ↘ → ↘ ↗ ↑
|
|
1600
|
+
← ↑ ↖ ↖ ↗ → ↘ ↓ ↖ ←
|
|
1601
|
+
↖ ↓ ← ↓ ↓ ↑ ↖ → ↗ ↗
|
|
1602
|
+
↘ ↘ ↙ ↘ ↓ ↗ ↖ ↘ ↙ ←
|
|
1603
|
+
↘ ↖ ↗ ↑ ↗ →
|
|
1604
|
+
Time taken: 13.92 seconds
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
**Solved puzzle**
|
|
1608
|
+
|
|
1609
|
+
This picture won't mean much as the game is about the sequence of moves not the final frame as shown here.
|
|
1610
|
+
|
|
1611
|
+
<img src="./images/inertia_solved.png" alt="Inertia solved" width="500">
|
|
1612
|
+
|
|
1613
|
+
---
|
|
1614
|
+
|
|
1615
|
+
## Guess (Puzzle Type #22)
|
|
1616
|
+
|
|
1617
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/guess.html)
|
|
1618
|
+
|
|
1619
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/guess.html#guess)
|
|
1620
|
+
|
|
1621
|
+
* [**Solver Code**][22]
|
|
1622
|
+
|
|
1623
|
+
<details>
|
|
1624
|
+
<summary><strong>Rules</strong></summary>
|
|
1625
|
+
You have a set of coloured pegs, and have to reproduce a predetermined sequence of them (chosen by the computer) within a certain number of guesses.
|
|
1626
|
+
|
|
1627
|
+
Each guess gets marked with the number of correctly-coloured pegs in the correct places (in black), and also the number of correctly-coloured pegs in the wrong places (in white).
|
|
1628
|
+
</details>
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
Unlike most other puzzles in this repo, 'Guess' is very different. Similar to minesweeper, Guess is a limited information dynamic puzzle where the next best move depends on information revealed by previous moves (The similarities to minesweeper stop here).
|
|
1632
|
+
|
|
1633
|
+
The solver is designed to take the state of the board at any timestep and always gives the next optimal guess. This might seem like an impossible task at first but it's actually not too bad. The optimal guess is defined to be the one that maximizes the Shannon entropy (i.e. maximizes the expected information gain).
|
|
1634
|
+
|
|
1635
|
+
The steps below formaly describe the algorithm that is also used in [this amazing 3Blue1Brown video](https://www.youtube.com/watch?v=v68zYyaEmEA) on solving Wordle using information theory. Where 3Blue1Browne describes the same steps below but for a slightly harder problem to solve wordle (a very similar game). The video intuitively justifies this algorithm and builds it from scratch using basic intuition.
|
|
1636
|
+
|
|
1637
|
+
To formalize the algorithm, let's first define our three inputs as
|
|
1638
|
+
- $N :=$ the number of pegs (the length of every guess)
|
|
1639
|
+
- must have $N \geq 1$ and by default $N = 4$ in the game
|
|
1640
|
+
- $C :=$ the set of possible colors
|
|
1641
|
+
- what actually matters is $|C|$, the number of possible choices for each peg, i.e. the number of colors
|
|
1642
|
+
- by default in the game, $C = \{R,Y,G,B,O,P\}$ (six distinct symbols; only $|C|$ matters) for Red, Yellow, Green, Blue, Orange, and Purple.
|
|
1643
|
+
- $\mathrm{MR} := ((m_1, r_1), (m_2, r_2), ..., (m_k, r_k))$ be the sequence of previous guesses and results where $(m_i, r_i)$ is the previous guess and result at round $i$ and $k\geq 0$ is the number of previous guesses the player has made
|
|
1644
|
+
- Note that $m_i$ has length $N$ and each element is $\in C$ by definition
|
|
1645
|
+
- $r_i$ is a triplet of non-negative integers that sum to $N$ by definition. This corresponds to counts of exact-match positions, color-only matches, and non-matches (visualized as black, white, and grey dots)
|
|
1646
|
+
|
|
1647
|
+
The algorithm is as follows
|
|
1648
|
+
|
|
1649
|
+
1. Define $G$ as the set of every possible guess that can be made
|
|
1650
|
+
|
|
1651
|
+
$$G := \{(c_1, \dots, c_N) \mid \forall i \in \{1, \dots, N\},\ c_i \in C \}$$
|
|
1652
|
+
|
|
1653
|
+
1. Note that $|G| = |C|^N$
|
|
1654
|
+
|
|
1655
|
+
2. Note that $m_i \in G$ for all $i \in \{1, 2, ..., k\}$ by definition.
|
|
1656
|
+
|
|
1657
|
+
2. Define $T$ as the set of every possible result triplet
|
|
1658
|
+
|
|
1659
|
+
$$T := \{(t_1, t_2, t_3) \in \mathbb{N}_0^3 : t_1 + t_2 + t_3 = N\}$$
|
|
1660
|
+
|
|
1661
|
+
1. Note that $r_i \in T$ for all $i \in \{1, 2, ..., k\}$ by definition.
|
|
1662
|
+
2. Note that $|T|=\binom{N+2}{2}$ (stars-and-bars)
|
|
1663
|
+
3. By default, $N = 4$ in the game so $|T|=15$
|
|
1664
|
+
|
|
1665
|
+
3. Define $f : G \times G \to T$ by $f(g_{\text{guess}}, g_{\text{truth}}) = t$ as the result triplet $(t_1, t_2, t_3)$ obtained when guessing $g_{\text{guess}}$ against ground truth $g_{\text{truth}}$. It is trivial to algorithmically make this function which simply counts from $g_1$ and $g_2.$ Look at the function `get_triplets` for a naive implementation of this.
|
|
1666
|
+
|
|
1667
|
+
4. Define $S$ as the subset of $G$ that is consistent with the previous guesses $m_i$ and results $r_i$
|
|
1668
|
+
|
|
1669
|
+
$$
|
|
1670
|
+
S := \{g \in G : \forall i \in \{1, 2, ..., k\}, f(m_i, g) = r_i\}
|
|
1671
|
+
$$
|
|
1672
|
+
1. Note that if there aren't previous guesses ($\mathrm{MR} = \emptyset$) then $S = G$
|
|
1673
|
+
2. Note that if $S = \emptyset$ then something is wrong with the previous guesses $\mathrm{MR}$ and there is no possible solution to the puzzle. The algorithm stops here and informs the user that the puzzle is unsolvable with the given guesses $\mathrm{MR}$ and that this should never happen unless there is a typo in the guesses $\mathrm{MR}$ (which is usually the case).
|
|
1674
|
+
|
|
1675
|
+
5. For each possible guess $g \in G$ and each triplet $t \in T$, count the number of possible solutions $s \in S$ that result in the triplet $t$ when guessing $g$. i.e.
|
|
1676
|
+
|
|
1677
|
+
$$D(g, t) := |\{s \in S: f(g, s) = t\}|$$
|
|
1678
|
+
|
|
1679
|
+
6. Calculate the entropy for each possible guess $g \in G$ as the sum of probability times the self-information for every triplet $t \in T$. i.e.
|
|
1680
|
+
|
|
1681
|
+
$$H : G \to \mathbb{R}, \quad H(g) = -\sum_{t \in T} P(t \mid g) \log_2 P(t \mid g)$$
|
|
1682
|
+
|
|
1683
|
+
1. where $P(t \mid g) = \frac{D(g, t)}{|S|}$
|
|
1684
|
+
2. By convention, terms with $P(t \mid g)=0$ contribute $0$ to the sum (interpreting $0\log 0 := 0)$.
|
|
1685
|
+
|
|
1686
|
+
7. Return the guess $g \in G$ that maximizes the entropy $H(g)$ (to break ties, choose $g$ that is also in $S$ such that it's possibly the correct solution as well, break further ties arbitrarily).
|
|
1687
|
+
1. i.e. return any $g^*\in (\mathrm{argmax}_{g\in G}\ H(g) \cap S)$ if exists
|
|
1688
|
+
2. otherwise return any $g\in \mathrm{argmax}_{g\in G}\ H(g)$.
|
|
1689
|
+
|
|
1690
|
+
|
|
1691
|
+
If you are at all interested in the above steps and want to understand more,
|
|
1692
|
+
I highly recommend watching [this amazing 3Blue1Brown video](https://www.youtube.com/watch?v=v68zYyaEmEA) on solving Wordle using information theory where he describes the same steps but a bits more complicated problem to solve Wordle (a very similar game).
|
|
1693
|
+
|
|
1694
|
+
Below is an example of how to utilize the solver while in the middle of a puzzle.
|
|
1695
|
+
|
|
1696
|
+
(This is the only solver that under the hood does not utilize any packages besides numpy)
|
|
1697
|
+
|
|
1698
|
+
**Unsolved puzzle**
|
|
1699
|
+
|
|
1700
|
+
Let's say we start and made two guesses to end up with the following puzzle:
|
|
1701
|
+
|
|
1702
|
+
<img src="./images/guess_1.png" alt="Guess Pre Move" width="500">
|
|
1703
|
+
|
|
1704
|
+
Code to utilize this package and solve the puzzle:
|
|
1705
|
+
|
|
1706
|
+
We encode the puzzle as a Board object then retreive the optimal next guess:
|
|
1707
|
+
```python
|
|
1708
|
+
from puzzle_solver import guess_solver as solver
|
|
1709
|
+
binst = solver.Board()
|
|
1710
|
+
binst.add_guess(('R', 'Y', 'G', 'B'), (1, 1, 2)) # 1 black dot, 1 white dot, 2 grey dots
|
|
1711
|
+
binst.add_guess(('R', 'G', 'O', 'P'), (0, 2, 2)) # 0 black dots, 2 white dots, 2 grey dots
|
|
1712
|
+
binst.best_next_guess()
|
|
1713
|
+
```
|
|
1714
|
+
|
|
1715
|
+
Note: the three numbers in each guess is the result of the guess: (# of black dots, # of white dots, # of grey dots)
|
|
1716
|
+
|
|
1717
|
+
Note: by default, the board will have 4 circles and 6 possible colors (R: Red, Y: Yellow, G: Green, B: Blue, O: Orange, P: Purple) but both of these are optional parameters to the Board to change behavior.
|
|
1718
|
+
|
|
1719
|
+
**Script Output 1/2**
|
|
1720
|
+
|
|
1721
|
+
Note that the output is next optimal guess that has the maximum Shannon entropy.
|
|
1722
|
+
```
|
|
1723
|
+
out of 1296 possible ground truths, only 57 are still possible.
|
|
1724
|
+
max entropy guess is: ['P', 'Y', 'Y', 'G'] with entropy 3.4511
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
So we make our next guess as (Purple, Yellow, Yellow, Green) and let's say we get this result: (2 black, 1 white, 1 grey)
|
|
1728
|
+
|
|
1729
|
+
<img src="./images/guess_2.png" alt="Guess Post 1 Move" width="500">
|
|
1730
|
+
|
|
1731
|
+
So we input that again to the solver to retreive the next optimal guess:
|
|
1732
|
+
|
|
1733
|
+
```python
|
|
1734
|
+
from puzzle_solver import guess_solver as solver
|
|
1735
|
+
binst = solver.Board()
|
|
1736
|
+
binst.add_guess(('R', 'Y', 'G', 'B'), (1, 1, 2)) # 1 black dot, 1 white dot, 2 grey dots
|
|
1737
|
+
binst.add_guess(('R', 'G', 'O', 'P'), (0, 2, 2)) # 0 black dots, 2 white dots, 2 grey dots
|
|
1738
|
+
binst.add_guess(('P', 'Y', 'Y', 'G'), (2, 1, 1)) # 2 black dots, 1 white dot, 1 grey dot
|
|
1739
|
+
binst.best_next_guess()
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
**Script Output 2/2**
|
|
1743
|
+
|
|
1744
|
+
```
|
|
1745
|
+
out of 1296 possible ground truths, only 3 are still possible.
|
|
1746
|
+
max entropy guess is: ['G', 'Y', 'Y', 'O'] with entropy 1.5850
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
So we make our fourth guess as (Green, Yellow, Yellow, Orange)
|
|
1750
|
+
|
|
1751
|
+
When we input the guess, we see that we correctly solve the puzzle!
|
|
1752
|
+
|
|
1753
|
+
<img src="./images/guess_3.png" alt="Guess Post 2 Moves" width="500">
|
|
1754
|
+
|
|
1755
|
+
Note that in this case, the correct guess was among multiple possible guesses
|
|
1756
|
+
|
|
1757
|
+
In the case when there's only one possible choice left, the solver will inform you that it's the garunteed solution.
|
|
1758
|
+
|
|
1759
|
+
---
|
|
1760
|
+
|
|
1761
|
+
---
|
|
1762
|
+
|
|
1763
|
+
## Quick Start
|
|
1764
|
+
|
|
1765
|
+
### 1) Install Python deps
|
|
1766
|
+
|
|
1767
|
+
Use a fresh conda environment:
|
|
1768
|
+
|
|
1769
|
+
```bash
|
|
1770
|
+
conda create -p ./env python=3.11
|
|
1771
|
+
conda activate ./env
|
|
1772
|
+
pip install -r requirements.txt
|
|
1773
|
+
````
|
|
1774
|
+
|
|
1775
|
+
### 2) Explore a puzzle
|
|
1776
|
+
|
|
1777
|
+
Each puzzle has its own module which can be imported directly from the repo (i.e. `from puzzle_solver import minesweeper_solver`) or you can run the test scripts directly from the CLI.
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
Example: Nonograms (Pattern)
|
|
1781
|
+
Docs: [https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/pattern.html#pattern](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/pattern.html#pattern)
|
|
1782
|
+
|
|
1783
|
+
```bash
|
|
1784
|
+
python -m tests.test_nonograms
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
This runs code like:
|
|
1788
|
+
|
|
1789
|
+
```python
|
|
1790
|
+
from puzzle_solver import nonograms_solver as solver
|
|
1791
|
+
top_numbers = [
|
|
1792
|
+
[8, 2],
|
|
1793
|
+
...
|
|
1794
|
+
[1, 5],
|
|
1795
|
+
] # top clues, omitted here for brevity
|
|
1796
|
+
side_numbers = [
|
|
1797
|
+
[7, 3],
|
|
1798
|
+
...
|
|
1799
|
+
[3, 2],
|
|
1800
|
+
] # side clues, omitted here for brevity
|
|
1801
|
+
binst = solver.Board(top=top_numbers, side=side_numbers)
|
|
1802
|
+
solutions = binst.solve_and_print()
|
|
1803
|
+
```
|
|
1804
|
+
|
|
1805
|
+
You’ll see the solution grid and status in the terminal.
|
|
1806
|
+
|
|
1807
|
+
---
|
|
1808
|
+
|
|
1809
|
+
## Why SAT / CP-SAT?
|
|
1810
|
+
|
|
1811
|
+
Many pencil puzzles can be modeled with:
|
|
1812
|
+
|
|
1813
|
+
- **Boolean decisions** (e.g., black/white, bulb/no-bulb)
|
|
1814
|
+
- **Linear constraints** (counts, separations, adjacency)
|
|
1815
|
+
- **All-different / visibility / reachability** constraints
|
|
1816
|
+
|
|
1817
|
+
This repo builds those constraints in Python and uses SAT/CP-SAT (e.g., OR-Tools) to search efficiently. It both demonstrates the modeling and provides usable solvers.
|
|
1818
|
+
|
|
1819
|
+
---
|
|
1820
|
+
|
|
1821
|
+
## What’s Inside
|
|
1822
|
+
|
|
1823
|
+
Each sub directory in `src/puzzle_solver/puzzles/` targets a different puzzle type. The following are the sub directories:
|
|
1824
|
+
|
|
1825
|
+
* `nonograms` — Picross/Griddlers (run-length constraints). ([Chapter 10][1])
|
|
1826
|
+
* `sudoku` — Sudoku (rows/cols/blocks all-different). ([Chapter 11][2])
|
|
1827
|
+
* `minesweeper` — Minesweeper (mines + counts). ([Chapter 12][3])
|
|
1828
|
+
* `guess` — Guess (similar to wordle, guess the colored circles). ([Chapter 15][22])
|
|
1829
|
+
* `dominosa` — Dominosa (dominoes + counts). ([Chapter 17][4])
|
|
1830
|
+
* `light_up` — *Akari* / Light Up (lighting & adjacency). ([Chapter 21][5])
|
|
1831
|
+
* `map` — Map (region coloring). ([Chapter 22][18])
|
|
1832
|
+
* `inertia` — Inertia (collect all gems without dying (with least number of moves; my addition)). ([Chapter 24][21])
|
|
1833
|
+
* `tents` — Tents (tree-tent matching). ([Chapter 25][6])
|
|
1834
|
+
* `bridges` — Bridges (island connections). ([Chapter 26][20])
|
|
1835
|
+
* `filling` — Filling (Fillomino-style), region sizes. ([Chapter 29][7])
|
|
1836
|
+
* `keen` — Keen (arithmetic operations). ([Chapter 30][8])
|
|
1837
|
+
* `towers` — Skyscrapers (permutation + visibility). ([Chapter 31][9])
|
|
1838
|
+
* `singles` — Singles (hiding numbers). ([Chapter 32][10])
|
|
1839
|
+
* `magnets` — Magnets (polarized dominoes + counts). ([Chapter 33][11])
|
|
1840
|
+
* `signpost` — Signpost (visible dominoes + counts). ([Chapter 34][12])
|
|
1841
|
+
* `range` — Range (rays & totals). ([Chapter 35][13])
|
|
1842
|
+
* `pearl` — Pearl (pearl game). ([Chapter 36][19])
|
|
1843
|
+
* `undead` — UnDead (Vampires/Zombies/Ghosts). ([Chapter 37][14])
|
|
1844
|
+
* `unruly` — Unruly (no triples + balance). ([Chapter 38][15])
|
|
1845
|
+
* `tracks` — Tracks (connected components). ([Chapter 40][16])
|
|
1846
|
+
* `mosaic` — Mosaic (Tapa-like tiling). ([Chapter 42][17])
|
|
1847
|
+
|
|
1848
|
+
---
|
|
1849
|
+
|
|
1850
|
+
## Testing
|
|
1851
|
+
|
|
1852
|
+
To run the tests, simply follow the instructions in Install Python deps section ([here](#1-install-python-deps)) and then run:
|
|
1853
|
+
|
|
1854
|
+
```bash
|
|
1855
|
+
conda activate ./env
|
|
1856
|
+
pytest
|
|
1857
|
+
```
|
|
1858
|
+
|
|
1859
|
+
the `pytest.ini` file is used to configure the pytest command to use `-n 4` to have 4 workers.
|
|
1860
|
+
|
|
1861
|
+
## Contributing
|
|
1862
|
+
|
|
1863
|
+
Issues and PRs welcome!
|
|
1864
|
+
|
|
1865
|
+
|
|
1866
|
+
* Python 3.11 recommended but not required.
|
|
1867
|
+
* Keep puzzle folders self-contained (inputs, solver, simple demo/CLI).
|
|
1868
|
+
* Prefer small, readable encodings with comments explaining each constraint.
|
|
1869
|
+
* If you add a new puzzle:
|
|
1870
|
+
|
|
1871
|
+
1. Create a directory in `src/puzzle_solver/puzzles/<name>/`,
|
|
1872
|
+
2. Add a minimal test script in `tests/test_<name>.py`,
|
|
1873
|
+
3. Document the modeling in code comments,
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
[1]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/nonograms "puzzle_solver/src/puzzle_solver/puzzles/nonograms at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1877
|
+
[2]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/sudoku "puzzle_solver/src/puzzle_solver/puzzles/sudoku at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1878
|
+
[3]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/minesweeper "puzzle_solver/src/puzzle_solver/puzzles/minesweeper at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1879
|
+
[22]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/guess "puzzle_solver/src/puzzle_solver/puzzles/guess at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1880
|
+
[4]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/dominosa "puzzle_solver/src/puzzle_solver/puzzles/dominosa at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1881
|
+
[5]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/light_up "puzzle_solver/src/puzzle_solver/puzzles/light_up at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1882
|
+
[18]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/map "puzzle_solver/src/puzzle_solver/puzzles/map at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1883
|
+
[21]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/inertia "puzzle_solver/src/puzzle_solver/puzzles/inertia at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1884
|
+
[6]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/tents "puzzle_solver/src/puzzle_solver/puzzles/tents at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1885
|
+
[20]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/bridges "puzzle_solver/src/puzzle_solver/puzzles/bridges at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1886
|
+
[7]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/filling "puzzle_solver/src/puzzle_solver/puzzles/filling at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1887
|
+
[8]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/keen "puzzle_solver/src/puzzle_solver/puzzles/keen at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1888
|
+
[9]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/towers "puzzle_solver/src/puzzle_solver/puzzles/towers at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1889
|
+
[10]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/singles "puzzle_solver/src/puzzle_solver/puzzles/singles at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1890
|
+
[11]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/magnets "puzzle_solver/src/puzzle_solver/puzzles/magnets at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1891
|
+
[12]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/signpost "puzzle_solver/src/puzzle_solver/puzzles/signpost at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1892
|
+
[13]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/range "puzzle_solver/src/puzzle_solver/puzzles/range at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1893
|
+
[19]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/pearl "puzzle_solver/src/puzzle_solver/puzzles/pearl at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1894
|
+
[14]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/undead "puzzle_solver/src/puzzle_solver/puzzles/undead at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1895
|
+
[15]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/unruly "puzzle_solver/src/puzzle_solver/puzzles/unruly at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1896
|
+
[16]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/tracks "puzzle_solver/src/puzzle_solver/puzzles/tracks at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
1897
|
+
[17]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/mosaic "puzzle_solver/src/puzzle_solver/puzzles/mosaic at master · Ar-Kareem/puzzle_solver · GitHub"
|