moving towards json engine

This commit is contained in:
Sander Land committed 2020-01-23 16:20:08 +01:00
1 parent def9ff372a
commit dcc80c6ad3
9 files changed
+402 -436

No files matched your search

+80 -95
View File
@@ -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}"
)