multi-puzzle-solver 1.0.2__tar.gz → 1.0.3__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 (127) hide show
  1. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/PKG-INFO +81 -1
  2. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/README.md +81 -1
  3. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/multi_puzzle_solver.egg-info/PKG-INFO +81 -1
  4. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/multi_puzzle_solver.egg-info/SOURCES.txt +2 -0
  5. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/__init__.py +3 -1
  6. multi_puzzle_solver-1.0.3/src/puzzle_solver/puzzles/pipes/pipes.py +81 -0
  7. multi_puzzle_solver-1.0.3/tests/test_pipes.py +72 -0
  8. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/pyproject.toml +0 -0
  9. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/setup.cfg +0 -0
  10. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/multi_puzzle_solver.egg-info/dependency_links.txt +0 -0
  11. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/multi_puzzle_solver.egg-info/requires.txt +0 -0
  12. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/multi_puzzle_solver.egg-info/top_level.txt +0 -0
  13. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/core/utils.py +0 -0
  14. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/core/utils_ortools.py +0 -0
  15. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/core/utils_visualizer.py +0 -0
  16. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/aquarium/aquarium.py +0 -0
  17. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/battleships/battleships.py +0 -0
  18. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/binairo/binairo.py +0 -0
  19. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/binairo/binairo_plus.py +0 -0
  20. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/black_box/black_box.py +0 -0
  21. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/bridges/bridges.py +0 -0
  22. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/chess_range/chess_melee.py +0 -0
  23. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/chess_range/chess_range.py +0 -0
  24. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/chess_range/chess_solo.py +0 -0
  25. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/dominosa/dominosa.py +0 -0
  26. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/filling/filling.py +0 -0
  27. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/flip/flip.py +0 -0
  28. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/flood_it/flood_it.py +0 -0
  29. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/flood_it/parse_map/parse_map.py +0 -0
  30. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/galaxies/galaxies.py +0 -0
  31. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +0 -0
  32. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/guess/guess.py +0 -0
  33. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/heyawake/heyawake.py +0 -0
  34. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/inertia/inertia.py +0 -0
  35. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/inertia/parse_map/parse_map.py +0 -0
  36. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/inertia/tsp.py +0 -0
  37. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/kakurasu/kakurasu.py +0 -0
  38. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/kakuro/kakuro.py +0 -0
  39. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/keen/keen.py +0 -0
  40. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/light_up/light_up.py +0 -0
  41. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/lits/lits.py +0 -0
  42. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/magnets/magnets.py +0 -0
  43. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/map/map.py +0 -0
  44. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/minesweeper/minesweeper.py +0 -0
  45. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/mosaic/mosaic.py +0 -0
  46. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/nonograms/nonograms.py +0 -0
  47. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/norinori/norinori.py +0 -0
  48. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/nurikabe/nurikabe.py +0 -0
  49. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/palisade/palisade.py +0 -0
  50. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/pearl/pearl.py +0 -0
  51. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/range/range.py +0 -0
  52. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/rectangles/rectangles.py +0 -0
  53. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/shakashaka/shakashaka.py +0 -0
  54. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/shingoki/shingoki.py +0 -0
  55. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/signpost/signpost.py +0 -0
  56. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/singles/singles.py +0 -0
  57. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/slant/parse_map/parse_map.py +0 -0
  58. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/slant/slant.py +0 -0
  59. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/slitherlink/slitherlink.py +0 -0
  60. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/star_battle/star_battle.py +0 -0
  61. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +0 -0
  62. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/stitches/parse_map/parse_map.py +0 -0
  63. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/stitches/stitches.py +0 -0
  64. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/sudoku/sudoku.py +0 -0
  65. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/tapa/tapa.py +0 -0
  66. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/tents/tents.py +0 -0
  67. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/thermometers/thermometers.py +0 -0
  68. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/towers/towers.py +0 -0
  69. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/tracks/tracks.py +0 -0
  70. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/undead/undead.py +0 -0
  71. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/unequal/unequal.py +0 -0
  72. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/unruly/unruly.py +0 -0
  73. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py +0 -0
  74. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/puzzles/yin_yang/yin_yang.py +0 -0
  75. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/src/puzzle_solver/utils/visualizer.py +0 -0
  76. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_aquarium.py +0 -0
  77. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_battleships.py +0 -0
  78. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_binairo.py +0 -0
  79. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_binairo_plus.py +0 -0
  80. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_black_box.py +0 -0
  81. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_bridges.py +0 -0
  82. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_chess_melee.py +0 -0
  83. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_chess_range.py +0 -0
  84. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_chess_solo.py +0 -0
  85. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_dominosa.py +0 -0
  86. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_filling.py +0 -0
  87. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_flip.py +0 -0
  88. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_flood_it.py +0 -0
  89. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_galaxies.py +0 -0
  90. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_guess.py +0 -0
  91. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_heyawake.py +0 -0
  92. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_inertia.py +0 -0
  93. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_kakurasu.py +0 -0
  94. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_kakuro.py +0 -0
  95. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_keen.py +0 -0
  96. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_light_up.py +0 -0
  97. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_lits.py +0 -0
  98. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_magnets.py +0 -0
  99. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_map.py +0 -0
  100. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_minesweeper.py +0 -0
  101. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_mosaic.py +0 -0
  102. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_nonograms.py +0 -0
  103. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_norinori.py +0 -0
  104. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_nurikabe.py +0 -0
  105. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_palisade.py +0 -0
  106. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_pearl.py +0 -0
  107. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_range.py +0 -0
  108. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_rectangles.py +0 -0
  109. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_shakashaka.py +0 -0
  110. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_shingoki.py +0 -0
  111. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_signpost.py +0 -0
  112. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_singles.py +0 -0
  113. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_slant.py +0 -0
  114. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_slitherlink.py +0 -0
  115. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_star_battle.py +0 -0
  116. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_stitches.py +0 -0
  117. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_sudoku.py +0 -0
  118. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_tapa.py +0 -0
  119. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_tents.py +0 -0
  120. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_thermometers.py +0 -0
  121. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_towers.py +0 -0
  122. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_tracks.py +0 -0
  123. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_undead.py +0 -0
  124. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_unequal.py +0 -0
  125. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_unruly.py +0 -0
  126. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/tests/test_utils.py +0 -0
  127. {multi_puzzle_solver-1.0.2 → multi_puzzle_solver-1.0.3}/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: 1.0.2
3
+ Version: 1.0.3
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
@@ -390,6 +390,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
390
390
  <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flood_it_unsolved.png" alt="Flood It" width="140">
391
391
  </a>
392
392
  </td>
393
+ <td align="center">
394
+ <a href="#pipes-puzzle-type-55"><b>Pipes</b><br><br>
395
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes" width="140">
396
+ </a>
397
+ </td>
393
398
  </tr>
394
399
  </table>
395
400
 
@@ -459,6 +464,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
459
464
  - [Sudoku Jigsaw (Puzzle Type #52)](#sudoku-jigsaw-puzzle-type-52)
460
465
  - [Sudoku Killer (Puzzle Type #53)](#sudoku-killer-puzzle-type-53)
461
466
  - [Flood It (Puzzle Type #54)](#flood-it-puzzle-type-54)
467
+ - [Pipes (Puzzle Type #55)](#pipes-puzzle-type-55)
462
468
  - [Why SAT / CP-SAT?](#why-sat--cp-sat)
463
469
  - [Testing](#testing)
464
470
  - [Contributing](#contributing)
@@ -5074,6 +5080,79 @@ Note that the solved solution on the bottom left says that only 18 moves were us
5074
5080
 
5075
5081
  ---
5076
5082
 
5083
+ ## Pipes (Puzzle Type #55)
5084
+
5085
+ Also called "Net"
5086
+
5087
+ * [**Play online 1**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/net.html)
5088
+
5089
+ * [**Play online 2**](https://www.puzzle-pipes.com/)
5090
+
5091
+ * [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/net.html#net)
5092
+
5093
+ * [**Solver Code**][55]
5094
+
5095
+ <details>
5096
+ <summary><strong>Rules</strong></summary>
5097
+
5098
+ You are given a grid of cells where each cell has 1, 2, 3, or 4 connections to its neighbors. Each cell can be freely rotated in multiple of 90 degrees, thus your can rotate the cells to be one of four possible states.
5099
+
5100
+ The goal is to create a single fully connected graph where each cell's connection must be towards another cell's connection. No loose ends or loops are allowed.
5101
+
5102
+ </details>
5103
+
5104
+ **Unsolved puzzle**
5105
+
5106
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_unsolved.png" alt="Pipes unsolved" width="500">
5107
+
5108
+ Code to utilize this package and solve the puzzle:
5109
+
5110
+ (Note: cells with 1 or 3 active connections only have 1 unique orientation under rotational symmetry. However, cells with 2 active connections can be either a straight line (2I) or curved line (2L))
5111
+
5112
+ ```python
5113
+ import numpy as np
5114
+ from puzzle_solver import pipes_solver as solver
5115
+ board=np.array([
5116
+ [ '1 ', '3 ', '3 ', '3 ', '1 ', '1 ', '2L', '2L', '2I', '1 ' ],
5117
+ [ '1 ', '1 ', '1 ', '3 ', '2I', '1 ', '2I', '3 ', '2I', '1 ' ],
5118
+ [ '2I', '1 ', '1 ', '3 ', '2L', '1 ', '3 ', '2I', '1 ', '1 ' ],
5119
+ [ '2I', '2I', '1 ', '3 ', '3 ', '3 ', '2L', '3 ', '3 ', '2L' ],
5120
+ [ '3 ', '3 ', '2I', '3 ', '1 ', '3 ', '2I', '2L', '1 ', '2L' ],
5121
+ [ '1 ', '1 ', '3 ', '2I', '3 ', '2L', '1 ', '1 ', '2L', '2L' ],
5122
+ [ '1 ', '1 ', '3 ', '1 ', '1 ', '1 ', '3 ', '3 ', '3 ', '2L' ],
5123
+ [ '3 ', '2I', '3 ', '3 ', '2L', '3 ', '3 ', '2I', '2L', '1 ' ],
5124
+ [ '1 ', '1 ', '3 ', '3 ', '3 ', '3 ', '1 ', '2L', '3 ', '2L' ],
5125
+ [ '1 ', '2I', '3 ', '2I', '1 ', '1 ', '1 ', '3 ', '1 ', '1 ' ],
5126
+ ])
5127
+ binst = solver.Board(board=board)
5128
+ solutions = binst.solve_and_print()
5129
+ ```
5130
+
5131
+ **Script Output**
5132
+
5133
+ ```python
5134
+ Solution found
5135
+ [['R' 'DLR' 'DLR' 'DLR' 'L' 'R' 'DL' 'DR' 'LR' 'L']
5136
+ ['D' 'U' 'U' 'UDR' 'LR' 'L' 'UD' 'UDR' 'LR' 'L']
5137
+ ['UD' 'D' 'R' 'ULR' 'DL' 'R' 'UDL' 'UD' 'D' 'D']
5138
+ ['UD' 'UD' 'R' 'DLR' 'ULR' 'DLR' 'UL' 'UDR' 'ULR' 'UL']
5139
+ ['UDR' 'ULR' 'LR' 'ULR' 'L' 'UDR' 'LR' 'UL' 'R' 'DL']
5140
+ ['U' 'R' 'DLR' 'LR' 'DLR' 'UL' 'D' 'D' 'DR' 'UL']
5141
+ ['D' 'R' 'UDL' 'D' 'U' 'D' 'UDR' 'ULR' 'ULR' 'DL']
5142
+ ['UDR' 'LR' 'ULR' 'UDL' 'DR' 'ULR' 'ULR' 'LR' 'DL' 'U']
5143
+ ['U' 'R' 'DLR' 'ULR' 'ULR' 'DLR' 'L' 'DR' 'ULR' 'DL']
5144
+ ['R' 'LR' 'ULR' 'LR' 'L' 'U' 'R' 'ULR' 'L' 'U']]
5145
+ Solutions found: 1
5146
+ status: OPTIMAL
5147
+ Time taken: 5.65 seconds
5148
+ ```
5149
+
5150
+ **Solved puzzle**
5151
+
5152
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes solved" width="500">
5153
+
5154
+ ---
5155
+
5077
5156
  ---
5078
5157
 
5079
5158
  ## Why SAT / CP-SAT?
@@ -5179,3 +5258,4 @@ Issues and PRs welcome!
5179
5258
  [52]: 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"
5180
5259
  [53]: 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"
5181
5260
  [54]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flood_it "puzzle_solver/src/puzzle_solver/puzzles/flood_it at master · Ar-Kareem/puzzle_solver · GitHub"
5261
+ [55]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/pipes "puzzle_solver/src/puzzle_solver/puzzles/pipes at master · Ar-Kareem/puzzle_solver · GitHub"
@@ -364,6 +364,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
364
364
  <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flood_it_unsolved.png" alt="Flood It" width="140">
365
365
  </a>
366
366
  </td>
367
+ <td align="center">
368
+ <a href="#pipes-puzzle-type-55"><b>Pipes</b><br><br>
369
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes" width="140">
370
+ </a>
371
+ </td>
367
372
  </tr>
368
373
  </table>
369
374
 
@@ -433,6 +438,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
433
438
  - [Sudoku Jigsaw (Puzzle Type #52)](#sudoku-jigsaw-puzzle-type-52)
434
439
  - [Sudoku Killer (Puzzle Type #53)](#sudoku-killer-puzzle-type-53)
435
440
  - [Flood It (Puzzle Type #54)](#flood-it-puzzle-type-54)
441
+ - [Pipes (Puzzle Type #55)](#pipes-puzzle-type-55)
436
442
  - [Why SAT / CP-SAT?](#why-sat--cp-sat)
437
443
  - [Testing](#testing)
438
444
  - [Contributing](#contributing)
@@ -5048,6 +5054,79 @@ Note that the solved solution on the bottom left says that only 18 moves were us
5048
5054
 
5049
5055
  ---
5050
5056
 
5057
+ ## Pipes (Puzzle Type #55)
5058
+
5059
+ Also called "Net"
5060
+
5061
+ * [**Play online 1**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/net.html)
5062
+
5063
+ * [**Play online 2**](https://www.puzzle-pipes.com/)
5064
+
5065
+ * [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/net.html#net)
5066
+
5067
+ * [**Solver Code**][55]
5068
+
5069
+ <details>
5070
+ <summary><strong>Rules</strong></summary>
5071
+
5072
+ You are given a grid of cells where each cell has 1, 2, 3, or 4 connections to its neighbors. Each cell can be freely rotated in multiple of 90 degrees, thus your can rotate the cells to be one of four possible states.
5073
+
5074
+ The goal is to create a single fully connected graph where each cell's connection must be towards another cell's connection. No loose ends or loops are allowed.
5075
+
5076
+ </details>
5077
+
5078
+ **Unsolved puzzle**
5079
+
5080
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_unsolved.png" alt="Pipes unsolved" width="500">
5081
+
5082
+ Code to utilize this package and solve the puzzle:
5083
+
5084
+ (Note: cells with 1 or 3 active connections only have 1 unique orientation under rotational symmetry. However, cells with 2 active connections can be either a straight line (2I) or curved line (2L))
5085
+
5086
+ ```python
5087
+ import numpy as np
5088
+ from puzzle_solver import pipes_solver as solver
5089
+ board=np.array([
5090
+ [ '1 ', '3 ', '3 ', '3 ', '1 ', '1 ', '2L', '2L', '2I', '1 ' ],
5091
+ [ '1 ', '1 ', '1 ', '3 ', '2I', '1 ', '2I', '3 ', '2I', '1 ' ],
5092
+ [ '2I', '1 ', '1 ', '3 ', '2L', '1 ', '3 ', '2I', '1 ', '1 ' ],
5093
+ [ '2I', '2I', '1 ', '3 ', '3 ', '3 ', '2L', '3 ', '3 ', '2L' ],
5094
+ [ '3 ', '3 ', '2I', '3 ', '1 ', '3 ', '2I', '2L', '1 ', '2L' ],
5095
+ [ '1 ', '1 ', '3 ', '2I', '3 ', '2L', '1 ', '1 ', '2L', '2L' ],
5096
+ [ '1 ', '1 ', '3 ', '1 ', '1 ', '1 ', '3 ', '3 ', '3 ', '2L' ],
5097
+ [ '3 ', '2I', '3 ', '3 ', '2L', '3 ', '3 ', '2I', '2L', '1 ' ],
5098
+ [ '1 ', '1 ', '3 ', '3 ', '3 ', '3 ', '1 ', '2L', '3 ', '2L' ],
5099
+ [ '1 ', '2I', '3 ', '2I', '1 ', '1 ', '1 ', '3 ', '1 ', '1 ' ],
5100
+ ])
5101
+ binst = solver.Board(board=board)
5102
+ solutions = binst.solve_and_print()
5103
+ ```
5104
+
5105
+ **Script Output**
5106
+
5107
+ ```python
5108
+ Solution found
5109
+ [['R' 'DLR' 'DLR' 'DLR' 'L' 'R' 'DL' 'DR' 'LR' 'L']
5110
+ ['D' 'U' 'U' 'UDR' 'LR' 'L' 'UD' 'UDR' 'LR' 'L']
5111
+ ['UD' 'D' 'R' 'ULR' 'DL' 'R' 'UDL' 'UD' 'D' 'D']
5112
+ ['UD' 'UD' 'R' 'DLR' 'ULR' 'DLR' 'UL' 'UDR' 'ULR' 'UL']
5113
+ ['UDR' 'ULR' 'LR' 'ULR' 'L' 'UDR' 'LR' 'UL' 'R' 'DL']
5114
+ ['U' 'R' 'DLR' 'LR' 'DLR' 'UL' 'D' 'D' 'DR' 'UL']
5115
+ ['D' 'R' 'UDL' 'D' 'U' 'D' 'UDR' 'ULR' 'ULR' 'DL']
5116
+ ['UDR' 'LR' 'ULR' 'UDL' 'DR' 'ULR' 'ULR' 'LR' 'DL' 'U']
5117
+ ['U' 'R' 'DLR' 'ULR' 'ULR' 'DLR' 'L' 'DR' 'ULR' 'DL']
5118
+ ['R' 'LR' 'ULR' 'LR' 'L' 'U' 'R' 'ULR' 'L' 'U']]
5119
+ Solutions found: 1
5120
+ status: OPTIMAL
5121
+ Time taken: 5.65 seconds
5122
+ ```
5123
+
5124
+ **Solved puzzle**
5125
+
5126
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes solved" width="500">
5127
+
5128
+ ---
5129
+
5051
5130
  ---
5052
5131
 
5053
5132
  ## Why SAT / CP-SAT?
@@ -5152,4 +5231,5 @@ Issues and PRs welcome!
5152
5231
  [51]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/kakuro "puzzle_solver/src/puzzle_solver/puzzles/kakuro at master · Ar-Kareem/puzzle_solver · GitHub"
5153
5232
  [52]: 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"
5154
5233
  [53]: 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"
5155
- [54]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flood_it "puzzle_solver/src/puzzle_solver/puzzles/flood_it at master · Ar-Kareem/puzzle_solver · GitHub"
5234
+ [54]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flood_it "puzzle_solver/src/puzzle_solver/puzzles/flood_it at master · Ar-Kareem/puzzle_solver · GitHub"
5235
+ [55]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/pipes "puzzle_solver/src/puzzle_solver/puzzles/pipes 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: 1.0.2
3
+ Version: 1.0.3
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
@@ -390,6 +390,11 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
390
390
  <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/flood_it_unsolved.png" alt="Flood It" width="140">
391
391
  </a>
392
392
  </td>
393
+ <td align="center">
394
+ <a href="#pipes-puzzle-type-55"><b>Pipes</b><br><br>
395
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes" width="140">
396
+ </a>
397
+ </td>
393
398
  </tr>
394
399
  </table>
395
400
 
@@ -459,6 +464,7 @@ These are all the puzzles that are implemented in this repo. <br> Click on any o
459
464
  - [Sudoku Jigsaw (Puzzle Type #52)](#sudoku-jigsaw-puzzle-type-52)
460
465
  - [Sudoku Killer (Puzzle Type #53)](#sudoku-killer-puzzle-type-53)
461
466
  - [Flood It (Puzzle Type #54)](#flood-it-puzzle-type-54)
467
+ - [Pipes (Puzzle Type #55)](#pipes-puzzle-type-55)
462
468
  - [Why SAT / CP-SAT?](#why-sat--cp-sat)
463
469
  - [Testing](#testing)
464
470
  - [Contributing](#contributing)
@@ -5074,6 +5080,79 @@ Note that the solved solution on the bottom left says that only 18 moves were us
5074
5080
 
5075
5081
  ---
5076
5082
 
5083
+ ## Pipes (Puzzle Type #55)
5084
+
5085
+ Also called "Net"
5086
+
5087
+ * [**Play online 1**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/net.html)
5088
+
5089
+ * [**Play online 2**](https://www.puzzle-pipes.com/)
5090
+
5091
+ * [**Instructions**](https://www.chiark.greenend.org.uk/~sgtatham/puzzles/doc/net.html#net)
5092
+
5093
+ * [**Solver Code**][55]
5094
+
5095
+ <details>
5096
+ <summary><strong>Rules</strong></summary>
5097
+
5098
+ You are given a grid of cells where each cell has 1, 2, 3, or 4 connections to its neighbors. Each cell can be freely rotated in multiple of 90 degrees, thus your can rotate the cells to be one of four possible states.
5099
+
5100
+ The goal is to create a single fully connected graph where each cell's connection must be towards another cell's connection. No loose ends or loops are allowed.
5101
+
5102
+ </details>
5103
+
5104
+ **Unsolved puzzle**
5105
+
5106
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_unsolved.png" alt="Pipes unsolved" width="500">
5107
+
5108
+ Code to utilize this package and solve the puzzle:
5109
+
5110
+ (Note: cells with 1 or 3 active connections only have 1 unique orientation under rotational symmetry. However, cells with 2 active connections can be either a straight line (2I) or curved line (2L))
5111
+
5112
+ ```python
5113
+ import numpy as np
5114
+ from puzzle_solver import pipes_solver as solver
5115
+ board=np.array([
5116
+ [ '1 ', '3 ', '3 ', '3 ', '1 ', '1 ', '2L', '2L', '2I', '1 ' ],
5117
+ [ '1 ', '1 ', '1 ', '3 ', '2I', '1 ', '2I', '3 ', '2I', '1 ' ],
5118
+ [ '2I', '1 ', '1 ', '3 ', '2L', '1 ', '3 ', '2I', '1 ', '1 ' ],
5119
+ [ '2I', '2I', '1 ', '3 ', '3 ', '3 ', '2L', '3 ', '3 ', '2L' ],
5120
+ [ '3 ', '3 ', '2I', '3 ', '1 ', '3 ', '2I', '2L', '1 ', '2L' ],
5121
+ [ '1 ', '1 ', '3 ', '2I', '3 ', '2L', '1 ', '1 ', '2L', '2L' ],
5122
+ [ '1 ', '1 ', '3 ', '1 ', '1 ', '1 ', '3 ', '3 ', '3 ', '2L' ],
5123
+ [ '3 ', '2I', '3 ', '3 ', '2L', '3 ', '3 ', '2I', '2L', '1 ' ],
5124
+ [ '1 ', '1 ', '3 ', '3 ', '3 ', '3 ', '1 ', '2L', '3 ', '2L' ],
5125
+ [ '1 ', '2I', '3 ', '2I', '1 ', '1 ', '1 ', '3 ', '1 ', '1 ' ],
5126
+ ])
5127
+ binst = solver.Board(board=board)
5128
+ solutions = binst.solve_and_print()
5129
+ ```
5130
+
5131
+ **Script Output**
5132
+
5133
+ ```python
5134
+ Solution found
5135
+ [['R' 'DLR' 'DLR' 'DLR' 'L' 'R' 'DL' 'DR' 'LR' 'L']
5136
+ ['D' 'U' 'U' 'UDR' 'LR' 'L' 'UD' 'UDR' 'LR' 'L']
5137
+ ['UD' 'D' 'R' 'ULR' 'DL' 'R' 'UDL' 'UD' 'D' 'D']
5138
+ ['UD' 'UD' 'R' 'DLR' 'ULR' 'DLR' 'UL' 'UDR' 'ULR' 'UL']
5139
+ ['UDR' 'ULR' 'LR' 'ULR' 'L' 'UDR' 'LR' 'UL' 'R' 'DL']
5140
+ ['U' 'R' 'DLR' 'LR' 'DLR' 'UL' 'D' 'D' 'DR' 'UL']
5141
+ ['D' 'R' 'UDL' 'D' 'U' 'D' 'UDR' 'ULR' 'ULR' 'DL']
5142
+ ['UDR' 'LR' 'ULR' 'UDL' 'DR' 'ULR' 'ULR' 'LR' 'DL' 'U']
5143
+ ['U' 'R' 'DLR' 'ULR' 'ULR' 'DLR' 'L' 'DR' 'ULR' 'DL']
5144
+ ['R' 'LR' 'ULR' 'LR' 'L' 'U' 'R' 'ULR' 'L' 'U']]
5145
+ Solutions found: 1
5146
+ status: OPTIMAL
5147
+ Time taken: 5.65 seconds
5148
+ ```
5149
+
5150
+ **Solved puzzle**
5151
+
5152
+ <img src="https://raw.githubusercontent.com/Ar-Kareem/puzzle_solver/master/images/pipes_solved.png" alt="Pipes solved" width="500">
5153
+
5154
+ ---
5155
+
5077
5156
  ---
5078
5157
 
5079
5158
  ## Why SAT / CP-SAT?
@@ -5179,3 +5258,4 @@ Issues and PRs welcome!
5179
5258
  [52]: 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"
5180
5259
  [53]: 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"
5181
5260
  [54]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/flood_it "puzzle_solver/src/puzzle_solver/puzzles/flood_it at master · Ar-Kareem/puzzle_solver · GitHub"
5261
+ [55]: https://github.com/Ar-Kareem/puzzle_solver/tree/master/src/puzzle_solver/puzzles/pipes "puzzle_solver/src/puzzle_solver/puzzles/pipes at master · Ar-Kareem/puzzle_solver · GitHub"
@@ -44,6 +44,7 @@ src/puzzle_solver/puzzles/norinori/norinori.py
44
44
  src/puzzle_solver/puzzles/nurikabe/nurikabe.py
45
45
  src/puzzle_solver/puzzles/palisade/palisade.py
46
46
  src/puzzle_solver/puzzles/pearl/pearl.py
47
+ src/puzzle_solver/puzzles/pipes/pipes.py
47
48
  src/puzzle_solver/puzzles/range/range.py
48
49
  src/puzzle_solver/puzzles/rectangles/rectangles.py
49
50
  src/puzzle_solver/puzzles/shakashaka/shakashaka.py
@@ -100,6 +101,7 @@ tests/test_norinori.py
100
101
  tests/test_nurikabe.py
101
102
  tests/test_palisade.py
102
103
  tests/test_pearl.py
104
+ tests/test_pipes.py
103
105
  tests/test_range.py
104
106
  tests/test_rectangles.py
105
107
  tests/test_shakashaka.py
@@ -29,6 +29,7 @@ from puzzle_solver.puzzles.nurikabe import nurikabe as nurikabe_solver
29
29
  from puzzle_solver.puzzles.palisade import palisade as palisade_solver
30
30
  from puzzle_solver.puzzles.lits import lits as lits_solver
31
31
  from puzzle_solver.puzzles.pearl import pearl as pearl_solver
32
+ from puzzle_solver.puzzles.pipes import pipes as pipes_solver
32
33
  from puzzle_solver.puzzles.range import range as range_solver
33
34
  from puzzle_solver.puzzles.rectangles import rectangles as rectangles_solver
34
35
  from puzzle_solver.puzzles.shakashaka import shakashaka as shakashaka_solver
@@ -85,6 +86,7 @@ __all__ = [
85
86
  palisade_solver,
86
87
  lits_solver,
87
88
  pearl_solver,
89
+ pipes_solver,
88
90
  range_solver,
89
91
  rectangles_solver,
90
92
  shakashaka_solver,
@@ -109,4 +111,4 @@ __all__ = [
109
111
  inertia_image_parser,
110
112
  ]
111
113
 
112
- __version__ = '1.0.2'
114
+ __version__ = '1.0.3'
@@ -0,0 +1,81 @@
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, Direction, get_next_pos, get_opposite_direction
6
+ from puzzle_solver.core.utils_ortools import generic_solve_all, SingleSolution, force_connected_component
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 all(c.item().strip() in ['1', '2L', '2I', '3', '4'] for c in np.nditer(board)), 'board must contain only 1, 2L, 2I, 3, 4. Found:' + str(set(c.item().strip() for c in np.nditer(board)) - set(['1', '2L', '2I', '3', '4']))
13
+ self.board = board
14
+ self.V, self.H = board.shape
15
+ self.model = cp_model.CpModel()
16
+ self.model_vars: dict[Pos, cp_model.IntVar] = {}
17
+
18
+ self.create_vars()
19
+ self.add_all_constraints()
20
+
21
+ def create_vars(self):
22
+ for pos in get_all_pos(self.V, self.H):
23
+ for direction in Direction:
24
+ mirrored = (get_next_pos(pos, direction), get_opposite_direction(direction))
25
+ if mirrored in self.model_vars:
26
+ self.model_vars[(pos, direction)] = self.model_vars[mirrored]
27
+ else:
28
+ self.model_vars[(pos, direction)] = self.model.NewBoolVar(f'{pos}:{direction}')
29
+
30
+ def add_all_constraints(self):
31
+ for pos in get_all_pos(self.V, self.H):
32
+ self.force_position(pos, get_char(self.board, pos).strip())
33
+ # single connected component
34
+ self.force_connected_component()
35
+
36
+ def force_connected_component(self):
37
+ def is_neighbor(pd1: tuple[Pos, Direction], pd2: tuple[Pos, Direction]) -> bool:
38
+ p1, d1 = pd1
39
+ p2, d2 = pd2
40
+ if p1 == p2 and d1 != d2: # same position, different direction, is neighbor
41
+ return True
42
+ if get_next_pos(p1, d1) == p2 and d2 == get_opposite_direction(d1):
43
+ return True
44
+ return False
45
+ force_connected_component(self.model, self.model_vars, is_neighbor=is_neighbor)
46
+
47
+ def force_position(self, pos: Pos, value: str):
48
+ # cells with 1 or 3 or 4 neighbors each only have 1 unique state under rotational symmetry
49
+ # cells with 2 neighbors can either be a straight line (2I) or curved line (2L)
50
+ if value == '1':
51
+ self.model.Add(lxp.sum([self.model_vars[(pos, direction)] for direction in Direction]) == 1)
52
+ elif value == '2L':
53
+ self.model.Add(self.model_vars[(pos, Direction.LEFT)] != self.model_vars[(pos, Direction.RIGHT)])
54
+ self.model.Add(self.model_vars[(pos, Direction.UP)] != self.model_vars[(pos, Direction.DOWN)])
55
+ elif value == '2I':
56
+ self.model.Add(self.model_vars[(pos, Direction.LEFT)] == self.model_vars[(pos, Direction.RIGHT)])
57
+ self.model.Add(self.model_vars[(pos, Direction.UP)] == self.model_vars[(pos, Direction.DOWN)])
58
+ self.model.Add(self.model_vars[(pos, Direction.UP)] != self.model_vars[(pos, Direction.RIGHT)])
59
+ elif value == '3':
60
+ self.model.Add(lxp.sum([self.model_vars[(pos, direction)] for direction in Direction]) == 3)
61
+ elif value == '4':
62
+ self.model.Add(lxp.sum([self.model_vars[(pos, direction)] for direction in Direction]) == 4)
63
+ else:
64
+ raise ValueError(f'invalid value: {value}')
65
+
66
+ def solve_and_print(self, verbose: bool = True):
67
+ def board_to_solution(board: Board, solver: cp_model.CpSolverSolutionCallback) -> SingleSolution:
68
+ assignment = {}
69
+ for pos in get_all_pos(self.V, self.H):
70
+ assignment[pos] = ''
71
+ for direction in Direction:
72
+ if solver.Value(board.model_vars[(pos, direction)]) == 1:
73
+ assignment[pos] += direction.name[0]
74
+ return SingleSolution(assignment=assignment)
75
+ def callback(single_res: SingleSolution):
76
+ print("Solution found")
77
+ res = np.full((self.V, self.H), ' ', dtype=object)
78
+ for pos in get_all_pos(self.V, self.H):
79
+ set_char(res, pos, single_res.assignment[pos])
80
+ print(res)
81
+ return generic_solve_all(self, board_to_solution, callback=callback if verbose else None, verbose=verbose)
@@ -0,0 +1,72 @@
1
+ import numpy as np
2
+
3
+ from puzzle_solver import pipes_solver as solver
4
+ from puzzle_solver.core.utils import get_pos
5
+
6
+ def test_easy():
7
+ # 4 x 4 easy
8
+ # https://www.puzzle-pipes.com/?e=MDo3LDI2NiwyMTg=
9
+ board=np.array([
10
+ [ '1', '2L', '2L', '1' ],
11
+ [ '2L', '3', '2I', '2I' ],
12
+ [ '1', '1', '3', '3' ],
13
+ [ '2L', '2I', '2L', '1' ]
14
+ ])
15
+ binst = solver.Board(board=board)
16
+ solutions = binst.solve_and_print()
17
+ assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
18
+ solution = solutions[0].assignment
19
+ ground = np.array([
20
+ ['D ', 'DR ', 'DL ', 'D ',],
21
+ ['UR ', 'UDL', 'UD ', 'UD ',],
22
+ ['D ', 'U ', 'UDR', 'UDL',],
23
+ ['UR ', 'LR ', 'UL ', 'U ',],])
24
+ ground_assignment = {get_pos(x=x, y=y): ground[y][x].strip() for x in range(ground.shape[1]) for y in range(ground.shape[0])}
25
+ 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}'
26
+ for pos in solution.keys():
27
+ assert solution[pos] == ground_assignment[pos], f'solution[{pos}] != ground_assignment[{pos}], {solution[pos]} != {ground_assignment[pos]}'
28
+
29
+
30
+ def test_ground():
31
+ # 7 x 7
32
+ # https://www.puzzle-pipes.com/?e=MjoyNTgsNjQz
33
+ board=np.array([
34
+ [ '1 ', '3 ', '2I', '1 ', '2L', '1 ', '1 ' ],
35
+ [ '1 ', '3 ', '3 ', '1 ', '2I', '2L', '2L' ],
36
+ [ '2L', '3 ', '2L', '3 ', '3 ', '3 ', '1 ' ],
37
+ [ '2L', '2L', '1 ', '3 ', '1 ', '2I', '1 ' ],
38
+ [ '2I', '1 ', '3 ', '3 ', '3 ', '3 ', '2I' ],
39
+ [ '1 ', '1 ', '3 ', '2L', '3 ', '2L', '2L' ],
40
+ [ '1 ', '2I', '2L', '1 ', '2L', '2I', '1 ' ],
41
+ ])
42
+ binst = solver.Board(board=board)
43
+ solutions = binst.solve_and_print()
44
+ assert len(solutions) == 1, f'unique solutions != 1, == {len(solutions)}'
45
+ solution = solutions[0].assignment
46
+
47
+
48
+ def test_ground2():
49
+ # 10 x 10
50
+ # https://www.puzzle-pipes.com/?e=MzoxLDE5OCw0ODU=
51
+ board=np.array([
52
+ [ '1 ', '3 ', '3 ', '3 ', '1 ', '1 ', '2L', '2L', '2I', '1 ' ],
53
+ [ '1 ', '1 ', '1 ', '3 ', '2I', '1 ', '2I', '3 ', '2I', '1 ' ],
54
+ [ '2I', '1 ', '1 ', '3 ', '2L', '1 ', '3 ', '2I', '1 ', '1 ' ],
55
+ [ '2I', '2I', '1 ', '3 ', '3 ', '3 ', '2L', '3 ', '3 ', '2L' ],
56
+ [ '3 ', '3 ', '2I', '3 ', '1 ', '3 ', '2I', '2L', '1 ', '2L' ],
57
+ [ '1 ', '1 ', '3 ', '2I', '3 ', '2L', '1 ', '1 ', '2L', '2L' ],
58
+ [ '1 ', '1 ', '3 ', '1 ', '1 ', '1 ', '3 ', '3 ', '3 ', '2L' ],
59
+ [ '3 ', '2I', '3 ', '3 ', '2L', '3 ', '3 ', '2I', '2L', '1 ' ],
60
+ [ '1 ', '1 ', '3 ', '3 ', '3 ', '3 ', '1 ', '2L', '3 ', '2L' ],
61
+ [ '1 ', '2I', '3 ', '2I', '1 ', '1 ', '1 ', '3 ', '1 ', '1 ' ],
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
+ if __name__ == '__main__':
70
+ test_easy()
71
+ test_ground()
72
+ test_ground2()