moving towards json engine
This commit is contained in:
1 parent
def9ff372a
commit
dcc80c6ad3
9 files changed
+402
-436
No files matched your search
@@ -1,71 +1,102 @@
|
||||
import numpy as np
|
||||
from move import Move
|
||||
|
||||
|
||||
class Board:
|
||||
_move_id_counter = 0 # used to make a map to all moves across all games
|
||||
class IllegalMoveException(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, board_size = 19):
|
||||
|
||||
class Board:
|
||||
_move_id_counter = 0 # used to make a map to all moves across all games
|
||||
|
||||
def __init__(self, board_size=19):
|
||||
self.board_size = board_size
|
||||
self.root = Move(None, (None, None))
|
||||
self.root = Move(1, (None, None)) # root is 1=white so black is first
|
||||
self.root.id = -1
|
||||
self.current_move = self.root
|
||||
self.all_moves = {}
|
||||
self.board = np.empty( (self.board_size,self.board_size ) ) # values are indexes in `chains`
|
||||
self.board.fill(np.nan)
|
||||
self.chains = [] # cache of chain id
|
||||
self._init_chains()
|
||||
|
||||
# -- move tree functions --
|
||||
def _init_chains(self):
|
||||
self.board = [[-1 for x in range(self.board_size)] for y in range(self.board_size)] # board pos -> chain id
|
||||
self.chains = [] # chain id -> chain
|
||||
self.prisoners = []
|
||||
self.last_capture = []
|
||||
try:
|
||||
for m in self.moves:
|
||||
self._validate_move_and_update_chains(m, True) # ignore ko since we didn't know if it was forced
|
||||
except IllegalMoveException as e:
|
||||
raise Exception(f"Unexpected illegal move ({str(e)})")
|
||||
|
||||
# -- move tree functions --
|
||||
def update_board(self,move):
|
||||
def neighbours_ix(cs):
|
||||
return {(x + dx, y + dy) for x, y in cs for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)] if x + dx >= 0 and y + dy >= 0 and y + dy < self.board_size and x + dx < self.board_size}
|
||||
def _validate_move_and_update_chains(self, move, ignore_ko):
|
||||
def neighbours(moves):
|
||||
return {
|
||||
self.board[m.coords[1] + dy][m.coords[0] + dx]
|
||||
for m in moves
|
||||
for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
||||
if 0 <= m.coords[0] + dx < self.board_size and 0 <= m.coords[1] + dy < self.board_size
|
||||
}
|
||||
|
||||
def neighbours(cs):
|
||||
return {self.board[y][x] for x, y in neighbours_ix(cs)}
|
||||
ko_or_snapback = len(self.last_capture) == 1 and self.last_capture[0] == move
|
||||
self.last_capture = []
|
||||
|
||||
nb_chains = list({int(c) for c in neighbours([move.coords]) if not np.isnan(c) and self.chains[int(c)][0].player == move.player})
|
||||
if move.is_pass:
|
||||
return
|
||||
|
||||
if self.board[move.coords[1]][move.coords[0]] != -1:
|
||||
raise IllegalMoveException("Space occupied")
|
||||
|
||||
nb_chains = list({c for c in neighbours([move]) if c >= 0 and self.chains[c][0].player == move.player})
|
||||
if nb_chains:
|
||||
self.board[move.coords[1], move.coords[0]] = nb_chains[0]
|
||||
self.board[np.isin(self.board, nb_chains)] = nb_chains[0]
|
||||
this_chain = nb_chains[0]
|
||||
self.board = [
|
||||
[nb_chains[0] if sq in nb_chains else sq for sq in line] for line in self.board
|
||||
] # merge chains connected by this move
|
||||
for oc in nb_chains[1:]:
|
||||
self.chains[nb_chains[0]] += self.chains[oc]
|
||||
self.chains[oc] = []
|
||||
self.chains[nb_chains[0]].append(move)
|
||||
else:
|
||||
self.board[move.coords[1], move.coords[0]] = len(self.chains)
|
||||
this_chain = len(self.chains)
|
||||
self.chains.append([move])
|
||||
opp_nb_chains = {int(c) for c in neighbours([move.coords]) if self.chains[int(c)][0].player != move.player}
|
||||
capture = False
|
||||
self.board[move.coords[1]][move.coords[0]] = this_chain
|
||||
|
||||
opp_nb_chains = {c for c in neighbours([move]) if c >= 0 and self.chains[c][0].player != move.player}
|
||||
for c in opp_nb_chains:
|
||||
if np.nan not in neighbours([m.coords for m in self.chains[c]]):
|
||||
capture = True
|
||||
if -1 not in neighbours(self.chains[c]):
|
||||
self.last_capture += self.chains[c]
|
||||
for om in self.chains[c]:
|
||||
self.board[om.coords[1], om.coords[0]] = np.nan
|
||||
self.board[om.coords[1]][om.coords[0]] = -1
|
||||
self.chains[c] = []
|
||||
if not capture:
|
||||
if np.nan not in neighbours([m.coords for m in self.chains[c]]):
|
||||
|
||||
if ko_or_snapback and len(self.last_capture) == 1 and not ignore_ko:
|
||||
raise IllegalMoveException("Ko")
|
||||
self.prisoners += self.last_capture
|
||||
|
||||
if -1 not in neighbours(self.chains[this_chain]):
|
||||
raise IllegalMoveException("Suicide")
|
||||
|
||||
# Play a Move from the current position, returns false if invalid.
|
||||
def play(self, move) -> bool:
|
||||
def play(self, move, ignore_ko=False):
|
||||
try:
|
||||
self._validate_move_and_update_chains(move, ignore_ko)
|
||||
except IllegalMoveException as e:
|
||||
self._init_chains() # restore
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
move = self.current_move.play(move) # traverse or append
|
||||
move = self.current_move.play(move) # traverse or append
|
||||
if not move.id:
|
||||
move.id = Board._move_id_counter
|
||||
Board._move_id_counter += 1
|
||||
self.all_moves[move.id] = move
|
||||
self.current_move = move
|
||||
return True
|
||||
return move
|
||||
|
||||
def undo(self):
|
||||
if self.current_move != self.root:
|
||||
self.current_move = self.current_move.parent
|
||||
self._init_chains()
|
||||
|
||||
@property
|
||||
def moves(self) -> list: # flat list of moves to current
|
||||
moves = []
|
||||
p = self.current_move
|
||||
@@ -74,82 +105,36 @@ class Board:
|
||||
p = p.parent
|
||||
return moves[::-1]
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
return self.moves.__iter__()
|
||||
|
||||
def __getitem__(self, ix):
|
||||
if ix == -1:
|
||||
return self.current_move
|
||||
else:
|
||||
return self.moves[ix]
|
||||
|
||||
@property
|
||||
def current_player(self):
|
||||
return self.current_move.player
|
||||
|
||||
# --analysis
|
||||
# --analysis
|
||||
|
||||
def store_analysis(self,json):
|
||||
id = int(json["id"])
|
||||
def store_analysis(self, json):
|
||||
if json["id"].starts_with("PASS_"):
|
||||
id = int(json["id"].lstrip("PASS_"))
|
||||
else:
|
||||
id = int(json["id"])
|
||||
move = self.all_moves.get(id)
|
||||
if move: # else this should be old
|
||||
move.set
|
||||
if move: # else this should be old
|
||||
move.set_analysis(json)
|
||||
else:
|
||||
print("WARNING: ORPHANED ANALYSIS FOUND - RECENT NEW GAME?")
|
||||
|
||||
# -- board visualization etc
|
||||
# -- board visualization etc
|
||||
# ko: single capture and
|
||||
# other not allowed: suicide
|
||||
|
||||
# todo - factor into global state etc? for valid move, cached etc
|
||||
@property
|
||||
def stones(self):
|
||||
board = np.empty( (self.board_size,self.board_size ) )
|
||||
def neighbours_ix(cs):
|
||||
return {(x+dx,y+dy) for x,y in cs for dy, dx in [(-1,0),(1,0),(0,-1),(0,1)] if x+dx >=0 and y+dy >=0 and y+dy < self.board_size and x+dx < self.board_size}
|
||||
def neighbours(cs):
|
||||
return {board[y][x] for x,y in neighbours_ix(cs) if not np.isnan(board[y][x]) }
|
||||
|
||||
board.fill(np.nan)
|
||||
moves = self.moves()
|
||||
chains = []
|
||||
for m in moves:
|
||||
nb_chains = list({int(c) for c in neighbours([m.coords]) if chains[int(c)][0].player==m.player})
|
||||
if nb_chains:
|
||||
board[m.coords[1],m.coords[0]] = nb_chains[0]
|
||||
board[ np.isin(board,nb_chains) ] = nb_chains[0]
|
||||
for oc in nb_chains[1:]:
|
||||
chains[nb_chains[0]] += chains[oc]
|
||||
chains[oc] = []
|
||||
chains[nb_chains[0]].append(m)
|
||||
else:
|
||||
board[m.coords[1],m.coords[0]] = len(chains)
|
||||
chains.append([m])
|
||||
opp_nb_chains = {int(c) for c in neighbours([m.coords]) if chains[int(c)][0].player != m.player}
|
||||
for c in opp_nb_chains:
|
||||
if np.nan not in neighbours([m.coords for m in chains[c]]):
|
||||
for om in chains[c]:
|
||||
board[om.coords[1],om.coords[0]] = np.nan
|
||||
chains[c] = []
|
||||
return chains
|
||||
return sum(self.chains, [])
|
||||
|
||||
def sgf(self):
|
||||
return "SGF[]"
|
||||
|
||||
if __name__ == "__main__":
|
||||
b=Board(9)
|
||||
b.play(Move(gtpcoords="A3",player=0))
|
||||
b.play(Move(gtpcoords="A9",player=0))
|
||||
b.play(Move(gtpcoords="B9",player=0))
|
||||
b.play(Move(gtpcoords="A4",player=0))
|
||||
b.play(Move(gtpcoords="C8",player=0))
|
||||
b.play(Move(gtpcoords="C9",player=0))
|
||||
print(b.stones)
|
||||
b.play(Move(gtpcoords="J9",player=1))
|
||||
b.play(Move(gtpcoords="J8", player=0))
|
||||
print(b.stones)
|
||||
b.play(Move(gtpcoords="H9", player=0))
|
||||
print(b.stones)
|
||||
b.play(Move(gtpcoords="J9", player=0))
|
||||
print(b.stones)
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"\n".join("".join("BW"[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board)
|
||||
+ f"\ncaptures: {self.prisoners}"
|
||||
)
|
||||
Reference in new issue
Block a user