multi-puzzle-solver 0.9.25__tar.gz → 0.9.26__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.9.25 → multi_puzzle_solver-0.9.26}/PKG-INFO +73 -3
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/README.md +73 -3
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/PKG-INFO +73 -3
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/__init__.py +2 -2
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/core/utils.py +3 -1
- multi_puzzle_solver-0.9.26/src/puzzle_solver/puzzles/flip/flip.py +77 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/palisade/palisade.py +1 -1
- multi_puzzle_solver-0.9.26/tests/test_flip.py +73 -0
- multi_puzzle_solver-0.9.25/src/puzzle_solver/puzzles/flip/flip.py +0 -48
- multi_puzzle_solver-0.9.25/tests/test_flip.py +0 -2
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/pyproject.toml +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/setup.cfg +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/SOURCES.txt +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/dependency_links.txt +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/requires.txt +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/top_level.txt +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/core/utils_ortools.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/aquarium/aquarium.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/battleships/battleships.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/binairo/binairo.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/black_box/black_box.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/bridges/bridges.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_melee.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_range.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_solo.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/dominosa/dominosa.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/filling/filling.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/galaxies/galaxies.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/guess/guess.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/inertia.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/parse_map/parse_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/tsp.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/kakurasu/kakurasu.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/keen/keen.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/light_up/light_up.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/lits/lits.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/magnets/magnets.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/map/map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/minesweeper/minesweeper.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/mosaic/mosaic.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/nonograms/nonograms.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/norinori/norinori.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/pearl/pearl.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/range/range.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/rectangles/rectangles.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/signpost/signpost.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/singles/singles.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slant/parse_map/parse_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slant/slant.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slitherlink/slitherlink.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/star_battle/star_battle.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/stitches/parse_map/parse_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/stitches/stitches.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/sudoku/sudoku.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tents/tents.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/thermometers/thermometers.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/towers/towers.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tracks/tracks.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/undead/undead.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/unequal/unequal.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/unruly/unruly.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/yin_yang/yin_yang.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/utils/visualizer.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_aquarium.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_battleships.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_binairo.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_black_box.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_bridges.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_melee.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_range.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_solo.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_dominosa.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_filling.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_galaxies.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_guess.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_inertia.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_kakurasu.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_keen.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_light_up.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_lits.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_magnets.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_map.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_minesweeper.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_mosaic.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_nonograms.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_norinori.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_palisade.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_pearl.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_range.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_rectangles.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_signpost.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_singles.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_slant.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_slitherlink.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_star_battle.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_stitches.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_sudoku.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_tents.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_thermometers.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_towers.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_tracks.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_undead.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_unequal.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_unruly.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_utils.py +0 -0
- {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_yin_yang.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-puzzle-solver
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.26
|
|
4
4
|
Summary: Efficient solvers for numerous popular and esoteric logic puzzles using CP-SAT
|
|
5
5
|
Author: Ar-Kareem
|
|
6
6
|
Project-URL: Homepage, https://github.com/Ar-Kareem/puzzle_solver
|
|
@@ -331,6 +331,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
331
331
|
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/palisade_solved.png" alt="Palisade" width="140">
|
|
332
332
|
</a>
|
|
333
333
|
</td>
|
|
334
|
+
<td align="center">
|
|
335
|
+
<a href="#flip-puzzle-type-44"><b>Flip</b><br><br>
|
|
336
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip" width="140">
|
|
337
|
+
</a>
|
|
338
|
+
</td>
|
|
334
339
|
</tr>
|
|
335
340
|
</table>
|
|
336
341
|
|
|
@@ -389,6 +394,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
389
394
|
- [Binairo (Puzzle Type #41)](#binairo-puzzle-type-41)
|
|
390
395
|
- [Rectangles (Puzzle Type #42)](#rectangles-puzzle-type-42)
|
|
391
396
|
- [Palisade (Puzzle Type #43)](#palisade-puzzle-type-43)
|
|
397
|
+
- [Flip (Puzzle Type #44)](#flip-puzzle-type-44)
|
|
392
398
|
- [Why SAT / CP-SAT?](#why-sat--cp-sat)
|
|
393
399
|
- [Testing](#testing)
|
|
394
400
|
- [Contributing](#contributing)
|
|
@@ -3730,14 +3736,13 @@ Applying the solution to the puzzle visually:
|
|
|
3730
3736
|
|
|
3731
3737
|
---
|
|
3732
3738
|
|
|
3733
|
-
|
|
3734
3739
|
## Palisade (Puzzle Type #43)
|
|
3735
3740
|
|
|
3736
3741
|
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/palisade.html)
|
|
3737
3742
|
|
|
3738
3743
|
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/palisade.html#palisade)
|
|
3739
3744
|
|
|
3740
|
-
* [**Solver Code**][
|
|
3745
|
+
* [**Solver Code**][43]
|
|
3741
3746
|
|
|
3742
3747
|
<details>
|
|
3743
3748
|
<summary><strong>Rules</strong></summary>
|
|
@@ -3819,6 +3824,70 @@ Applying the solution to the puzzle visually:
|
|
|
3819
3824
|
|
|
3820
3825
|
---
|
|
3821
3826
|
|
|
3827
|
+
## Flip (Puzzle Type #44)
|
|
3828
|
+
|
|
3829
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/flip.html)
|
|
3830
|
+
|
|
3831
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/flip.html#flip)
|
|
3832
|
+
|
|
3833
|
+
* [**Solver Code**][44]
|
|
3834
|
+
|
|
3835
|
+
<details>
|
|
3836
|
+
<summary><strong>Rules</strong></summary>
|
|
3837
|
+
|
|
3838
|
+
You have a grid of squares, some light and some dark. Your aim is to light all the squares up at the same time. You can choose any square and flip its state from light to dark or dark to light, but when you do so, other squares around it change state as well.
|
|
3839
|
+
|
|
3840
|
+
</details>
|
|
3841
|
+
|
|
3842
|
+
**Unsolved puzzle**
|
|
3843
|
+
|
|
3844
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip unsolved" width="500">
|
|
3845
|
+
|
|
3846
|
+
Code to utilize this package and solve the puzzle:
|
|
3847
|
+
|
|
3848
|
+
(Note: the solver also supports random mapping of squares to the neighbors they flip, see the test cases in `tests/test_flip.py` for usage examples)
|
|
3849
|
+
|
|
3850
|
+
```python
|
|
3851
|
+
import numpy as np
|
|
3852
|
+
from puzzle_solver import flip_solver as solver
|
|
3853
|
+
board = np.array([
|
|
3854
|
+
['B', 'W', 'W', 'W', 'W', 'W', 'W'],
|
|
3855
|
+
['B', 'B', 'W', 'W', 'W', 'B', 'B'],
|
|
3856
|
+
['W', 'B', 'W', 'W', 'B', 'B', 'W'],
|
|
3857
|
+
['B', 'B', 'B', 'W', 'W', 'B', 'W'],
|
|
3858
|
+
['W', 'W', 'B', 'B', 'W', 'B', 'W'],
|
|
3859
|
+
['B', 'W', 'B', 'B', 'W', 'W', 'W'],
|
|
3860
|
+
['B', 'W', 'B', 'W', 'W', 'B', 'B'],
|
|
3861
|
+
])
|
|
3862
|
+
binst = solver.Board(board=board)
|
|
3863
|
+
solutions = binst.solve_and_print()
|
|
3864
|
+
```
|
|
3865
|
+
|
|
3866
|
+
**Script Output**
|
|
3867
|
+
|
|
3868
|
+
The output tells you which squares to tap to solve the puzzle.
|
|
3869
|
+
|
|
3870
|
+
```python
|
|
3871
|
+
Solution found
|
|
3872
|
+
[['T' ' ' 'T' 'T' 'T' ' ' ' ']
|
|
3873
|
+
[' ' ' ' ' ' 'T' ' ' 'T' ' ']
|
|
3874
|
+
[' ' 'T' ' ' ' ' 'T' ' ' ' ']
|
|
3875
|
+
['T' ' ' 'T' ' ' ' ' 'T' ' ']
|
|
3876
|
+
[' ' ' ' ' ' 'T' ' ' ' ' 'T']
|
|
3877
|
+
['T' ' ' 'T' ' ' 'T' 'T' 'T']
|
|
3878
|
+
[' ' ' ' ' ' ' ' ' ' 'T' 'T']]
|
|
3879
|
+
Solutions found: 1
|
|
3880
|
+
status: OPTIMAL
|
|
3881
|
+
```
|
|
3882
|
+
|
|
3883
|
+
**Solved puzzle**
|
|
3884
|
+
|
|
3885
|
+
This picture won't mean much as the game is about the sequence of moves not the final frame as shown here.
|
|
3886
|
+
|
|
3887
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_solved.png" alt="Flip solved" width="500">
|
|
3888
|
+
|
|
3889
|
+
---
|
|
3890
|
+
|
|
3822
3891
|
---
|
|
3823
3892
|
|
|
3824
3893
|
## Why SAT / CP-SAT?
|
|
@@ -3913,3 +3982,4 @@ Issues and PRs welcome!
|
|
|
3913
3982
|
[41]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/binairo "puzzle_solver/src/puzzle_solver/puzzles/binairo at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3914
3983
|
[42]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/rectangles "puzzle_solver/src/puzzle_solver/puzzles/rectangles at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3915
3984
|
[43]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/palisade "puzzle_solver/src/puzzle_solver/puzzles/palisade at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3985
|
+
[44]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flip "puzzle_solver/src/puzzle_solver/puzzles/flip at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
@@ -305,6 +305,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
305
305
|
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/palisade_solved.png" alt="Palisade" width="140">
|
|
306
306
|
</a>
|
|
307
307
|
</td>
|
|
308
|
+
<td align="center">
|
|
309
|
+
<a href="#flip-puzzle-type-44"><b>Flip</b><br><br>
|
|
310
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip" width="140">
|
|
311
|
+
</a>
|
|
312
|
+
</td>
|
|
308
313
|
</tr>
|
|
309
314
|
</table>
|
|
310
315
|
|
|
@@ -363,6 +368,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
363
368
|
- [Binairo (Puzzle Type #41)](#binairo-puzzle-type-41)
|
|
364
369
|
- [Rectangles (Puzzle Type #42)](#rectangles-puzzle-type-42)
|
|
365
370
|
- [Palisade (Puzzle Type #43)](#palisade-puzzle-type-43)
|
|
371
|
+
- [Flip (Puzzle Type #44)](#flip-puzzle-type-44)
|
|
366
372
|
- [Why SAT / CP-SAT?](#why-sat--cp-sat)
|
|
367
373
|
- [Testing](#testing)
|
|
368
374
|
- [Contributing](#contributing)
|
|
@@ -3704,14 +3710,13 @@ Applying the solution to the puzzle visually:
|
|
|
3704
3710
|
|
|
3705
3711
|
---
|
|
3706
3712
|
|
|
3707
|
-
|
|
3708
3713
|
## Palisade (Puzzle Type #43)
|
|
3709
3714
|
|
|
3710
3715
|
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/palisade.html)
|
|
3711
3716
|
|
|
3712
3717
|
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/palisade.html#palisade)
|
|
3713
3718
|
|
|
3714
|
-
* [**Solver Code**][
|
|
3719
|
+
* [**Solver Code**][43]
|
|
3715
3720
|
|
|
3716
3721
|
<details>
|
|
3717
3722
|
<summary><strong>Rules</strong></summary>
|
|
@@ -3793,6 +3798,70 @@ Applying the solution to the puzzle visually:
|
|
|
3793
3798
|
|
|
3794
3799
|
---
|
|
3795
3800
|
|
|
3801
|
+
## Flip (Puzzle Type #44)
|
|
3802
|
+
|
|
3803
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/flip.html)
|
|
3804
|
+
|
|
3805
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/flip.html#flip)
|
|
3806
|
+
|
|
3807
|
+
* [**Solver Code**][44]
|
|
3808
|
+
|
|
3809
|
+
<details>
|
|
3810
|
+
<summary><strong>Rules</strong></summary>
|
|
3811
|
+
|
|
3812
|
+
You have a grid of squares, some light and some dark. Your aim is to light all the squares up at the same time. You can choose any square and flip its state from light to dark or dark to light, but when you do so, other squares around it change state as well.
|
|
3813
|
+
|
|
3814
|
+
</details>
|
|
3815
|
+
|
|
3816
|
+
**Unsolved puzzle**
|
|
3817
|
+
|
|
3818
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip unsolved" width="500">
|
|
3819
|
+
|
|
3820
|
+
Code to utilize this package and solve the puzzle:
|
|
3821
|
+
|
|
3822
|
+
(Note: the solver also supports random mapping of squares to the neighbors they flip, see the test cases in `tests/test_flip.py` for usage examples)
|
|
3823
|
+
|
|
3824
|
+
```python
|
|
3825
|
+
import numpy as np
|
|
3826
|
+
from puzzle_solver import flip_solver as solver
|
|
3827
|
+
board = np.array([
|
|
3828
|
+
['B', 'W', 'W', 'W', 'W', 'W', 'W'],
|
|
3829
|
+
['B', 'B', 'W', 'W', 'W', 'B', 'B'],
|
|
3830
|
+
['W', 'B', 'W', 'W', 'B', 'B', 'W'],
|
|
3831
|
+
['B', 'B', 'B', 'W', 'W', 'B', 'W'],
|
|
3832
|
+
['W', 'W', 'B', 'B', 'W', 'B', 'W'],
|
|
3833
|
+
['B', 'W', 'B', 'B', 'W', 'W', 'W'],
|
|
3834
|
+
['B', 'W', 'B', 'W', 'W', 'B', 'B'],
|
|
3835
|
+
])
|
|
3836
|
+
binst = solver.Board(board=board)
|
|
3837
|
+
solutions = binst.solve_and_print()
|
|
3838
|
+
```
|
|
3839
|
+
|
|
3840
|
+
**Script Output**
|
|
3841
|
+
|
|
3842
|
+
The output tells you which squares to tap to solve the puzzle.
|
|
3843
|
+
|
|
3844
|
+
```python
|
|
3845
|
+
Solution found
|
|
3846
|
+
[['T' ' ' 'T' 'T' 'T' ' ' ' ']
|
|
3847
|
+
[' ' ' ' ' ' 'T' ' ' 'T' ' ']
|
|
3848
|
+
[' ' 'T' ' ' ' ' 'T' ' ' ' ']
|
|
3849
|
+
['T' ' ' 'T' ' ' ' ' 'T' ' ']
|
|
3850
|
+
[' ' ' ' ' ' 'T' ' ' ' ' 'T']
|
|
3851
|
+
['T' ' ' 'T' ' ' 'T' 'T' 'T']
|
|
3852
|
+
[' ' ' ' ' ' ' ' ' ' 'T' 'T']]
|
|
3853
|
+
Solutions found: 1
|
|
3854
|
+
status: OPTIMAL
|
|
3855
|
+
```
|
|
3856
|
+
|
|
3857
|
+
**Solved puzzle**
|
|
3858
|
+
|
|
3859
|
+
This picture won't mean much as the game is about the sequence of moves not the final frame as shown here.
|
|
3860
|
+
|
|
3861
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_solved.png" alt="Flip solved" width="500">
|
|
3862
|
+
|
|
3863
|
+
---
|
|
3864
|
+
|
|
3796
3865
|
---
|
|
3797
3866
|
|
|
3798
3867
|
## Why SAT / CP-SAT?
|
|
@@ -3886,4 +3955,5 @@ Issues and PRs welcome!
|
|
|
3886
3955
|
[40]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/yin_yang "puzzle_solver/src/puzzle_solver/puzzles/yin_yang at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3887
3956
|
[41]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/binairo "puzzle_solver/src/puzzle_solver/puzzles/binairo at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3888
3957
|
[42]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/rectangles "puzzle_solver/src/puzzle_solver/puzzles/rectangles at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3889
|
-
[43]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/palisade "puzzle_solver/src/puzzle_solver/puzzles/palisade at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3958
|
+
[43]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/palisade "puzzle_solver/src/puzzle_solver/puzzles/palisade at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3959
|
+
[44]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flip "puzzle_solver/src/puzzle_solver/puzzles/flip at master · Ar-Kareem/puzzle_solver · GitHub"
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-puzzle-solver
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.26
|
|
4
4
|
Summary: Efficient solvers for numerous popular and esoteric logic puzzles using CP-SAT
|
|
5
5
|
Author: Ar-Kareem
|
|
6
6
|
Project-URL: Homepage, https://github.com/Ar-Kareem/puzzle_solver
|
|
@@ -331,6 +331,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
331
331
|
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/palisade_solved.png" alt="Palisade" width="140">
|
|
332
332
|
</a>
|
|
333
333
|
</td>
|
|
334
|
+
<td align="center">
|
|
335
|
+
<a href="#flip-puzzle-type-44"><b>Flip</b><br><br>
|
|
336
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip" width="140">
|
|
337
|
+
</a>
|
|
338
|
+
</td>
|
|
334
339
|
</tr>
|
|
335
340
|
</table>
|
|
336
341
|
|
|
@@ -389,6 +394,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
|
|
|
389
394
|
- [Binairo (Puzzle Type #41)](#binairo-puzzle-type-41)
|
|
390
395
|
- [Rectangles (Puzzle Type #42)](#rectangles-puzzle-type-42)
|
|
391
396
|
- [Palisade (Puzzle Type #43)](#palisade-puzzle-type-43)
|
|
397
|
+
- [Flip (Puzzle Type #44)](#flip-puzzle-type-44)
|
|
392
398
|
- [Why SAT / CP-SAT?](#why-sat--cp-sat)
|
|
393
399
|
- [Testing](#testing)
|
|
394
400
|
- [Contributing](#contributing)
|
|
@@ -3730,14 +3736,13 @@ Applying the solution to the puzzle visually:
|
|
|
3730
3736
|
|
|
3731
3737
|
---
|
|
3732
3738
|
|
|
3733
|
-
|
|
3734
3739
|
## Palisade (Puzzle Type #43)
|
|
3735
3740
|
|
|
3736
3741
|
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/palisade.html)
|
|
3737
3742
|
|
|
3738
3743
|
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/palisade.html#palisade)
|
|
3739
3744
|
|
|
3740
|
-
* [**Solver Code**][
|
|
3745
|
+
* [**Solver Code**][43]
|
|
3741
3746
|
|
|
3742
3747
|
<details>
|
|
3743
3748
|
<summary><strong>Rules</strong></summary>
|
|
@@ -3819,6 +3824,70 @@ Applying the solution to the puzzle visually:
|
|
|
3819
3824
|
|
|
3820
3825
|
---
|
|
3821
3826
|
|
|
3827
|
+
## Flip (Puzzle Type #44)
|
|
3828
|
+
|
|
3829
|
+
* [**Play online**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/flip.html)
|
|
3830
|
+
|
|
3831
|
+
* [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/flip.html#flip)
|
|
3832
|
+
|
|
3833
|
+
* [**Solver Code**][44]
|
|
3834
|
+
|
|
3835
|
+
<details>
|
|
3836
|
+
<summary><strong>Rules</strong></summary>
|
|
3837
|
+
|
|
3838
|
+
You have a grid of squares, some light and some dark. Your aim is to light all the squares up at the same time. You can choose any square and flip its state from light to dark or dark to light, but when you do so, other squares around it change state as well.
|
|
3839
|
+
|
|
3840
|
+
</details>
|
|
3841
|
+
|
|
3842
|
+
**Unsolved puzzle**
|
|
3843
|
+
|
|
3844
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_unsolved.png" alt="Flip unsolved" width="500">
|
|
3845
|
+
|
|
3846
|
+
Code to utilize this package and solve the puzzle:
|
|
3847
|
+
|
|
3848
|
+
(Note: the solver also supports random mapping of squares to the neighbors they flip, see the test cases in `tests/test_flip.py` for usage examples)
|
|
3849
|
+
|
|
3850
|
+
```python
|
|
3851
|
+
import numpy as np
|
|
3852
|
+
from puzzle_solver import flip_solver as solver
|
|
3853
|
+
board = np.array([
|
|
3854
|
+
['B', 'W', 'W', 'W', 'W', 'W', 'W'],
|
|
3855
|
+
['B', 'B', 'W', 'W', 'W', 'B', 'B'],
|
|
3856
|
+
['W', 'B', 'W', 'W', 'B', 'B', 'W'],
|
|
3857
|
+
['B', 'B', 'B', 'W', 'W', 'B', 'W'],
|
|
3858
|
+
['W', 'W', 'B', 'B', 'W', 'B', 'W'],
|
|
3859
|
+
['B', 'W', 'B', 'B', 'W', 'W', 'W'],
|
|
3860
|
+
['B', 'W', 'B', 'W', 'W', 'B', 'B'],
|
|
3861
|
+
])
|
|
3862
|
+
binst = solver.Board(board=board)
|
|
3863
|
+
solutions = binst.solve_and_print()
|
|
3864
|
+
```
|
|
3865
|
+
|
|
3866
|
+
**Script Output**
|
|
3867
|
+
|
|
3868
|
+
The output tells you which squares to tap to solve the puzzle.
|
|
3869
|
+
|
|
3870
|
+
```python
|
|
3871
|
+
Solution found
|
|
3872
|
+
[['T' ' ' 'T' 'T' 'T' ' ' ' ']
|
|
3873
|
+
[' ' ' ' ' ' 'T' ' ' 'T' ' ']
|
|
3874
|
+
[' ' 'T' ' ' ' ' 'T' ' ' ' ']
|
|
3875
|
+
['T' ' ' 'T' ' ' ' ' 'T' ' ']
|
|
3876
|
+
[' ' ' ' ' ' 'T' ' ' ' ' 'T']
|
|
3877
|
+
['T' ' ' 'T' ' ' 'T' 'T' 'T']
|
|
3878
|
+
[' ' ' ' ' ' ' ' ' ' 'T' 'T']]
|
|
3879
|
+
Solutions found: 1
|
|
3880
|
+
status: OPTIMAL
|
|
3881
|
+
```
|
|
3882
|
+
|
|
3883
|
+
**Solved puzzle**
|
|
3884
|
+
|
|
3885
|
+
This picture won't mean much as the game is about the sequence of moves not the final frame as shown here.
|
|
3886
|
+
|
|
3887
|
+
<img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flip_solved.png" alt="Flip solved" width="500">
|
|
3888
|
+
|
|
3889
|
+
---
|
|
3890
|
+
|
|
3822
3891
|
---
|
|
3823
3892
|
|
|
3824
3893
|
## Why SAT / CP-SAT?
|
|
@@ -3913,3 +3982,4 @@ Issues and PRs welcome!
|
|
|
3913
3982
|
[41]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/binairo "puzzle_solver/src/puzzle_solver/puzzles/binairo at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3914
3983
|
[42]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/rectangles "puzzle_solver/src/puzzle_solver/puzzles/rectangles at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3915
3984
|
[43]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/palisade "puzzle_solver/src/puzzle_solver/puzzles/palisade at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
3985
|
+
[44]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flip "puzzle_solver/src/puzzle_solver/puzzles/flip at master · Ar-Kareem/puzzle_solver · GitHub"
|
|
@@ -8,7 +8,7 @@ from puzzle_solver.puzzles.chess_range import chess_solo as chess_solo_solver
|
|
|
8
8
|
from puzzle_solver.puzzles.chess_range import chess_melee as chess_melee_solver
|
|
9
9
|
from puzzle_solver.puzzles.dominosa import dominosa as dominosa_solver
|
|
10
10
|
from puzzle_solver.puzzles.filling import filling as filling_solver
|
|
11
|
-
|
|
11
|
+
from puzzle_solver.puzzles.flip import flip as flip_solver
|
|
12
12
|
from puzzle_solver.puzzles.galaxies import galaxies as galaxies_solver
|
|
13
13
|
from puzzle_solver.puzzles.guess import guess as guess_solver
|
|
14
14
|
from puzzle_solver.puzzles.inertia import inertia as inertia_solver
|
|
@@ -45,4 +45,4 @@ from puzzle_solver.puzzles.yin_yang import yin_yang as yin_yang_solver
|
|
|
45
45
|
|
|
46
46
|
from puzzle_solver.puzzles.inertia.parse_map.parse_map import main as inertia_image_parser
|
|
47
47
|
|
|
48
|
-
__version__ = '0.9.
|
|
48
|
+
__version__ = '0.9.26'
|
|
@@ -42,7 +42,9 @@ def get_next_pos(cur_pos: Pos, direction: Union[Direction, Direction8]) -> Pos:
|
|
|
42
42
|
return get_pos(cur_pos.x+delta_x, cur_pos.y+delta_y)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def get_neighbors4(pos: Pos, V: int, H: int) -> Iterable[Pos]:
|
|
45
|
+
def get_neighbors4(pos: Pos, V: int, H: int, include_self: bool = False) -> Iterable[Pos]:
|
|
46
|
+
if include_self:
|
|
47
|
+
yield pos
|
|
46
48
|
for dx, dy in ((1,0),(-1,0),(0,1),(0,-1)):
|
|
47
49
|
p2 = get_pos(x=pos.x+dx, y=pos.y+dy)
|
|
48
50
|
if in_bounds(p2, V, H):
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from ortools.sat.python import cp_model
|
|
5
|
+
|
|
6
|
+
from puzzle_solver.core.utils import Pos, get_all_pos, get_neighbors4, set_char, get_char, Direction, get_next_pos
|
|
7
|
+
from puzzle_solver.core.utils_ortools import generic_solve_all, SingleSolution
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Board:
|
|
11
|
+
def __init__(self, board: np.array, random_mapping: Optional[dict[Pos, Any]] = None):
|
|
12
|
+
assert board.ndim == 2, f'board must be 2d, got {board.ndim}'
|
|
13
|
+
assert all((c.item() in ['B', 'W']) for c in np.nditer(board)), 'board must contain only B or W'
|
|
14
|
+
self.board = board
|
|
15
|
+
self.V, self.H = board.shape
|
|
16
|
+
|
|
17
|
+
if random_mapping is None:
|
|
18
|
+
self.tap_mapping: dict[Pos, set[Pos]] = {pos: list(get_neighbors4(pos, self.V, self.H, include_self=True)) for pos in get_all_pos(self.V, self.H)}
|
|
19
|
+
else:
|
|
20
|
+
mapping_value = list(random_mapping.values())[0]
|
|
21
|
+
if isinstance(mapping_value, (set, list, tuple)) and isinstance(list(mapping_value)[0], Pos):
|
|
22
|
+
self.tap_mapping: dict[Pos, set[Pos]] = {pos: set(random_mapping[pos]) for pos in get_all_pos(self.V, self.H)}
|
|
23
|
+
elif isinstance(mapping_value, (set, list, tuple)) and isinstance(list(mapping_value)[0], str): # strings like "L", "UR", etc.
|
|
24
|
+
def _to_pos(pos: Pos, s: str) -> Pos:
|
|
25
|
+
d = {'L': Direction.LEFT, 'R': Direction.RIGHT, 'U': Direction.UP, 'D': Direction.DOWN}[s[0]]
|
|
26
|
+
r = get_next_pos(pos, d)
|
|
27
|
+
if len(s) == 1:
|
|
28
|
+
return r
|
|
29
|
+
else:
|
|
30
|
+
return _to_pos(r, s[1:])
|
|
31
|
+
self.tap_mapping: dict[Pos, set[Pos]] = {pos: set(_to_pos(pos, s) for s in random_mapping[pos]) for pos in get_all_pos(self.V, self.H)}
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f'invalid random_mapping: {random_mapping}')
|
|
34
|
+
for k, v in self.tap_mapping.items():
|
|
35
|
+
if k not in v:
|
|
36
|
+
v.add(k)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
self.model = cp_model.CpModel()
|
|
40
|
+
self.model_vars: dict[Pos, cp_model.IntVar] = {}
|
|
41
|
+
|
|
42
|
+
self.create_vars()
|
|
43
|
+
self.add_all_constraints()
|
|
44
|
+
|
|
45
|
+
def create_vars(self):
|
|
46
|
+
for pos in get_all_pos(self.V, self.H):
|
|
47
|
+
self.model_vars[pos] = self.model.NewBoolVar(f'tap:{pos}')
|
|
48
|
+
|
|
49
|
+
def add_all_constraints(self):
|
|
50
|
+
for pos in get_all_pos(self.V, self.H):
|
|
51
|
+
# the state of a position is its starting state + if it is tapped + if any pos pointing to it is tapped
|
|
52
|
+
c = get_char(self.board, pos)
|
|
53
|
+
pos_that_will_turn_me = [k for k,v in self.tap_mapping.items() if pos in v]
|
|
54
|
+
literals = [self.model_vars[p] for p in pos_that_will_turn_me]
|
|
55
|
+
if c == 'W': # if started as white then needs an even number of taps while xor checks for odd number
|
|
56
|
+
literals.append(self.model.NewConstant(True))
|
|
57
|
+
elif c == 'B':
|
|
58
|
+
pass
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError(f'invalid character: {c}')
|
|
61
|
+
self.model.AddBoolXOr(literals)
|
|
62
|
+
|
|
63
|
+
def solve_and_print(self, verbose: bool = True):
|
|
64
|
+
def board_to_solution(board: Board, solver: cp_model.CpSolverSolutionCallback) -> SingleSolution:
|
|
65
|
+
assignment: dict[Pos, int] = {}
|
|
66
|
+
for pos, var in board.model_vars.items():
|
|
67
|
+
assignment[pos] = solver.Value(var)
|
|
68
|
+
return SingleSolution(assignment=assignment)
|
|
69
|
+
def callback(single_res: SingleSolution):
|
|
70
|
+
print("Solution found")
|
|
71
|
+
res = np.full((self.V, self.H), ' ', dtype=object)
|
|
72
|
+
for pos in get_all_pos(self.V, self.H):
|
|
73
|
+
c = get_char(self.board, pos)
|
|
74
|
+
c = 'T' if single_res.assignment[pos] == 1 else ' '
|
|
75
|
+
set_char(res, pos, c)
|
|
76
|
+
print(res)
|
|
77
|
+
return generic_solve_all(self, board_to_solution, callback=callback if verbose else None, verbose=verbose)
|
|
@@ -44,7 +44,7 @@ def get_valid_translations(shape: Shape, board: np.array) -> set[Pos]:
|
|
|
44
44
|
if c != ' ' and c != str(shape_borders[i]): # there is a clue and it doesn't match my translated shape, skip
|
|
45
45
|
break
|
|
46
46
|
else:
|
|
47
|
-
yield
|
|
47
|
+
yield frozenset(get_pos(x=p[0], y=p[1]) for p in body)
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from puzzle_solver import flip_solver as solver
|
|
4
|
+
from puzzle_solver.core.utils import Pos, get_pos
|
|
5
|
+
|
|
6
|
+
def test_toy():
|
|
7
|
+
board = np.array([
|
|
8
|
+
['W', 'B', 'B'],
|
|
9
|
+
['B', 'B', 'W'],
|
|
10
|
+
['B', 'B', 'B'],
|
|
11
|
+
])
|
|
12
|
+
binst = solver.Board(board=board)
|
|
13
|
+
solutions = binst.solve_and_print()
|
|
14
|
+
assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
|
|
15
|
+
solution = solutions[0].assignment
|
|
16
|
+
ground = np.array([
|
|
17
|
+
['T', ' ', ' '],
|
|
18
|
+
['T', ' ', 'T'],
|
|
19
|
+
['T', 'T', 'T'],
|
|
20
|
+
])
|
|
21
|
+
ground_assignment = {get_pos(x=x, y=y): 1 if ground[y][x] == 'T' else 0 for x in range(ground.shape[1]) for y in range(ground.shape[0])}
|
|
22
|
+
assert set(solution.keys()) == set(ground_assignment.keys()), f'solution keys != ground assignment keys, {set(solution.keys()) - set(ground_assignment.keys())} \n\n\n{solution} \n\n\n{ground_assignment}'
|
|
23
|
+
for pos in solution.keys():
|
|
24
|
+
assert solution[pos] == ground_assignment[pos], f'solution[{pos}] != ground_assignment[{pos}], {solution[pos]} != {ground_assignment[pos]}'
|
|
25
|
+
|
|
26
|
+
def test_toy_random():
|
|
27
|
+
# 3 X 3 RANDOM
|
|
28
|
+
# https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/flip.html
|
|
29
|
+
board = np.array([
|
|
30
|
+
['B', 'W', 'B'],
|
|
31
|
+
['W', 'W', 'W'],
|
|
32
|
+
['W', 'B', 'W'],
|
|
33
|
+
])
|
|
34
|
+
random_mapping = {
|
|
35
|
+
Pos(x=0, y=0): ['R', 'D'],
|
|
36
|
+
Pos(x=1, y=0): ['L', 'D', 'DR'],
|
|
37
|
+
Pos(x=2, y=0): ['L', 'D'],
|
|
38
|
+
|
|
39
|
+
Pos(x=0, y=1): ['U', 'R', 'D'],
|
|
40
|
+
Pos(x=1, y=1): ['U', 'UL', 'UR'],
|
|
41
|
+
Pos(x=2, y=1): ['U', 'D', 'DL'],
|
|
42
|
+
|
|
43
|
+
Pos(x=0, y=2): ['U', 'R', 'UR'],
|
|
44
|
+
Pos(x=1, y=2): ['L', 'R', 'UL'],
|
|
45
|
+
Pos(x=2, y=2): ['L', 'U'],
|
|
46
|
+
}
|
|
47
|
+
binst = solver.Board(board=board, random_mapping=random_mapping)
|
|
48
|
+
solutions = binst.solve_and_print()
|
|
49
|
+
assert len(solutions) == 2
|
|
50
|
+
|
|
51
|
+
def test_ground():
|
|
52
|
+
# 7 x 7 NORMAL
|
|
53
|
+
# https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/flip.html#7x7c%23786091446619314
|
|
54
|
+
board = np.array([
|
|
55
|
+
['B', 'W', 'W', 'W', 'W', 'W', 'W'],
|
|
56
|
+
['B', 'B', 'W', 'W', 'W', 'B', 'B'],
|
|
57
|
+
['W', 'B', 'W', 'W', 'B', 'B', 'W'],
|
|
58
|
+
['B', 'B', 'B', 'W', 'W', 'B', 'W'],
|
|
59
|
+
['W', 'W', 'B', 'B', 'W', 'B', 'W'],
|
|
60
|
+
['B', 'W', 'B', 'B', 'W', 'W', 'W'],
|
|
61
|
+
['B', 'W', 'B', 'W', 'W', 'B', 'B'],
|
|
62
|
+
])
|
|
63
|
+
binst = solver.Board(board=board)
|
|
64
|
+
solutions = binst.solve_and_print()
|
|
65
|
+
assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
|
|
66
|
+
solution = solutions[0].assignment
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
test_toy()
|
|
72
|
+
test_toy_random()
|
|
73
|
+
test_ground()
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from ortools.sat.python import cp_model
|
|
3
|
-
from ortools.sat.python.cp_model import LinearExpr as lxp
|
|
4
|
-
|
|
5
|
-
from puzzle_solver.core.utils import Pos, get_all_pos, set_char, get_char, get_neighbors8
|
|
6
|
-
from puzzle_solver.core.utils_ortools import generic_solve_all, SingleSolution
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Board:
|
|
10
|
-
def __init__(self, board: np.array):
|
|
11
|
-
assert board.ndim == 2, f'board must be 2d, got {board.ndim}'
|
|
12
|
-
assert board.shape[0] == board.shape[1], 'board must be square'
|
|
13
|
-
assert all((c.item() == ' ') or str(c.item()).isdecimal() for c in np.nditer(board)), 'board must contain only space or digits'
|
|
14
|
-
self.board = board
|
|
15
|
-
self.N = board.shape[0]
|
|
16
|
-
self.model = cp_model.CpModel()
|
|
17
|
-
self.model_vars: dict[Pos, cp_model.IntVar] = {}
|
|
18
|
-
|
|
19
|
-
self.create_vars()
|
|
20
|
-
self.add_all_constraints()
|
|
21
|
-
|
|
22
|
-
def create_vars(self):
|
|
23
|
-
for pos in get_all_pos(self.N):
|
|
24
|
-
self.model_vars[pos] = self.model.NewBoolVar(f'{pos}')
|
|
25
|
-
|
|
26
|
-
def add_all_constraints(self):
|
|
27
|
-
for pos in get_all_pos(self.N):
|
|
28
|
-
c = get_char(self.board, pos)
|
|
29
|
-
if not str(c).isdecimal():
|
|
30
|
-
continue
|
|
31
|
-
neighbour_vars = [self.model_vars[p] for p in get_neighbors8(pos, self.N, include_self=True)]
|
|
32
|
-
self.model.Add(lxp.sum(neighbour_vars) == int(c))
|
|
33
|
-
|
|
34
|
-
def solve_and_print(self, verbose: bool = True):
|
|
35
|
-
def board_to_solution(board: Board, solver: cp_model.CpSolverSolutionCallback) -> SingleSolution:
|
|
36
|
-
assignment: dict[Pos, int] = {}
|
|
37
|
-
for pos, var in board.model_vars.items():
|
|
38
|
-
assignment[pos] = solver.Value(var)
|
|
39
|
-
return SingleSolution(assignment=assignment)
|
|
40
|
-
def callback(single_res: SingleSolution):
|
|
41
|
-
print("Solution found")
|
|
42
|
-
res = np.full((self.N, self.N), ' ', dtype=object)
|
|
43
|
-
for pos in get_all_pos(self.N):
|
|
44
|
-
c = get_char(self.board, pos)
|
|
45
|
-
c = 'B' if single_res.assignment[pos] == 1 else ' '
|
|
46
|
-
set_char(res, pos, c)
|
|
47
|
-
print(res)
|
|
48
|
-
return generic_solve_all(self, board_to_solution, callback=callback if verbose else None, verbose=verbose)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/core/utils_ortools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/guess/guess.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/tsp.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/keen/keen.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/lits/lits.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/map/map.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/mosaic/mosaic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/pearl/pearl.py
RENAMED
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/range/range.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slant/slant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/sudoku/sudoku.py
RENAMED
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tents/tents.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/towers/towers.py
RENAMED
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tracks/tracks.py
RENAMED
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/undead/undead.py
RENAMED
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/unruly/unruly.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/utils/visualizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|