more bug fixes

This commit is contained in:
Sander Land committed 2020-01-26 00:04:03 +01:00
1 parent 99930f4a7c
commit c1dc9c87fc
4 files changed
+44 -52

No files matched your search

+10 -16
View File
@@ -54,12 +54,12 @@ class Move:
def analysis_ready(self): def analysis_ready(self):
return self.analysis and self.pass_analysis return self.analysis and self.pass_analysis
def format_score(self,score=None): def format_score(self, score=None):
score = score or self.score score = score or self.score
return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}" return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}"
def comment(self,sgf=False, eval=False, hints=False): def comment(self, sgf=False, eval=False, hints=False):
if not self.parent: # root if not self.parent: # root
return "" return ""
text = f"Move {self.move_number}: {self.bw_player()} {self.gtp()} {'(AI Move)' if self.robot else ''}\n" text = f"Move {self.move_number}: {self.bw_player()} {self.gtp()} {'(AI Move)' if self.robot else ''}\n"
text += self.x_comment text += self.x_comment
@@ -89,8 +89,8 @@ class Move:
if points_lost > 0.5: if points_lost > 0.5:
text += f"Estimate point loss: {points_lost:.1f}\n" text += f"Estimate point loss: {points_lost:.1f}\n"
if eval or sgf: # show undos on move itself in both sgf and while playing if eval or sgf: # show undos on move itself in both sgf and while playing
undids = [m.gtp() + (f"({m.evaluation_info[0]*100:.1f}% efficient)" if m.evaluation_info[0] else "") for m in self.parent.children if m!=self] undids = [m.gtp() + (f"({m.evaluation_info[0]*100:.1f}% efficient)" if m.evaluation_info[0] else "") for m in self.parent.children if m != self]
if undids: if undids:
text += "Other attempted move(s): " + ", ".join(undids) + "\n" text += "Other attempted move(s): " + ", ".join(undids) + "\n"
@@ -104,13 +104,13 @@ class Move:
if self.parent and self.parent.analysis_ready and self.analysis_ready: if self.parent and self.parent.analysis_ready and self.analysis_ready:
return self.evaluation, self.parent.temperature_stats[2] return self.evaluation, self.parent.temperature_stats[2]
else: else:
return None,None return None, None
# needing own analysis ready # needing own analysis ready
@property @property
def temperature_stats(self): def temperature_stats(self):
best = float(self.analysis[0]["scoreLead"]) best = float(self.analysis[0]["scoreLead"])
worst= float(self.pass_analysis[0]["scoreLead"]) worst = float(self.pass_analysis[0]["scoreLead"])
return best, worst, abs(best - worst) return best, worst, abs(best - worst)
@property @property
@@ -178,7 +178,6 @@ class Move:
class Board: class Board:
def __init__(self, board_size=19): def __init__(self, board_size=19):
self.board_size = board_size self.board_size = board_size
self.root = Move(1, (None, None)) # root is 1=white so black is first self.root = Move(1, (None, None)) # root is 1=white so black is first
@@ -219,9 +218,7 @@ class Board:
nb_chains = list({c for c in neighbours([move]) if c >= 0 and self.chains[c][0].player == move.player}) nb_chains = list({c for c in neighbours([move]) if c >= 0 and self.chains[c][0].player == move.player})
if nb_chains: if nb_chains:
this_chain = nb_chains[0] this_chain = nb_chains[0]
self.board = [ 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
[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:]: for oc in nb_chains[1:]:
self.chains[nb_chains[0]] += self.chains[oc] self.chains[nb_chains[0]] += self.chains[oc]
self.chains[oc] = [] self.chains[oc] = []
@@ -299,10 +296,7 @@ class Board:
@property @property
def prisoner_count(self): def prisoner_count(self):
return [sum([m.player==player for m in self.prisoners]) for player in [0,1]] return [sum([m.player == player for m in self.prisoners]) for player in [0, 1]]
def __str__(self): def __str__(self):
return ( return "\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board) + f"\ncaptures: {self.prisoner_count}"
"\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board)
+ f"\ncaptures: {self.prisoner_count}"
)
+3 -1
View File
@@ -7,7 +7,9 @@
}, },
"board": { "board": {
"size": 19, "size": 19,
"komi": 6.5 "komi_19": 6.5,
"komi_13": 6.5,
"komi_9": 6.5
}, },
"ui": { "ui": {
"size_min": 1, "size_min": 1,
+23 -27
View File
@@ -22,17 +22,14 @@ class EngineControls(GridLayout):
self.command = shlex.split(Config.get("engine")["command"]) self.command = shlex.split(Config.get("engine")["command"])
analysis_settings = Config.get("analysis") analysis_settings = Config.get("analysis")
self.visits = [ self.visits = [[analysis_settings["pass_visits"], analysis_settings["visits"]], [analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"]]]
[analysis_settings["pass_visits"], analysis_settings["visits"]],
[analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"]],
]
self.train_settings = Config.get("trainer") self.train_settings = Config.get("trainer")
self.debug = Config.get("debug")["level"] self.debug = Config.get("debug")["level"]
self.board_size = Config.get("board")["size"] self.board_size = Config.get("board")["size"]
self.komi = Config.get("board")["komi"]
self.ready = False self.ready = False
self.message_queue = None self.message_queue = None
self.board = Board(self.board_size) self.board = Board(self.board_size)
self.komi = 6.5 # loaded from config in init
self.outstanding_analysis_queries = [] # allows faster interaction while kata is starting self.outstanding_analysis_queries = [] # allows faster interaction while kata is starting
self.kata = None self.kata = None
@@ -87,11 +84,11 @@ class EngineControls(GridLayout):
# mr.waiting_for_analysis # mr.waiting_for_analysis
self.redraw() self.redraw()
def update_evaluation(self,undo_triggered = False): def update_evaluation(self, undo_triggered=False):
current_move = self.board.current_move current_move = self.board.current_move
if self.eval.active(current_move.player): if self.eval.active(current_move.player):
self.info.text = current_move.comment(eval=self.eval.active(current_move.player), hints=self.hints.active(current_move.player)) self.info.text = current_move.comment(eval=self.eval.active(current_move.player), hints=self.hints.active(current_move.player))
self.evaluation.text = '' self.evaluation.text = ""
if current_move.analysis_ready and self.eval.active(current_move.player): if current_move.analysis_ready and self.eval.active(current_move.player):
self.score.text = current_move.format_score().replace("-", "\u2013") self.score.text = current_move.format_score().replace("-", "\u2013")
self.temperature.text = f"{current_move.temperature_stats[2]:.1f}" self.temperature.text = f"{current_move.temperature_stats[2]:.1f}"
@@ -111,7 +108,7 @@ class EngineControls(GridLayout):
self.board.undo() self.board.undo()
undo_triggered = True undo_triggered = True
if len(current_move.parent.children) >= ts["num_undo_prompts"] + 1: if len(current_move.parent.children) >= ts["num_undo_prompts"] + 1:
best_move = sorted([m for m in current_move.parent.children], key=lambda m: -(m.evaluation_info[0] or 0) )[0] best_move = sorted([m for m in current_move.parent.children], key=lambda m: -(m.evaluation_info[0] or 0))[0]
best_move.x_comment = f"Automatically played as best option after max. {ts['num_undo_prompts']} undo(s).\n" best_move.x_comment = f"Automatically played as best option after max. {ts['num_undo_prompts']} undo(s).\n"
self.board.play(best_move) self.board.play(best_move)
self.update_evaluation(undo_triggered=True) self.update_evaluation(undo_triggered=True)
@@ -127,24 +124,21 @@ class EngineControls(GridLayout):
# select move # select move
current_move = self.board.current_move current_move = self.board.current_move
pos_moves = [ pos_moves = [(d["move"], float(d["scoreMean"]), d["evaluation"]) for d in current_move.ai_moves if int(d["visits"]) >= ts["balance_play_min_visits"]]
(d["move"], float(d["scoreMean"]), d["evaluation"]) sel_moves = pos_moves[:1]
for d in current_move.ai_moves
if int(d["visits"]) >= ts["balance_play_min_visits"]
]
sel_moves = [pos_moves[0][0]]
# don't play suicidal to balance score - pass when it's best # don't play suicidal to balance score - pass when it's best
if self.ai_balance.active and pos_moves[0][0] != "pass": if self.ai_balance.active and pos_moves[0][0] != "pass":
sel_moves = [ sel_moves = [
move (move, score, eval)
for move, score, eval in pos_moves for move, score, eval in pos_moves
if eval > ts["balance_play_randomize_eval"] if eval > ts["balance_play_randomize_eval"]
and -current_move.player_sign * score > 0
or eval > ts["balance_play_min_eval"] or eval > ts["balance_play_min_eval"]
and -current_move.player_sign * score > ts["balance_play_target_score"] and -current_move.player_sign * score > ts["balance_play_target_score"]
] or sel_moves ] or sel_moves
aimove = Move(player=self.board.current_player, gtpcoords=random.choice(sel_moves), robot=True) aimove = Move(player=self.board.current_player, gtpcoords=random.choice(sel_moves)[0], robot=True)
if len(sel_moves) > 1: if len(sel_moves) > 1:
aimove.x_comment = "{'AI Balance on, moves considered: " + ", ".join(f"{move} ({aimove.format_score(score)})" for move, score, eval in sel_moves) + "\n" aimove.x_comment = "AI Balance on, moves considered: " + ", ".join(f"{move} ({aimove.format_score(score)})" for move, score, _ in sel_moves) + "\n"
self.play(aimove) self.play(aimove)
def _do_undo(self): def _do_undo(self):
@@ -159,22 +153,20 @@ class EngineControls(GridLayout):
return return
self.board.undo() self.board.undo()
def _do_init(self, board_size, komi=None): def _do_init(self, board_size):
self.board_size = board_size self.board_size = board_size
self.komi = Config.get("board")[f"komi_{board_size}"]
self.board = Board(board_size) self.board = Board(board_size)
self._request_analysis(self.board.root) self._request_analysis(self.board.root)
self.redraw(include_board=True) self.redraw(include_board=True)
self.ready = True self.ready = True
def _do_analyze_sgf(self, sgf): def _do_analyze_sgf(self, sgf):
self._do_init(self.board_size, self.komi) self._do_init(self.board_size)
sgfmoves = re.findall(r"([BW])\[([a-z]{2})\]", sgf) sgfmoves = re.findall(r"([BW])\[([a-z]{2})\]", sgf)
moves = [Move(player=Move.PLAYERS.index(p.upper()), sgfcoords=(mv, self.board_size)) for p, mv in sgfmoves] moves = [Move(player=Move.PLAYERS.index(p.upper()), sgfcoords=(mv, self.board_size)) for p, mv in sgfmoves]
for move in moves: for move in moves:
self.play(move) self.play(move)
while not all(m.analysis for m in moves):
time.sleep(0.05)
self.info.text = f"{sum([1 if m.analysis else 0 for m in moves])}/{len(moves)} analyzed"
# analysis thread # analysis thread
def _analysis_read_thread(self): def _analysis_read_thread(self):
@@ -213,9 +205,7 @@ class EngineControls(GridLayout):
if self.debug: if self.debug:
print("query", query) print("query", query)
self._send_analysis_query(query) self._send_analysis_query(query)
query.update( query.update({"id": f"PASS_{move_id}", "maxVisits": self.visits[fast][0], "includeOwnership": False})
{"id": f"PASS_{move_id}", "maxVisits": self.visits[fast][0], "includeOwnership": False}
)
query["moves"] += [[move.bw_player(next_move=True), "pass"]] query["moves"] += [[move.bw_player(next_move=True), "pass"]]
query["analyzeTurns"][0] += 1 query["analyzeTurns"][0] += 1
self._send_analysis_query(query) self._send_analysis_query(query)
@@ -225,13 +215,19 @@ class EngineControls(GridLayout):
return f"(;GM[1]FF[4]SZ[{self.board_size}]KM[{self.komi}]RU[JP];" + ";".join(mvs) + ")" return f"(;GM[1]FF[4]SZ[{self.board_size}]KM[{self.komi}]RU[JP];" + ";".join(mvs) + ")"
def format_move(move, prev_move): def format_move(move, prev_move):
undos = [m for m in prev_move.children if m!=move] undos = [m for m in prev_move.children if m != move]
undo_cr = "".join(f"MA[{u.sgfcoords(self.board_size)}]" for u in undos if u.coords[0]) undo_cr = "".join(f"MA[{u.sgfcoords(self.board_size)}]" for u in undos if u.coords[0])
if prev_move.analysis and prev_move.analysis[0]["move"] != "pass" and (move.evaluation_info[0] or 0.0) < self.train_settings['sgf_show_best_move_threshold']: if (
prev_move.analysis
and prev_move.analysis[0]["move"] != "pass"
and (move.evaluation_info[0] or 0.0) < self.train_settings["sgf_show_best_move_threshold"]
and prev_move.analysis[0]["move"] != move.gtp()
):
best_sq = f"SQ[{Move(gtpcoords=prev_move.analysis[0]['move'], player=0).sgfcoords(self.board_size)}]" best_sq = f"SQ[{Move(gtpcoords=prev_move.analysis[0]['move'], player=0).sgfcoords(self.board_size)}]"
else: else:
best_sq = "" best_sq = ""
return move.sgf(self.board_size) + f"C[{move.comment(sgf=True)}]{undo_cr}{best_sq}" return move.sgf(self.board_size) + f"C[{move.comment(sgf=True)}]{undo_cr}{best_sq}"
moves = self.board.moves moves = self.board.moves
sgfmoves_small = [mv.sgf(self.board_size) for mv in moves] sgfmoves_small = [mv.sgf(self.board_size) for mv in moves]
sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(moves, [self.board.root] + moves[:-1])] sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(moves, [self.board.root] + moves[:-1])]
+8 -8
View File
@@ -50,25 +50,25 @@ class BadukPanWidget(Widget):
else: else:
xd, xp = self._find_closest(touch.x) xd, xp = self._find_closest(touch.x)
yd, yp = self._find_closest(touch.y) yd, yp = self._find_closest(touch.y)
stones_here = [m for m in self.engine.board.stones if m.coords == (xp,yp)] stones_here = [m for m in self.engine.board.stones if m.coords == (xp, yp)]
if stones_here and max(yd, xd) < self.grid_size / 2: # load old comment if stones_here and max(yd, xd) < self.grid_size / 2: # load old comment
self.engine.info.text = stones_here[-1].comment(sgf=True) self.engine.info.text = stones_here[-1].comment(sgf=True)
self.ghost_stone = None self.ghost_stone = None
self.redraw() # remove ghost self.redraw() # remove ghost
# drawing functions # drawing functions
def on_size(self, *args): def on_size(self, *args):
self.draw_board() self.draw_board()
self.redraw() self.redraw()
def draw_stone(self, x, y, col, innercol=None, evalcol=None, evalsize=10.0): def draw_stone(self, x, y, col, innercol=None, evalcol=None, evalsize=10.0, scale=1.0):
draw_circle((self.gridpos[x], self.gridpos[y]), self.stone_size, col) draw_circle((self.gridpos[x], self.gridpos[y]), self.stone_size * scale, col)
if evalcol: if evalcol:
evalsize = min(self.EVAL_BOUNDS[1], max(evalsize, self.EVAL_BOUNDS[0])) / self.EVAL_BOUNDS[1] evalsize = min(self.EVAL_BOUNDS[1], max(evalsize, self.EVAL_BOUNDS[0])) / self.EVAL_BOUNDS[1]
draw_circle((self.gridpos[x], self.gridpos[y]), math.sqrt(evalsize) * self.stone_size * 0.5, evalcol) draw_circle((self.gridpos[x], self.gridpos[y]), math.sqrt(evalsize) * self.stone_size * scale * 0.5, evalcol)
if innercol: if innercol:
Color(*innercol) Color(*innercol)
Line(circle=(self.gridpos[x], self.gridpos[y], self.stone_size * 0.45 / 0.85), width=1.75) Line(circle=(self.gridpos[x], self.gridpos[y], self.stone_size * scale * 0.45 / 0.85), width=1.75)
def _eval_spectrum(self, score): def _eval_spectrum(self, score):
score = max(0, score) score = max(0, score)
@@ -153,11 +153,11 @@ class BadukPanWidget(Widget):
# hints # hints
if self.engine.hints.active(current_player): if self.engine.hints.active(current_player):
for d in last_move.ai_moves: for i, d in enumerate(last_move.ai_moves):
move = Move(gtpcoords=d["move"], player=0) move = Move(gtpcoords=d["move"], player=0)
c = [*self._eval_spectrum(d["evaluation"]), 0.5] c = [*self._eval_spectrum(d["evaluation"]), 0.5]
if move.coords[0] is not None and move.coords not in undo_coords: if move.coords[0] is not None and move.coords not in undo_coords:
self.draw_stone(move.coords[0], move.coords[1], c) self.draw_stone(move.coords[0], move.coords[1], c, scale=1.0 if i == 0 else 0.8)
# hover next move ghost stone # hover next move ghost stone
if self.ghost_stone: if self.ghost_stone: