many bugfixes
This commit is contained in:
1 parent
da5d99194b
commit
99930f4a7c
5 files changed
+51
-49
No files matched your search
@@ -6,9 +6,11 @@ class Move:
|
|||||||
GTP_COORD = "ABCDEFGHJKLMNOPQRSTUVWYXYZ"
|
GTP_COORD = "ABCDEFGHJKLMNOPQRSTUVWYXYZ"
|
||||||
PLAYERS = "BW"
|
PLAYERS = "BW"
|
||||||
SGF_COORD = [chr(i) for i in range(97, 123)]
|
SGF_COORD = [chr(i) for i in range(97, 123)]
|
||||||
|
_move_id_counter = -1
|
||||||
|
|
||||||
def __init__(self, player, coords=None, gtpcoords=None, sgfcoords=None, robot=False):
|
def __init__(self, player, coords=None, gtpcoords=None, sgfcoords=None, robot=False):
|
||||||
self.id = None
|
Move._move_id_counter += 1
|
||||||
|
self.id = Move._move_id_counter
|
||||||
self.player = player
|
self.player = player
|
||||||
self.coords = coords or (gtpcoords and self.gtp2ix(gtpcoords)) or self.sgf2ix(sgfcoords)
|
self.coords = coords or (gtpcoords and self.gtp2ix(gtpcoords)) or self.sgf2ix(sgfcoords)
|
||||||
self.children = []
|
self.children = []
|
||||||
@@ -30,6 +32,7 @@ class Move:
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return self.gtp().__hash__()
|
return self.gtp().__hash__()
|
||||||
|
|
||||||
|
# move tree building
|
||||||
def play(self, move):
|
def play(self, move):
|
||||||
try:
|
try:
|
||||||
return self.children[self.children.index(move)]
|
return self.children[self.children.index(move)]
|
||||||
@@ -39,14 +42,6 @@ class Move:
|
|||||||
self.children.append(move)
|
self.children.append(move)
|
||||||
return move
|
return move
|
||||||
|
|
||||||
def temperature(self):
|
|
||||||
if self.analysis:
|
|
||||||
best_score = float(self.analysis[0]["scoreLead"])
|
|
||||||
worst_score = -float(self.pass_analysis[0]["scoreLead"])
|
|
||||||
return best_score - worst_score
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
### various analysis functions
|
### various analysis functions
|
||||||
def set_analysis(self, analysis_blob, is_pass):
|
def set_analysis(self, analysis_blob, is_pass):
|
||||||
if is_pass:
|
if is_pass:
|
||||||
@@ -68,6 +63,8 @@ class Move:
|
|||||||
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
|
||||||
|
|
||||||
|
if eval and not sgf: # show undos and on previous move as well while playing
|
||||||
text += "".join(f"Auto undid move {m.gtp()} ({m.evaluation*100:.1f}% efficient)\n" for m in self.children if m.auto_undid)
|
text += "".join(f"Auto undid move {m.gtp()} ({m.evaluation*100:.1f}% efficient)\n" for m in self.children if m.auto_undid)
|
||||||
|
|
||||||
if self.analysis_ready:
|
if self.analysis_ready:
|
||||||
@@ -81,7 +78,7 @@ class Move:
|
|||||||
text += f"Top move was {self.parent.analysis[0]['move']} ({self.format_score(prev_best_score)})\n"
|
text += f"Top move was {self.parent.analysis[0]['move']} ({self.format_score(prev_best_score)})\n"
|
||||||
text += f"Pass score was {self.format_score(prev_worst_score)}\n"
|
text += f"Pass score was {self.format_score(prev_worst_score)}\n"
|
||||||
if prev_temperature < 0.5:
|
if prev_temperature < 0.5:
|
||||||
text += f"Previous temperature ({prev_temperature}) too low for evaluation\n"
|
text += f"Previous temperature ({prev_temperature:.1f}) too low for evaluation\n"
|
||||||
else:
|
else:
|
||||||
if sgf or eval:
|
if sgf or eval:
|
||||||
text += f"Evaluation: {100*self.evaluation:.1f}% efficient\n"
|
text += f"Evaluation: {100*self.evaluation:.1f}% efficient\n"
|
||||||
@@ -91,6 +88,12 @@ class Move:
|
|||||||
points_lost = self.player_sign * (prev_best_score - score)
|
points_lost = self.player_sign * (prev_best_score - score)
|
||||||
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
|
||||||
|
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:
|
||||||
|
text += "Other attempted move(s): " + ", ".join(undids) + "\n"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
text = "No analysis available" if sgf else "Analyzing move..."
|
text = "No analysis available" if sgf else "Analyzing move..."
|
||||||
return text
|
return text
|
||||||
@@ -175,14 +178,12 @@ class Move:
|
|||||||
|
|
||||||
|
|
||||||
class Board:
|
class Board:
|
||||||
_move_id_counter = 0 # used to make a map to all moves across all games
|
|
||||||
|
|
||||||
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
|
||||||
self.root.id = -1
|
|
||||||
self.current_move = self.root
|
self.current_move = self.root
|
||||||
self.all_moves = {-1: self.root}
|
self.all_moves = {self.root.id: self.root}
|
||||||
self._init_chains()
|
self._init_chains()
|
||||||
|
|
||||||
# -- move tree functions --
|
# -- move tree functions --
|
||||||
@@ -253,9 +254,6 @@ class Board:
|
|||||||
raise
|
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.all_moves[move.id] = move
|
||||||
self.current_move = move
|
self.current_move = move
|
||||||
return move
|
return move
|
||||||
@@ -295,13 +293,14 @@ class Board:
|
|||||||
def stones(self):
|
def stones(self):
|
||||||
return sum(self.chains, [])
|
return sum(self.chains, [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def game_ended(self):
|
||||||
|
return self.current_move.parent and self.current_move.is_pass and self.current_move.parent.is_pass
|
||||||
|
|
||||||
@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 sgf(self):
|
|
||||||
return "SGF[]"
|
|
||||||
|
|
||||||
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)
|
"\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board)
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@
|
|||||||
"balance_play_min_visits": 20,
|
"balance_play_min_visits": 20,
|
||||||
"undo_eval_threshold": 0.875,
|
"undo_eval_threshold": 0.875,
|
||||||
"undo_point_threshold": 1,
|
"undo_point_threshold": 1,
|
||||||
"num_undo_prompts": 1
|
"num_undo_prompts": 1,
|
||||||
|
"sgf_show_best_move_threshold": 0.95
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"level": 1
|
"level": 1
|
||||||
|
|||||||
+22
-21
@@ -87,7 +87,7 @@ class EngineControls(GridLayout):
|
|||||||
# mr.waiting_for_analysis
|
# mr.waiting_for_analysis
|
||||||
self.redraw()
|
self.redraw()
|
||||||
|
|
||||||
def update_evaluation(self):
|
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))
|
||||||
@@ -100,6 +100,7 @@ class EngineControls(GridLayout):
|
|||||||
|
|
||||||
if current_move.analysis_ready and current_move.parent and current_move.parent.analysis_ready and not current_move.children:
|
if current_move.analysis_ready and current_move.parent and current_move.parent.analysis_ready and not current_move.children:
|
||||||
# handle automatic undo
|
# handle automatic undo
|
||||||
|
|
||||||
if self.auto_undo.active(current_move.player) and not self.ai_auto.active(current_move.player) and not current_move.auto_undid:
|
if self.auto_undo.active(current_move.player) and not self.ai_auto.active(current_move.player) and not current_move.auto_undid:
|
||||||
ts = self.train_settings
|
ts = self.train_settings
|
||||||
# TODO: is this overly generous wrt low visit outdated evaluations?
|
# TODO: is this overly generous wrt low visit outdated evaluations?
|
||||||
@@ -108,13 +109,14 @@ class EngineControls(GridLayout):
|
|||||||
if eval < ts["undo_eval_threshold"] and points_lost >= ts["undo_point_threshold"]:
|
if eval < ts["undo_eval_threshold"] and points_lost >= ts["undo_point_threshold"]:
|
||||||
current_move.auto_undid = True
|
current_move.auto_undid = True
|
||||||
self.board.undo()
|
self.board.undo()
|
||||||
|
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()
|
self.update_evaluation(undo_triggered=True)
|
||||||
# ai player doesn't technically need parent ready, but don't want to override waiting for undo
|
# ai player doesn't technically need parent ready, but don't want to override waiting for undo
|
||||||
elif self.ai_auto.active(1 - current_move.player) and not current_move.children:
|
elif self.ai_auto.active(1 - current_move.player) and not current_move.children and not undo_triggered and not self.board.game_ended:
|
||||||
self._do_aimove()
|
self._do_aimove()
|
||||||
|
|
||||||
def _do_aimove(self):
|
def _do_aimove(self):
|
||||||
@@ -130,20 +132,20 @@ class EngineControls(GridLayout):
|
|||||||
for d in current_move.ai_moves
|
for d in current_move.ai_moves
|
||||||
if int(d["visits"]) >= ts["balance_play_min_visits"]
|
if int(d["visits"]) >= ts["balance_play_min_visits"]
|
||||||
]
|
]
|
||||||
selmove = pos_moves[0][0]
|
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":
|
||||||
selmoves = [
|
sel_moves = [
|
||||||
move
|
move
|
||||||
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"]
|
||||||
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
|
||||||
selmove = random.choice(selmoves) # TODO: some kind of when further ahead play worse?
|
aimove = Move(player=self.board.current_player, gtpcoords=random.choice(sel_moves), robot=True)
|
||||||
print('SEL',selmoves)
|
if len(sel_moves) > 1:
|
||||||
print('POS',pos_moves, 'MOVE', selmove)
|
aimove.x_comment = "{'AI Balance on, moves considered: " + ", ".join(f"{move} ({aimove.format_score(score)})" for move, score, eval in sel_moves) + "\n"
|
||||||
self.play(Move(player=self.board.current_player, gtpcoords=selmove, robot=True))
|
self.play(aimove)
|
||||||
|
|
||||||
def _do_undo(self):
|
def _do_undo(self):
|
||||||
if self.ai_auto.active and self.board.current_move.robot:
|
if self.ai_auto.active and self.board.current_move.robot:
|
||||||
@@ -218,22 +220,21 @@ class EngineControls(GridLayout):
|
|||||||
query["analyzeTurns"][0] += 1
|
query["analyzeTurns"][0] += 1
|
||||||
self._send_analysis_query(query)
|
self._send_analysis_query(query)
|
||||||
|
|
||||||
|
|
||||||
def sgf(self):
|
def sgf(self):
|
||||||
def sgfify(mvs):
|
def sgfify(mvs):
|
||||||
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(m, pm):
|
def format_move(move, prev_move):
|
||||||
undo_comment = "".join(f"\nUndo: {u.gtp()} was {100*u.evaluation:.1f}%" for u in pm.undos if u.evaluation)
|
undos = [m for m in prev_move.children if m!=move]
|
||||||
undo_cr = "".join(f"MA[{u.sgfcoords(self.board_size)}]" for u in pm.undos if u.coords[0])
|
undo_cr = "".join(f"MA[{u.sgfcoords(self.board_size)}]" for u in undos if u.coords[0])
|
||||||
if pm.analysis and pm.analysis[0]["move"] != "pass":
|
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']:
|
||||||
best_sq = f"SQ[{Move(gtpcoords=pm.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 m.sgf(self.board_size) + f"C[{m.comment}{undo_comment}]{undo_cr}{best_sq}"
|
return move.sgf(self.board_size) + f"C[{move.comment(sgf=True)}]{undo_cr}{best_sq}"
|
||||||
|
moves = self.board.moves
|
||||||
sgfmoves_small = [mv.sgf(self.board_size) for mv in self.moves[1:]]
|
sgfmoves_small = [mv.sgf(self.board_size) for mv in moves]
|
||||||
sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(self.moves[1:], self.moves[:-1])]
|
sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(moves, [self.board.root] + moves[:-1])]
|
||||||
|
|
||||||
with open("out.sgf", "w") as f:
|
with open("out.sgf", "w") as f:
|
||||||
f.write(sgfify(sgfmoves))
|
f.write(sgfify(sgfmoves))
|
||||||
|
|||||||
+1
-1
@@ -201,7 +201,7 @@
|
|||||||
size_hint: 0.2, 0.5
|
size_hint: 0.2, 0.5
|
||||||
text: 'lock\nai'
|
text: 'lock\nai'
|
||||||
id: ai_lock
|
id: ai_lock
|
||||||
on_active: self.checkbox.disabled = hints.black.disabled = hints.white.disabled = ai_move.disabled = auto_undo.black.disabled = auto_undo.white.disabled = ai_auto.checkbox.disabled = True
|
on_active: self.checkbox.disabled = hints.black.disabled = hints.white.disabled = ai_auto.black.disabled = ai_auto.white.disabled = auto_undo.black.disabled = auto_undo.white.disabled = ai_move.disabled = True
|
||||||
GridLayout:
|
GridLayout:
|
||||||
cols: 2
|
cols: 2
|
||||||
rows: 1
|
rows: 1
|
||||||
|
|||||||
+8
-7
@@ -128,14 +128,14 @@ class BadukPanWidget(Widget):
|
|||||||
inner = COLORS[1 - m.player] if (m == last_move) else None
|
inner = COLORS[1 - m.player] if (m == last_move) else None
|
||||||
self.draw_stone(m.coords[0], m.coords[1], COLORS[m.player], inner, evalcol, evalsize)
|
self.draw_stone(m.coords[0], m.coords[1], COLORS[m.player], inner, evalcol, evalsize)
|
||||||
|
|
||||||
# ownership
|
# ownership - allow one move out of date for smooth animation
|
||||||
if self.engine.ownership.active and last_move.ownership:
|
ownership = last_move.ownership or (last_move.parent and last_move.parent.ownership)
|
||||||
ownership = last_move.ownership
|
if self.engine.ownership.active and ownership:
|
||||||
rsz = self.grid_size * 0.2
|
rsz = self.grid_size * 0.2
|
||||||
ix = 0
|
ix = 0
|
||||||
for y in range(self.engine.board_size - 1, -1, -1):
|
for y in range(self.engine.board_size - 1, -1, -1):
|
||||||
for x in range(self.engine.board_size):
|
for x in range(self.engine.board_size):
|
||||||
ix_owner = current_player if ownership[ix] > 0 else 1 - current_player
|
ix_owner = 0 if ownership[ix] > 0 else 1
|
||||||
if ix_owner != (has_stone.get((x, y), -1)):
|
if ix_owner != (has_stone.get((x, y), -1)):
|
||||||
Color(*COLORS[ix_owner], abs(ownership[ix]))
|
Color(*COLORS[ix_owner], abs(ownership[ix]))
|
||||||
Rectangle(pos=(self.gridpos[x] - rsz / 2, self.gridpos[y] - rsz / 2), size=(rsz, rsz))
|
Rectangle(pos=(self.gridpos[x] - rsz / 2, self.gridpos[y] - rsz / 2), size=(rsz, rsz))
|
||||||
@@ -145,9 +145,10 @@ class BadukPanWidget(Widget):
|
|||||||
undo_coords = set()
|
undo_coords = set()
|
||||||
alpha = Config.get("ui")["undo_alpha"]
|
alpha = Config.get("ui")["undo_alpha"]
|
||||||
for m in last_move.children:
|
for m in last_move.children:
|
||||||
if m.evaluation and m.coords[0] is not None:
|
eval_info = m.evaluation_info
|
||||||
|
if eval_info[0] and m.coords[0] is not None:
|
||||||
undo_coords.add(m.coords)
|
undo_coords.add(m.coords)
|
||||||
evalcol = (*self._eval_spectrum(m.evaluation), alpha)
|
evalcol = (*self._eval_spectrum(eval_info[0]), alpha)
|
||||||
self.draw_stone(m.coords[0], m.coords[1], (*COLORS[m.player][:3], alpha), Config.get("ui")["undo_circle_col"], evalcol, self.EVAL_BOUNDS[1])
|
self.draw_stone(m.coords[0], m.coords[1], (*COLORS[m.player][:3], alpha), Config.get("ui")["undo_circle_col"], evalcol, self.EVAL_BOUNDS[1])
|
||||||
|
|
||||||
# hints
|
# hints
|
||||||
@@ -165,7 +166,7 @@ class BadukPanWidget(Widget):
|
|||||||
# pass circle
|
# pass circle
|
||||||
passed = len(moves) > 1 and last_move.is_pass
|
passed = len(moves) > 1 and last_move.is_pass
|
||||||
if passed:
|
if passed:
|
||||||
if len(moves) > 2 and moves[-2].is_pass:
|
if self.engine.board.game_ended:
|
||||||
text = "game\nend"
|
text = "game\nend"
|
||||||
else:
|
else:
|
||||||
text = "pass"
|
text = "pass"
|
||||||
|
|||||||
Reference in new issue
Block a user