-
Notifications
You must be signed in to change notification settings - Fork 0
/
host.py
executable file
·517 lines (419 loc) · 16.3 KB
/
host.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
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
import sys
import random
import timeit
import math
import argparse
from collections import Counter
from copy import deepcopy
# from read import *
# from write import writeNextInput
class GO:
def __init__(self, n):
"""
Go game.
:param n: size of the board n*n
"""
self.size = n
#self.previous_board = None # Store the previous board
self.X_move = True # X chess plays first
self.died_pieces = [] # Intialize died pieces to be empty
self.n_move = 0 # Trace the number of moves
self.max_move = n * n - 1 # The max movement of a Go game
self.komi = n/2 # Komi rule
self.verbose = False # Verbose only when there is a manual player
def init_board(self, n):
'''
Initialize a board with size n*n.
:param n: width and height of the board.
:return: None.
'''
board = [[0 for x in range(n)] for y in range(n)] # Empty space marked as 0
# 'X' pieces marked as 1
# 'O' pieces marked as 2
self.board = board
self.previous_board = deepcopy(board)
def set_board(self, piece_type, previous_board, board):
'''
Initialize board status.
:param previous_board: previous board state.
:param board: current board state.
:return: None.
'''
# 'X' pieces marked as 1
# 'O' pieces marked as 2
for i in range(self.size):
for j in range(self.size):
if previous_board[i][j] == piece_type and board[i][j] != piece_type:
self.died_pieces.append((i, j))
# self.piece_type = piece_type
self.previous_board = previous_board
self.board = board
def compare_board(self, board1, board2):
for i in range(self.size):
for j in range(self.size):
if board1[i][j] != board2[i][j]:
return False
return True
def copy_board(self):
'''
Copy the current board for potential testing.
:param: None.
:return: the copied board instance.
'''
return deepcopy(self)
def detect_neighbor(self, i, j):
'''
Detect all the neighbors of a given stone.
:param i: row number of the board.
:param j: column number of the board.
:return: a list containing the neighbors row and column (row, column) of position (i, j).
'''
board = self.board
neighbors = []
# Detect borders and add neighbor coordinates
if i > 0: neighbors.append((i-1, j))
if i < len(board) - 1: neighbors.append((i+1, j))
if j > 0: neighbors.append((i, j-1))
if j < len(board) - 1: neighbors.append((i, j+1))
return neighbors
def detect_neighbor_ally(self, i, j):
'''
Detect the neighbor allies of a given stone.
:param i: row number of the board.
:param j: column number of the board.
:return: a list containing the neighbored allies row and column (row, column) of position (i, j).
'''
board = self.board
neighbors = self.detect_neighbor(i, j) # Detect neighbors
group_allies = []
# Iterate through neighbors
for piece in neighbors:
# Add to allies list if having the same color
if board[piece[0]][piece[1]] == board[i][j]:
group_allies.append(piece)
return group_allies
def ally_dfs(self, i, j):
'''
Using DFS to search for all allies of a given stone.
:param i: row number of the board.
:param j: column number of the board.
:return: a list containing the all allies row and column (row, column) of position (i, j).
'''
stack = [(i, j)] # stack for DFS serach
ally_members = [] # record allies positions during the search
while stack:
piece = stack.pop()
ally_members.append(piece)
neighbor_allies = self.detect_neighbor_ally(piece[0], piece[1])
for ally in neighbor_allies:
if ally not in stack and ally not in ally_members:
stack.append(ally)
return ally_members
def find_liberty(self, i, j):
'''
Find liberty of a given stone. If a group of allied stones has no liberty, they all die.
:param i: row number of the board.
:param j: column number of the board.
:return: boolean indicating whether the given stone still has liberty.
'''
board = self.board
ally_members = self.ally_dfs(i, j)
for member in ally_members:
neighbors = self.detect_neighbor(member[0], member[1])
for piece in neighbors:
# If there is empty space around a piece, it has liberty
if board[piece[0]][piece[1]] == 0:
return True
# If none of the pieces in a allied group has an empty space, it has no liberty
return False
def find_died_pieces(self, piece_type):
'''
Find the died stones that has no liberty in the board for a given piece type.
:param piece_type: 1('X') or 2('O').
:return: a list containing the dead pieces row and column(row, column).
'''
board = self.board
died_pieces = []
for i in range(len(board)):
for j in range(len(board)):
# Check if there is a piece at this position:
if board[i][j] == piece_type:
# The piece die if it has no liberty
if not self.find_liberty(i, j):
died_pieces.append((i,j))
return died_pieces
def remove_died_pieces(self, piece_type):
'''
Remove the dead stones in the board.
:param piece_type: 1('X') or 2('O').
:return: locations of dead pieces.
'''
died_pieces = self.find_died_pieces(piece_type)
if not died_pieces: return []
self.remove_certain_pieces(died_pieces)
return died_pieces
def remove_certain_pieces(self, positions):
'''
Remove the stones of certain locations.
:param positions: a list containing the pieces to be removed row and column(row, column)
:return: None.
'''
board = self.board
for piece in positions:
board[piece[0]][piece[1]] = 0
self.update_board(board)
def place_chess(self, i, j, piece_type):
'''
Place a chess stone in the board.
:param i: row number of the board.
:param j: column number of the board.
:param piece_type: 1('X') or 2('O').
:return: boolean indicating whether the placement is valid.
'''
board = self.board
valid_place = self.valid_place_check(i, j, piece_type)
if not valid_place:
return False
self.previous_board = deepcopy(board)
board[i][j] = piece_type
self.update_board(board)
# Remove the following line for HW2 CS561 S2020
# self.n_move += 1
return True
def valid_place_check(self, i, j, piece_type, test_check=False):
'''
Check whether a placement is valid.
:param i: row number of the board.
:param j: column number of the board.
:param piece_type: 1(white piece) or 2(black piece).
:param test_check: boolean if it's a test check.
:return: boolean indicating whether the placement is valid.
'''
board = self.board
verbose = self.verbose
if test_check:
verbose = False
# Check if the place is in the board range
if not (i >= 0 and i < len(board)):
if verbose:
print(('Invalid placement. row should be in the range 1 to {}.').format(len(board) - 1))
return False
if not (j >= 0 and j < len(board)):
if verbose:
print(('Invalid placement. column should be in the range 1 to {}.').format(len(board) - 1))
return False
# Check if the place already has a piece
if board[i][j] != 0:
if verbose:
print('Invalid placement. There is already a chess in this position.')
return False
# Copy the board for testing
test_go = self.copy_board()
test_board = test_go.board
# Check if the place has liberty
test_board[i][j] = piece_type
test_go.update_board(test_board)
if test_go.find_liberty(i, j):
return True
# If not, remove the died pieces of opponent and check again
test_go.remove_died_pieces(3 - piece_type)
if not test_go.find_liberty(i, j):
if verbose:
print('Invalid placement. No liberty found in this position.')
return False
# Check special case: repeat placement causing the repeat board state (KO rule)
else:
if self.died_pieces and self.compare_board(self.previous_board, test_go.board):
if verbose:
print('Invalid placement. A repeat move not permitted by the KO rule.')
return False
return True
def update_board(self, new_board):
'''
Update the board with new_board
:param new_board: new board.
:return: None.
'''
self.board = new_board
def visualize_board(self):
'''
Visualize the board.
:return: None
'''
board = self.board
print('-' * len(board) * 2)
for i in range(len(board)):
for j in range(len(board)):
if board[i][j] == 0:
print(' ', end=' ')
elif board[i][j] == 1:
print('X', end=' ')
else:
print('O', end=' ')
print()
print('-' * len(board) * 2)
def game_end(self, piece_type, action="MOVE"):
'''
Check if the game should end.
:param piece_type: 1('X') or 2('O').
:param action: "MOVE" or "PASS".
:return: boolean indicating whether the game should end.
'''
# Case 1: max move reached
if self.n_move >= self.max_move:
return True
# Case 2: two players all pass the move.
if self.compare_board(self.previous_board, self.board) and action == "PASS":
return True
return False
def score(self, piece_type):
'''
Get score of a player by counting the number of stones.
:param piece_type: 1('X') or 2('O').
:return: boolean indicating whether the game should end.
'''
board = self.board
cnt = 0
for i in range(self.size):
for j in range(self.size):
if board[i][j] == piece_type:
cnt += 1
return cnt
def judge_winner(self):
'''
Judge the winner of the game by number of pieces for each player.
:param: None.
:return: piece type of winner of the game (0 if it's a tie).
'''
cnt_1 = self.score(1)
cnt_2 = self.score(2)
if cnt_1 > cnt_2 + self.komi: return 1
elif cnt_1 < cnt_2 + self.komi: return 2
else: return 0
def play(self, player1, player2, verbose=False):
'''
The game starts!
:param player1: Player instance.
:param player2: Player instance.
:param verbose: whether print input hint and error information
:return: piece type of winner of the game (0 if it's a tie).
'''
self.init_board(self.size)
# Print input hints and error message if there is a manual player
if player1.type == 'manual' or player2.type == 'manual':
self.verbose = True
print('----------Input "exit" to exit the program----------')
print('X stands for black chess, O stands for white chess.')
self.visualize_board()
verbose = self.verbose
# Game starts!
while 1:
piece_type = 1 if self.X_move else 2
# Judge if the game should end
if self.game_end(piece_type):
result = self.judge_winner()
if verbose:
print('Game ended.')
if result == 0:
print('The game is a tie.')
else:
print('The winner is {}'.format('X' if result == 1 else 'O'))
return result
if verbose:
player = "X" if piece_type == 1 else "O"
print(player + " makes move...")
# Game continues
if piece_type == 1: action = player1.get_input(self, piece_type)
else: action = player2.get_input(self, piece_type)
if verbose:
player = "X" if piece_type == 1 else "O"
print(action)
if action != "PASS":
# If invalid input, continue the loop. Else it places a chess on the board.
if not self.place_chess(action[0], action[1], piece_type):
if verbose:
self.visualize_board()
continue
self.died_pieces = self.remove_died_pieces(3 - piece_type) # Remove the dead pieces of opponent
else:
self.previous_board = deepcopy(self.board)
if verbose:
self.visualize_board() # Visualize the board again
print()
self.n_move += 1
self.X_move = not self.X_move # Players take turn
def readInput(n, path="input.txt"):
with open(path, 'r') as f:
lines = f.readlines()
piece_type = int(lines[0])
previous_board = [[int(x) for x in line.rstrip('\n')] for line in lines[1:n+1]]
board = [[int(x) for x in line.rstrip('\n')] for line in lines[n+1: 2*n+1]]
return piece_type, previous_board, board
def writeOutput(result, path="output.txt"):
res = ""
if result == "PASS":
res = "PASS"
else:
res += str(result[0]) + ',' + str(result[1])
with open(path, 'w') as f:
f.write(res)
def readOutput(path="output.txt"):
with open(path, 'r') as f:
position = f.readline().strip().split(',')
if position[0] == "PASS":
return "PASS", -1, -1
x = int(position[0])
y = int(position[1])
return "MOVE", x, y
def writeNextInput(piece_type, previous_board, board, path="input.txt"):
res = ""
res += str(piece_type) + "\n"
for item in previous_board:
res += "".join([str(x) for x in item])
res += "\n"
for item in board:
res += "".join([str(x) for x in item])
res += "\n"
with open(path, 'w') as f:
f.write(res[:-1]);
def judge(n_move, verbose=False):
N = 5
piece_type, previous_board, board = readInput(N)
go = GO(N)
go.verbose = verbose
go.set_board(piece_type, previous_board, board)
go.n_move = n_move
try:
action, x, y = readOutput()
except:
print("output.txt not found or invalid format")
sys.exit(3-piece_type)
if action == "MOVE":
if not go.place_chess(x, y, piece_type):
print('Game end.')
print('The winner is {}'.format('X' if 3 - piece_type == 1 else 'O'))
sys.exit(3 - piece_type)
go.died_pieces = go.remove_died_pieces(3 - piece_type)
if verbose:
go.visualize_board()
print()
if go.game_end(piece_type, action):
result = go.judge_winner()
if verbose:
print('Game end.')
if result == 0:
print('The game is a tie.')
else:
print('The winner is {}'.format('X' if result == 1 else 'O'))
sys.exit(result)
piece_type = 2 if piece_type == 1 else 1
if action == "PASS":
go.previous_board = go.board
writeNextInput(piece_type, go.previous_board, go.board)
sys.exit(0)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--move", "-m", type=int, help="number of total moves", default=0)
parser.add_argument("--verbose", "-v", type=bool, help="print board", default=False)
args = parser.parse_args()
judge(args.move, args.verbose)