diff --git a/katrain/KataGo/katago b/katrain/KataGo/katago index a85dce4..6a2a9d5 100755 Binary files a/katrain/KataGo/katago and b/katrain/KataGo/katago differ diff --git a/katrain/__main__.py b/katrain/__main__.py index 744e016..3eeecd4 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -227,7 +227,7 @@ class KaTrainGui(Screen, KaTrainBase): ): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly. self._do_ai_move(cn) Clock.schedule_once(self.board_gui.play_stone_sound, 0.25) - if len(self.engine.queries) == 0 and self.idle_analysis: + if self.engine.is_idle() and self.idle_analysis: self("analyze-extra", "extra", continuous=True) Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger? diff --git a/katrain/core/engine.py b/katrain/core/engine.py index e4b4bf9..3002f0b 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -157,7 +157,7 @@ class KataGoEngine: t.join() def is_idle(self): - return not self.queries + return not self.queries and self.write_queue.empty() def _read_stderr_thread(self): while self.katago_process is not None: diff --git a/katrain/core/game.py b/katrain/core/game.py index 591ed04..f1a33d7 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -109,7 +109,8 @@ class Game: def analyze_all_nodes(self, priority=0, analyze_fast=False, even_if_present=True): for node in self.root.nodes_in_tree: - if even_if_present or not node.analysis_loaded: + # forced, or not present, or something went wrong in loading + if even_if_present or not node.analysis_from_sgf or not node.load_analysis(): node.clear_analysis() node.analyze(self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast) diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index a381764..9d02be6 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -33,19 +33,6 @@ def analysis_dumps(analysis): ] -def analysis_loads(property_array, board_squares, version): - if version > ANALYSIS_FORMAT_VERSION: - raise ValueError(f"Can not decode analysis data with version {version}, please update {PROGRAM_NAME}") - ownership_data, policy_data, main_data, *_ = [ - gzip.decompress(base64.standard_b64decode(data)) for data in property_array - ] - return { - **json.loads(main_data), - "policy": unpack_floats(policy_data, board_squares + 1), - "ownership": unpack_floats(ownership_data, board_squares), - } - - class GameNode(SGFNode): """Represents a single game node, with one or more moves and placements.""" @@ -60,7 +47,7 @@ class GameNode(SGFNode): self.end_state = None self.shortcuts_to = [] self.shortcut_from = None - self.analysis_loaded = False + self.analysis_from_sgf = None self.clear_analysis() def add_shortcut(self, to_node): # collapses the branch between them @@ -78,15 +65,31 @@ class GameNode(SGFNode): from_node.shortcuts_to = [(m, v) for m, v in from_node.shortcuts_to if m != self] self.shortcut_from = None + def load_analysis(self): + if not self.analysis_from_sgf: + return False + try: + szx, szy = self.root.board_size + board_squares = szx * szy + version = self.root.get_property("KTV", ANALYSIS_FORMAT_VERSION) + if version > ANALYSIS_FORMAT_VERSION: + raise ValueError(f"Can not decode analysis data with version {version}, please update {PROGRAM_NAME}") + ownership_data, policy_data, main_data, *_ = [ + gzip.decompress(base64.standard_b64decode(data)) for data in self.analysis_from_sgf + ] + self.analysis = { + **json.loads(main_data), + "policy": unpack_floats(policy_data, board_squares + 1), + "ownership": unpack_floats(ownership_data, board_squares), + } + return True + except Exception as e: + print(f"Error in loading analysis: {e}") + return False + def add_list_property(self, property: str, values: List): if property == "KT": - try: - szx, szy = self.root.board_size - version = self.root.get_property("KTV", "") - self.analysis = analysis_loads(values, szx * szy, version) - self.analysis_loaded = True - except Exception as e: - print(f"Error in loading analysis: {e}") + self.analysis_from_sgf = values elif property == "C": comments = [ # strip out all previously auto generated comments c diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 60f81ff..ef4f732 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -43,7 +43,6 @@ class PlayAnalyzeSelect(MDFloatLayout): cb.active = bool(active) if cb.tri_state: cb.slashed = active is None - cb.set_icon() for id, (panel_state, button_state) in state.get("panels", {}).items(): self.katrain.controls.ids[id].set_option_state(button_state) self.katrain.controls.ids[id].state = panel_state diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index e2e1873..571c180 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -385,6 +385,11 @@ class Timer(BGBoxLayout): class TriStateMDCheckbox(MDCheckbox): tri_state = BooleanProperty(False) slashed = BooleanProperty(False) + checkbox_icon_slashed = StringProperty("checkbox-blank-off-outline") + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.bind(slashed=self.update_icon) def _do_press(self): if not self.tri_state: @@ -398,15 +403,15 @@ class TriStateMDCheckbox(MDCheckbox): else: self.state = "down" self.slashed = False - self.set_icon() + self.update_icon() - def set_icon(self): - if self.slashed: - self.icon = "checkbox-blank-off-outline" + def update_icon(self, *args): + if self.tri_state and self.slashed: + self.icon = self.checkbox_icon_slashed elif self.state == "down": - self.icon = "checkbox-marked-outline" + self.icon = self.checkbox_icon_down else: - self.icon = "checkbox-blank-outline" + self.icon = self.checkbox_icon_normal class AnalysisToggle(MDBoxLayout): diff --git a/katrain/popups.kv b/katrain/popups.kv index 70ce6b5..1ea95c2 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -298,6 +298,7 @@ Label: size_hint: 0.1, 1 text: '&' + font_size: sp(Theme.DESC_FONT_SIZE) * 0.8 LabelledSpinner: id: top_moves_show_secondary value_refs: TOP_MOVE_OPTIONS