-
Notifications
You must be signed in to change notification settings - Fork 9
/
gamestate.py
229 lines (201 loc) · 7.79 KB
/
gamestate.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
from numpy import zeros, int_
from unionfind import UnionFind
from meta import GameMeta
class GameState:
"""
Stores information representing the current state of a game of hex, namely
the board and the current turn. Also provides functions for playing game.
"""
# dictionary associating numbers with players
# PLAYERS = {"none": 0, "white": 1, "black": 2}
# move value of -1 indicates the game has ended so no move is possible
# GAME_OVER = -1
# represent edges in the union find structure for detecting the connection
# for player 1 Edge1 is high and EDGE2 is low
# for player 2 Edge1 is left and EDGE2 is right
# neighbor_patterns = ((-1, 0), (0, -1), (-1, 1), (0, 1), (1, 0), (1, -1))
def __init__(self, size):
"""
Initialize the game board and give white first turn.
Also create our union find structures for win checking.
Args:
size (int): The board size
"""
self.size = size
self.to_play = GameMeta.PLAYERS['white']
self.board = zeros((size, size))
self.board = int_(self.board)
self.white_played = 0
self.black_played = 0
self.white_groups = UnionFind()
self.black_groups = UnionFind()
self.white_groups.set_ignored_elements([GameMeta.EDGE1, GameMeta.EDGE2])
self.black_groups.set_ignored_elements([GameMeta.EDGE1, GameMeta.EDGE2])
def play(self, cell: tuple) -> None:
"""
Play a stone of the player that owns the current turn in input cell.
Args:
cell (tuple): row and column of the cell
"""
if self.to_play == GameMeta.PLAYERS['white']:
self.place_white(cell)
self.to_play = GameMeta.PLAYERS['black']
elif self.to_play == GameMeta.PLAYERS['black']:
self.place_black(cell)
self.to_play = GameMeta.PLAYERS['white']
def get_num_played(self) -> dict:
return {'white': self.white_played, 'black': self.black_played}
def get_white_groups(self) -> dict:
"""
Returns (dict): group of white groups for unionfind check
"""
return self.white_groups.get_groups()
def get_black_groups(self) -> dict:
"""
Returns (dict): group of white groups for unionfind check
"""
return self.black_groups.get_groups()
def place_white(self, cell: tuple) -> None:
"""
Place a white stone regardless of whose turn it is.
Args:
cell (tuple): row and column of the cell
"""
if self.board[cell] == GameMeta.PLAYERS['none']:
self.board[cell] = GameMeta.PLAYERS['white']
self.white_played += 1
else:
raise ValueError("Cell occupied")
# if the placed cell touches a white edge connect it appropriately
if cell[0] == 0:
self.white_groups.join(GameMeta.EDGE1, cell)
if cell[0] == self.size - 1:
self.white_groups.join(GameMeta.EDGE2, cell)
# join any groups connected by the new white stone
for n in self.neighbors(cell):
if self.board[n] == GameMeta.PLAYERS['white']:
self.white_groups.join(n, cell)
def place_black(self, cell: tuple) -> None:
"""
Place a black stone regardless of whose turn it is.
Args:
cell (tuple): row and column of the cell
"""
if self.board[cell] == GameMeta.PLAYERS['none']:
self.board[cell] = GameMeta.PLAYERS['black']
self.black_played += 1
else:
raise ValueError("Cell occupied")
# if the placed cell touches a black edge connect it appropriately
if cell[1] == 0:
self.black_groups.join(GameMeta.EDGE1, cell)
if cell[1] == self.size - 1:
self.black_groups.join(GameMeta.EDGE2, cell)
# join any groups connected by the new black stone
for n in self.neighbors(cell):
if self.board[n] == GameMeta.PLAYERS['black']:
self.black_groups.join(n, cell)
def would_lose(self, cell: tuple, color: int) -> bool:
"""
Return True is the move indicated by cell and color would lose the game,
False otherwise.
"""
connect1 = False
connect2 = False
if color == GameMeta.PLAYERS['black']:
if cell[1] == 0:
connect1 = True
elif cell[1] == self.size - 1:
connect2 = True
for n in self.neighbors(cell):
if self.black_groups.connected(GameMeta.EDGE1, n):
connect1 = True
elif self.black_groups.connected(GameMeta.EDGE2, n):
connect2 = True
elif color == GameMeta.PLAYERS['white']:
if cell[0] == 0:
connect1 = True
elif cell[0] == self.size - 1:
connect2 = True
for n in self.neighbors(cell):
if self.white_groups.connected(GameMeta.EDGE1, n):
connect1 = True
elif self.white_groups.connected(GameMeta.EDGE2, n):
connect2 = True
return connect1 and connect2
def turn(self) -> int:
"""
Return the player with the next move.
"""
return self.to_play
def set_turn(self, player: int) -> None:
"""
Set the player to take the next move.
Raises:
ValueError if player turn is not 1 or 2
"""
if player in GameMeta.PLAYERS.values() and player != GameMeta.PLAYERS['none']:
self.to_play = player
else:
raise ValueError('Invalid turn: ' + str(player))
@property
def winner(self) -> int:
"""
Return a number corresponding to the winning player,
or none if the game is not over.
"""
if self.white_groups.connected(GameMeta.EDGE1, GameMeta.EDGE2):
return GameMeta.PLAYERS['white']
elif self.black_groups.connected(GameMeta.EDGE1, GameMeta.EDGE2):
return GameMeta.PLAYERS['black']
else:
return GameMeta.PLAYERS['none']
def neighbors(self, cell: tuple) -> list:
"""
Return list of neighbors of the passed cell.
Args:
cell tuple):
"""
x = cell[0]
y = cell[1]
return [(n[0] + x, n[1] + y) for n in GameMeta.NEIGHBOR_PATTERNS
if (0 <= n[0] + x < self.size and 0 <= n[1] + y < self.size)]
def moves(self) -> list:
"""
Get a list of all moves possible on the current board.
"""
moves = []
for y in range(self.size):
for x in range(self.size):
if self.board[x, y] == GameMeta.PLAYERS['none']:
moves.append((x, y))
return moves
def __str__(self):
"""
Print an ascii representation of the game board.
Notes:
Used for gtp interface
"""
white = 'W'
black = 'B'
empty = '.'
ret = '\n'
coord_size = len(str(self.size))
offset = 1
ret += ' ' * (offset + 1)
for x in range(self.size):
ret += chr(ord('A') + x) + ' ' * offset * 2
ret += '\n'
for y in range(self.size):
ret += str(y + 1) + ' ' * (offset * 2 + coord_size - len(str(y + 1)))
for x in range(self.size):
if self.board[x, y] == GameMeta.PLAYERS['white']:
ret += white
elif self.board[x, y] == GameMeta.PLAYERS['black']:
ret += black
else:
ret += empty
ret += ' ' * offset * 2
ret += white + "\n" + ' ' * offset * (y + 1)
ret += ' ' * (offset * 2 + 1) + (black + ' ' * offset * 2) * self.size
return ret