multi-puzzle-solver 1.1.8__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.
- multi_puzzle_solver-1.1.8.dist-info/METADATA +4326 -0
- multi_puzzle_solver-1.1.8.dist-info/RECORD +106 -0
- multi_puzzle_solver-1.1.8.dist-info/WHEEL +5 -0
- multi_puzzle_solver-1.1.8.dist-info/top_level.txt +1 -0
- puzzle_solver/__init__.py +184 -0
- puzzle_solver/core/utils.py +298 -0
- puzzle_solver/core/utils_ortools.py +333 -0
- puzzle_solver/core/utils_visualizer.py +575 -0
- puzzle_solver/puzzles/abc_view/abc_view.py +75 -0
- puzzle_solver/puzzles/aquarium/aquarium.py +97 -0
- puzzle_solver/puzzles/area_51/area_51.py +159 -0
- puzzle_solver/puzzles/battleships/battleships.py +139 -0
- puzzle_solver/puzzles/binairo/binairo.py +98 -0
- puzzle_solver/puzzles/binairo/binairo_plus.py +7 -0
- puzzle_solver/puzzles/black_box/black_box.py +243 -0
- puzzle_solver/puzzles/branches/branches.py +64 -0
- puzzle_solver/puzzles/bridges/bridges.py +104 -0
- puzzle_solver/puzzles/chess_range/chess_melee.py +6 -0
- puzzle_solver/puzzles/chess_range/chess_range.py +406 -0
- puzzle_solver/puzzles/chess_range/chess_solo.py +9 -0
- puzzle_solver/puzzles/chess_sequence/chess_sequence.py +262 -0
- puzzle_solver/puzzles/circle_9/circle_9.py +44 -0
- puzzle_solver/puzzles/clouds/clouds.py +81 -0
- puzzle_solver/puzzles/connect_the_dots/connect_the_dots.py +50 -0
- puzzle_solver/puzzles/cow_and_cactus/cow_and_cactus.py +66 -0
- puzzle_solver/puzzles/dominosa/dominosa.py +67 -0
- puzzle_solver/puzzles/filling/filling.py +94 -0
- puzzle_solver/puzzles/flip/flip.py +64 -0
- puzzle_solver/puzzles/flood_it/flood_it.py +174 -0
- puzzle_solver/puzzles/flood_it/parse_map/parse_map.py +197 -0
- puzzle_solver/puzzles/galaxies/galaxies.py +110 -0
- puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +216 -0
- puzzle_solver/puzzles/guess/guess.py +232 -0
- puzzle_solver/puzzles/heyawake/heyawake.py +152 -0
- puzzle_solver/puzzles/hidden_stars/hidden_stars.py +52 -0
- puzzle_solver/puzzles/hidoku/hidoku.py +59 -0
- puzzle_solver/puzzles/inertia/inertia.py +121 -0
- puzzle_solver/puzzles/inertia/parse_map/parse_map.py +207 -0
- puzzle_solver/puzzles/inertia/tsp.py +400 -0
- puzzle_solver/puzzles/kakurasu/kakurasu.py +38 -0
- puzzle_solver/puzzles/kakuro/kakuro.py +81 -0
- puzzle_solver/puzzles/kakuro/krypto_kakuro.py +95 -0
- puzzle_solver/puzzles/keen/keen.py +76 -0
- puzzle_solver/puzzles/kropki/kropki.py +94 -0
- puzzle_solver/puzzles/light_up/light_up.py +58 -0
- puzzle_solver/puzzles/linesweeper/linesweeper.py +71 -0
- puzzle_solver/puzzles/link_a_pix/link_a_pix.py +91 -0
- puzzle_solver/puzzles/lits/lits.py +138 -0
- puzzle_solver/puzzles/magnets/magnets.py +96 -0
- puzzle_solver/puzzles/map/map.py +56 -0
- puzzle_solver/puzzles/mathema_grids/mathema_grids.py +119 -0
- puzzle_solver/puzzles/mathrax/mathrax.py +93 -0
- puzzle_solver/puzzles/minesweeper/minesweeper.py +123 -0
- puzzle_solver/puzzles/mosaic/mosaic.py +38 -0
- puzzle_solver/puzzles/n_queens/n_queens.py +71 -0
- puzzle_solver/puzzles/nonograms/nonograms.py +121 -0
- puzzle_solver/puzzles/nonograms/nonograms_colored.py +220 -0
- puzzle_solver/puzzles/norinori/norinori.py +96 -0
- puzzle_solver/puzzles/number_path/number_path.py +76 -0
- puzzle_solver/puzzles/numbermaze/numbermaze.py +97 -0
- puzzle_solver/puzzles/nurikabe/nurikabe.py +130 -0
- puzzle_solver/puzzles/palisade/palisade.py +91 -0
- puzzle_solver/puzzles/pearl/pearl.py +107 -0
- puzzle_solver/puzzles/pipes/pipes.py +82 -0
- puzzle_solver/puzzles/range/range.py +59 -0
- puzzle_solver/puzzles/rectangles/rectangles.py +128 -0
- puzzle_solver/puzzles/ripple_effect/ripple_effect.py +83 -0
- puzzle_solver/puzzles/rooms/rooms.py +75 -0
- puzzle_solver/puzzles/schurs_numbers/schurs_numbers.py +73 -0
- puzzle_solver/puzzles/shakashaka/shakashaka.py +201 -0
- puzzle_solver/puzzles/shingoki/shingoki.py +116 -0
- puzzle_solver/puzzles/signpost/signpost.py +93 -0
- puzzle_solver/puzzles/singles/singles.py +53 -0
- puzzle_solver/puzzles/slant/parse_map/parse_map.py +135 -0
- puzzle_solver/puzzles/slant/slant.py +111 -0
- puzzle_solver/puzzles/slitherlink/slitherlink.py +130 -0
- puzzle_solver/puzzles/snail/snail.py +97 -0
- puzzle_solver/puzzles/split_ends/split_ends.py +93 -0
- puzzle_solver/puzzles/star_battle/star_battle.py +75 -0
- puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +7 -0
- puzzle_solver/puzzles/stitches/parse_map/parse_map.py +267 -0
- puzzle_solver/puzzles/stitches/stitches.py +96 -0
- puzzle_solver/puzzles/sudoku/sudoku.py +267 -0
- puzzle_solver/puzzles/suguru/suguru.py +55 -0
- puzzle_solver/puzzles/suko/suko.py +54 -0
- puzzle_solver/puzzles/tapa/tapa.py +97 -0
- puzzle_solver/puzzles/tatami/tatami.py +64 -0
- puzzle_solver/puzzles/tents/tents.py +80 -0
- puzzle_solver/puzzles/thermometers/thermometers.py +82 -0
- puzzle_solver/puzzles/towers/towers.py +89 -0
- puzzle_solver/puzzles/tracks/tracks.py +88 -0
- puzzle_solver/puzzles/trees_logic/trees_logic.py +48 -0
- puzzle_solver/puzzles/troix/dumplings.py +7 -0
- puzzle_solver/puzzles/troix/troix.py +75 -0
- puzzle_solver/puzzles/twiddle/twiddle.py +112 -0
- puzzle_solver/puzzles/undead/undead.py +130 -0
- puzzle_solver/puzzles/unequal/unequal.py +128 -0
- puzzle_solver/puzzles/unruly/unruly.py +54 -0
- puzzle_solver/puzzles/vectors/vectors.py +94 -0
- puzzle_solver/puzzles/vermicelli/vermicelli.py +74 -0
- puzzle_solver/puzzles/walls/walls.py +52 -0
- puzzle_solver/puzzles/yajilin/yajilin.py +87 -0
- puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py +172 -0
- puzzle_solver/puzzles/yin_yang/yin_yang.py +103 -0
- puzzle_solver/utils/etc/parser/board_color_digit.py +497 -0
- puzzle_solver/utils/visualizer.py +155 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse
|
|
3
|
+
from typing import Any, Mapping, Tuple
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
10
|
+
|
|
11
|
+
RGB = Tuple[int, int, int]
|
|
12
|
+
|
|
13
|
+
def render_board_image(
|
|
14
|
+
board: np.ndarray,
|
|
15
|
+
colors: Mapping[Any, RGB],
|
|
16
|
+
cell_size: int = 64,
|
|
17
|
+
grid: bool = True,
|
|
18
|
+
bg_default: RGB = (240, 240, 240),
|
|
19
|
+
text_color: RGB = (0, 0, 0),
|
|
20
|
+
padding: int = 20,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Render a 2D numpy array as a colored grid image with centered text labels.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
board: 2D numpy array (dtype can be object/str/int/etc.). Each cell's value
|
|
27
|
+
is looked up in `colors` for its fill color.
|
|
28
|
+
colors: Dict-like mapping from cell values to RGB tuples (0-255).
|
|
29
|
+
cell_size: Square side length (pixels) for each cell.
|
|
30
|
+
grid: Whether to draw grid lines around cells.
|
|
31
|
+
bg_default: Fill color when a cell's value is not in `colors`.
|
|
32
|
+
text_color: Color for text labels.
|
|
33
|
+
padding: Extra pixels around the entire grid on each side.
|
|
34
|
+
"""
|
|
35
|
+
if board.ndim != 2:
|
|
36
|
+
raise ValueError("`board` must be a 2D numpy array.")
|
|
37
|
+
|
|
38
|
+
rows, cols = board.shape
|
|
39
|
+
width = cols * cell_size + padding * 2
|
|
40
|
+
height = rows * cell_size + padding * 2
|
|
41
|
+
|
|
42
|
+
img = Image.new("RGB", (width, height), color=bg_default)
|
|
43
|
+
draw = ImageDraw.Draw(img)
|
|
44
|
+
|
|
45
|
+
# font size
|
|
46
|
+
desired_pt = max(10, int(cell_size * 0.4))
|
|
47
|
+
font = ImageFont.truetype("DejaVuSans.ttf", desired_pt)
|
|
48
|
+
|
|
49
|
+
missing_values = set()
|
|
50
|
+
for r in range(rows):
|
|
51
|
+
for c in range(cols):
|
|
52
|
+
val = board[r, c]
|
|
53
|
+
if isinstance(val, np.generic):
|
|
54
|
+
val = val.item()
|
|
55
|
+
if val not in colors:
|
|
56
|
+
print(f'missing value: {val} of type {type(val)} at position {r}, {c}')
|
|
57
|
+
missing_values.add(val)
|
|
58
|
+
fill = colors.get(val, bg_default)
|
|
59
|
+
|
|
60
|
+
x0 = padding + c * cell_size
|
|
61
|
+
y0 = padding + r * cell_size
|
|
62
|
+
x1 = x0 + cell_size
|
|
63
|
+
y1 = y0 + cell_size
|
|
64
|
+
|
|
65
|
+
draw.rectangle([x0, y0, x1, y1], fill=fill)
|
|
66
|
+
|
|
67
|
+
if grid:
|
|
68
|
+
draw.rectangle([x0, y0, x1, y1], outline=(0, 0, 0), width=1)
|
|
69
|
+
|
|
70
|
+
# Center the text
|
|
71
|
+
text = "" if val is None else str(val)
|
|
72
|
+
if text:
|
|
73
|
+
# textbbox gives more accurate sizing than textsize
|
|
74
|
+
bbox = draw.textbbox((0, 0), text, font=font)
|
|
75
|
+
tw = bbox[2] - bbox[0]
|
|
76
|
+
th = bbox[3] - bbox[1]
|
|
77
|
+
tx = x0 + (cell_size - tw) / 2
|
|
78
|
+
ty = y0 + (cell_size - th) / 2
|
|
79
|
+
draw.text((tx, ty), text, fill=text_color, font=font)
|
|
80
|
+
|
|
81
|
+
img.show()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_input():
|
|
88
|
+
parser = argparse.ArgumentParser()
|
|
89
|
+
parser.add_argument('--read_stdin', action='store_true')
|
|
90
|
+
parser.add_argument('--clipboard', action='store_true')
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
if args.read_stdin:
|
|
93
|
+
# read from stdin until the line starts with "output json: "
|
|
94
|
+
print('reading board from stdin until the line starts with "output json: "')
|
|
95
|
+
json_path = None
|
|
96
|
+
while True:
|
|
97
|
+
line = sys.stdin.readline()
|
|
98
|
+
if line.startswith('output json: '):
|
|
99
|
+
json_path = line.split('output json: ')[1].strip()
|
|
100
|
+
break
|
|
101
|
+
with open(json_path, 'r') as f:
|
|
102
|
+
board = np.array(json.load(f))
|
|
103
|
+
print(f'read board from {json_path}')
|
|
104
|
+
elif args.clipboard:
|
|
105
|
+
import pyperclip
|
|
106
|
+
pasted = pyperclip.paste()
|
|
107
|
+
print('got from clipboard: ', pasted)
|
|
108
|
+
print('eval: ', eval(pasted))
|
|
109
|
+
board = np.array(eval(pasted))
|
|
110
|
+
assert board.ndim == 2, f'board must be a 2D numpy array, got {board.ndim}'
|
|
111
|
+
assert board.shape[0] > 0, 'board must have at least one row'
|
|
112
|
+
assert board.shape[1] > 0, 'board must have at least one column'
|
|
113
|
+
else:
|
|
114
|
+
# with open('src/puzzle_solver/puzzles/stitches/parse_map/input_output/MTM6OSw4MjEsNDAx.json', 'r') as f:
|
|
115
|
+
# board = np.array(json.load(f))
|
|
116
|
+
board = np.array([
|
|
117
|
+
['01', '123', '01', '01', '02', '02', '02', '03', '03', '03', '03', '04', '05', '05', '05'],
|
|
118
|
+
['01', '02', '02', '02', '02', '06', '07', '07', '03', '08', '03', '04', '04', '05', '09'],
|
|
119
|
+
['01', '01', '02', '11', '06', '06', '06', '12', '12', '08', '13', '13', '13', '09', '09'],
|
|
120
|
+
['01', '11', '11', '11', '14', '06', '06', '12', '12', '15', '15', '13', '09', '09', '09'],
|
|
121
|
+
['01', '01', '11', '11', '14', '12', '12', '12', '16', '16', '15', '13', '13', '17', '09'],
|
|
122
|
+
['01', '11', '11', '14', '14', '12', '42', '42', '42', '15', '15', '13', '13', '17', '18'],
|
|
123
|
+
['01', '11', '11', '14', '14', '12', '12', '43', '15', '15', '20', '13', '13', '17', '18'],
|
|
124
|
+
['01', '01', '11', '19', '19', '19', '43', '43', '44', '20', '20', '20', '13', '17', '18'],
|
|
125
|
+
['01', '22', '23', '23', '23', '19', '43', '21', '21', '24', '24', '24', '25', '17', '17'],
|
|
126
|
+
['22', '22', '22', '23', '19', '19', '26', '24', '24', '24', '28', '28', '25', '17', '33'],
|
|
127
|
+
['22', '22', '23', '23', '27', '27', '26', '26', '24', '24', '29', '29', '25', '25', '33'],
|
|
128
|
+
['22', '22', '35', '27', '27', '26', '26', '26', '26', '30', '30', '30', '25', '34', '34'],
|
|
129
|
+
['37', '22', '35', '35', '35', '35', '35', '26', '26', '30', '31', '31', '32', '32', '40'],
|
|
130
|
+
['37', '37', '37', '36', '36', '35', '26', '26', '26', '40', '40', '40', '40', '40', '40'],
|
|
131
|
+
['37', '37', '37', '37', '35', '35', '38', '38', '39', '39', '40', '40', '40', '41', '41'],
|
|
132
|
+
])
|
|
133
|
+
return board
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
# to read numpy array from clipboard run: python .\src\puzzle_solver\utils\visualizer.py --clipboard
|
|
137
|
+
board = get_input()
|
|
138
|
+
print('Visualizing board:')
|
|
139
|
+
print('[')
|
|
140
|
+
for i,row in enumerate(board):
|
|
141
|
+
print(' [' + ', '.join([f"'{c}'" for c in row]) + ']', end='')
|
|
142
|
+
if i != len(board) - 1:
|
|
143
|
+
print(',')
|
|
144
|
+
print('\n]')
|
|
145
|
+
# rcolors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255), (255, 255, 255), (128, 128, 128)]
|
|
146
|
+
vs =[0, 128, 255]
|
|
147
|
+
rcolors = [(v1, v2, v3) for v1 in vs for v2 in vs for v3 in vs if (v1, v2, v3) != (0, 0, 0)]
|
|
148
|
+
random.shuffle(rcolors)
|
|
149
|
+
nums = set([c.item() for c in np.nditer(board)])
|
|
150
|
+
colors = {c: rcolors[i % len(rcolors)] for i, c in enumerate(nums)}
|
|
151
|
+
print(nums)
|
|
152
|
+
print('max i:', max(nums))
|
|
153
|
+
if all(str(c).isdigit() for c in nums):
|
|
154
|
+
print('skipped:', set(range(int(max(nums)) + 1)) - set(int(i) for i in nums))
|
|
155
|
+
render_board_image(board, colors)
|