multi-puzzle-solver 0.9.30__py3-none-any.whl → 1.0.2__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.30.dist-info → multi_puzzle_solver-1.0.2.dist-info}/METADATA +331 -76
- multi_puzzle_solver-1.0.2.dist-info/RECORD +69 -0
- puzzle_solver/__init__.py +58 -1
- puzzle_solver/core/utils_ortools.py +8 -6
- puzzle_solver/core/utils_visualizer.py +23 -41
- puzzle_solver/puzzles/binairo/binairo.py +4 -4
- puzzle_solver/puzzles/black_box/black_box.py +5 -11
- puzzle_solver/puzzles/bridges/bridges.py +1 -1
- puzzle_solver/puzzles/chess_range/chess_range.py +3 -3
- puzzle_solver/puzzles/chess_range/chess_solo.py +1 -1
- puzzle_solver/puzzles/filling/filling.py +3 -3
- puzzle_solver/puzzles/flood_it/flood_it.py +174 -0
- puzzle_solver/puzzles/flood_it/parse_map/parse_map.py +198 -0
- puzzle_solver/puzzles/galaxies/galaxies.py +1 -1
- puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +3 -3
- puzzle_solver/puzzles/guess/guess.py +1 -1
- puzzle_solver/puzzles/heyawake/heyawake.py +3 -3
- puzzle_solver/puzzles/inertia/inertia.py +1 -1
- puzzle_solver/puzzles/inertia/parse_map/parse_map.py +13 -10
- puzzle_solver/puzzles/inertia/tsp.py +5 -7
- puzzle_solver/puzzles/kakuro/kakuro.py +1 -1
- puzzle_solver/puzzles/keen/keen.py +2 -2
- puzzle_solver/puzzles/minesweeper/minesweeper.py +2 -3
- puzzle_solver/puzzles/nonograms/nonograms.py +3 -3
- puzzle_solver/puzzles/norinori/norinori.py +2 -2
- puzzle_solver/puzzles/nurikabe/nurikabe.py +2 -2
- puzzle_solver/puzzles/range/range.py +1 -1
- puzzle_solver/puzzles/rectangles/rectangles.py +2 -6
- puzzle_solver/puzzles/shingoki/shingoki.py +1 -1
- puzzle_solver/puzzles/signpost/signpost.py +2 -2
- puzzle_solver/puzzles/slant/parse_map/parse_map.py +7 -5
- puzzle_solver/puzzles/slitherlink/slitherlink.py +1 -1
- puzzle_solver/puzzles/stitches/parse_map/parse_map.py +6 -5
- puzzle_solver/puzzles/stitches/stitches.py +1 -1
- puzzle_solver/puzzles/sudoku/sudoku.py +91 -20
- puzzle_solver/puzzles/tents/tents.py +2 -2
- puzzle_solver/puzzles/thermometers/thermometers.py +1 -1
- puzzle_solver/puzzles/towers/towers.py +1 -1
- puzzle_solver/puzzles/undead/undead.py +1 -1
- puzzle_solver/puzzles/unruly/unruly.py +1 -1
- puzzle_solver/puzzles/yin_yang/yin_yang.py +1 -1
- puzzle_solver/utils/visualizer.py +1 -1
- multi_puzzle_solver-0.9.30.dist-info/RECORD +0 -67
- {multi_puzzle_solver-0.9.30.dist-info → multi_puzzle_solver-1.0.2.dist-info}/WHEEL +0 -0
- {multi_puzzle_solver-0.9.30.dist-info → multi_puzzle_solver-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from typing import Union, Optional
|
|
2
|
+
from collections import defaultdict
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
4
5
|
from ortools.sat.python import cp_model
|
|
@@ -35,32 +36,77 @@ def get_block_pos(i: int, Bv: int, Bh: int) -> list[Pos]:
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class Board:
|
|
38
|
-
def __init__(self,
|
|
39
|
+
def __init__(self,
|
|
40
|
+
board: np.array,
|
|
41
|
+
constrain_blocks: bool = True,
|
|
42
|
+
block_size: Optional[tuple[int, int]] = None,
|
|
43
|
+
sandwich: Optional[dict[str, list[int]]] = None,
|
|
44
|
+
unique_diagonal: bool = False,
|
|
45
|
+
jigsaw: Optional[np.array] = None,
|
|
46
|
+
killer: Optional[tuple[np.array, dict[str, int]]] = None,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
board: 2d array of characters
|
|
50
|
+
constrain_blocks: whether to constrain the blocks. If True, each block must contain all numbers from 1 to 9 exactly once.
|
|
51
|
+
block_size: tuple of block size (vertical, horizontal). If not provided, the block size is the square root of the board size.
|
|
52
|
+
sandwich: dictionary of sandwich clues (side, bottom). If provided, the sum of the values between 1 and 9 for each row and column is equal to the clue.
|
|
53
|
+
unique_diagonal: whether to constrain the 2 diagonals to be unique. If True, each diagonal must contain all numbers from 1 to 9 exactly once.
|
|
54
|
+
killer: tuple of (killer board, killer clues). If provided, the killer board must be a 2d array of ids of the killer blocks. The killer clues must be a dictionary of killer block ids to clues.
|
|
55
|
+
Each numbers in a killer block must be unique and sum to the clue.
|
|
56
|
+
"""
|
|
39
57
|
assert board.ndim == 2, f'board must be 2d, got {board.ndim}'
|
|
40
58
|
assert board.shape[0] == board.shape[1], 'board must be square'
|
|
41
59
|
assert all(isinstance(i.item(), str) and len(i.item()) == 1 and (i.item().isalnum() or i.item() == ' ') for i in np.nditer(board)), 'board must contain only alphanumeric characters or space'
|
|
42
60
|
self.board = board
|
|
43
61
|
self.V, self.H = board.shape
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
self.L = max(self.V, self.H)
|
|
63
|
+
self.constrain_blocks = constrain_blocks
|
|
64
|
+
self.unique_diagonal = unique_diagonal
|
|
65
|
+
self.sandwich = None
|
|
66
|
+
self.jigsaw_id_to_pos = None
|
|
67
|
+
self.killer = None
|
|
68
|
+
|
|
69
|
+
if self.constrain_blocks:
|
|
70
|
+
if block_size is None:
|
|
71
|
+
B = np.sqrt(self.V) # block size
|
|
72
|
+
assert B.is_integer(), 'board size must be a perfect square or provide block_size'
|
|
73
|
+
Bv, Bh = int(B), int(B)
|
|
74
|
+
else:
|
|
75
|
+
Bv, Bh = block_size
|
|
76
|
+
assert Bv * Bh == self.V, 'block size must be a factor of board size'
|
|
77
|
+
# can be different in 4x3 for example
|
|
78
|
+
self.Bv = Bv
|
|
79
|
+
self.Bh = Bh
|
|
80
|
+
self.B = Bv * Bh # block count
|
|
48
81
|
else:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
assert block_size is None, 'cannot set block size if blocks are not constrained'
|
|
83
|
+
|
|
84
|
+
if jigsaw is not None:
|
|
85
|
+
if self.constrain_blocks is not None:
|
|
86
|
+
print('Warning: jigsaw and blocks are both constrained, are you sure you want to do this?')
|
|
87
|
+
assert jigsaw.ndim == 2, f'jigsaw must be 2d, got {jigsaw.ndim}'
|
|
88
|
+
assert jigsaw.shape[0] == self.V and jigsaw.shape[1] == self.H, 'jigsaw must be the same size as the board'
|
|
89
|
+
assert all(isinstance(i.item(), str) and i.item().isdecimal() for i in np.nditer(jigsaw)), 'jigsaw must contain only digits or space'
|
|
90
|
+
self.jigsaw_id_to_pos: dict[int, list[Pos]] = defaultdict(list)
|
|
91
|
+
for pos in get_all_pos(self.V, self.H):
|
|
92
|
+
v = get_char(jigsaw, pos)
|
|
93
|
+
if v.isdecimal():
|
|
94
|
+
self.jigsaw_id_to_pos[int(v)].append(pos)
|
|
95
|
+
assert all(len(pos_list) <= self.L for pos_list in self.jigsaw_id_to_pos.values()), 'jigsaw areas cannot be larger than the number of digits'
|
|
96
|
+
|
|
55
97
|
if sandwich is not None:
|
|
56
98
|
assert set(sandwich.keys()) == set(['side', 'bottom']), 'sandwich must contain only side and bottom'
|
|
57
99
|
assert len(sandwich['side']) == self.H, 'side must be equal to board width'
|
|
58
100
|
assert len(sandwich['bottom']) == self.V, 'bottom must be equal to board height'
|
|
59
101
|
self.sandwich = sandwich
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
|
|
103
|
+
if killer is not None:
|
|
104
|
+
assert killer[0].ndim == 2, f'killer board must be 2d, got {killer[0].ndim}'
|
|
105
|
+
assert killer[0].shape[0] == self.V and killer[0].shape[1] == self.H, 'killer board must be the same size as the board'
|
|
106
|
+
assert all(isinstance(i.item(), str) and i.item().isdecimal() for i in np.nditer(killer[0])), 'killer board must contain only digits or space'
|
|
107
|
+
assert set(killer[1].keys()).issubset(set(killer[0].flatten())), f'killer clues must contain all killer block ids, {set(killer[0].flatten()) - set(killer[1].keys())}'
|
|
108
|
+
self.killer = killer
|
|
109
|
+
|
|
64
110
|
self.model = cp_model.CpModel()
|
|
65
111
|
self.model_vars: dict[Pos, cp_model.IntVar] = {}
|
|
66
112
|
|
|
@@ -69,7 +115,7 @@ class Board:
|
|
|
69
115
|
|
|
70
116
|
def create_vars(self):
|
|
71
117
|
for pos in get_all_pos(self.V, self.H):
|
|
72
|
-
self.model_vars[pos] = self.model.NewIntVar(1, self.
|
|
118
|
+
self.model_vars[pos] = self.model.NewIntVar(1, self.L, f'{pos}')
|
|
73
119
|
|
|
74
120
|
def add_all_constraints(self):
|
|
75
121
|
# some squares are already filled
|
|
@@ -86,16 +132,21 @@ class Board:
|
|
|
86
132
|
for col in range(self.H):
|
|
87
133
|
col_vars = [self.model_vars[pos] for pos in get_col_pos(col, V=self.V)]
|
|
88
134
|
self.model.AddAllDifferent(col_vars)
|
|
89
|
-
# each block
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
135
|
+
if self.constrain_blocks: # each block must contain all numbers from 1 to 9 exactly once
|
|
136
|
+
for block_i in range(self.B):
|
|
137
|
+
block_vars = [self.model_vars[p] for p in get_block_pos(block_i, Bv=self.Bv, Bh=self.Bh)]
|
|
138
|
+
self.model.AddAllDifferent(block_vars)
|
|
93
139
|
if self.sandwich is not None:
|
|
94
140
|
self.add_sandwich_constraints()
|
|
95
141
|
if self.unique_diagonal:
|
|
96
142
|
self.add_unique_diagonal_constraints()
|
|
143
|
+
if self.jigsaw_id_to_pos is not None:
|
|
144
|
+
self.add_jigsaw_constraints()
|
|
145
|
+
if self.killer is not None:
|
|
146
|
+
self.add_killer_constraints()
|
|
97
147
|
|
|
98
148
|
def add_sandwich_constraints(self):
|
|
149
|
+
"""Sandwich constraints, enforce that the sum of the values between 1 and 9 for each row and column is equal to the clue."""
|
|
99
150
|
for c, clue in enumerate(self.sandwich['bottom']):
|
|
100
151
|
if clue is None or int(clue) < 0:
|
|
101
152
|
continue
|
|
@@ -113,6 +164,26 @@ class Board:
|
|
|
113
164
|
anti_diagonal_vars = [self.model_vars[get_pos(x=i, y=self.V-i-1)] for i in range(min(self.V, self.H))]
|
|
114
165
|
self.model.AddAllDifferent(anti_diagonal_vars)
|
|
115
166
|
|
|
167
|
+
def add_jigsaw_constraints(self):
|
|
168
|
+
"""All digits in one jigsaw area must be unique."""
|
|
169
|
+
for pos_list in self.jigsaw_id_to_pos.values():
|
|
170
|
+
self.model.AddAllDifferent([self.model_vars[p] for p in pos_list])
|
|
171
|
+
|
|
172
|
+
def add_killer_constraints(self):
|
|
173
|
+
"""Killer constraints, enforce that the sum of the values in each killer block is equal to the clue and all numbers in a block are unique."""
|
|
174
|
+
killer_board, killer_clues = self.killer
|
|
175
|
+
# change clue keys to ints
|
|
176
|
+
killer_clues = {int(k): v for k, v in killer_clues.items()}
|
|
177
|
+
killer_id_to_pos = defaultdict(list)
|
|
178
|
+
for pos in get_all_pos(self.V, self.H):
|
|
179
|
+
v = get_char(killer_board, pos)
|
|
180
|
+
if v.isdecimal():
|
|
181
|
+
killer_id_to_pos[int(v)].append(pos)
|
|
182
|
+
for killer_id, pos_list in killer_id_to_pos.items():
|
|
183
|
+
self.model.AddAllDifferent([self.model_vars[p] for p in pos_list])
|
|
184
|
+
clue = killer_clues[killer_id]
|
|
185
|
+
self.model.Add(sum([self.model_vars[p] for p in pos_list]) == clue)
|
|
186
|
+
|
|
116
187
|
def solve_and_print(self, verbose: bool = True):
|
|
117
188
|
def board_to_solution(board: Board, solver: cp_model.CpSolverSolutionCallback) -> SingleSolution:
|
|
118
189
|
assignment: dict[Pos, int] = {}
|
|
@@ -45,14 +45,14 @@ class Board:
|
|
|
45
45
|
if get_char(self.board, neighbour) != ' ':
|
|
46
46
|
continue
|
|
47
47
|
self.model.Add(self.is_tent[neighbour] == 0).OnlyEnforceIf(self.is_tent[pos])
|
|
48
|
-
# - the number of tents in each row and column matches the numbers around the edge of the grid
|
|
48
|
+
# - the number of tents in each row and column matches the numbers around the edge of the grid
|
|
49
49
|
for row in range(self.N):
|
|
50
50
|
row_vars = [self.is_tent[pos] for pos in get_row_pos(row, self.N)]
|
|
51
51
|
self.model.Add(lxp.sum(row_vars) == self.sides['side'][row])
|
|
52
52
|
for col in range(self.N):
|
|
53
53
|
col_vars = [self.is_tent[pos] for pos in get_col_pos(col, self.N)]
|
|
54
54
|
self.model.Add(lxp.sum(col_vars) == self.sides['top'][col])
|
|
55
|
-
# - it is possible to match tents to trees so that each tree is orthogonally adjacent to its own tent (but may also be adjacent to other tents).
|
|
55
|
+
# - it is possible to match tents to trees so that each tree is orthogonally adjacent to its own tent (but may also be adjacent to other tents).
|
|
56
56
|
# for each tree, one of the following must be true:
|
|
57
57
|
# a tent on its left has direction RIGHT
|
|
58
58
|
# a tent on its right has direction LEFT
|
|
@@ -120,7 +120,7 @@ class Board:
|
|
|
120
120
|
# create a single bool which decides if I can see it or not
|
|
121
121
|
res = self.model.NewBoolVar(name)
|
|
122
122
|
self.model.AddBoolAnd(lits).OnlyEnforceIf(res)
|
|
123
|
-
self.model.AddBoolOr([res] + [
|
|
123
|
+
self.model.AddBoolOr([res] + [lit.Not() for lit in lits])
|
|
124
124
|
return res
|
|
125
125
|
|
|
126
126
|
def solve_and_print(self, verbose: bool = True):
|
|
@@ -131,7 +131,7 @@ class Board:
|
|
|
131
131
|
pos = get_pos(x=i, y=self.N-1)
|
|
132
132
|
beam_result = beam(self.board, pos, Direction.UP)
|
|
133
133
|
self.model.add(self.get_var(beam_result) == ground)
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
if self.monster_count is not None:
|
|
136
136
|
for monster, count in self.monster_count.items():
|
|
137
137
|
if count == -1:
|
|
@@ -49,7 +49,7 @@ class Board:
|
|
|
49
49
|
continue
|
|
50
50
|
v = 1 if c == 'B' else 0
|
|
51
51
|
self.model.Add(self.model_vars[pos] == v)
|
|
52
|
-
# no three consecutive squares, horizontally or vertically, are the same colour
|
|
52
|
+
# no three consecutive squares, horizontally or vertically, are the same colour
|
|
53
53
|
for pos in get_all_pos(self.V, self.H):
|
|
54
54
|
horiz, vert = get_3_consecutive_horiz_and_vert(pos, self.V, self.H)
|
|
55
55
|
if len(horiz) == 3:
|
|
@@ -48,7 +48,7 @@ class Board:
|
|
|
48
48
|
continue
|
|
49
49
|
self.model.AddBoolOr([self.B[tl], self.B[tr], self.B[bl], self.B[br]])
|
|
50
50
|
self.model.AddBoolOr([self.B[tl].Not(), self.B[tr].Not(), self.B[bl].Not(), self.B[br].Not()])
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
def disallow_checkers(self):
|
|
53
53
|
# from https://ralphwaldo.github.io/yinyang_summary.html
|
|
54
54
|
for pos in get_all_pos(self.V, self.H): # disallow (WB/BW) and (BW/WB)
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
puzzle_solver/__init__.py,sha256=f79JI0EQhfQi12yO6gvuzzLtxXQgojVrq-v8HZrzjS0,3693
|
|
2
|
-
puzzle_solver/core/utils.py,sha256=XBW5j-IwtJMPMP-ycmY6SqRCM1NOVl5O6UeoGqNj618,8153
|
|
3
|
-
puzzle_solver/core/utils_ortools.py,sha256=_i8cixHOB5XGqqcr-493bOiZgYJidnvxQMEfj--Trns,10278
|
|
4
|
-
puzzle_solver/core/utils_visualizer.py,sha256=2jBnS2PeI4keFf-rneScSxX669zXu5F1lkClZ5EkMhE,21152
|
|
5
|
-
puzzle_solver/puzzles/aquarium/aquarium.py,sha256=BUfkAS2d9eG3TdMoe1cOGGeNYgKUebRvn-z9nsC9gvE,5708
|
|
6
|
-
puzzle_solver/puzzles/battleships/battleships.py,sha256=RuYCrs4j0vUjlU139NRYYP-uNPAgO0V7hAzbsHrRwD8,7446
|
|
7
|
-
puzzle_solver/puzzles/binairo/binairo.py,sha256=4xgYd1ewYIQCqEzsHdgp6hWzyW_TF_2rt6PO8QLFKWU,6838
|
|
8
|
-
puzzle_solver/puzzles/binairo/binairo_plus.py,sha256=TvLG3olwANtft3LuCF-y4OofpU9PNa4IXDqgZqsD-g0,267
|
|
9
|
-
puzzle_solver/puzzles/black_box/black_box.py,sha256=ZnHDVt6PFS_r1kMNSsbz9hav1hxIrNDUvPyERGPjLjM,15635
|
|
10
|
-
puzzle_solver/puzzles/bridges/bridges.py,sha256=15A9uV4xjoqPRo_9CTnoKeGRxS3z2aMF619T1n0dTOQ,5402
|
|
11
|
-
puzzle_solver/puzzles/chess_range/chess_melee.py,sha256=D-_Oi8OyxsVe1j3dIKYwRlxgeb3NWLmDWGcv-oclY0c,195
|
|
12
|
-
puzzle_solver/puzzles/chess_range/chess_range.py,sha256=uMQGTIwzGskHIhI-tPYjT9a3wHUBIkZ18eXjV9IpUE4,21071
|
|
13
|
-
puzzle_solver/puzzles/chess_range/chess_solo.py,sha256=U3v766UsZHx_dC3gxqU90VbjAXn-OlYhtrnnvJYFvrQ,401
|
|
14
|
-
puzzle_solver/puzzles/chess_sequence/chess_sequence.py,sha256=6ap3Wouf2PxHV4P56B9ol1QT98Ym6VHaxorQZWl6LnY,13692
|
|
15
|
-
puzzle_solver/puzzles/dominosa/dominosa.py,sha256=Nmb7pn8U27QJwGy9F3wo8ylqo2_U51OAo3GN2soaNpc,7195
|
|
16
|
-
puzzle_solver/puzzles/filling/filling.py,sha256=vrOIil285_r3IQ0F4c9mUBWMRVlPH4vowog_z1tCGdI,5567
|
|
17
|
-
puzzle_solver/puzzles/flip/flip.py,sha256=ZngJLUhRNc7qqo2wtNLdMPx4u9w9JTUge27PmdXyDCw,3985
|
|
18
|
-
puzzle_solver/puzzles/galaxies/galaxies.py,sha256=p10lpmW0FjtneFCMEjG1FSiEpQuvD8zZG9FG8zYGoes,5582
|
|
19
|
-
puzzle_solver/puzzles/galaxies/parse_map/parse_map.py,sha256=v5TCrdREeOB69s9_QFgPHKA7flG69Im1HVzIdxH0qQc,9355
|
|
20
|
-
puzzle_solver/puzzles/guess/guess.py,sha256=sH-NlYhxM3DNbhk4eGde09kgM0KaDvSbLrpHQiwcFGo,10791
|
|
21
|
-
puzzle_solver/puzzles/heyawake/heyawake.py,sha256=qMnc_CuHn8K5Rw40tefjueI1pycpHQ7eN1R9Xg5WEuw,5601
|
|
22
|
-
puzzle_solver/puzzles/inertia/inertia.py,sha256=gJBahkh69CrSWNscalKEoP1j4X-Q3XpbIBMiG9PUpU0,5657
|
|
23
|
-
puzzle_solver/puzzles/inertia/tsp.py,sha256=gobiISHtARA4Elq0jr90p6Yhq11ULjGoqsS-rLFhYcc,15389
|
|
24
|
-
puzzle_solver/puzzles/inertia/parse_map/parse_map.py,sha256=A9JQTNqamUdzlwqks0XQp3Hge3mzyTIVK6YtDJvqpL4,8422
|
|
25
|
-
puzzle_solver/puzzles/kakurasu/kakurasu.py,sha256=VNGMJnBHDi6WkghLObRLhUvkmrPaGphTTUDMC0TkQvQ,2064
|
|
26
|
-
puzzle_solver/puzzles/kakuro/kakuro.py,sha256=Jf0Iilv32EPcaWikX92_vgBOVRp5MAE27aFRmnLotGQ,4374
|
|
27
|
-
puzzle_solver/puzzles/keen/keen.py,sha256=tDb6C5S3Q0JAKPsdw-84WQ6PxRADELZHr_BK8FDH-NA,5039
|
|
28
|
-
puzzle_solver/puzzles/light_up/light_up.py,sha256=iSA1rjZMFsnI0V0Nxivxox4qZkB7PvUrROSHXcoUXds,4541
|
|
29
|
-
puzzle_solver/puzzles/lits/lits.py,sha256=3fPIkhAIUz8JokcfaE_ZM3b0AFEnf5xPzGJ2qnm8SWY,7099
|
|
30
|
-
puzzle_solver/puzzles/magnets/magnets.py,sha256=-Wl49JD_PKeq735zQVMQ3XSQX6gdHiY-7PKw-Sh16jw,6474
|
|
31
|
-
puzzle_solver/puzzles/map/map.py,sha256=sxc57tapB8Tsgam-yoDitln1o-EB_SbIYvO6WEYy3us,2582
|
|
32
|
-
puzzle_solver/puzzles/minesweeper/minesweeper.py,sha256=LiQVOGkWCsc1WtX8CdPgL_WwAcaeUFuoi5_eqH8U2Og,5876
|
|
33
|
-
puzzle_solver/puzzles/mosaic/mosaic.py,sha256=QX_nVpVKQg8OfaUcqFk9tKqsDyVqvZc6-XWvfI3YcSw,2175
|
|
34
|
-
puzzle_solver/puzzles/nonograms/nonograms.py,sha256=1jmDTOCnmivmBlwtMDyyk3TVqH5IjapzLn7zLQ4qubk,6056
|
|
35
|
-
puzzle_solver/puzzles/norinori/norinori.py,sha256=uC8vXAw35xsTmpmTeKqYW7tbcssms9LCcXFBONtV2Ng,4743
|
|
36
|
-
puzzle_solver/puzzles/nurikabe/nurikabe.py,sha256=VMJjB9KAKmfBkG1mDT3Jf2I1PZJb--Qx0BicN8xL4eg,6519
|
|
37
|
-
puzzle_solver/puzzles/palisade/palisade.py,sha256=T-LXlaLU5OwUQ24QWJWhBUFUktg0qDODTilNmBaXs4I,5014
|
|
38
|
-
puzzle_solver/puzzles/pearl/pearl.py,sha256=OhzpMYpxqvR3GCd5NH4ETT0NO4X753kRi6p5omYLChM,6798
|
|
39
|
-
puzzle_solver/puzzles/range/range.py,sha256=rruvD5ZSaOgvQuX6uGV_Dkr82nSiWZ5kDz03_j7Tt24,4425
|
|
40
|
-
puzzle_solver/puzzles/rectangles/rectangles.py,sha256=zaPg3qI9TNxr2iXmNi2kOL8R2RsS9DyQPUTY3ukgYIA,7033
|
|
41
|
-
puzzle_solver/puzzles/shakashaka/shakashaka.py,sha256=PRpg_qI7XA3ysAo_g1TRJsT3VwB5Vial2UcFyBOMwKQ,9571
|
|
42
|
-
puzzle_solver/puzzles/shingoki/shingoki.py,sha256=uwX1ZIGGDlshMtsZedlgGYE8hDB1ou3h6aBnZEr_l8I,7425
|
|
43
|
-
puzzle_solver/puzzles/signpost/signpost.py,sha256=-0_S6ycwzwlUf9-ZhP127Rgo5gMBOHiTM6t08dLLDac,3869
|
|
44
|
-
puzzle_solver/puzzles/singles/singles.py,sha256=KKn_Yl-eW874Bl1UmmcqoQ5vhNiO1JbM7fxKczOV5M4,2847
|
|
45
|
-
puzzle_solver/puzzles/slant/slant.py,sha256=xF-N4PuXYfx638NP1f1mi6YncIZB4mLtXtdS79XyPbg,6122
|
|
46
|
-
puzzle_solver/puzzles/slant/parse_map/parse_map.py,sha256=dxnALSDXe9wU0uSD0QEXnzoh1q801mj1ePTNLtG0n60,4796
|
|
47
|
-
puzzle_solver/puzzles/slitherlink/slitherlink.py,sha256=e1A_f_3J-QXN9fmt_Nf3FsYnp-TmE9TRKN06Wn4NnAU,7056
|
|
48
|
-
puzzle_solver/puzzles/star_battle/star_battle.py,sha256=IX6w4H3sifN01kPPtrAVRCK0Nl_xlXXSHvJKw8K1EuE,3718
|
|
49
|
-
puzzle_solver/puzzles/star_battle/star_battle_shapeless.py,sha256=lj05V0Y3A3NjMo1boMkPIwBhMtm6SWydjgAMeCf5EIo,225
|
|
50
|
-
puzzle_solver/puzzles/stitches/stitches.py,sha256=iK8t02q43gH3FPbuIDn4dK0sbaOgZOnw8yHNRNvNuIU,6534
|
|
51
|
-
puzzle_solver/puzzles/stitches/parse_map/parse_map.py,sha256=f49ZGVBPXjAGgqZnqPab6PcO_DsFDFZnG3uA8b-1d7k,10441
|
|
52
|
-
puzzle_solver/puzzles/sudoku/sudoku.py,sha256=SE4TM_gic6Jj0fkDR_NzUJdX2XKyQ8eeOnVAQ011Xbo,8870
|
|
53
|
-
puzzle_solver/puzzles/tapa/tapa.py,sha256=TsOQhnEvlC1JxaWiEjQg2KxRXJR49GrN71DsMvPpia8,5337
|
|
54
|
-
puzzle_solver/puzzles/tents/tents.py,sha256=iyVK2WXfIT5j_9qqlQg0WmwvixwXlZSsHGK3XA-KpII,6283
|
|
55
|
-
puzzle_solver/puzzles/thermometers/thermometers.py,sha256=nsvJZkm7G8FALT27bpaB0lv5E_AWawqmvapQI8QcYXw,4015
|
|
56
|
-
puzzle_solver/puzzles/towers/towers.py,sha256=QvL0Pp-Z2ewCeq9ZkNrh8MShKOh-Y52sFBSudve68wk,6496
|
|
57
|
-
puzzle_solver/puzzles/tracks/tracks.py,sha256=98xds9SKNqtOLFTRUX_KSMC7XYmZo567LOFeqotVQaM,7237
|
|
58
|
-
puzzle_solver/puzzles/undead/undead.py,sha256=IrCUfzQFBem658P5KKqldG7vd2TugTHehcwseCarerM,6604
|
|
59
|
-
puzzle_solver/puzzles/unequal/unequal.py,sha256=ExY2XDCrqROCDpRLfHo8uVr1zuli1QvbCdNCiDhlCac,6978
|
|
60
|
-
puzzle_solver/puzzles/unruly/unruly.py,sha256=sDF0oKT50G-NshyW2DYrvAgD9q9Ku9ANUyNhGSAu7cQ,3827
|
|
61
|
-
puzzle_solver/puzzles/yin_yang/yin_yang.py,sha256=WrRdNhmKhIARdGOt_36gpRxRzrfLGv3wl7igBpPFM64,5259
|
|
62
|
-
puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py,sha256=drjfoHqmFf6U-ZQUwrBbfGINRxDQpgbvy4U3D9QyMhM,6617
|
|
63
|
-
puzzle_solver/utils/visualizer.py,sha256=tsX1yEKwmwXBYuBJpx_oZGe2UUt1g5yV73G3UbtmvtE,6817
|
|
64
|
-
multi_puzzle_solver-0.9.30.dist-info/METADATA,sha256=yxPV6ZvkvGPOs1O2HpIob3e94uFQXjpm5JJKdCXyc2s,335384
|
|
65
|
-
multi_puzzle_solver-0.9.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
66
|
-
multi_puzzle_solver-0.9.30.dist-info/top_level.txt,sha256=exwVUQa-anK9vYrpKzBPvH8bX43iElWI4VeNiAyBGJY,14
|
|
67
|
-
multi_puzzle_solver-0.9.30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|