-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheckers.py
475 lines (369 loc) · 19.4 KB
/
checkers.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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
import argparse
import copy
import sys
import time
class State:
# This class is used to represent a state.
# board : a list of lists that represents the 8*8 board
def __init__(self, board):
self.board = board
self.width = 8
self.height = 8
# init with red player's turn
# self.turn = 'r'
def display(self):
for i in self.board:
for j in i:
print(j, end="")
print("")
print("")
def deepcopy_move_diagonally_and_return_new_state(state, row, col, new_row, new_col):
"""
Move diagonally one square
"""
new_board = copy.deepcopy(state.board)
new_board[new_row][new_col] = new_board[row][col]
new_board[row][col] = '.'
# if the piece reaches the end of the board, become a king
if new_row == 0 and new_board[new_row][new_col] == 'r':
new_board[new_row][new_col] = 'R'
elif new_row == 7 and new_board[new_row][new_col] == 'b':
new_board[new_row][new_col] = 'B'
new_state = State(new_board)
# new_state.turn = get_next_turn(state.turn)
return new_state
def nested_list_to_only_elements(nested_list):
"""
Recursively turns a nested list to a list of just the elements
"""
element_list = []
for element in nested_list:
if isinstance(element, list):
element_list.extend(nested_list_to_only_elements(element))
else:
element_list.append(element)
return element_list
def deepcopy_jump_recursively_and_return_new_state(state, row, col, new_row, new_col, curr_turn):
"""
Keeps jumping diagonally until it can't
"""
new_successor_states = []
new_board = copy.deepcopy(state.board)
# initial jump from row, col to new_row, new_col
new_board[new_row][new_col] = new_board[row][col]
new_board[row][col] = '.'
# remove the piece that was jumped over
if new_row < row and new_col < col:
new_board[row-1][col-1] = '.'
elif new_row < row and new_col > col:
new_board[row-1][col+1] = '.'
elif new_row > row and new_col < col:
new_board[row+1][col-1] = '.'
elif new_row > row and new_col > col:
new_board[row+1][col+1] = '.'
# if the piece reaches the end of the board, become a king and end the jump
if new_row == 0 and new_board[new_row][new_col] == 'r':
new_board[new_row][new_col] = 'R'
new_state = State(new_board)
# new_state.turn = get_next_turn(state.turn)
return new_state
elif new_row == 7 and new_board[new_row][new_col] == 'b':
new_board[new_row][new_col] = 'B'
new_state = State(new_board)
# new_state.turn = get_next_turn(state.turn)
return new_state
# check if piece at new_row, new_col can jump left or right
if curr_turn == 'r':
# check if new r piece can jump left
if (new_row-2 >= 0) and (new_col-2 >= 0) and new_board[new_row-1][new_col-1] in get_opp_char(curr_turn) and new_board[new_row-2][new_col-2] == '.':
# print("JUMP FORWARDS LEFT")
# new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row-2, new_col-2, get_next_turn(curr_turn)))
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row-2, new_col-2, "r"))
# check if new r piece can jump right
if (new_row-2 >= 0) and (new_col+2 < 8) and new_board[new_row-1][new_col+1] in get_opp_char(curr_turn) and new_board[new_row-2][new_col+2] == '.':
# print("JUMP FORWARDS RIGHT")
# print("JUMPING FROM ", new_row, new_col, " TO ", new_row-2, new_col+2)
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row-2, new_col+2, "r"))
if new_board[new_row][new_col] == 'R':
# check if new R piece can backwards jump left
if (new_row+2 < 8) and (new_col-2 >= 0) and new_board[new_row+1][new_col-1] in get_opp_char(curr_turn) and new_board[new_row+2][new_col-2] == '.':
# print("JUMP BACKWARDS LEFT")
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row+2, new_col-2, "r"))
# check if new R piece can backwards jump right
if (new_row+2 < 8) and (new_col+2 < 8) and new_board[new_row+1][new_col+1] in get_opp_char(curr_turn) and new_board[new_row+2][new_col+2] == '.':
# print("JUMP BACKWARDS RIGHT")
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row+2, new_col+2, "r"))
elif curr_turn == 'b':
# check if new b piece can jump left
if (new_row+2 < 8) and (new_col-2 >= 0) and new_board[new_row+1][new_col-1] in get_opp_char(curr_turn) and new_board[new_row+2][new_col-2] == '.':
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row+2, new_col-2, "b"))
# check if new b piece can jump right
if (new_row+2 < 8) and (new_col+2 < 8) and new_board[new_row+1][new_col+1] in get_opp_char(curr_turn) and new_board[new_row+2][new_col+2] == '.':
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row+2, new_col+2, "b"))
if new_board[new_row][new_col] == 'B':
# check if new B piece can backwards jump left
if (new_row-2 >= 0) and (new_col-2 >= 0) and new_board[new_row-1][new_col-1] in get_opp_char(curr_turn) and new_board[new_row-2][new_col-2] == '.':
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row-2, new_col-2, "b"))
# check if new B piece can backwards jump right
if (new_row-2 >= 0) and (new_col+2 < 8) and new_board[new_row-1][new_col+1] in get_opp_char(curr_turn) and new_board[new_row-2][new_col+2] == '.':
new_successor_states.append(deepcopy_jump_recursively_and_return_new_state(State(new_board), new_row, new_col, new_row-2, new_col+2, "b"))
# if piece can't jump anymore, return new state after 1 jump, else return set of successor states
if len(new_successor_states) == 0:
# print("PRINT NEW BOARD:")
new_state = State(new_board)
# new_state.turn = get_next_turn(state.turn)
return new_state
else:
result = nested_list_to_only_elements(new_successor_states)
return result
def add_to_list(list_, item):
# if isinstance(item, list):
# list_.extend(item)
if isinstance(item[1], list):
for succ in item[1]:
list_.append((item[0],succ))
else:
list_.append(item)
def generate_successors(state, curr_turn):
'''
Takes in a state
Returns a list of successtors to that current state
'''
board = state.board
# curr_turn = state.turn
# new_successor_states contains the list of successor states in a tuple (isJump, new_state)
new_successor_states = []
for row in range(len(board)):
for col in range(len(board[row])):
# check if the current player can jump
canJump = False
# if red's turn and the piece is red
if curr_turn == 'r' and board[row][col] in ['r', 'R']:
# check if piece can jump diagonally left
if (row-2 >= 0) and (col-2 >= 0) and board[row-1][col-1] in get_opp_char(curr_turn) and board[row-2][col-2] == '.':
# print("PIECE:" , board[row][col], "CAN JUMP LEFT")
# print("PIECE COORDINATES:", row, col)
new_succ = deepcopy_jump_recursively_and_return_new_state(state, row, col, row-2, col-2, "r")
add_to_list(new_successor_states, (True, new_succ))
canJump = True
# check if piece can jump diagonally right
if (row-2 >= 0) and (col+2 < 8) and board[row-1][col+1] in get_opp_char(curr_turn) and board[row-2][col+2] == '.':
new_succ = deepcopy_jump_recursively_and_return_new_state(state, row, col, row-2, col+2, "r")
add_to_list(new_successor_states, (True, new_succ))
canJump = True
if board[row][col] == "R":
# check if piece can jump backwards diagonally left
if (row+2 < 8) and (col-2 >= 0) and board[row+1][col-1] in get_opp_char(curr_turn) and board[row+2][col-2] == '.':
# print("INITIAL JUMP BACKWARDS LEFT")
new_succ = deepcopy_jump_recursively_and_return_new_state(state, row, col, row+2, col-2, "r")
add_to_list(new_successor_states, (True, new_succ))
canJump = True
# check if piece can jump backwards diagonally right
if (row+2 < 8) and (col+2 < 8) and board[row+1][col+1] in get_opp_char(curr_turn) and board[row+2][col+2] == '.':
# print("INITIAL JUMP BACKWARDS RIGHT")
new_succ = deepcopy_jump_recursively_and_return_new_state(state, row, col, row+2, col+2, "r")
add_to_list(new_successor_states, (True, new_succ))
canJump = True
# check if piece can move diagonally left
if canJump == False and (row-1 >= 0) and (col-1 >= 0) and board[row-1][col-1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row-1, col-1)))
# check if piece can move diagonally right
if canJump == False and (row-1 >= 0) and (col+1 < 8) and board[row-1][col+1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row-1, col+1)))
# check if piece is a king
if board[row][col] == 'R':
# check if piece can move backwards diagonally left
if canJump == False and (row+1 < 8) and (col-1 >= 0) and board[row+1][col-1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row+1, col-1)))
# check if piece can move backwards diagonally right
if canJump == False and (row+1 < 8) and (col+1 < 8) and board[row+1][col+1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row+1, col+1)))
elif curr_turn == 'b' and board[row][col] in ['b', 'B']:
# check if piece can jump diagonally left
if (row+2 < 8) and (col-2 >= 0) and board[row+1][col-1] in get_opp_char(curr_turn) and board[row+2][col-2] == '.':
add_to_list(new_successor_states, (True, deepcopy_jump_recursively_and_return_new_state(state, row, col, row+2, col-2, "b")))
canJump = True
# check if piece can jump diagonally right
if (row+2 < 8) and (col+2 < 8) and board[row+1][col+1] in get_opp_char(curr_turn) and board[row+2][col+2] == '.':
add_to_list(new_successor_states, (True, deepcopy_jump_recursively_and_return_new_state(state, row, col, row+2, col+2, "b")))
canJump = True
if board[row][col] == 'B':
# check if piece can jump backwards diagonally left
if (row-2 >= 0) and (col-2 >= 0) and board[row-1][col-1] in get_opp_char(curr_turn) and board[row-2][col-2] == '.':
add_to_list(new_successor_states, (True, deepcopy_jump_recursively_and_return_new_state(state, row, col, row-2, col-2, "b")))
canJump = True
# check if piece can jump backwards diagonally right
if (row-2 >= 0) and (col+2 < 8) and board[row-1][col+1] in get_opp_char(curr_turn) and board[row-2][col+2] == '.':
add_to_list(new_successor_states, (True, deepcopy_jump_recursively_and_return_new_state(state, row, col, row-2, col+2, "b")))
canJump = True
# check if piece can move diagonally left
if canJump == False and (row+1 < 8) and (col-1 >= 0) and board[row+1][col-1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row+1, col-1)))
# check if piece can move diagonally right
if canJump == False and (row+1 < 8) and (col+1 < 8) and board[row+1][col+1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row+1, col+1)))
# check if piece is a king
if board[row][col] == 'B':
# check if piece can move backwards diagonally left
if canJump == False and (row-1 >= 0) and (col-1 >= 0) and board[row-1][col-1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row-1, col-1)))
# check if piece can move backwards diagonally right
if canJump == False and (row-1 >= 0) and (col+1 < 8) and board[row-1][col+1] == '.':
add_to_list(new_successor_states, (False, deepcopy_move_diagonally_and_return_new_state(state, row, col, row-1, col+1)))
hasJump = False
for successor in new_successor_states:
if successor[0] == True:
hasJump = True
break
final_successor_states = []
if hasJump == True:
for successor in new_successor_states:
if successor[0] == True:
final_successor_states.append(successor[1])
else:
for successor in new_successor_states:
final_successor_states.append(successor[1])
return final_successor_states
def return_utility(state):
exists_red_piece = False
exists_black_piece = False
for row in state.board:
for piece in row:
if exists_red_piece and exists_black_piece:
break
if piece in ['r', 'R']:
exists_red_piece = True
elif piece in ['b', 'B']:
exists_black_piece = True
if not exists_red_piece:
return -1000000000
elif not exists_black_piece:
return 1000000000
else:
return return_non_terminal_utility(state)
def return_non_terminal_utility(state):
num_red_normal_pieces = 0
num_black_normal_pieces = 0
num_red_king_pieces = 0
num_black_king_pieces = 0
row_counter = 7
for row in state.board:
for piece in row:
if piece == 'r':
num_red_normal_pieces += (1 + row_counter)
elif piece == 'b':
num_black_normal_pieces += (1 + (7 - row_counter))
elif piece == 'R':
num_red_king_pieces += 1
elif piece == 'B':
num_black_king_pieces += 1
row_counter -= 1
simple_eval = (12*num_red_king_pieces + num_red_normal_pieces) - (12*num_black_king_pieces + num_black_normal_pieces)
return simple_eval
def alphabeta_search(state, depth_limit, curr_turn):
v = alphabeta_max_node(state, curr_turn, -2000000000, 2000000000, depth_limit)
return v[1]
# cache is a dictionary
cached = {} # you can use this to implement state caching!
# cached = {state: (value v, depth, successor)}
def alphabeta_max_node(state, turn, alpha, beta, current_depth):
if state in cached and cached[state][1] >= current_depth:
return (cached[state][0], cached[state][2])
if current_depth == 0:
return (return_utility(state), None)
successors = generate_successors(state, turn)
if len(successors) == 0:
cached[state] = (-1000000000, current_depth, None)
return (-1000000000, None)
# sort successors by utility function
successors.sort(key=lambda x: return_utility(x), reverse=True)
v = -2000000000
best = state
for child in successors:
tempval, tempstate = alphabeta_min_node(child, get_next_turn(turn), alpha, beta, current_depth-1)
if tempval > v:
v = tempval
best = child
if tempval > beta:
cached[state] = (v, current_depth, child)
return (v, child)
alpha = max(alpha, tempval)
cached[state] = (v, current_depth, best)
return (v, best)
def alphabeta_min_node(state, turn, alpha, beta, current_depth):
if state in cached and cached[state][1] >= current_depth:
return (cached[state][0], cached[state][2])
if current_depth == 0:
return (return_utility(state), None)
successors = generate_successors(state, turn)
if len(successors) == 0:
cached[state] = (1000000000, current_depth, None)
return (1000000000, None)
# sort successors by utility function
successors.sort(key=lambda x: return_utility(x), reverse=False)
v = 2000000000
best = state
for child in successors:
tempval, tempstate = alphabeta_max_node(child, get_next_turn(turn), alpha, beta, current_depth-1)
if tempval < v:
v = tempval
best = child
if tempval < alpha:
cached[state] = (v, current_depth, child)
return (v, child)
beta = min(beta, tempval)
cached[state] = (v, current_depth, best)
return (v, best)
def get_curr_char(player):
if player in ['b', 'B']:
return ['b', 'B']
else:
return ['r', 'R']
def get_opp_char(player):
if player in ['b', 'B']:
return ['r', 'R']
else:
return ['b', 'B']
def get_next_turn(curr_turn):
if curr_turn == 'r':
return 'b'
else:
return 'r'
def read_from_file(filename):
f = open(filename)
lines = f.readlines()
board = [[str(x) for x in l.rstrip()] for l in lines]
f.close()
return board
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
"--inputfile",
type=str,
required=True,
help="The input file that contains the puzzles."
)
parser.add_argument(
"--outputfile",
type=str,
required=True,
help="The output file that contains the solution."
)
args = parser.parse_args()
initial_board = read_from_file(args.inputfile)
state = State(initial_board)
turn = 'r'
depth_limit = 8
ctr = 0
# Main Checkers Game Loop with Alpha Beta Pruning
start_time = time.time()
sys.stdout = open(args.outputfile, 'w')
state.display()
while return_utility(state) < 1000000000:
new_state = alphabeta_search(state, depth_limit, turn)
turn = get_next_turn(turn)
new_state.display()
state = new_state
sys.stdout = sys.__stdout__
end_time = time.time()
print("TIME ELAPSED: " + str(end_time - start_time))