multi-puzzle-solver 0.9.25__py3-none-any.whl → 0.9.26__py3-none-any.whl
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.dist-info → multi_puzzle_solver-0.9.26.dist-info}/METADATA +73 -3
- {multi_puzzle_solver-0.9.25.dist-info → multi_puzzle_solver-0.9.26.dist-info}/RECORD +8 -8
- puzzle_solver/__init__.py +2 -2
- puzzle_solver/core/utils.py +3 -1
- puzzle_solver/puzzles/flip/flip.py +45 -16
- puzzle_solver/puzzles/palisade/palisade.py +1 -1
- {multi_puzzle_solver-0.9.25.dist-info → multi_puzzle_solver-0.9.26.dist-info}/WHEEL +0 -0
- {multi_puzzle_solver-0.9.25.dist-info → multi_puzzle_solver-0.9.26.dist-info}/top_level.txt +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"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
puzzle_solver/__init__.py,sha256=
|
|
2
|
-
puzzle_solver/core/utils.py,sha256=
|
|
1
|
+
puzzle_solver/__init__.py,sha256=ScIPz0Gi0xGaY-v7N0JcfFgKUvfdSF3lUggAh8yagdg,3201
|
|
2
|
+
puzzle_solver/core/utils.py,sha256=LyrdhExCcdp7jWJJv1cu2urgS2gpcI44OsIipPeBAeQ,14113
|
|
3
3
|
puzzle_solver/core/utils_ortools.py,sha256=_i8cixHOB5XGqqcr-493bOiZgYJidnvxQMEfj--Trns,10278
|
|
4
4
|
puzzle_solver/puzzles/aquarium/aquarium.py,sha256=BUfkAS2d9eG3TdMoe1cOGGeNYgKUebRvn-z9nsC9gvE,5708
|
|
5
5
|
puzzle_solver/puzzles/battleships/battleships.py,sha256=RuYCrs4j0vUjlU139NRYYP-uNPAgO0V7hAzbsHrRwD8,7446
|
|
@@ -12,7 +12,7 @@ puzzle_solver/puzzles/chess_range/chess_solo.py,sha256=U3v766UsZHx_dC3gxqU90VbjA
|
|
|
12
12
|
puzzle_solver/puzzles/chess_sequence/chess_sequence.py,sha256=6ap3Wouf2PxHV4P56B9ol1QT98Ym6VHaxorQZWl6LnY,13692
|
|
13
13
|
puzzle_solver/puzzles/dominosa/dominosa.py,sha256=Nmb7pn8U27QJwGy9F3wo8ylqo2_U51OAo3GN2soaNpc,7195
|
|
14
14
|
puzzle_solver/puzzles/filling/filling.py,sha256=vrOIil285_r3IQ0F4c9mUBWMRVlPH4vowog_z1tCGdI,5567
|
|
15
|
-
puzzle_solver/puzzles/flip/flip.py,sha256=
|
|
15
|
+
puzzle_solver/puzzles/flip/flip.py,sha256=ZngJLUhRNc7qqo2wtNLdMPx4u9w9JTUge27PmdXyDCw,3985
|
|
16
16
|
puzzle_solver/puzzles/galaxies/galaxies.py,sha256=p10lpmW0FjtneFCMEjG1FSiEpQuvD8zZG9FG8zYGoes,5582
|
|
17
17
|
puzzle_solver/puzzles/galaxies/parse_map/parse_map.py,sha256=v5TCrdREeOB69s9_QFgPHKA7flG69Im1HVzIdxH0qQc,9355
|
|
18
18
|
puzzle_solver/puzzles/guess/guess.py,sha256=sH-NlYhxM3DNbhk4eGde09kgM0KaDvSbLrpHQiwcFGo,10791
|
|
@@ -29,7 +29,7 @@ puzzle_solver/puzzles/minesweeper/minesweeper.py,sha256=LiQVOGkWCsc1WtX8CdPgL_Ww
|
|
|
29
29
|
puzzle_solver/puzzles/mosaic/mosaic.py,sha256=QX_nVpVKQg8OfaUcqFk9tKqsDyVqvZc6-XWvfI3YcSw,2175
|
|
30
30
|
puzzle_solver/puzzles/nonograms/nonograms.py,sha256=1jmDTOCnmivmBlwtMDyyk3TVqH5IjapzLn7zLQ4qubk,6056
|
|
31
31
|
puzzle_solver/puzzles/norinori/norinori.py,sha256=uC8vXAw35xsTmpmTeKqYW7tbcssms9LCcXFBONtV2Ng,4743
|
|
32
|
-
puzzle_solver/puzzles/palisade/palisade.py,sha256=
|
|
32
|
+
puzzle_solver/puzzles/palisade/palisade.py,sha256=GTtzuc1OZCm3D5p-Po7LzK1d-whJkNSZ9G9rWr3vFMc,4966
|
|
33
33
|
puzzle_solver/puzzles/pearl/pearl.py,sha256=OhzpMYpxqvR3GCd5NH4ETT0NO4X753kRi6p5omYLChM,6798
|
|
34
34
|
puzzle_solver/puzzles/range/range.py,sha256=rruvD5ZSaOgvQuX6uGV_Dkr82nSiWZ5kDz03_j7Tt24,4425
|
|
35
35
|
puzzle_solver/puzzles/rectangles/rectangles.py,sha256=V7p6GSCwYrFfILDWiLLUbX08WlnPbQKdhQm8bMa2Mgw,7060
|
|
@@ -53,7 +53,7 @@ puzzle_solver/puzzles/unruly/unruly.py,sha256=sDF0oKT50G-NshyW2DYrvAgD9q9Ku9ANUy
|
|
|
53
53
|
puzzle_solver/puzzles/yin_yang/yin_yang.py,sha256=WrRdNhmKhIARdGOt_36gpRxRzrfLGv3wl7igBpPFM64,5259
|
|
54
54
|
puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py,sha256=drjfoHqmFf6U-ZQUwrBbfGINRxDQpgbvy4U3D9QyMhM,6617
|
|
55
55
|
puzzle_solver/utils/visualizer.py,sha256=tsX1yEKwmwXBYuBJpx_oZGe2UUt1g5yV73G3UbtmvtE,6817
|
|
56
|
-
multi_puzzle_solver-0.9.
|
|
57
|
-
multi_puzzle_solver-0.9.
|
|
58
|
-
multi_puzzle_solver-0.9.
|
|
59
|
-
multi_puzzle_solver-0.9.
|
|
56
|
+
multi_puzzle_solver-0.9.26.dist-info/METADATA,sha256=iVRpKnMdTJNLrbdh9eP19RsPCloN1iEFwtlvcwy_peg,211081
|
|
57
|
+
multi_puzzle_solver-0.9.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
58
|
+
multi_puzzle_solver-0.9.26.dist-info/top_level.txt,sha256=exwVUQa-anK9vYrpKzBPvH8bX43iElWI4VeNiAyBGJY,14
|
|
59
|
+
multi_puzzle_solver-0.9.26.dist-info/RECORD,,
|
puzzle_solver/__init__.py
CHANGED
|
@@ -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'
|
puzzle_solver/core/utils.py
CHANGED
|
@@ -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):
|
|
@@ -1,18 +1,41 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from ortools.sat.python import cp_model
|
|
3
|
-
from ortools.sat.python.cp_model import LinearExpr as lxp
|
|
4
5
|
|
|
5
|
-
from puzzle_solver.core.utils import Pos, get_all_pos, set_char, get_char,
|
|
6
|
+
from puzzle_solver.core.utils import Pos, get_all_pos, get_neighbors4, set_char, get_char, Direction, get_next_pos
|
|
6
7
|
from puzzle_solver.core.utils_ortools import generic_solve_all, SingleSolution
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Board:
|
|
10
|
-
def __init__(self, board: np.array):
|
|
11
|
+
def __init__(self, board: np.array, random_mapping: Optional[dict[Pos, Any]] = None):
|
|
11
12
|
assert board.ndim == 2, f'board must be 2d, got {board.ndim}'
|
|
12
|
-
assert
|
|
13
|
-
assert all((c.item() == ' ') or str(c.item()).isdecimal() for c in np.nditer(board)), 'board must contain only space or digits'
|
|
13
|
+
assert all((c.item() in ['B', 'W']) for c in np.nditer(board)), 'board must contain only B or W'
|
|
14
14
|
self.board = board
|
|
15
|
-
self.
|
|
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
|
+
|
|
16
39
|
self.model = cp_model.CpModel()
|
|
17
40
|
self.model_vars: dict[Pos, cp_model.IntVar] = {}
|
|
18
41
|
|
|
@@ -20,16 +43,22 @@ class Board:
|
|
|
20
43
|
self.add_all_constraints()
|
|
21
44
|
|
|
22
45
|
def create_vars(self):
|
|
23
|
-
for pos in get_all_pos(self.
|
|
24
|
-
self.model_vars[pos] = self.model.NewBoolVar(f'{pos}')
|
|
46
|
+
for pos in get_all_pos(self.V, self.H):
|
|
47
|
+
self.model_vars[pos] = self.model.NewBoolVar(f'tap:{pos}')
|
|
25
48
|
|
|
26
49
|
def add_all_constraints(self):
|
|
27
|
-
for pos in get_all_pos(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
|
|
28
52
|
c = get_char(self.board, pos)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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)
|
|
33
62
|
|
|
34
63
|
def solve_and_print(self, verbose: bool = True):
|
|
35
64
|
def board_to_solution(board: Board, solver: cp_model.CpSolverSolutionCallback) -> SingleSolution:
|
|
@@ -39,10 +68,10 @@ class Board:
|
|
|
39
68
|
return SingleSolution(assignment=assignment)
|
|
40
69
|
def callback(single_res: SingleSolution):
|
|
41
70
|
print("Solution found")
|
|
42
|
-
res = np.full((self.
|
|
43
|
-
for pos in get_all_pos(self.
|
|
71
|
+
res = np.full((self.V, self.H), ' ', dtype=object)
|
|
72
|
+
for pos in get_all_pos(self.V, self.H):
|
|
44
73
|
c = get_char(self.board, pos)
|
|
45
|
-
c = '
|
|
74
|
+
c = 'T' if single_res.assignment[pos] == 1 else ' '
|
|
46
75
|
set_char(res, pos, c)
|
|
47
76
|
print(res)
|
|
48
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
|
|
|
File without changes
|
|
File without changes
|