many bugfixes
This commit is contained in:
1 parent
da5d99194b
commit
99930f4a7c
5 files changed
+52
-50
No files matched your search
@@ -6,9 +6,11 @@ class Move:
|
||||
GTP_COORD = "ABCDEFGHJKLMNOPQRSTUVWYXYZ"
|
||||
PLAYERS = "BW"
|
||||
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):
|
||||
self.id = None
|
||||
Move._move_id_counter += 1
|
||||
self.id = Move._move_id_counter
|
||||
self.player = player
|
||||
self.coords = coords or (gtpcoords and self.gtp2ix(gtpcoords)) or self.sgf2ix(sgfcoords)
|
||||
self.children = []
|
||||
@@ -30,6 +32,7 @@ class Move:
|
||||
def __hash__(self):
|
||||
return self.gtp().__hash__()
|
||||
|
||||
# move tree building
|
||||
def play(self, move):
|
||||
try:
|
||||
return self.children[self.children.index(move)]
|
||||
@@ -39,14 +42,6 @@ class Move:
|
||||
self.children.append(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
|
||||
def set_analysis(self, analysis_blob, is_pass):
|
||||
if is_pass:
|
||||
@@ -68,7 +63,9 @@ class Move:
|
||||
return ""
|
||||
text = f"Move {self.move_number}: {self.bw_player()} {self.gtp()} {'(AI Move)' if self.robot else ''}\n"
|
||||
text += self.x_comment
|
||||
text += "".join(f"Auto undid move {m.gtp()} ({m.evaluation*100:.1f}% efficient)\n" for m in self.children if m.auto_undid)
|
||||
|
||||
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)
|
||||
|
||||
if self.analysis_ready:
|
||||
score, _, temperature = self.temperature_stats
|
||||
@@ -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"Pass score was {self.format_score(prev_worst_score)}\n"
|
||||
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:
|
||||
if sgf or eval:
|
||||
text += f"Evaluation: {100*self.evaluation:.1f}% efficient\n"
|
||||
@@ -91,6 +88,12 @@ class Move:
|
||||
points_lost = self.player_sign * (prev_best_score - score)
|
||||
if points_lost > 0.5:
|
||||
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:
|
||||
text = "No analysis available" if sgf else "Analyzing move..."
|
||||
return text
|
||||
@@ -175,14 +178,12 @@ class Move:
|
||||
|
||||
|
||||
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(1, (None, None)) # root is 1=white so black is first
|
||||
self.root.id = -1
|
||||
self.current_move = self.root
|
||||
self.all_moves = {-1: self.root}
|
||||
self.all_moves = {self.root.id: self.root}
|
||||
self._init_chains()
|
||||
|
||||
# -- move tree functions --
|
||||
@@ -253,9 +254,6 @@ class Board:
|
||||
raise
|
||||
|
||||
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 move
|
||||
@@ -295,13 +293,14 @@ class Board:
|
||||
def stones(self):
|
||||
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
|
||||
def prisoner_count(self):
|
||||
return [sum([m.player==player for m in self.prisoners]) for player in [0,1]]
|
||||
|
||||
def sgf(self):
|
||||
return "SGF[]"
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"\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,
|
||||
"undo_eval_threshold": 0.875,
|
||||
"undo_point_threshold": 1,
|
||||
"num_undo_prompts": 1
|
||||
"num_undo_prompts": 1,
|
||||
"sgf_show_best_move_threshold": 0.95
|
||||
},
|
||||
"debug": {
|
||||
"level": 1
|
||||
|
||||
+22
-21
@@ -87,7 +87,7 @@ class EngineControls(GridLayout):
|
||||
# mr.waiting_for_analysis
|
||||
self.redraw()
|
||||
|
||||
def update_evaluation(self):
|
||||
def update_evaluation(self,undo_triggered = False):
|
||||
current_move = self.board.current_move
|
||||
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))
|
||||
@@ -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:
|
||||
# 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:
|
||||
ts = self.train_settings
|
||||
# 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"]:
|
||||
current_move.auto_undid = True
|
||||
self.board.undo()
|
||||
undo_triggered = True
|
||||
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.x_comment = f"Automatically played as best option after max. {ts['num_undo_prompts']} undo(s).\n"
|
||||
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
|
||||
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()
|
||||
|
||||
def _do_aimove(self):
|
||||
@@ -130,20 +132,20 @@ class EngineControls(GridLayout):
|
||||
for d in current_move.ai_moves
|
||||
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
|
||||
if self.ai_balance.active and pos_moves[0][0] != "pass":
|
||||
selmoves = [
|
||||
sel_moves = [
|
||||
move
|
||||
for move, score, eval in pos_moves
|
||||
if eval > ts["balance_play_randomize_eval"]
|
||||
or eval > ts["balance_play_min_eval"]
|
||||
and current_move.player_sign * score > ts["balance_play_target_score"]
|
||||
]
|
||||
selmove = random.choice(selmoves) # TODO: some kind of when further ahead play worse?
|
||||
print('SEL',selmoves)
|
||||
print('POS',pos_moves, 'MOVE', selmove)
|
||||
self.play(Move(player=self.board.current_player, gtpcoords=selmove, robot=True))
|
||||
and -current_move.player_sign * score > ts["balance_play_target_score"]
|
||||
] or sel_moves
|
||||
aimove = Move(player=self.board.current_player, gtpcoords=random.choice(sel_moves), robot=True)
|
||||
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"
|
||||
self.play(aimove)
|
||||
|
||||
def _do_undo(self):
|
||||
if self.ai_auto.active and self.board.current_move.robot:
|
||||
@@ -218,22 +220,21 @@ class EngineControls(GridLayout):
|
||||
query["analyzeTurns"][0] += 1
|
||||
self._send_analysis_query(query)
|
||||
|
||||
|
||||
def sgf(self):
|
||||
def sgfify(mvs):
|
||||
return f"(;GM[1]FF[4]SZ[{self.board_size}]KM[{self.komi}]RU[JP];" + ";".join(mvs) + ")"
|
||||
|
||||
def format_move(m, pm):
|
||||
undo_comment = "".join(f"\nUndo: {u.gtp()} was {100*u.evaluation:.1f}%" for u in pm.undos if u.evaluation)
|
||||
undo_cr = "".join(f"MA[{u.sgfcoords(self.board_size)}]" for u in pm.undos if u.coords[0])
|
||||
if pm.analysis and pm.analysis[0]["move"] != "pass":
|
||||
best_sq = f"SQ[{Move(gtpcoords=pm.analysis[0]['move'], player=0).sgfcoords(self.board_size)}]"
|
||||
def format_move(move, prev_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])
|
||||
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=prev_move.analysis[0]['move'], player=0).sgfcoords(self.board_size)}]"
|
||||
else:
|
||||
best_sq = ""
|
||||
return m.sgf(self.board_size) + f"C[{m.comment}{undo_comment}]{undo_cr}{best_sq}"
|
||||
|
||||
sgfmoves_small = [mv.sgf(self.board_size) for mv in self.moves[1:]]
|
||||
sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(self.moves[1:], self.moves[:-1])]
|
||||
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 moves]
|
||||
sgfmoves = [format_move(mv, pmv) for mv, pmv in zip(moves, [self.board.root] + moves[:-1])]
|
||||
|
||||
with open("out.sgf", "w") as f:
|
||||
f.write(sgfify(sgfmoves))
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@
|
||||
size_hint: 0.2, 0.5
|
||||
text: 'lock\nai'
|
||||
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:
|
||||
cols: 2
|
||||
rows: 1
|
||||
|
||||
+8
-7
@@ -128,14 +128,14 @@ class BadukPanWidget(Widget):
|
||||
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)
|
||||
|
||||
# ownership
|
||||
if self.engine.ownership.active and last_move.ownership:
|
||||
ownership = last_move.ownership
|
||||
# ownership - allow one move out of date for smooth animation
|
||||
ownership = last_move.ownership or (last_move.parent and last_move.parent.ownership)
|
||||
if self.engine.ownership.active and ownership:
|
||||
rsz = self.grid_size * 0.2
|
||||
ix = 0
|
||||
for y in range(self.engine.board_size - 1, -1, -1):
|
||||
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)):
|
||||
Color(*COLORS[ix_owner], abs(ownership[ix]))
|
||||
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()
|
||||
alpha = Config.get("ui")["undo_alpha"]
|
||||
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)
|
||||
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])
|
||||
|
||||
# hints
|
||||
@@ -165,7 +166,7 @@ class BadukPanWidget(Widget):
|
||||
# pass circle
|
||||
passed = len(moves) > 1 and last_move.is_pass
|
||||
if passed:
|
||||
if len(moves) > 2 and moves[-2].is_pass:
|
||||
if self.engine.board.game_ended:
|
||||
text = "game\nend"
|
||||
else:
|
||||
text = "pass"
|
||||
|
||||
Reference in new issue
Block a user