multi-puzzle-solver 0.9.22__py3-none-any.whl → 0.9.25__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.

@@ -2,7 +2,7 @@ import numpy as np
2
2
  from collections import defaultdict
3
3
  from ortools.sat.python import cp_model
4
4
 
5
- from puzzle_solver.core.utils import Pos, get_all_pos, get_char, set_char, get_pos, Direction, get_row_pos, get_col_pos, get_next_pos, in_bounds, get_opposite_direction
5
+ from puzzle_solver.core.utils import Pos, get_all_pos, get_char, set_char, get_pos, Direction, get_row_pos, get_col_pos, get_next_pos, in_bounds, get_opposite_direction, render_grid
6
6
  from puzzle_solver.core.utils_ortools import generic_solve_all, SingleSolution, force_connected_component
7
7
 
8
8
 
@@ -20,6 +20,10 @@ class Board:
20
20
  self.cell_borders_to_corners: dict[CellBorder, set[Corner]] = defaultdict(set) # for every cell border, a set of all corners it is connected to
21
21
  self.corners_to_cell_borders: dict[Corner, set[CellBorder]] = defaultdict(set) # opposite direction
22
22
 
23
+ # 2N^2 + 2N edges
24
+ # 4*edges (fully connected component)
25
+ # model variables = edges (on/off) + 4*edges (fully connected component)
26
+ # = 9N^2 + 9N
23
27
  self.model = cp_model.CpModel()
24
28
  self.model_vars: dict[CellBorder, cp_model.IntVar] = {} # one entry for every unique variable in the model
25
29
  self.cell_borders: dict[CellBorder, cp_model.IntVar] = {} # for every position and direction, one entry for that edge (thus the same edge variables are used in opposite directions of neighboring cells)
@@ -87,10 +91,11 @@ class Board:
87
91
  if not val.isdecimal():
88
92
  continue
89
93
  self.model.Add(sum(variables) == int(val))
94
+
95
+ corner_sum_domain = cp_model.Domain.FromValues([0, 2]) # sum of edges touching a corner is 0 or 2
90
96
  for corner in self.corner_vars: # a corder always has 0 or 2 active edges
91
- g = self.model.NewBoolVar(f'corner_gate_{corner}')
92
- self.model.Add(sum(self.corner_vars[corner]) == 0).OnlyEnforceIf(g.Not())
93
- self.model.Add(sum(self.corner_vars[corner]) == 2).OnlyEnforceIf(g)
97
+ self.model.AddLinearExpressionInDomain(sum(self.corner_vars[corner]), corner_sum_domain)
98
+
94
99
  # single connected component
95
100
  def is_neighbor(cb1: CellBorder, cb2: CellBorder) -> bool:
96
101
  cb1_corners = self.cell_borders_to_corners[cb1]
@@ -118,131 +123,7 @@ class Board:
118
123
  continue
119
124
  c = ''.join(sorted(single_res.assignment[pos]))
120
125
  set_char(res, pos, c)
121
- print(render_grid(cell_flags=res, center_char=lambda c, r: self.board[r, c] if self.board[r, c] != ' ' else '·'))
126
+ # replace " " with "·"
127
+ board = np.where(self.board == ' ', '·', self.board)
128
+ print(render_grid(cell_flags=res, center_char=board))
122
129
  return generic_solve_all(self, board_to_solution, callback=callback if verbose else None, verbose=verbose, max_solutions=999)
123
-
124
-
125
-
126
-
127
-
128
- def render_grid(cell_flags: np.ndarray = None,
129
- H: np.ndarray = None,
130
- V: np.ndarray = None,
131
- mark_centers: bool = True,
132
- center_char: str = '·',
133
- show_axes: bool = True,
134
- scale_x: int = 2) -> str:
135
- """
136
- AI generated this because I don't currently care about the details of rendering to the terminal and I did it in a quick and dirty way while the AI made it in a pretty way, and this looks good during my development.
137
- cell_flags: np.ndarray of shape (N, N) with characters 'U', 'D', 'L', 'R' to represent the edges of the cells.
138
- OR:
139
- H: (N+1, N) horizontal edges between corners
140
- V: (N, N+1) vertical edges between corners
141
- scale_x: horizontal stretch factor (>=1). Try 2 or 3 for squarer cells.
142
- """
143
- if cell_flags is not None:
144
- N = cell_flags.shape[0]
145
- H = np.zeros((N+1, N), dtype=bool)
146
- V = np.zeros((N, N+1), dtype=bool)
147
- for r in range(N):
148
- for c in range(N):
149
- s = cell_flags[r, c]
150
- if 'U' in s: H[r, c] = True # edge between (r,c) and (r, c+1) above the cell
151
- if 'D' in s: H[r+1, c] = True # edge below the cell
152
- if 'L' in s: V[r, c] = True # edge left of the cell
153
- if 'R' in s: V[r, c+1] = True # edge right of the cell
154
- assert H is not None and V is not None, 'H and V must be provided'
155
- # Bitmask for corner connections
156
- U, R, D, L = 1, 2, 4, 8
157
- JUNCTION = {
158
- 0: ' ',
159
- U: '│', D: '│', U|D: '│',
160
- L: '─', R: '─', L|R: '─',
161
- U|R: '└', R|D: '┌', D|L: '┐', L|U: '┘',
162
- U|D|L: '┤', U|D|R: '├', L|R|U: '┴', L|R|D: '┬',
163
- U|R|D|L: '┼',
164
- }
165
-
166
- assert scale_x >= 1
167
- N = V.shape[0]
168
- assert H.shape == (N+1, N) and V.shape == (N, N+1)
169
-
170
- rows = 2*N + 1
171
- cols = 2*N*scale_x + 1 # stretched width
172
- canvas = [[' ']*cols for _ in range(rows)]
173
-
174
- def x_corner(c): # x of corner column c
175
- return (2*c) * scale_x
176
- def x_between(c,k): # kth in-between column (1..scale_x) between c and c+1 corners
177
- return (2*c) * scale_x + k
178
-
179
- # horizontal edges: fill the stretched band between corners with '─'
180
- for r in range(N+1):
181
- rr = 2*r
182
- for c in range(N):
183
- if H[r, c]:
184
- # previously: for k in range(1, scale_x*2, 2):
185
- for k in range(1, scale_x*2): # 1..(2*scale_x-1), no gaps
186
- canvas[rr][x_between(c, k)] = '─'
187
-
188
- # vertical edges: draw at the corner columns (no horizontal stretching needed)
189
- for r in range(N):
190
- rr = 2*r + 1
191
- for c in range(N+1):
192
- if V[r, c]:
193
- canvas[rr][x_corner(c)] = '│'
194
-
195
- # junctions at corners
196
- for r in range(N+1):
197
- rr = 2*r
198
- for c in range(N+1):
199
- m = 0
200
- if r > 0 and V[r-1, c]: m |= U
201
- if c < N and H[r, c]: m |= R
202
- if r < N and V[r, c]: m |= D
203
- if c > 0 and H[r, c-1]: m |= L
204
- canvas[rr][x_corner(c)] = JUNCTION[m]
205
-
206
- # centers (help count exact widths/heights)
207
- if mark_centers:
208
- for r in range(N):
209
- rr = 2*r + 1
210
- for c in range(N):
211
- # center lies midway across the stretched span
212
- xc = x_corner(c) + scale_x # middle-ish; works for any integer scale_x
213
- canvas[rr][xc] = center_char if isinstance(center_char, str) else center_char(c, r)
214
-
215
- # turn canvas rows into strings
216
- art_rows = [''.join(row) for row in canvas]
217
-
218
- if not show_axes:
219
- return '\n'.join(art_rows)
220
-
221
- # ── Axes ────────────────────────────────────────────────────────────────
222
- gut = max(2, len(str(N-1))) # left gutter width
223
- gutter = ' ' * gut
224
- top_tens = list(gutter + ' ' * cols)
225
- top_ones = list(gutter + ' ' * cols)
226
-
227
- for c in range(N):
228
- xc_center = x_corner(c) + scale_x
229
- if N >= 10:
230
- top_tens[gut + xc_center] = str((c // 10) % 10)
231
- top_ones[gut + xc_center] = str(c % 10)
232
-
233
- # tiny corner labels
234
- if gut >= 2:
235
- top_tens[gut-2:gut] = list(' ')
236
- top_ones[gut-2:gut] = list(' ')
237
-
238
- labeled = []
239
- for r, line in enumerate(art_rows):
240
- if r % 2 == 1: # cell-center row
241
- label = str(r//2).rjust(gut)
242
- else:
243
- label = ' ' * gut
244
- labeled.append(label + line)
245
-
246
- return ''.join(top_tens) + '\n' + ''.join(top_ones) + '\n' + '\n'.join(labeled)
247
-
248
-
@@ -1,3 +1,4 @@
1
+ # THIS PARSER IS STILL VERY BUGGY
1
2
 
2
3
  def extract_lines(bw):
3
4
  horizontal = np.copy(bw)
@@ -157,11 +158,13 @@ def extract_yinyang_board(image_path, debug=False):
157
158
  return board
158
159
 
159
160
  if __name__ == "__main__":
161
+ # THIS PARSER IS STILL VERY BUGGY
160
162
  # python .\src\puzzle_solver\puzzles\yin_yang\parse_map\parse_map.py | python .\src\puzzle_solver\utils\visualizer.py --read_stdin
161
163
  import cv2
162
164
  import numpy as np
163
165
  from pathlib import Path
164
- image_path = Path(__file__).parent / "input_output" / "MzoyLDcwMSw2NTY=.png"
166
+ image_path = Path(__file__).parent / "input_output" / "OTozLDY2MSw3MjE=.png"
167
+ # image_path = Path(__file__).parent / "input_output" / "MzoyLDcwMSw2NTY=.png"
165
168
  # image_path = Path(__file__).parent / "input_output" / "Njo5MDcsNDk4.png"
166
169
  # image_path = Path(__file__).parent / "input_output" / "MTE6Niw0NjEsMTIx.png"
167
170
  assert image_path.exists(), f"Image file does not exist: {image_path}"