From c12d8e75808a2e09efe0be83bf0e1f254b6a5bda Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 19 Apr 2020 22:40:14 +0200 Subject: [PATCH] ermagerd graphs --- engine.py | 2 +- game_node.py | 5 +++-- gui.kv | 2 ++ gui/__init__.py | 3 ++- gui/badukpan.py | 22 ++++++++++------------ gui/controls.py | 26 ++++++++++++++++++++++++-- gui/kivyutils.py | 4 +++- katrain.py | 9 ++++----- 8 files changed, 49 insertions(+), 24 deletions(-) diff --git a/engine.py b/engine.py index 5f2a08f..9576f2a 100644 --- a/engine.py +++ b/engine.py @@ -60,7 +60,7 @@ class KataGoEngine: return not self.queries def _analysis_read_thread(self): - while True: + while self.katago_process is not None: line = self.katago_process.stdout.readline() if b"Uncaught exception" in line: self.katrain.log(f"KataGo Engine Failed: {line.decode()}", OUTPUT_ERROR) diff --git a/game_node.py b/game_node.py index 4b7ea83..d0f4a80 100644 --- a/game_node.py +++ b/game_node.py @@ -94,8 +94,9 @@ class GameNode(SGFNode): return self.player_sign(single_move.player) * (parent_score - score) @property - def score(self): - return self.analysis["root"]["scoreLead"] # TODO: update for rootInfo + def score(self) -> Optional[float]: + if self.analysis_ready: + return self.analysis["root"]["scoreLead"] @staticmethod def player_sign(player): diff --git a/gui.kv b/gui.kv index 58653c9..f85a0bd 100644 --- a/gui.kv +++ b/gui.kv @@ -249,6 +249,7 @@ ai_lock: ai_lock ai_move: ai_move auto_undo: auto_undo + graph: graph katrain: self.parent BoxLayout: size_hint: 1,0.05 @@ -323,6 +324,7 @@ text: 'show\npolicy' on_active: root.katrain.update_state() LargeLabel: + id: graph text: 'graph?' size_hint: 1,0.75 CensorableLabel: diff --git a/gui/__init__.py b/gui/__init__.py index b3fb2db..0a909d0 100644 --- a/gui/__init__.py +++ b/gui/__init__.py @@ -7,7 +7,8 @@ from gui.kivyutils import ( LabelledFloatInput, LabelledIntInput, StyledButton, -DarkLabel,BaseCircleWithText, + DarkLabel, + BaseCircleWithText, LabelledSpinner, LabelledCheckBox, LabelledTextInput, diff --git a/gui/badukpan.py b/gui/badukpan.py index 2cc8faf..73429f1 100644 --- a/gui/badukpan.py +++ b/gui/badukpan.py @@ -179,22 +179,19 @@ class BadukPanWidget(Widget): # likewise for policy, although it makes slightly less sense here policy = current_node.policy or (current_node.parent and current_node.parent.policy) + pass_btn = katrain.board_controls.pass_btn + pass_btn.canvas.after.clear() if katrain.controls.policy.active and policy and not katrain.controls.ownership.active: - n_legal_moves = sum([p > 0 for p in policy]) - avg_policy = 1.0 / n_legal_moves - best_move_policy = max(policy) # num legal moves scale? - rsz = self.grid_size * 0.2 ix = 0 for y in range(board_size - 1, -1, -1): for x in range(board_size): if policy[ix] > 0: - policy_delta = best_move_policy - policy[ix] - polcol = self.eval_color(0.1 * policy_delta / avg_policy) - Color(*polcol) - Rectangle(pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz)) + polsize = math.sqrt(policy[ix]) + self.draw_stone(x, y, (1, 0, 0, 0.5), scale=polsize) # TODO: config? ix = ix + 1 - policy_delta = best_move_policy - policy[ix] - katrain.board_controls.pass_btn.face_color = (*self.eval_color(100 * policy_delta), 1) + polsize = math.sqrt(policy[ix]) + with pass_btn.canvas.after: + draw_circle((pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2), polsize * pass_btn.height / 2, (1, 0, 0, 0.5)) # children of current moves in undo / review undo_coords = set() @@ -217,10 +214,11 @@ class BadukPanWidget(Widget): if move.coords is not None and move.coords not in undo_coords: if i == 0: scale = 1.0 + c[3] = 0.8 elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config? - scale = 0.6 # TODO: config? + scale = 0.8 else: - scale = 0.85 + scale = 1.0 self.draw_stone(move.coords[0], move.coords[1], c, scale=scale) # hover next move ghost stone diff --git a/gui/controls.py b/gui/controls.py index 6b5aaad..8d80bcc 100644 --- a/gui/controls.py +++ b/gui/controls.py @@ -1,4 +1,6 @@ +from kivy.graphics.vertex_instructions import SmoothLine, Line from kivy.uix.boxlayout import BoxLayout +from kivy.graphics.context_instructions import Color class Controls(BoxLayout): @@ -34,9 +36,13 @@ class Controls(BoxLayout): for el in [self.ai_lock.checkbox, self.analyze_tab_button, self.ai_auto.white, self.ai_auto.black, self.ai_move]: el.disabled = False - # handles showing completed analysis and triggered actions like auto undo and ai move + def on_size(self, *args): + self.update_evaluation() + + # handles showing completed analysis and score graph def update_evaluation(self): - current_node = self.parent.game.current_node + katrain = self.parent + current_node = katrain.game.current_node move = current_node.single_move current_player_is_human_or_both_robots = True # move not self.ai_auto.active(current_node.player) or self.ai_auto.active(1 - current_node.player) # TODO FIX @@ -54,6 +60,22 @@ class Controls(BoxLayout): self.info.text = info + game_node = katrain.game.current_node + scores = [n.score for n in game_node.nodes_from_root] + # TODO: like redo, what is the node to redo / should we append? cache? + self.graph.canvas.clear() + with self.graph.canvas: + pt = [] + nnscores = [s for s in scores if s is not None] + [-5, 5] + scale = max(max(*nnscores), -min(*nnscores)) * 1.05 + xscale = self.graph.width * 0.9 / max(len(scores), 20) + ls = 0 + for i, s in enumerate(scores): + ls = s or ls + pt.extend([self.graph.pos[0] + 0.05 * self.graph.width + i * xscale, self.graph.pos[1] + self.graph.height / 2 * (1 + ls / scale)]) + Color(0, 0, 0) + Line(points=pt, width=1.0) # just set points? + if False: # TODO: UNDO AND AI MOVE if current_node.analysis_ready and current_node.parent and current_node.parent.analysis_ready and not current_node.children and not current_node.x_comment.get("undo"): # handle automatic undo diff --git a/gui/kivyutils.py b/gui/kivyutils.py index 2b6dab1..1670dfe 100644 --- a/gui/kivyutils.py +++ b/gui/kivyutils.py @@ -25,12 +25,15 @@ class CheckBoxHint(BoxLayout): def on_active(self, *args): pass + class DarkLabel(Label): pass + class BaseCircleWithText(DarkLabel): radius = NumericProperty(0.48) + class LabelledTextInput(TextInput): input_property = StringProperty("") @@ -113,7 +116,6 @@ class CensorableLabel(BoxLayout): return self.value.text - def draw_text(pos, text, **kw): label = CoreLabel(text=text, bold=True, **kw) label.refresh() diff --git a/katrain.py b/katrain.py index 34e0030..5f19e9c 100644 --- a/katrain.py +++ b/katrain.py @@ -21,7 +21,6 @@ from gui.popups import NewGamePopup, ConfigPopup from kivy.lang import Builder - class KaTrainGui(BoxLayout): """Top level class responsible for tying everything together""" @@ -89,9 +88,9 @@ class KaTrainGui(BoxLayout): # Handle prisoners and next player display prisoners = self.game.prisoner_count - top,bot = self.board_controls.black_prisoners,self.board_controls.white_prisoners - if self.game.next_player == 'W': - top, bot = bot,top + top, bot = self.board_controls.black_prisoners, self.board_controls.white_prisoners + if self.game.next_player == "W": + top, bot = bot, top self.board_controls.mid_circles_container.clear_widgets() self.board_controls.mid_circles_container.add_widget(bot) self.board_controls.mid_circles_container.add_widget(top) @@ -283,7 +282,7 @@ class KaTrainApp(App): if __name__ == "__main__": - with open("gui.kv", encoding='utf-8') as f: # avoid windows using another encoding + with open("gui.kv", encoding="utf-8") as f: # avoid windows using another encoding Builder.load_string(f.read()) app = KaTrainApp() signal.signal(signal.SIGINT, app.signal_handler)