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.

Files changed (109) hide show
  1. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/PKG-INFO +73 -3
  2. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/README.md +73 -3
  3. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/PKG-INFO +73 -3
  4. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/__init__.py +2 -2
  5. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/core/utils.py +3 -1
  6. multi_puzzle_solver-0.9.26/src/puzzle_solver/puzzles/flip/flip.py +77 -0
  7. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/palisade/palisade.py +1 -1
  8. multi_puzzle_solver-0.9.26/tests/test_flip.py +73 -0
  9. multi_puzzle_solver-0.9.25/src/puzzle_solver/puzzles/flip/flip.py +0 -48
  10. multi_puzzle_solver-0.9.25/tests/test_flip.py +0 -2
  11. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/pyproject.toml +0 -0
  12. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/setup.cfg +0 -0
  13. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/SOURCES.txt +0 -0
  14. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/dependency_links.txt +0 -0
  15. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/requires.txt +0 -0
  16. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/multi_puzzle_solver.egg-info/top_level.txt +0 -0
  17. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/core/utils_ortools.py +0 -0
  18. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/aquarium/aquarium.py +0 -0
  19. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/battleships/battleships.py +0 -0
  20. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/binairo/binairo.py +0 -0
  21. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/black_box/black_box.py +0 -0
  22. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/bridges/bridges.py +0 -0
  23. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_melee.py +0 -0
  24. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_range.py +0 -0
  25. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/chess_range/chess_solo.py +0 -0
  26. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/dominosa/dominosa.py +0 -0
  27. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/filling/filling.py +0 -0
  28. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/galaxies/galaxies.py +0 -0
  29. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +0 -0
  30. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/guess/guess.py +0 -0
  31. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/inertia.py +0 -0
  32. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/parse_map/parse_map.py +0 -0
  33. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/inertia/tsp.py +0 -0
  34. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/kakurasu/kakurasu.py +0 -0
  35. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/keen/keen.py +0 -0
  36. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/light_up/light_up.py +0 -0
  37. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/lits/lits.py +0 -0
  38. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/magnets/magnets.py +0 -0
  39. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/map/map.py +0 -0
  40. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/minesweeper/minesweeper.py +0 -0
  41. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/mosaic/mosaic.py +0 -0
  42. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/nonograms/nonograms.py +0 -0
  43. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/norinori/norinori.py +0 -0
  44. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/pearl/pearl.py +0 -0
  45. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/range/range.py +0 -0
  46. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/rectangles/rectangles.py +0 -0
  47. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/signpost/signpost.py +0 -0
  48. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/singles/singles.py +0 -0
  49. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slant/parse_map/parse_map.py +0 -0
  50. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slant/slant.py +0 -0
  51. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/slitherlink/slitherlink.py +0 -0
  52. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/star_battle/star_battle.py +0 -0
  53. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +0 -0
  54. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/stitches/parse_map/parse_map.py +0 -0
  55. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/stitches/stitches.py +0 -0
  56. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/sudoku/sudoku.py +0 -0
  57. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tents/tents.py +0 -0
  58. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/thermometers/thermometers.py +0 -0
  59. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/towers/towers.py +0 -0
  60. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/tracks/tracks.py +0 -0
  61. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/undead/undead.py +0 -0
  62. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/unequal/unequal.py +0 -0
  63. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/unruly/unruly.py +0 -0
  64. {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
  65. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/puzzles/yin_yang/yin_yang.py +0 -0
  66. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/src/puzzle_solver/utils/visualizer.py +0 -0
  67. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_aquarium.py +0 -0
  68. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_battleships.py +0 -0
  69. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_binairo.py +0 -0
  70. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_black_box.py +0 -0
  71. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_bridges.py +0 -0
  72. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_melee.py +0 -0
  73. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_range.py +0 -0
  74. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_chess_solo.py +0 -0
  75. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_dominosa.py +0 -0
  76. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_filling.py +0 -0
  77. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_galaxies.py +0 -0
  78. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_guess.py +0 -0
  79. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_inertia.py +0 -0
  80. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_kakurasu.py +0 -0
  81. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_keen.py +0 -0
  82. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_light_up.py +0 -0
  83. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_lits.py +0 -0
  84. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_magnets.py +0 -0
  85. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_map.py +0 -0
  86. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_minesweeper.py +0 -0
  87. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_mosaic.py +0 -0
  88. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_nonograms.py +0 -0
  89. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_norinori.py +0 -0
  90. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_palisade.py +0 -0
  91. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_pearl.py +0 -0
  92. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_range.py +0 -0
  93. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_rectangles.py +0 -0
  94. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_signpost.py +0 -0
  95. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_singles.py +0 -0
  96. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_slant.py +0 -0
  97. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_slitherlink.py +0 -0
  98. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_star_battle.py +0 -0
  99. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_stitches.py +0 -0
  100. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_sudoku.py +0 -0
  101. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_tents.py +0 -0
  102. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_thermometers.py +0 -0
  103. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_towers.py +0 -0
  104. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_tracks.py +0 -0
  105. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_undead.py +0 -0
  106. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_unequal.py +0 -0
  107. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_unruly.py +0 -0
  108. {multi_puzzle_solver-0.9.25 → multi_puzzle_solver-0.9.26}/tests/test_utils.py +0 -0
  109. {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.25
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**][42]
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**][42]
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi-puzzle-solver
3
- Version: 0.9.25
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**][42]
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
- # from puzzle_solver.puzzles.flip import flip as flip_solver
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.25'
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 tuple(get_pos(x=p[0], y=p[1]) for p in body)
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)
@@ -1,2 +0,0 @@
1
-
2
-