-
Notifications
You must be signed in to change notification settings - Fork 21
/
fen-to-board.py
executable file
·343 lines (287 loc) · 12.4 KB
/
fen-to-board.py
1
2
3
4
5
6
7
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# Define the initial board as a list of strings
original_board = [
" /*",
" a7 ----- d7 ----- g7",
" | | |",
" | b6 -- d6 -- f6 |",
" | | | | |",
" | | c5-d5-e5 | |",
" a4-b4-c4 e4-f4-g4",
" | | c3-d3-e3 | |",
" | | | | |",
" | b2 -- d2 -- f2 |",
" | | |",
" a1 ----- d1 ----- g1",
" */",
]
# Map positions to their coordinates (line index, column index)
positions_coords = {
# Outer ring positions
'a7': (1, 4), 'd7': (1, 13), 'g7': (1, 22),
'a4': (6, 4), 'g4': (6, 22),
'a1': (11, 4), 'd1': (11, 13), 'g1': (11, 22),
# Middle ring positions
'b6': (3, 7), 'd6': (3, 13), 'f6': (3, 19),
'b4': (6, 7), 'f4': (6, 19),
'b2': (9, 7), 'd2': (9, 13), 'f2': (9, 19),
# Inner ring positions
'c5': (5, 10), 'd5': (5, 13), 'e5': (5, 16),
'c4': (6, 10), 'e4': (6, 16),
'c3': (7, 10), 'd3': (7, 13), 'e3': (7, 16),
}
# Positions in the order they appear in the FEN string
inner_ring_positions = ['d5', 'e5', 'e4', 'e3', 'd3', 'c3', 'c4', 'c5']
middle_ring_positions = ['d6', 'f6', 'f4', 'f2', 'd2', 'b2', 'b4', 'b6']
outer_ring_positions = ['d7', 'g7', 'g4', 'g1', 'd1', 'a1', 'a4', 'a7']
# Build mappings between position names and their ring and index
positions_index = {} # Map position name to (ring, index)
index_to_position = {} # Map (ring, index) to position name
# Assign indices to positions in each ring
for idx, pos_name in enumerate(outer_ring_positions):
ring = 3 # Outer ring
index = idx
positions_index[pos_name] = (ring, index)
index_to_position[(ring, index)] = pos_name
for idx, pos_name in enumerate(middle_ring_positions):
ring = 2 # Middle ring
index = idx
positions_index[pos_name] = (ring, index)
index_to_position[(ring, index)] = pos_name
for idx, pos_name in enumerate(inner_ring_positions):
ring = 1 # Inner ring
index = idx
positions_index[pos_name] = (ring, index)
index_to_position[(ring, index)] = pos_name
# Function to parse the FEN string and build the board state
def parse_fen(fen_string):
parts = fen_string.strip().split(' ', 1)
positions_fen = parts[0]
rest_of_fen = parts[1] if len(parts) > 1 else ''
fen_parts = positions_fen.split('/')
if len(fen_parts) != 3:
raise ValueError("Invalid FEN string: expected 3 parts separated by '/'")
inner_fen, middle_fen, outer_fen = fen_parts
board_state = {}
# Process inner ring
for i, fen_char in enumerate(inner_fen):
if fen_char != '*':
pos_name = inner_ring_positions[i]
board_state[pos_name] = fen_char
# Process middle ring
for i, fen_char in enumerate(middle_fen):
if fen_char != '*':
pos_name = middle_ring_positions[i]
board_state[pos_name] = fen_char
# Process outer ring
for i, fen_char in enumerate(outer_fen):
if fen_char != '*':
pos_name = outer_ring_positions[i]
board_state[pos_name] = fen_char
return board_state, rest_of_fen
# Function to update the board display based on the board state
def update_board(board_state, board):
for pos_name, piece in board_state.items():
if pos_name in positions_coords:
line_idx, col_idx = positions_coords[pos_name]
# Replace the first character with a space and the second with the piece symbol
line = list(board[line_idx])
line[col_idx] = ' '
line[col_idx + 1] = piece
board[line_idx] = ''.join(line)
# Function to apply transformations to the board state
def apply_transformation(board_state, transformation):
rotation_steps, flip_v, flip_h, flip_io = transformation
transformed_board_state = {}
for pos_name, piece in board_state.items():
ring, index = positions_index[pos_name]
# Apply inner/outer flip
if flip_io:
if ring == 1:
ring = 3
elif ring == 3:
ring = 1
# Middle ring remains the same
# Apply vertical flip (reflection over horizontal axis)
if flip_v:
index = (8 - index) % 8
# Apply horizontal flip (reflection over vertical axis)
if flip_h:
index = (4 - index) % 8
# Apply rotation (in 45-degree increments)
index = (index + rotation_steps) % 8
# Map back to position name
new_pos_name = index_to_position.get((ring, index))
if new_pos_name:
transformed_board_state[new_pos_name] = piece
return transformed_board_state
# Function to transform a single position
def transform_position(pos_name, transformation):
rotation_steps, flip_v, flip_h, flip_io = transformation
if pos_name not in positions_index:
raise ValueError(f"Invalid position name: {pos_name}")
ring, index = positions_index[pos_name]
# Apply inner/outer flip
if flip_io:
if ring == 1:
ring = 3
elif ring == 3:
ring = 1
# Middle ring remains the same
# Apply vertical flip (reflection over horizontal axis)
if flip_v:
index = (8 - index) % 8
# Apply horizontal flip (reflection over vertical axis)
if flip_h:
index = (4 - index) % 8
# Apply rotation (in 45-degree increments)
index = (index + rotation_steps) % 8
# Map back to position name
new_pos_name = index_to_position.get((ring, index))
if not new_pos_name:
raise ValueError(f"Transformation resulted in invalid position for ring {ring} and index {index}")
return new_pos_name
# Function to represent the board state in a canonical form for comparison
def board_state_to_canonical(board_state):
items = sorted(board_state.items())
return tuple(items)
# Function to generate the FEN string from the board state
def board_state_to_fen(board_state, rest_of_fen=''):
# Inner ring
inner_fen_list = []
for pos_name in inner_ring_positions:
piece = board_state.get(pos_name, '*')
inner_fen_list.append(piece)
inner_fen = ''.join(inner_fen_list)
# Middle ring
middle_fen_list = []
for pos_name in middle_ring_positions:
piece = board_state.get(pos_name, '*')
middle_fen_list.append(piece)
middle_fen = ''.join(middle_fen_list)
# Outer ring
outer_fen_list = []
for pos_name in outer_ring_positions:
piece = board_state.get(pos_name, '*')
outer_fen_list.append(piece)
outer_fen = ''.join(outer_fen_list)
# Combine the FEN parts
positions_fen = '/'.join([inner_fen, middle_fen, outer_fen])
if rest_of_fen:
fen_string = positions_fen + ' ' + rest_of_fen
else:
fen_string = positions_fen
return fen_string
# DualWriter class to write to both console and file
class DualWriter:
def __init__(self, file, original_stdout):
self.file = file
self.console = original_stdout
def write(self, message):
self.console.write(message)
self.file.write(message)
def flush(self):
self.console.flush()
self.file.flush()
# Main code for processing input from a file
import re
from itertools import product
import sys
def main():
# Check if the input filename is provided as a command-line argument
if len(sys.argv) < 2:
print("Usage: python script.py <input_filename>")
sys.exit(1)
input_filename = sys.argv[1]
# Read the content from the input file
try:
with open(input_filename, 'r', encoding='utf-8') as infile:
input_text = infile.read()
except FileNotFoundError:
print(f"Error: File '{input_filename}' not found.")
sys.exit(1)
except IOError as e:
print(f"Error reading file '{input_filename}': {e}")
sys.exit(1)
# Save the original stdout
original_stdout = sys.stdout
# Open the output file in write mode to overwrite existing content
try:
with open('opening-book.txt', 'w', encoding='utf-8') as f:
# Redirect stdout to write to both console and file
sys.stdout = DualWriter(f, original_stdout)
# Pattern to match the FEN string inside quotes
fen_pattern = r'"([^"]+)"'
fen_match = re.search(fen_pattern, input_text)
if fen_match:
fen_string = fen_match.group(1)
else:
print("Invalid input. Please provide a valid FEN string.")
return
# Pattern to match the positions inside <String>[ ... ]
positions_pattern = r'<String>\[\s*(.*?)\s*\]'
positions_match = re.search(positions_pattern, input_text, re.DOTALL)
if positions_match:
positions_text = positions_match.group(1)
# Split the positions by commas and strip quotes
positions_list = [pos.strip().strip('"') for pos in positions_text.split(',') if pos.strip()]
else:
print("Invalid input. Please provide valid positions.")
return
try:
# Parse the FEN string to get the board state
board_state, rest_of_fen = parse_fen(fen_string)
# Prepare to generate transformations
rotation_steps_list = [0, 2, 4, 6] # Corresponds to 0°, 90°, 180°, 270°
flip_v_options = [False, True] # Vertical flip
flip_h_options = [False, True] # Horizontal flip
flip_io_options = [False, True] # Inner/outer flip
# Generate all combinations of transformations
transformations = list(product(rotation_steps_list, flip_v_options, flip_h_options, flip_io_options))
unique_board_states = set()
board_states_list = []
# Apply each transformation and collect unique board states
for transformation in transformations:
transformed_board_state = apply_transformation(board_state, transformation)
canonical_state = board_state_to_canonical(transformed_board_state)
if canonical_state not in unique_board_states:
unique_board_states.add(canonical_state)
board_states_list.append((transformation, transformed_board_state))
# Output the transformed boards
print(" //////////////////////////////////////////////////////////////////////////////")
print("")
for idx, (transformation, transformed_board_state) in enumerate(board_states_list):
rotation_steps, flip_v, flip_h, flip_io = transformation
# Reset the board to the original state for each transformation
board = original_board[:]
update_board(transformed_board_state, board)
# Print the board drawing
print(" /*")
for line in board[1:-1]: # Skip the first and last line (the /* and */ comments)
print(line)
print(" */")
# Transform the positions_list with handling of optional 'x' prefix
transformed_positions_list = []
for pos in positions_list:
if pos.startswith('x'):
prefix = 'x'
coord = pos[1:]
else:
prefix = ''
coord = pos
transformed_coord = transform_position(coord, transformation)
transformed_pos = prefix + transformed_coord
transformed_positions_list.append(transformed_pos)
# Generate the transformed FEN string
transformed_fen_string = board_state_to_fen(transformed_board_state, rest_of_fen)
# Output the Dart code snippet
print(f' "{transformed_fen_string}": <String>[')
for pos in transformed_positions_list:
print(f' "{pos}",')
print(' ],\n')
except Exception as e:
print("Invalid input. Please provide a valid FEN string or positions.")
finally:
# Restore the original stdout
sys.stdout = original_stdout
if __name__ == "__main__":
main()