diff --git a/README.md b/README.md index c6db987..411ae17 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ Keyboard shortcuts are shown with **[key]**. * **[a]**: Deeper analysis: Re-evaluate the position using more visits, usually resulting in a more accurate evaluation. * **[s]**: Equalize visits: Re-evaluate all currently shown next moves with the same visits as the current top move. Useful to increase confidence in the suggestions with high uncertainty. * **[d]**: Analyze all moves: Evaluate all possible next moves. This can take a bit of time even though 'fast_visits' is used, but can be useful to see how many reasonable next moves are available. + * **[f]**: Re-analyze game: ... + * **[spacebar]**: Turn continuous analysis on/off. * **[enter]** AI move. Makes the AI move for the current player regardless of current player selection. ## Keyboard and mouse shortcuts @@ -112,7 +114,7 @@ In addition to shortcuts mentioned above and those shown in the main menu: * **[Shift]**: Open the main menu. * **[~]** or **[ ` ]** or **[m]**: Cycles through more minimalistic UI modes. * **[p]**: Pass -* **[spacebar]**: Pause/Resume timer +* **[b]**: Pause/Resume timer * **[arrow left]** or **[z]**: Undo move. Hold shift for 10 moves at a time, or ctrl to skip to the start. * **[arrow right]** or **[x]**: Redo move. Hold shift for 10 moves at a time, or ctrl to skip to the start. * **[arrow up/down]** Switch branch, as would be expected from the move tree. diff --git a/katrain/__main__.py b/katrain/__main__.py index 9437e37..1034003 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -89,6 +89,7 @@ class KaTrainGui(Screen, KaTrainBase): self.teacher_settings_popup = None self.timer_settings_popup = None + self.idle_analysis = False self.message_queue = Queue() self._keyboard = Window.request_keyboard(None, self, "") @@ -113,6 +114,10 @@ class KaTrainGui(Screen, KaTrainBase): def play_analyze_mode(self): return self.play_mode.mode + def toggle_continuous_analysis(self): + self.idle_analysis = not self.idle_analysis + self.update_state() + def start(self): if self.engine: return @@ -182,7 +187,9 @@ class KaTrainGui(Screen, KaTrainBase): and not (teaching_undo and cn.auto_undo is None) ): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly. self._do_ai_move(cn) - Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) + if len(self.engine.queries) == 0 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? def update_player(self, bw, **kwargs): super().update_player(bw, **kwargs) @@ -197,7 +204,7 @@ class KaTrainGui(Screen, KaTrainBase): def _message_loop_thread(self): while True: - game, msg, *args = self.message_queue.get() + game, msg, args, kwargs = self.message_queue.get() try: self.log(f"Message Loop Received {msg}: {args} for Game {game}", OUTPUT_EXTRA_DEBUG) if game != self.game.game_id: @@ -206,19 +213,19 @@ class KaTrainGui(Screen, KaTrainBase): ) continue fn = getattr(self, f"_do_{msg.replace('-','_')}") - fn(*args) + fn(*args, **kwargs) self.update_state() except Exception as exc: self.log(f"Exception in processing message {msg} {args}: {exc}", OUTPUT_ERROR) traceback.print_exc() - def __call__(self, message, *args): + def __call__(self, message, *args, **kwargs): if self.game: if message.endswith("popup"): # gui code needs to run in main kivy thread. fn = getattr(self, f"_do_{message.replace('-', '_')}") - Clock.schedule_once(lambda _dt: fn(*args), -1) + Clock.schedule_once(lambda _dt: fn(*args, **kwargs), -1) else: # game related actions - self.message_queue.put([self.game.game_id, message, *args]) + self.message_queue.put([self.game.game_id, message, args, kwargs]) def _do_new_game(self, move_tree=None, analyze_fast=False): mode = self.play_analyze_mode @@ -266,8 +273,8 @@ class KaTrainGui(Screen, KaTrainBase): except IllegalMoveException as e: self.controls.set_status(f"Illegal Move: {str(e)}") - def _do_analyze_extra(self, mode): - self.game.analyze_extra(mode) + def _do_analyze_extra(self, mode, **kwargs): + self.game.analyze_extra(mode, **kwargs) def _do_new_game_popup(self): self.controls.timer.paused = True @@ -397,6 +404,7 @@ class KaTrainGui(Screen, KaTrainBase): "a": ("analyze-extra", "extra"), "s": ("analyze-extra", "equalize"), "d": ("analyze-extra", "sweep"), + "f": ("analyze-extra", "game"), "p": ("play", None), "down": ("switch-branch", 1), "up": ("switch-branch", -1), @@ -429,8 +437,10 @@ class KaTrainGui(Screen, KaTrainBase): elif keycode[1] == "shift": self.nav_drawer.set_state("toggle") elif keycode[1] == "spacebar": + self.toggle_continuous_analysis() + elif keycode[1] == "b" and "ctrl" not in modifiers: self.controls.timer.paused = not self.controls.timer.paused - elif keycode[1] in ["`", "~", "m"]: + elif keycode[1] in ["`", "~", "m"] and "ctrl" not in modifiers: self.zen = (self.zen + 1) % 3 elif keycode[1] in ["left", "z"]: self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) diff --git a/katrain/core/game.py b/katrain/core/game.py index 95e2e5b..4e9f753 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -317,16 +317,29 @@ class Game: f.write(sgf) return i18n._("sgf written").format(file_name=file_name) - def analyze_extra(self, mode): + def analyze_extra(self, mode, **kwargs): stones = {s.coords for s in self.stones} cn = self.current_node engine = self.engines[cn.next_player] if mode == "extra": - visits = cn.analysis_visits_requested + engine.config["max_visits"] + if kwargs.get("continuous", False): + visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.1)) + else: + visits = cn.analysis_visits_requested + engine.config["fast_visits"] + self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits)) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return + if mode == "game": + nodes = self.root.nodes_in_tree + min_visits = min(node.analysis_visits_requested for node in nodes) + visits = min_visits + engine.config["max_visits"] + for node in nodes: + node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False) + self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits)) + return + elif mode == "sweep": board_size_x, board_size_y = self.board_size if cn.analysis_ready: @@ -354,7 +367,7 @@ class Game: visits = engine.config["fast_visits"] self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits)) priority = -1_000_000_000 - else: # mode=='equalize': + elif mode == "equalize": if not cn.analysis_ready: self.katrain.controls.set_status(i18n._("wait-before-equalize"), self.current_node) return @@ -363,6 +376,8 @@ class Game: visits = max(d["visits"] for d in cn.analysis["moves"].values()) self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits)) priority = -1_000 + else: + raise ValueError("Invalid analysis mode") for move in analyze_moves: cn.analyze( engine, priority, visits=visits, refine_move=move, time_limit=False diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index c0b3ef1..a0e31e3 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -69,8 +69,9 @@ class GameNode(SGFNode): # various analysis functions def analyze(self, engine, priority=0, visits=None, time_limit=True, refine_move=None, analyze_fast=False): - if visits and not refine_move: - self.analysis_visits_requested = max(visits, engine.config["max_visits"]) + if not refine_move: + req_visits = visits or (engine.config["fast_visits"] if analyze_fast else engine.config["max_visits"]) + self.analysis_visits_requested = max(req_visits, self.analysis_visits_requested) engine.request_analysis( self, lambda result: self.set_analysis(result, refine_move), diff --git a/katrain/gui.kv b/katrain/gui.kv index 2f76090..cd30c74 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -721,6 +721,16 @@ icon: 'img/Sweep.png' shortcut: 'd' on_action: root.katrain("analyze-extra", "sweep") + AnalysisMenuItem: + text: i18n._("analysis:game") + icon: 'img/analysis.png' + shortcut: 'f' + on_action: root.katrain("analyze-extra", "game") + AnalysisMenuItem: + text: i18n._("analysis:continuous") + icon: 'img/off.png' + shortcut: 'space' + on_action: root.katrain.toggle_continuous_analysis() AnalysisMenuItem: text: i18n._("analysis:aimove") icon: 'img/ai.png' diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index 9d5b9a8..8eb4c03 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -383,15 +383,15 @@ class CollapsablePanel(MDBoxLayout): option_active=self.build_options, options_spacing=self.build_options, ) - self.bind(state=self._on_state,size_hint_y_open=self._on_size, height_open=self._on_size) + self.bind(state=self._on_state, size_hint_y_open=self._on_size, height_open=self._on_size) MDApp.get_running_app().bind(language=lambda *_: Clock.schedule_once(self.build_options, 0)) self.build_options() - def _on_state(self,*_args): + def _on_state(self, *_args): self.build() self.trigger_select(ix=None) - def _on_size(self,*_args): + def _on_size(self, *_args): height, size_hint_y = 1, None if self.state == "open" and self.contents: if self.height_open: @@ -402,7 +402,6 @@ class CollapsablePanel(MDBoxLayout): height = self.header.height self.height, self.size_hint_y = height, size_hint_y - @property def option_state(self): return {option: active for option, active in zip(self.options, self.option_active)} diff --git a/katrain/gui/style.py b/katrain/gui/style.py index e3523af..1d84822 100644 --- a/katrain/gui/style.py +++ b/katrain/gui/style.py @@ -59,4 +59,4 @@ ENGINE_BUSY_COL = EVAL_COLORS[2] ENGINE_READY_COL = EVAL_COLORS[-1] # info -INFO_PV_COLOR = to_hexcol(RED) +INFO_PV_COLOR = to_hexcol(YELLOW) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index eff006d..691d5ff 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -222,13 +222,12 @@ class RankGraph(Graph): half_seg = self.segment_length // 2 ranks = {"B": [], "W": []} - dx = self.segment_length//4 + dx = self.segment_length // 4 for segment_mid in range(0, len(nodes), dx): - bounds = (max(0,segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) - for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1]+1], num_intersec).items(): + bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) + for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks - print(ranks) self.redraw_trigger() def update_value(self, node): diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 98569aa..8376848 100644 Binary files a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index 2a9b66c..eaa954a 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -572,3 +572,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index cfda43f..bcfc2c6 100644 Binary files a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index 63f6973..cbab7b4 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -615,3 +615,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 2757973..c6ca5ca 100644 Binary files a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 1a7ac7a..518a233 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -98,6 +98,15 @@ msgstr "Equalize visits of potential moves" msgid "analysis:sweep" msgstr "Fast analysis of all moves" +msgid "analysis:sweep" +msgstr "Fast analysis of all moves" + +msgid "analysis:game" +msgstr "Re-analyze entire game" + +msgid "analysis:continuous" +msgstr "Continuously analyze" + msgid "analysis:aimove" msgstr "Force AI Move" @@ -113,6 +122,10 @@ msgstr "Analyzing entire board using {visits} visits/move" msgid "equalizing analysis" msgstr "Equalizing analysis of candidate moves to {visits} visits" +#. status message +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + #. right panel buttons and below board msgid "pass-button-text" msgstr "Pass" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 0e7f4e4..74e83df 100644 Binary files a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 9246ba7..0ee6641 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -610,3 +610,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 857c300..145fba7 100644 Binary files a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 21e3d57..7e66bcf 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -649,3 +649,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 473ea52..3459326 100644 Binary files a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index dce0fe4..9ce8469 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -587,3 +587,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 7ad03ae..70212a2 100644 Binary files a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 1fcfe38..5f6d215 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -612,3 +612,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/img/analysis.png b/katrain/img/analysis.png new file mode 100644 index 0000000..8b142af Binary files /dev/null and b/katrain/img/analysis.png differ diff --git a/katrain/img/off.png b/katrain/img/off.png new file mode 100644 index 0000000..c6b35b5 Binary files /dev/null and b/katrain/img/off.png differ