multi-puzzle-solver 0.9.10__py3-none-any.whl → 0.9.13__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.10.dist-info → multi_puzzle_solver-0.9.13.dist-info}/METADATA +97 -1
- {multi_puzzle_solver-0.9.10.dist-info → multi_puzzle_solver-0.9.13.dist-info}/RECORD +12 -10
- puzzle_solver/__init__.py +2 -1
- puzzle_solver/core/utils.py +1 -1
- puzzle_solver/core/utils_ortools.py +91 -1
- puzzle_solver/puzzles/battleships/battleships.py +0 -1
- puzzle_solver/puzzles/lits/lits.py +255 -0
- puzzle_solver/puzzles/norinori/norinori.py +255 -0
- puzzle_solver/puzzles/stitches/parse_map/parse_map.py +212 -211
- puzzle_solver/utils/visualizer.py +15 -1
- {multi_puzzle_solver-0.9.10.dist-info → multi_puzzle_solver-0.9.13.dist-info}/WHEEL +0 -0
- {multi_puzzle_solver-0.9.10.dist-info → multi_puzzle_solver-0.9.13.dist-info}/top_level.txt +0 -0
|
@@ -1,211 +1,212 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file is a simple helper that parses the images from https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/inertia.html and converts them to a json file.
|
|
3
|
-
Look at the ./input_output/ directory for examples of input images and output json files.
|
|
4
|
-
The output json is used in the test_solve.py file to test the solver.
|
|
5
|
-
"""
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import numpy as np
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# print(f"
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
cv.
|
|
55
|
-
cv.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if output['
|
|
83
|
-
dfs(x, y
|
|
84
|
-
if output['
|
|
85
|
-
dfs(x
|
|
86
|
-
if output['
|
|
87
|
-
dfs(x
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
src
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
horizontal_idx
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
width
|
|
122
|
-
print(f"
|
|
123
|
-
print(f"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
top
|
|
143
|
-
|
|
144
|
-
left
|
|
145
|
-
|
|
146
|
-
right
|
|
147
|
-
|
|
148
|
-
bottom =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
axs[0, 0].
|
|
153
|
-
axs[0,
|
|
154
|
-
axs[0, 1].
|
|
155
|
-
axs[
|
|
156
|
-
axs[1, 0].
|
|
157
|
-
axs[1,
|
|
158
|
-
axs[1, 1].
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
axs[0,
|
|
165
|
-
axs[
|
|
166
|
-
axs[1,
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
print(
|
|
182
|
-
print('
|
|
183
|
-
print('
|
|
184
|
-
print('
|
|
185
|
-
print('
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
# main(Path(__file__).parent / 'input_output' / '
|
|
210
|
-
# main(Path(__file__).parent / 'input_output' / '
|
|
211
|
-
main(Path(__file__).parent / 'input_output' / '
|
|
1
|
+
"""
|
|
2
|
+
This file is a simple helper that parses the images from https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/inertia.html and converts them to a json file.
|
|
3
|
+
Look at the ./input_output/ directory for examples of input images and output json files.
|
|
4
|
+
The output json is used in the test_solve.py file to test the solver.
|
|
5
|
+
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import numpy as np
|
|
8
|
+
cv = None
|
|
9
|
+
Image = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def extract_lines(bw):
|
|
13
|
+
# Create the images that will use to extract the horizontal and vertical lines
|
|
14
|
+
horizontal = np.copy(bw)
|
|
15
|
+
vertical = np.copy(bw)
|
|
16
|
+
|
|
17
|
+
cols = horizontal.shape[1]
|
|
18
|
+
horizontal_size = cols // 5
|
|
19
|
+
# Create structure element for extracting horizontal lines through morphology operations
|
|
20
|
+
horizontalStructure = cv.getStructuringElement(cv.MORPH_RECT, (horizontal_size, 1))
|
|
21
|
+
horizontal = cv.erode(horizontal, horizontalStructure)
|
|
22
|
+
horizontal = cv.dilate(horizontal, horizontalStructure)
|
|
23
|
+
horizontal_means = np.mean(horizontal, axis=1)
|
|
24
|
+
horizontal_cutoff = np.percentile(horizontal_means, 50)
|
|
25
|
+
# location where the horizontal lines are
|
|
26
|
+
horizontal_idx = np.where(horizontal_means > horizontal_cutoff)[0]
|
|
27
|
+
# print(f"horizontal_idx: {horizontal_idx}")
|
|
28
|
+
height = len(horizontal_idx)
|
|
29
|
+
# show_wait_destroy("horizontal", horizontal) # this has the horizontal lines
|
|
30
|
+
|
|
31
|
+
rows = vertical.shape[0]
|
|
32
|
+
verticalsize = rows // 5
|
|
33
|
+
# Create structure element for extracting vertical lines through morphology operations
|
|
34
|
+
verticalStructure = cv.getStructuringElement(cv.MORPH_RECT, (1, verticalsize))
|
|
35
|
+
vertical = cv.erode(vertical, verticalStructure)
|
|
36
|
+
vertical = cv.dilate(vertical, verticalStructure)
|
|
37
|
+
vertical_means = np.mean(vertical, axis=0)
|
|
38
|
+
vertical_cutoff = np.percentile(vertical_means, 50)
|
|
39
|
+
vertical_idx = np.where(vertical_means > vertical_cutoff)[0]
|
|
40
|
+
# print(f"vertical_idx: {vertical_idx}")
|
|
41
|
+
width = len(vertical_idx)
|
|
42
|
+
# print(f"height: {height}, width: {width}")
|
|
43
|
+
# print(f"vertical_means: {vertical_means}")
|
|
44
|
+
# show_wait_destroy("vertical", vertical) # this has the vertical lines
|
|
45
|
+
|
|
46
|
+
vertical = cv.bitwise_not(vertical)
|
|
47
|
+
# show_wait_destroy("vertical_bit", vertical)
|
|
48
|
+
|
|
49
|
+
return horizontal_idx, vertical_idx
|
|
50
|
+
|
|
51
|
+
def show_wait_destroy(winname, img):
|
|
52
|
+
cv.imshow(winname, img)
|
|
53
|
+
cv.moveWindow(winname, 500, 0)
|
|
54
|
+
cv.waitKey(0)
|
|
55
|
+
cv.destroyWindow(winname)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def mean_consecutives(arr: np.ndarray) -> np.ndarray:
|
|
59
|
+
"""if a sequence of values is consecutive, then average the values"""
|
|
60
|
+
sums = []
|
|
61
|
+
counts = []
|
|
62
|
+
for i in range(len(arr)):
|
|
63
|
+
if i == 0:
|
|
64
|
+
sums.append(arr[i])
|
|
65
|
+
counts.append(1)
|
|
66
|
+
elif arr[i] == arr[i-1] + 1:
|
|
67
|
+
sums[-1] += arr[i]
|
|
68
|
+
counts[-1] += 1
|
|
69
|
+
else:
|
|
70
|
+
sums.append(arr[i])
|
|
71
|
+
counts.append(1)
|
|
72
|
+
return np.array(sums) // np.array(counts)
|
|
73
|
+
|
|
74
|
+
def dfs(x, y, out, output, current_num):
|
|
75
|
+
if x < 0 or x >= out.shape[1] or y < 0 or y >= out.shape[0]:
|
|
76
|
+
return
|
|
77
|
+
if out[y, x] != ' ':
|
|
78
|
+
return
|
|
79
|
+
out[y, x] = current_num
|
|
80
|
+
if output['top'][y, x] == 0:
|
|
81
|
+
dfs(x, y-1, out, output, current_num)
|
|
82
|
+
if output['left'][y, x] == 0:
|
|
83
|
+
dfs(x-1, y, out, output, current_num)
|
|
84
|
+
if output['right'][y, x] == 0:
|
|
85
|
+
dfs(x+1, y, out, output, current_num)
|
|
86
|
+
if output['bottom'][y, x] == 0:
|
|
87
|
+
dfs(x, y+1, out, output, current_num)
|
|
88
|
+
|
|
89
|
+
def main(image):
|
|
90
|
+
global Image
|
|
91
|
+
global cv
|
|
92
|
+
import matplotlib.pyplot as plt
|
|
93
|
+
from PIL import Image as Image_module
|
|
94
|
+
import cv2 as cv_module
|
|
95
|
+
Image = Image_module
|
|
96
|
+
cv = cv_module
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
image_path = Path(image)
|
|
100
|
+
output_path = image_path.parent / (image_path.stem + '.json')
|
|
101
|
+
src = cv.imread(image, cv.IMREAD_COLOR)
|
|
102
|
+
assert src is not None, f'Error opening image: {image}'
|
|
103
|
+
if len(src.shape) != 2:
|
|
104
|
+
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
|
|
105
|
+
else:
|
|
106
|
+
gray = src
|
|
107
|
+
# now the image is in grayscale
|
|
108
|
+
|
|
109
|
+
# Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
|
|
110
|
+
gray = cv.bitwise_not(gray)
|
|
111
|
+
bw = cv.adaptiveThreshold(gray.copy(), 255, cv.ADAPTIVE_THRESH_MEAN_C, \
|
|
112
|
+
cv.THRESH_BINARY, 15, -2)
|
|
113
|
+
# show_wait_destroy("binary", bw)
|
|
114
|
+
|
|
115
|
+
# show_wait_destroy("src", src)
|
|
116
|
+
horizontal_idx, vertical_idx = extract_lines(bw)
|
|
117
|
+
horizontal_idx = mean_consecutives(horizontal_idx)
|
|
118
|
+
vertical_idx = mean_consecutives(vertical_idx)
|
|
119
|
+
height = len(horizontal_idx)
|
|
120
|
+
width = len(vertical_idx)
|
|
121
|
+
print(f"height: {height}, width: {width}")
|
|
122
|
+
print(f"horizontal_idx: {horizontal_idx}")
|
|
123
|
+
print(f"vertical_idx: {vertical_idx}")
|
|
124
|
+
arr = np.zeros((height - 1, width - 1), dtype=object)
|
|
125
|
+
output = {'top': arr.copy(), 'left': arr.copy(), 'right': arr.copy(), 'bottom': arr.copy()}
|
|
126
|
+
target = 200_000
|
|
127
|
+
hists = {'top': {}, 'left': {}, 'right': {}, 'bottom': {}}
|
|
128
|
+
for j in range(height - 1):
|
|
129
|
+
for i in range(width - 1):
|
|
130
|
+
hidx1, hidx2 = horizontal_idx[j], horizontal_idx[j+1]
|
|
131
|
+
vidx1, vidx2 = vertical_idx[i], vertical_idx[i+1]
|
|
132
|
+
hidx1 = max(0, hidx1 - 2)
|
|
133
|
+
hidx2 = min(src.shape[0], hidx2 + 4)
|
|
134
|
+
vidx1 = max(0, vidx1 - 2)
|
|
135
|
+
vidx2 = min(src.shape[1], vidx2 + 4)
|
|
136
|
+
cell = src[hidx1:hidx2, vidx1:vidx2]
|
|
137
|
+
mid_x = cell.shape[1] // 2
|
|
138
|
+
mid_y = cell.shape[0] // 2
|
|
139
|
+
# show_wait_destroy(f"cell_{i}_{j}", cell)
|
|
140
|
+
cell = cv.bitwise_not(cell) # invert colors
|
|
141
|
+
top = cell[0:10, mid_y-5:mid_y+5]
|
|
142
|
+
hists['top'][j, i] = np.sum(top)
|
|
143
|
+
left = cell[mid_x-5:mid_x+5, 0:10]
|
|
144
|
+
hists['left'][j, i] = np.sum(left)
|
|
145
|
+
right = cell[mid_x-5:mid_x+5, -10:]
|
|
146
|
+
hists['right'][j, i] = np.sum(right)
|
|
147
|
+
bottom = cell[-10:, mid_y-5:mid_y+5]
|
|
148
|
+
hists['bottom'][j, i] = np.sum(bottom)
|
|
149
|
+
|
|
150
|
+
fig, axs = plt.subplots(2, 2)
|
|
151
|
+
axs[0, 0].hist(list(hists['top'].values()), bins=100)
|
|
152
|
+
axs[0, 0].set_title('Top')
|
|
153
|
+
axs[0, 1].hist(list(hists['left'].values()), bins=100)
|
|
154
|
+
axs[0, 1].set_title('Left')
|
|
155
|
+
axs[1, 0].hist(list(hists['right'].values()), bins=100)
|
|
156
|
+
axs[1, 0].set_title('Right')
|
|
157
|
+
axs[1, 1].hist(list(hists['bottom'].values()), bins=100)
|
|
158
|
+
axs[1, 1].set_title('Bottom')
|
|
159
|
+
target_top = np.mean(list(hists['top'].values()))
|
|
160
|
+
target_left = np.mean(list(hists['left'].values()))
|
|
161
|
+
target_right = np.mean(list(hists['right'].values()))
|
|
162
|
+
target_bottom = np.mean(list(hists['bottom'].values()))
|
|
163
|
+
axs[0, 0].axvline(target_top, color='red')
|
|
164
|
+
axs[0, 1].axvline(target_left, color='red')
|
|
165
|
+
axs[1, 0].axvline(target_right, color='red')
|
|
166
|
+
axs[1, 1].axvline(target_bottom, color='red')
|
|
167
|
+
# plt.show()
|
|
168
|
+
# 1/0
|
|
169
|
+
print(f"target_top: {target_top}, target_left: {target_left}, target_right: {target_right}, target_bottom: {target_bottom}")
|
|
170
|
+
for j in range(height - 1):
|
|
171
|
+
for i in range(width - 1):
|
|
172
|
+
if hists['top'][j, i] > target_top:
|
|
173
|
+
output['top'][j, i] = 1
|
|
174
|
+
if hists['left'][j, i] > target_left:
|
|
175
|
+
output['left'][j, i] = 1
|
|
176
|
+
if hists['right'][j, i] > target_right:
|
|
177
|
+
output['right'][j, i] = 1
|
|
178
|
+
if hists['bottom'][j, i] > target_bottom:
|
|
179
|
+
output['bottom'][j, i] = 1
|
|
180
|
+
print(f"cell_{j}_{i}", end=': ')
|
|
181
|
+
print('T' if output['top'][j, i] else '', end='')
|
|
182
|
+
print('L' if output['left'][j, i] else '', end='')
|
|
183
|
+
print('R' if output['right'][j, i] else '', end='')
|
|
184
|
+
print('B' if output['bottom'][j, i] else '', end='')
|
|
185
|
+
print(' Sums: ', hists['top'][j, i], hists['left'][j, i], hists['right'][j, i], hists['bottom'][j, i])
|
|
186
|
+
|
|
187
|
+
current_count = 0
|
|
188
|
+
out = np.full_like(output['top'], ' ', dtype='U2')
|
|
189
|
+
for j in range(out.shape[0]):
|
|
190
|
+
for i in range(out.shape[1]):
|
|
191
|
+
if out[j, i] == ' ':
|
|
192
|
+
dfs(i, j, out, output, str(current_count).zfill(2))
|
|
193
|
+
current_count += 1
|
|
194
|
+
|
|
195
|
+
with open(output_path, 'w') as f:
|
|
196
|
+
f.write('[\n')
|
|
197
|
+
for i, row in enumerate(out):
|
|
198
|
+
f.write(' ' + str(row.tolist()).replace("'", '"'))
|
|
199
|
+
if i != len(out) - 1:
|
|
200
|
+
f.write(',')
|
|
201
|
+
f.write('\n')
|
|
202
|
+
f.write(']')
|
|
203
|
+
print('output json: ', output_path)
|
|
204
|
+
|
|
205
|
+
if __name__ == '__main__':
|
|
206
|
+
# to run this script and visualize the output, in the root run:
|
|
207
|
+
# python .\src\puzzle_solver\puzzles\stitches\parse_map\parse_map.py | python .\src\puzzle_solver\utils\visualizer.py --read_stdin
|
|
208
|
+
# main(Path(__file__).parent / 'input_output' / 'MTM6OSw4MjEsNDAx.png')
|
|
209
|
+
# main(Path(__file__).parent / 'input_output' / 'weekly_oct_3rd_2025.png')
|
|
210
|
+
# main(Path(__file__).parent / 'input_output' / 'star_battle_67f73ff90cd8cdb4b3e30f56f5261f4968f5dac940bc6.png')
|
|
211
|
+
# main(Path(__file__).parent / 'input_output' / 'LITS_MDoxNzksNzY3.png')
|
|
212
|
+
main(Path(__file__).parent / 'input_output' / 'lits_OTo3LDMwNiwwMTU=.png')
|
|
@@ -3,6 +3,7 @@ import argparse
|
|
|
3
3
|
from typing import Any, Mapping, Tuple
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
6
|
+
import random
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
from PIL import Image, ImageDraw, ImageFont
|
|
@@ -86,6 +87,7 @@ def render_board_image(
|
|
|
86
87
|
def get_input():
|
|
87
88
|
parser = argparse.ArgumentParser()
|
|
88
89
|
parser.add_argument('--read_stdin', action='store_true')
|
|
90
|
+
parser.add_argument('--clipboard', action='store_true')
|
|
89
91
|
args = parser.parse_args()
|
|
90
92
|
if args.read_stdin:
|
|
91
93
|
# read from stdin until the line starts with "output json: "
|
|
@@ -99,6 +101,15 @@ def get_input():
|
|
|
99
101
|
with open(json_path, 'r') as f:
|
|
100
102
|
board = np.array(json.load(f))
|
|
101
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'
|
|
102
113
|
else:
|
|
103
114
|
# with open('src/puzzle_solver/puzzles/stitches/parse_map/input_output/MTM6OSw4MjEsNDAx.json', 'r') as f:
|
|
104
115
|
# board = np.array(json.load(f))
|
|
@@ -122,6 +133,7 @@ def get_input():
|
|
|
122
133
|
return board
|
|
123
134
|
|
|
124
135
|
if __name__ == '__main__':
|
|
136
|
+
# to read numpy array from clipboard run: python .\src\puzzle_solver\utils\visualizer.py --clipboard
|
|
125
137
|
board = get_input()
|
|
126
138
|
print('Visualizing board:')
|
|
127
139
|
print('[')
|
|
@@ -133,9 +145,11 @@ if __name__ == '__main__':
|
|
|
133
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)]
|
|
134
146
|
vs =[0, 128, 255]
|
|
135
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)
|
|
136
149
|
nums = set([c.item() for c in np.nditer(board)])
|
|
137
150
|
colors = {c: rcolors[i % len(rcolors)] for i, c in enumerate(nums)}
|
|
138
151
|
print(nums)
|
|
139
152
|
print('max i:', max(nums))
|
|
140
|
-
|
|
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))
|
|
141
155
|
render_board_image(board, colors)
|
|
File without changes
|
|
File without changes
|