-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathothello.py
254 lines (216 loc) · 7.86 KB
/
othello.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
# -*- coding: utf-8 -*-
from __future__ import print_function
import numpy as np
from contextlib import contextmanager
import sys
import traceback
from util import Hash, LRUCache
class Board(object):
BLANK = 0
BLACK = 1
WHITE = 2
DIRECTIONS = [(1, 0), (-1, 0),
(0, 1), (0, -1),
(1, 1), (-1, -1),
(-1, 1), (1, -1)]
@classmethod
def opponent(cls, player):
if player == cls.BLACK:
return cls.WHITE
else:
return cls.BLACK
def __init__(self, size=8):
assert size % 2 == 0
self._size = size
self.init_board()
self._feasible_pos_cache = LRUCache(900000)
self._board_state_cache = LRUCache(3500000)
self._hash = Hash()
def init_board(self):
self._board = np.zeros((self._size, self._size), dtype=np.int)
i = self._size // 2
self._board[i-1][i-1] = Board.WHITE
self._board[i-1][i] = Board.BLACK
self._board[i][i] = Board.WHITE
self._board[i][i-1] = Board.BLACK
def set_board(self, board):
self._board = np.array(board, dtype=np.int)
def cache_status(self):
return self._feasible_pos_cache.size(), self._board_state_cache.size()
def feasible_pos(self, player, enable_cache=True):
h = self._hash(self._board) + player
if enable_cache and self._feasible_pos_cache.contains(h):
return self._feasible_pos_cache.get(h)
pos = []
xs, ys = np.where(self._board == Board.BLANK)
for i,j in zip(xs.tolist(), ys.tolist()):
if self.is_feasible(i, j, player):
pos.append((i, j))
self._feasible_pos_cache.put(h, pos)
return pos
def is_terminal_state(self):
h = self._hash(self._board)
if self._board_state_cache.contains(h):
return self._board_state_cache.get(h)
xs, ys = np.where(self._board == Board.BLANK)
for i,j in zip(xs.tolist(), ys.tolist()):
for di, dj in Board.DIRECTIONS:
black, white = False, False
for d in range(1, self._size):
ii = i + di * d
jj = j + dj * d
if not (ii < self._size and ii >= 0 and jj < self._size and jj >= 0):
break
piece = self._board[ii][jj]
if piece == Board.BLANK:
break
if piece == Board.WHITE:
white = True
else:
black = True
if black and white:
self._board_state_cache.put(h, False)
return False
self._board_state_cache.put(h, True)
return True
def flip(self, i, j, player):
assert self._board[i][j] == Board.BLANK
cnt = 0
for di, dj in Board.DIRECTIONS:
for d in range(1, self._size):
ii = i + di * d
jj = j + dj * d
if not (ii < self._size and ii >= 0 and jj < self._size and jj >= 0):
break
if self._board[ii][jj] == Board.BLANK:
break
if self._board[ii][jj] == player:
for x in range(1, d):
self._board[i+di*x][j+dj*x] = player
cnt += 1
break
assert cnt > 0, "\n{}\n{}\n{}".format(self._board, (i,j), player)
self._board[i][j] = player
@contextmanager
def flip2(self, i, j, player):
backup = self._board.copy()
self.flip(i, j, player)
yield self
self._board = backup
def score(self, player):
return np.sum(self._board == player)
@classmethod
def _wins(cls, b, player):
s1 = np.sum(b == player)
s2 = np.sum(b == Board.opponent(player))
return s1 > s2
def wins(self, player):
s1 = self.score(player)
s2 = self.score(Board.opponent(player))
return s1 > s2
@property
def blanks(self):
return np.sum(self.board == Board.BLANK)
def __str__(self):
return str(self._board)
def __repr__(self):
return str(self._board)
@property
def board(self):
return self._board
@property
def size(self):
return self._size
def is_feasible(self, i, j, player):
cnt = 0
for di, dj in Board.DIRECTIONS:
for d in range(1, self._size):
ii = i + di * d
jj = j + dj * d
if not (ii < self._size and ii >= 0 and jj < self._size and jj >= 0):
break
if self._board[ii][jj] == Board.BLANK:
break
if self._board[ii][jj] == player:
cnt += (d-1)
break
return cnt > 0
def _is_valid_pos(self, i, j):
return (i < self._size and i >= 0 and j < self._size and j >= 0)
def _cmd_symbol(self, i, j):
if self._board[i][j] == Board.BLANK:
return '-'
elif self._board[i][j] == Board.BLACK:
return "*"
else:
return "o"
def print_for_player(self, player):
prt = sys.stdout.write
if player not in (Board.BLACK, Board.WHITE):
pos = []
else:
pos = self.feasible_pos(player)
rows, columns = self.board.shape
for i in range(0, rows):
for j in range(0, columns):
try:
idx = pos.index((i,j))
prt(chr(ord("A") + idx))
except:
prt(self._cmd_symbol(i, j))
prt(" ")
prt("\n")
prt("\nBlack score: {0}, White score: {1}\n".format(self.score(Board.BLACK),
self.score(Board.WHITE)))
sys.stdout.flush()
class Game(object):
def __init__(self, black_player, white_player, verbose=0):
assert black_player.role == Board.BLACK
assert white_player.role == Board.WHITE
self._players = [black_player, white_player]
self._verbose = verbose
self._black_wins = 0
self._white_wins = 0
self._ties = 0
def game_stat(self):
return self._black_wins, self._white_wins, self._ties
def run(self):
board = Board()
turn = 0
for p in self._players:
p.begin_of_game(board)
while not board.is_terminal_state():
pos = board.feasible_pos(self._players[turn].role)
if len(pos) > 0:
if self._verbose > 1:
board.print_for_player(self._players[turn].role)
try:
i,j = self._players[turn].play(board)
board.flip(i, j, self._players[turn].role)
idx = pos.index((i,j))
if self._verbose > 1:
print("player {0}: {1}".format(self._players[turn].role, chr(ord("A") + idx)))
except:
if self._verbose > 0:
print("player {0} failed".format(self._players[turn].role))
print("-"*60)
traceback.print_exc()
print("-"*60)
break
turn = (turn+1) % 2
for p in self._players:
p.end_of_game(board)
if self._verbose > 1:
print('-'*60)
print('final result')
print('-'*60)
board.print_for_player(Board.BLANK)
print('-'*60)
black_score = board.score(Board.BLACK)
white_score = board.score(Board.WHITE)
if black_score > white_score:
self._black_wins += 1
elif white_score > black_score:
self._white_wins += 1
else:
self._ties += 1