diff --git a/config.json b/config.json index 29879b2..6d02848 100644 --- a/config.json +++ b/config.json @@ -16,7 +16,7 @@ "komi_19": 6.5, "komi_13": 6.5, "komi_9": 6.5, - "sweep_visits_frac": 0.2 + "sweep_visits_frac": 0.05 }, "trainer": { "num_undo_prompts": [1,1,1,0.5,0], @@ -35,7 +35,6 @@ "outline": {"B": [0.3,0.3,0.3,0.5], "W": [0.7, 0.7, 0.7,0.5] }, "ghost_alpha": 0.5, "top_move_x_alpha": 0.3, - "_child_alpha": 0.5, "_child_scale": 0.95, "eval_colors": [[0.537, 0.129, 0.42], [1, 0, 0], [1,0.5,0], [0.88, 0.70, 0.30], [0.67, 0.95, 0.18], [0.117, 0.588, 0]], "board_color": [0.85, 0.68, 0.40], diff --git a/game_node.py b/game_node.py index 8e74b91..79aa883 100644 --- a/game_node.py +++ b/game_node.py @@ -81,8 +81,8 @@ class GameNode(SGFNode): text += f"Estimated point loss: {points_lost:.1f}\n" if sgf or hints: policy_ranking = self.parent.policy_ranking - policy_ix = [ix+1 for (m,p),ix in zip(policy_ranking,range(len(policy_ranking))) if m==single_move] - if not policy_ix or policy_ix[0]!=1: + policy_ix = [ix + 1 for (m, p), ix in zip(policy_ranking, range(len(policy_ranking))) if m == single_move] + if not policy_ix or policy_ix[0] != 1: text += f"Top policy move was {policy_ranking[0][0].gtp()}\n" if policy_ix: text += f"Your move was #{policy_ix} according to NN policy\n" diff --git a/gui/badukpan.py b/gui/badukpan.py index 51a072d..a995cd7 100644 --- a/gui/badukpan.py +++ b/gui/badukpan.py @@ -110,7 +110,7 @@ class BadukPanWidget(Widget): # grid lines margin = 1.5 margin_x = margin + (max_board_size - board_size_x) / 2 - margin_y = margin + (max_board_size - board_size_y) / 2 + margin_y = margin - 0.125 + (max_board_size - board_size_y) / 2 self.grid_size = board_px_size / (max_board_size - 1 + 1.5 * margin) self.stone_size = self.grid_size * self.ui_config["stone_size"] @@ -215,15 +215,16 @@ class BadukPanWidget(Widget): # children of current moves in undo / review undo_coords = set() - alpha = self.ui_config["_child_alpha"] - for child_node in current_node.children: - points_lost = child_node.points_lost - m = child_node.single_move - if m and m.coords is not None: - undo_coords.add(m.coords) - evalcol = (*self.eval_color(points_lost), alpha) if points_lost is not None else None - scale = self.ui_config.get("_child_scale", 0.95) - self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale) + if katrain.controls.show_children.active: + alpha = self.ui_config["ghost_alpha"] + for child_node in current_node.children: + points_lost = child_node.points_lost + m = child_node.single_move + if m and m.coords is not None: + undo_coords.add(m.coords) + evalcol = (*self.eval_color(points_lost), alpha) if points_lost is not None else None + scale = self.ui_config.get("_child_scale", 0.95) + self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale) # hints if katrain.controls.hints.active and not game_ended: diff --git a/gui/controls.py b/gui/controls.py index b31489e..09e2ab1 100644 --- a/gui/controls.py +++ b/gui/controls.py @@ -11,7 +11,7 @@ class Controls(BoxLayout): def set_status(self, msg, at_node=None): self.status = msg - self.status_node = at_node or self.parent.game and self.parent.game.current_node + self.status_node = at_node or self.katrain.game and self.parent.game.current_node self.info.text = msg self.update_evaluation() @@ -21,6 +21,12 @@ class Controls(BoxLayout): else: self.play_tab_button.trigger_action(duration=0) + def switch_mode(self): + if self.play_tab_button.state == "down": + self.select_mode("analyze") + else: + self.select_mode("play") + def show_evaluation_stats(self, node): if node.analysis_ready: self.score.text = node.format_score().replace("-", "\u2013") @@ -49,7 +55,7 @@ class Controls(BoxLayout): # handles showing completed analysis and score graph def update_evaluation(self): - katrain = self.parent + katrain = self.katrain current_node = katrain.game and katrain.game.current_node info = "" diff --git a/gui/kivyutils.py b/gui/kivyutils.py index 750b90f..93d1f29 100644 --- a/gui/kivyutils.py +++ b/gui/kivyutils.py @@ -47,7 +47,7 @@ class ToggleButtonContainer(GridLayout): selected = StringProperty("") group = StringProperty(None) autosize = BooleanProperty(True) - margin = ListProperty([0, 0, 1, 1]) # TODO outer vs inner margin? + spacing = ListProperty((1, 1)) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -71,7 +71,7 @@ class ToggleButtonContainer(GridLayout): for i, opt in enumerate(self.options): state = "down" if opt == self.selected else "normal" - self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, margin=self.margin, on_press=state_handler)) + self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, on_press=state_handler)) Clock.schedule_once(self._size, 0) def _size(self, _dt): @@ -166,6 +166,7 @@ class ScoreGraph(Label): line_points = ListProperty([]) dot_pos = ListProperty([0, 0]) highlighted_index = NumericProperty(None) + min_scale = NumericProperty(1) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -184,7 +185,7 @@ class ScoreGraph(Label): if nodes: values = [n.score if n and n.score else 0 for n in nodes] val_range = min(values or [0]), max(values or [0]) - scale = math.ceil(max(3, max(-val_range[0], val_range[1]) * 1.05)) + scale = math.ceil(max(self.min_scale, max(-val_range[0], val_range[1]) * 1.05)) xscale = self.width * 0.9 / max(len(values), 20) available_height = self.height * (1 - 2 * self.marginy) diff --git a/katrain.kv b/katrain.kv index bff45ae..5235465 100644 --- a/katrain.kv +++ b/katrain.kv @@ -23,9 +23,9 @@ Color: rgba: root.face_color or [0,0,0,0] RoundedRectangle: - size: self.size[0]-self.margin[0]-self.margin[2],self.size[1]-self.margin[1]-self.margin[3] - pos: (self.pos[0]+self.margin[0],self.pos[1]+self.margin[1]) - radius: root.radius or (0.0,) + size: self.size[0],self.size[1] + pos: (self.pos[0],self.pos[1]) + radius: root.radius : font_size: self.size[1] * 0.45 @@ -43,6 +43,7 @@ : max_lines: 1 + : bold: True radius: (self.size[1]/3,self.size[1]/3,0,0) @@ -60,6 +61,7 @@ size: (root.icon_size or 0,root.icon_size or 0) # square pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0] source: root.icon + mipmap: True : background_normal: '' @@ -71,6 +73,7 @@ size: (root.icon_size or 0,root.icon_size or 0) # square pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0] source: root.icon + mipmap: True : @@ -100,6 +103,7 @@ color: WHITE : + font_size: dp(12) color: BLACK halign: 'center' valign: 'center' @@ -110,14 +114,16 @@ text: '' label: '' CheckBoxHintLabel: - size_hint: 0.6,1 + size_hint: 0.6,None + height: self.font_size * 1.5 + id: desc text: root.label - font_size: self.height * 0.6 + font_size: dp(20) on_press: value.opacity = 1 if value.opacity==0 else 0 DarkLabel: - size_hint: 0.4,1 + size_hint: 0.4,None text: root.text - font_size: self.height * 0.6 + font_size: desc.font_size id: value bold: True @@ -182,16 +188,16 @@ size: self.highlight_size,self.highlight_size CheckBoxHintLabel: id: range_label_top - font_size: 0.15 * root.height - pos: root.pos[0] + root.marginx * root.width + 3, root.pos[1] + root.height * (1 - 2 * root.marginy) - 0.15 * root.height - 1 + font_size: 0.12 * root.height + pos: root.pos[0] + root.marginx * root.width + 3, root.pos[1] + root.height * (1 - 2 * root.marginy) - self.font_size - 1 valign: 'bottom' halign: 'left' text: 'B+0' color: GREY CheckBoxHintLabel: id: range_label_bottom - font_size: 0.15 * root.height - pos: root.pos[0] + root.marginx * root.width + 3, root.pos[1] + font_size: 0.12 * root.height + pos: root.pos[0] + root.marginx * root.width + 3, root.pos[1] + 1 valign: 'bottom' halign: 'left' text: 'W+0' @@ -212,30 +218,32 @@ pass_btn: pass_btn Label: size_hint: 0.05, 1 - StyledButton: - text: 'Pass' - font_size: 0.35*self.size[1] - id: pass_btn - on_press: root.katrain("play",None) - size_hint: 0.1, 1 - margin: (0, self.size[1]/5, 0, self.size[1]/5) + BoxLayout: + size_hint: 0.15, 1 + padding: self.size[1]/5 + StyledButton: + text: 'Pass' + font_size: 0.6*self.size[1] + id: pass_btn + on_press: root.katrain("play",None) Label: - size_hint: 0.1, 1 - TransparentIconButton: - icon: 'img/flaticon/previous999.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('undo',999) - TransparentIconButton: - icon: 'img/flaticon/previous5.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('undo',5) - TransparentIconButton: - icon: 'img/flaticon/previous.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('undo') + size_hint: 0.05, 1 + BoxLayout: + size_hint: 0.3, 1 + padding: [0, self.size[1]*0.3] + spacing: 0 + TransparentIconButton: + icon: 'img/flaticon/previous999.png' + size_hint: 0.1, 1 + on_press: root.katrain('undo',999) + TransparentIconButton: + icon: 'img/flaticon/previous5.png' + size_hint: 0.1, 1 + on_press: root.katrain('undo',5) + TransparentIconButton: + icon: 'img/flaticon/previous.png' + size_hint: 0.1, 1 + on_press: root.katrain('undo') FloatLayout: size_hint: 0.2, 1 id: mid_circles_container @@ -247,32 +255,32 @@ id: white_prisoners pos: (self.parent.pos[0]+self.height/3,self.parent.pos[1]) if self.parent else (0,0) radius: 0.4 - TransparentIconButton: - icon: 'img/flaticon/next.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('redo') - TransparentIconButton: - icon: 'img/flaticon/next5.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('redo',5) - TransparentIconButton: - icon: 'img/flaticon/next999.png' - margin: (3, self.size[1]/3, 3, self.size[1]/3) - size_hint: 0.1, 1 - on_press: root.katrain('redo',999) + BoxLayout: + size_hint: 0.3, 1 + padding: [0, self.size[1]*0.3] + TransparentIconButton: + icon: 'img/flaticon/next.png' + size_hint: 0.1, 1 + on_press: root.katrain('redo') + TransparentIconButton: + icon: 'img/flaticon/next5.png' + size_hint: 0.1, 1 + on_press: root.katrain('redo',5) + TransparentIconButton: + icon: 'img/flaticon/next999.png' + size_hint: 0.1, 1 + on_press: root.katrain('redo',999) Label: size_hint: 0.07, 1 - StyledButton: - text: 'AI Move' - font_size: 0.35*self.size[1] - on_press: root.katrain("ai-move",None) - size_hint: 0.14, 1 - margin: (0, self.size[1]/5, 0, self.size[1]/5) + BoxLayout: + size_hint: 0.2, 1 + padding: self.size[1]/5 + StyledButton: + text: 'AI Move' + font_size: 0.6*self.size[1] + on_press: root.katrain("ai-move",None) Label: - size_hint: 0.04, 1 - + size_hint: 0.05, 1 : orientation: 'vertical' @@ -282,6 +290,7 @@ win_rate: win_rate points_lost: points_lost score: score + show_children: show_children hints: hints ownership: ownership policy: policy @@ -289,22 +298,17 @@ player_mode_groups: {'B':B_player_mode,'W':W_player_mode} ai_mode_groups: {'B':B_AI_mode,'W':W_AI_mode} graph: graph - katrain: self.parent - canvas.before: - Color: - rgba: 0.95, 0.95, 0.95, 1 - Rectangle: - pos: self.pos - size: root.size + katrain: self.parent.parent BoxLayout: size_hint: 1,0.05 orientation: 'horizontal' + padding: 5,1,5,0 + spacing: 2 StyledTabButton: text: 'Play' group: 'tabs' state: 'down' id: play_tab_button - margin: (2,0,1,3) on_press: play_tab.size_hint[0] = 1 play_tab.opacity = 1 @@ -315,7 +319,6 @@ StyledTabButton: text: 'Analyze' id: analyze_tab_button - margin: (1,0,2,3) group: 'tabs' on_press: analyze_tab.size_hint[0] = 1 @@ -330,42 +333,57 @@ BoxLayout: # ----------- analyze ----------- size_hint: 0,1 id: analyze_tab + padding: [3,3] opacity: 0 orientation: 'vertical' - Label: - size_hint: 1,0.02 GridLayout: - cols: 4 + cols: 5 rows: 2 - size_hint: 1, 0.5 + size_hint: 1, 0.3 CheckBoxHintLabel: - text: 'show\nall dots' + text: 'next\nmoves' CheckBoxHintLabel: - text: 'show\ntop move' + text: 'all\ndots' CheckBoxHintLabel: - text: 'show\nowner' + text: 'top\nmoves' CheckBoxHintLabel: - text: 'show\npolicy' + text: 'owner' + CheckBoxHintLabel: + text: 'NN\npolicy' + CheckBox: + id: show_children + active: True + on_active: root.katrain.update_state() CheckBox: id: eval - default_active: False on_active: root.katrain.update_state() CheckBox: id: hints - default_active: False on_active: root.katrain.update_state() - CheckBox: id: ownership - default_active: False on_active: root.katrain.update_state() CheckBox: id: policy - default_active: False on_active: root.katrain.update_state() + BoxLayout: + spacing:1 + padding: 1,2,1,2 + size_hint: 1,0.2 + DarkLabel: + text: 'Analysis' + StyledButton: + text: 'Extra' + size_hint: 1, 1 + StyledButton: + text: 'Equalize' + size_hint: 1, 1 + StyledButton: + text: 'Sweep' + size_hint: 1, 1 ScoreGraph: id: graph - size_hint: 1,0.5 + size_hint: 1,0.7 CensorableLabel: id: win_rate size_hint: 1, 0.15 @@ -386,6 +404,7 @@ GridLayout: cols: 4 rows: 2 + spacing: 1 size_hint: 1,0.125 BlackCircleWithText: text: 'B' @@ -433,6 +452,8 @@ BoxLayout: orientation: 'horizontal' size_hint: 1, None + spacing: 2 + padding: 2 height: self.width * 0.25 StyledIconButton: icon: 'img/flaticon/go-game.png' @@ -457,25 +478,37 @@ board_controls: board_controls board_aspect_ratio: 1.075 BoxLayout: + size_hint: 1,1 canvas.before: Color: rgb: root.board_gui.board_color if root.board_gui else (0,0,0) Rectangle: - pos: root.pos - size: root.size + pos: self.pos + size: self.size orientation: 'vertical' - size_hint: 1 - controls.size_hint[0], 1 + size_hint: 1 - controls_box.size_hint[0], 1 BadukPanWidget: id: board_gui - size_hint: (1,1) + size_hint: 1,1 BadukPanControls: id: board_controls size_hint: (1,root.board_aspect_ratio-1) - Controls: - id: controls - size_hint: max(0.25,(root.width - root.height/root.board_aspect_ratio)/root.width), 1 - - + BoxLayout: + orientation: 'vertical' + id: controls_box + size_hint: max(0.25,(root.width - root.height/root.board_aspect_ratio)/root.width), 1 + canvas.before: + Color: + rgba: 0.95, 0.95, 0.95, 1 + Rectangle: + pos: self.pos + size: self.size + Controls: + id: controls + size_hint: 1, 1 - filler.size_hint[1] + Label: + id: filler + size_hint: 1, max(0.0001, ((controls_box.height / controls_box.width) - 3)/(controls_box.height / controls_box.width)) # limits aspect ratio : fast: fast diff --git a/katrain.py b/katrain.py index bee8641..35663b1 100644 --- a/katrain.py +++ b/katrain.py @@ -51,7 +51,7 @@ class KaTrainGui(BoxLayout): config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json") try: self.log(f"Using config file {config_file}", OUTPUT_INFO) - self._config_store = JsonStore(config_file,indent=4) + self._config_store = JsonStore(config_file, indent=4) self._config = dict(self._config_store) except Exception as e: self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR) @@ -190,7 +190,7 @@ class KaTrainGui(BoxLayout): for pl in Move.PLAYERS: if not self.game.root.get_first(f"P{pl}"): _, model_file = os.path.split(self.engine.config["model"]) - self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if 'ai' in self.controls.player_mode(pl) else "Player"] + self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if "ai" in self.controls.player_mode(pl) else "Player"] msg = self.game.write_sgf(self.config("files/sgf_save")) self.log(msg, OUTPUT_INFO) self.controls.set_status(msg) @@ -200,16 +200,17 @@ class KaTrainGui(BoxLayout): return # if in new game or load, don't allow keyboard shortcuts shortcuts = { - "u": self.controls.eval, - "i": self.controls.hints, - "p": self.controls.policy, - "o": self.controls.ownership, + "q": self.controls.show_children, + "w": self.controls.eval, + "e": self.controls.hints, + "r": self.controls.policy, + "t": self.controls.ownership, "a": ("ai-move",), + "s": ("analyze-extra", "extra"), + "f": ("analyze-extra", "refine"), + "d": ("analyze-extra", "sweep"), "right": ("switch-branch", 1), "left": ("switch-branch", -1), - "z": ("analyze-extra", "sweep"), - "x": ("analyze-extra", "extra"), - "c": ("analyze-extra", "refine"), } if keycode[1] in shortcuts.keys(): shortcut = shortcuts[keycode[1]] @@ -217,9 +218,11 @@ class KaTrainGui(BoxLayout): shortcut.trigger_action(duration=0) else: self(*shortcut) - elif keycode[1] == "up": + elif keycode[1] == "tab": + self.controls.switch_mode() + elif keycode[1] in ["up", "z"]: self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) - elif keycode[1] == "down": + elif keycode[1] in ["down", "x"]: self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) elif keycode[1] == "n" and "ctrl" in modifiers: self("new-game-popup")