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.
Files changed (106) hide show
  1. multi_puzzle_solver-1.1.8.dist-info/METADATA +4326 -0
  2. multi_puzzle_solver-1.1.8.dist-info/RECORD +106 -0
  3. multi_puzzle_solver-1.1.8.dist-info/WHEEL +5 -0
  4. multi_puzzle_solver-1.1.8.dist-info/top_level.txt +1 -0
  5. puzzle_solver/__init__.py +184 -0
  6. puzzle_solver/core/utils.py +298 -0
  7. puzzle_solver/core/utils_ortools.py +333 -0
  8. puzzle_solver/core/utils_visualizer.py +575 -0
  9. puzzle_solver/puzzles/abc_view/abc_view.py +75 -0
  10. puzzle_solver/puzzles/aquarium/aquarium.py +97 -0
  11. puzzle_solver/puzzles/area_51/area_51.py +159 -0
  12. puzzle_solver/puzzles/battleships/battleships.py +139 -0
  13. puzzle_solver/puzzles/binairo/binairo.py +98 -0
  14. puzzle_solver/puzzles/binairo/binairo_plus.py +7 -0
  15. puzzle_solver/puzzles/black_box/black_box.py +243 -0
  16. puzzle_solver/puzzles/branches/branches.py +64 -0
  17. puzzle_solver/puzzles/bridges/bridges.py +104 -0
  18. puzzle_solver/puzzles/chess_range/chess_melee.py +6 -0
  19. puzzle_solver/puzzles/chess_range/chess_range.py +406 -0
  20. puzzle_solver/puzzles/chess_range/chess_solo.py +9 -0
  21. puzzle_solver/puzzles/chess_sequence/chess_sequence.py +262 -0
  22. puzzle_solver/puzzles/circle_9/circle_9.py +44 -0
  23. puzzle_solver/puzzles/clouds/clouds.py +81 -0
  24. puzzle_solver/puzzles/connect_the_dots/connect_the_dots.py +50 -0
  25. puzzle_solver/puzzles/cow_and_cactus/cow_and_cactus.py +66 -0
  26. puzzle_solver/puzzles/dominosa/dominosa.py +67 -0
  27. puzzle_solver/puzzles/filling/filling.py +94 -0
  28. puzzle_solver/puzzles/flip/flip.py +64 -0
  29. puzzle_solver/puzzles/flood_it/flood_it.py +174 -0
  30. puzzle_solver/puzzles/flood_it/parse_map/parse_map.py +197 -0
  31. puzzle_solver/puzzles/galaxies/galaxies.py +110 -0
  32. puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +216 -0
  33. puzzle_solver/puzzles/guess/guess.py +232 -0
  34. puzzle_solver/puzzles/heyawake/heyawake.py +152 -0
  35. puzzle_solver/puzzles/hidden_stars/hidden_stars.py +52 -0
  36. puzzle_solver/puzzles/hidoku/hidoku.py +59 -0
  37. puzzle_solver/puzzles/inertia/inertia.py +121 -0
  38. puzzle_solver/puzzles/inertia/parse_map/parse_map.py +207 -0
  39. puzzle_solver/puzzles/inertia/tsp.py +400 -0
  40. puzzle_solver/puzzles/kakurasu/kakurasu.py +38 -0
  41. puzzle_solver/puzzles/kakuro/kakuro.py +81 -0
  42. puzzle_solver/puzzles/kakuro/krypto_kakuro.py +95 -0
  43. puzzle_solver/puzzles/keen/keen.py +76 -0
  44. puzzle_solver/puzzles/kropki/kropki.py +94 -0
  45. puzzle_solver/puzzles/light_up/light_up.py +58 -0
  46. puzzle_solver/puzzles/linesweeper/linesweeper.py +71 -0
  47. puzzle_solver/puzzles/link_a_pix/link_a_pix.py +91 -0
  48. puzzle_solver/puzzles/lits/lits.py +138 -0
  49. puzzle_solver/puzzles/magnets/magnets.py +96 -0
  50. puzzle_solver/puzzles/map/map.py +56 -0
  51. puzzle_solver/puzzles/mathema_grids/mathema_grids.py +119 -0
  52. puzzle_solver/puzzles/mathrax/mathrax.py +93 -0
  53. puzzle_solver/puzzles/minesweeper/minesweeper.py +123 -0
  54. puzzle_solver/puzzles/mosaic/mosaic.py +38 -0
  55. puzzle_solver/puzzles/n_queens/n_queens.py +71 -0
  56. puzzle_solver/puzzles/nonograms/nonograms.py +121 -0
  57. puzzle_solver/puzzles/nonograms/nonograms_colored.py +220 -0
  58. puzzle_solver/puzzles/norinori/norinori.py +96 -0
  59. puzzle_solver/puzzles/number_path/number_path.py +76 -0
  60. puzzle_solver/puzzles/numbermaze/numbermaze.py +97 -0
  61. puzzle_solver/puzzles/nurikabe/nurikabe.py +130 -0
  62. puzzle_solver/puzzles/palisade/palisade.py +91 -0
  63. puzzle_solver/puzzles/pearl/pearl.py +107 -0
  64. puzzle_solver/puzzles/pipes/pipes.py +82 -0
  65. puzzle_solver/puzzles/range/range.py +59 -0
  66. puzzle_solver/puzzles/rectangles/rectangles.py +128 -0
  67. puzzle_solver/puzzles/ripple_effect/ripple_effect.py +83 -0
  68. puzzle_solver/puzzles/rooms/rooms.py +75 -0
  69. puzzle_solver/puzzles/schurs_numbers/schurs_numbers.py +73 -0
  70. puzzle_solver/puzzles/shakashaka/shakashaka.py +201 -0
  71. puzzle_solver/puzzles/shingoki/shingoki.py +116 -0
  72. puzzle_solver/puzzles/signpost/signpost.py +93 -0
  73. puzzle_solver/puzzles/singles/singles.py +53 -0
  74. puzzle_solver/puzzles/slant/parse_map/parse_map.py +135 -0
  75. puzzle_solver/puzzles/slant/slant.py +111 -0
  76. puzzle_solver/puzzles/slitherlink/slitherlink.py +130 -0
  77. puzzle_solver/puzzles/snail/snail.py +97 -0
  78. puzzle_solver/puzzles/split_ends/split_ends.py +93 -0
  79. puzzle_solver/puzzles/star_battle/star_battle.py +75 -0
  80. puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +7 -0
  81. puzzle_solver/puzzles/stitches/parse_map/parse_map.py +267 -0
  82. puzzle_solver/puzzles/stitches/stitches.py +96 -0
  83. puzzle_solver/puzzles/sudoku/sudoku.py +267 -0
  84. puzzle_solver/puzzles/suguru/suguru.py +55 -0
  85. puzzle_solver/puzzles/suko/suko.py +54 -0
  86. puzzle_solver/puzzles/tapa/tapa.py +97 -0
  87. puzzle_solver/puzzles/tatami/tatami.py +64 -0
  88. puzzle_solver/puzzles/tents/tents.py +80 -0
  89. puzzle_solver/puzzles/thermometers/thermometers.py +82 -0
  90. puzzle_solver/puzzles/towers/towers.py +89 -0
  91. puzzle_solver/puzzles/tracks/tracks.py +88 -0
  92. puzzle_solver/puzzles/trees_logic/trees_logic.py +48 -0
  93. puzzle_solver/puzzles/troix/dumplings.py +7 -0
  94. puzzle_solver/puzzles/troix/troix.py +75 -0
  95. puzzle_solver/puzzles/twiddle/twiddle.py +112 -0
  96. puzzle_solver/puzzles/undead/undead.py +130 -0
  97. puzzle_solver/puzzles/unequal/unequal.py +128 -0
  98. puzzle_solver/puzzles/unruly/unruly.py +54 -0
  99. puzzle_solver/puzzles/vectors/vectors.py +94 -0
  100. puzzle_solver/puzzles/vermicelli/vermicelli.py +74 -0
  101. puzzle_solver/puzzles/walls/walls.py +52 -0
  102. puzzle_solver/puzzles/yajilin/yajilin.py +87 -0
  103. puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py +172 -0
  104. puzzle_solver/puzzles/yin_yang/yin_yang.py +103 -0
  105. puzzle_solver/utils/etc/parser/board_color_digit.py +497 -0
  106. 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)