diff --git a/LICENSE b/LICENSE index 9911f0c..a652d7a 100644 --- a/LICENSE +++ b/LICENSE @@ -6,7 +6,8 @@ For on related licenses for these binaries and libraries see https://github.com/ 2. Icons from www.flaticon.com, used with permission with the following attributions: - Equalize icon and Thrash Icon: derived from work by bqlqn from www.flaticon.com - Local search icon, Insert icon: derived from work by Pixel Perfect from www.flaticon.com -- Other Menu icons and Flag icons: derived from work by Freepik from www.flaticon.com +- Other Menu icons, Finish, and Flag icons: derived from work by Freepik from www.flaticon.com +- Collapse branch icon: derived from work by Kirill Kazachek from www.flaticon.com 3. The True Type Font DIGITAL-7 version 1.02 by Sizenko Alexander, which is free for non-commercial use. diff --git a/katrain/__main__.py b/katrain/__main__.py index 8c5c24c..01b1493 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -324,6 +324,9 @@ class KaTrainGui(Screen, KaTrainBase): def _do_analyze_extra(self, mode, **kwargs): self.game.analyze_extra(mode, **kwargs) + def _do_play_to_end(self): + self.game.play_to_end() + def _do_select_box(self): self.controls.set_status(i18n._("analysis:region:start"), STATUS_INFO) self.board_gui.selecting_region_of_interest = True @@ -498,6 +501,7 @@ class KaTrainGui(Screen, KaTrainBase): "g": ("select-box",), "i": ("insert-mode",), "p": ("play", None), + "l": ("play-to-end",), "n": ("next-mistake",), "b": ("undo", "branch"), "down": ("switch-branch", 1), @@ -517,7 +521,6 @@ class KaTrainGui(Screen, KaTrainBase): def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers): if self.controls.note.focus: return # when making notes, don't allow keyboard shortcuts - popup = self.popup_open if popup: if keycode[1] in ["f5", "f6", "f7", "f8"]: # switch between popups @@ -552,6 +555,12 @@ class KaTrainGui(Screen, KaTrainBase): self("undo", 999) elif keycode[1] == "end": self("redo", 999) + elif keycode[1] == "pageup": + self.controls.move_tree.make_selected_node_main_branch() + elif keycode[1] == "delete" and ctrl_pressed: + self.controls.move_tree.delete_selected_node() + elif keycode[1] == "c" and not ctrl_pressed: + self.controls.move_tree.toggle_selected_node_collapse() elif keycode[1] == "n" and ctrl_pressed: self("new-game-popup") elif keycode[1] == "l" and ctrl_pressed: diff --git a/katrain/core/game.py b/katrain/core/game.py index 00d8bac..ea886bb 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -93,6 +93,14 @@ class Game: self.set_current_node(self.root) self.main_time_used = 0 + + # restore shortcuts + shortcut_id_to_node = {node.get_property("KTSID", None): node for node in self.root.nodes_in_tree} + for node in self.root.nodes_in_tree: + shortcut_id = node.get_property("KTSF", None) + if shortcut_id and shortcut_id in shortcut_id_to_node: + shortcut_id_to_node[shortcut_id].add_shortcut(node) + threading.Thread( target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast, even_if_present=False), daemon=True, @@ -264,7 +272,9 @@ class Game: n_times = 9999 break_on_branch = True for _ in range(n_times): - if not cn.is_root: + if cn.shortcut_from: + cn = cn.shortcut_from + elif not cn.is_root: cn = cn.parent if break_on_branch and len(cn.children) > 1: break @@ -276,7 +286,11 @@ class Game: cn = self.current_node # avoid race conditions for move in range(n_times): if cn.children: - cn = cn.ordered_children[0] + child = cn.ordered_children[0] + shortcut_to = [m for m, v in cn.shortcuts_to if child == v] # are we about to go to a shortcut node? + if shortcut_to: + child = shortcut_to[0] + cn = child if ( move > 0 and stop_on_mistake is not None @@ -512,6 +526,39 @@ class Game: engine, priority=priority, visits=visits, refine_move=move, time_limit=False, report_every=None ) # explicitly requested so take as long as you need + def play_to_end(self): + cn = self.current_node + count = 0 + if not cn.analysis_exists: + return + + def analyze_and_play_policy(node): + nonlocal count, cn + cand = node.candidate_moves + if cand: + move = Move.from_gtp(cand[0]["move"], player=node.next_player) + else: + polmoves = node.policy_ranking + move = polmoves[0][1] if polmoves else Move(None) + if move.is_pass: + cn.add_shortcut(node) + self.set_current_node(node) + self.katrain.controls.set_status("", STATUS_INFO) + return + count += 1 + new_node = GameNode(parent=node, move=move) + self.katrain.controls.set_status(i18n._("playtoend:status").format(num_moves=count), STATUS_INFO) + + def set_analysis(result, _partial): + new_node.set_analysis(result) + analyze_and_play_policy(new_node) + + self.engines[node.next_player].request_analysis( + new_node, callback=set_analysis, priority=-1000, analyze_fast=True, + ) + + threading.Thread(target=analyze_and_play_policy, args=(cn,), daemon=True).start() + def analyze_undo(self, node): train_config = self.katrain.config("trainer") move = node.move diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index 5fdfda2..c3995ba 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -57,9 +57,26 @@ class GameNode(SGFNode): self.time_used = 0 self.undo_threshold = random.random() # for fractional undos self.end_state = None + self.shortcuts_to = [] + self.shortcut_from = None self.analysis_loaded = False self.clear_analysis() + def add_shortcut(self, to_node): # collapses the branch between them + nodes = [to_node] + while nodes[-1].parent and nodes[-1] != self: # ensure on path + nodes.append(nodes[-1].parent) + if nodes[-1] == self and len(nodes) > 2: + via = nodes[-2] + self.shortcuts_to.append((to_node, via)) # and first child + to_node.shortcut_from = self + + def remove_shortcut(self): + from_node = self.shortcut_from + if from_node: + from_node.shortcuts_to = [(m, v) for m, v in from_node.shortcuts_to if m != self] + self.shortcut_from = None + def add_list_property(self, property: str, values: List): if property == "KT": try: @@ -121,6 +138,14 @@ class GameNode(SGFNode): *comments, f"\nSGF with generated by {PROGRAM_NAME} {VERSION}{SGF_INTERNAL_COMMENTS_MARKER}.\n", ] + if self.shortcut_from: + properties["KTSF"] = [id(self.shortcut_from)] + elif "KTSF" in properties: + del properties["KTSF"] + if self.shortcuts_to: + properties["KTSID"] = [id(self)] + elif "KTSID" in properties: + del properties["KTSID"] if note: comments.append(f"{self.note}") if comments: @@ -178,7 +203,11 @@ class GameNode(SGFNode): cur.update(move_analysis) def set_analysis( - self, analysis_json: Dict, refine_move: Optional[Move], additional_moves: bool, partial_result: bool = False + self, + analysis_json: Dict, + refine_move: Optional[Move] = None, + additional_moves: bool = False, + partial_result: bool = False, ): if refine_move: pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else [] diff --git a/katrain/gui.kv b/katrain/gui.kv index eb5b3a3..29adef8 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -650,9 +650,10 @@ font_size: self.height * 0.5 size_hint_y: None - height: 50 + height: dp(50) background_color: BOX_BACKGROUND_COLOR - content_width: icon.width + label.texture_size[0] * 1.33 + width_margin: 1.33 + content_width: icon.width + label.texture_size[0] * self.width_margin MDFloatLayout: id: icon size_hint: None, 1 @@ -716,7 +717,7 @@ : on_close: app.gui.analysis_controls.close_dropdown() background_color: LIGHTER_BACKGROUND_COLOR - height: 45 + height: dp(45) canvas.before: Color: rgba: WHITE @@ -777,6 +778,11 @@ icon: 'img/ai.png' shortcut: 'enter' on_action: root.katrain("ai-move") + AnalysisMenuItem: + text: i18n._("analysis:playtoend") + icon: 'img/Finish.png' + shortcut: 'l' + on_action: root.katrain("play-to-end") : diff --git a/katrain/gui/widgets/movetree.py b/katrain/gui/widgets/movetree.py index 844dd41..ca36e0a 100644 --- a/katrain/gui/widgets/movetree.py +++ b/katrain/gui/widgets/movetree.py @@ -25,6 +25,7 @@ from katrain.gui.style import ( STONE_TEXT_COLORS, WHITE, YELLOW, + GREY, ) @@ -84,23 +85,43 @@ class MoveTreeCanvas(Widget): self.menu_selected_node = selected_node def delete_selected_node(self): - if self.menu_selected_node and self.menu_selected_node.parent: - parent = self.menu_selected_node.parent - parent.children.remove(self.menu_selected_node) + selected_node = self.menu_selected_node or self.scroll_view_widget.current_node + if selected_node and selected_node.parent: + if selected_node.shortcut_from: + parent = selected_node.shortcut_from + via = [v for m, v in parent.shortcuts_to if m == selected_node] + selected_node.remove_shortcut() + if via: # should always be + parent.children.remove(via[0]) + else: + parent = selected_node.parent + parent.children.remove(selected_node) self.set_game_node(parent) self.is_open = False def make_selected_node_main_branch(self): - - if self.menu_selected_node and self.menu_selected_node.parent: - node = self.menu_selected_node + selected_node = self.menu_selected_node or self.scroll_view_widget.current_node + if selected_node and selected_node.parent: + node = selected_node while node.parent is not None: node.parent.children.remove(node) node.parent.children.insert(0, node) node = node.parent - self.set_game_node(self.menu_selected_node) + self.set_game_node(selected_node) self.is_open = False + def toggle_selected_node_collapse(self): + selected_node = self.menu_selected_node or self.scroll_view_widget.current_node + if selected_node and selected_node.parent: + if selected_node.shortcut_from: + selected_node.remove_shortcut() + else: # find branching point + node = selected_node.parent + while len(node.children) == 1 and not node.is_root and not node.shortcut_from: + node = node.parent + node.add_shortcut(selected_node) + self.scroll_view_widget.redraw_tree_trigger() + def switch_branch(self, direction=1): pos = self.move_pos.get(self.scroll_view_widget.current_node) if not self.scroll_view_widget: @@ -120,24 +141,36 @@ class MoveTreeCanvas(Widget): root = current_node.root + def children_with_shortcuts(move): + shortcuts = move.shortcuts_to + via = [v for m, v in shortcuts] # children that are shortcut + return [m for m in move.ordered_children if m not in via] + [m for m, v in shortcuts] + self.move_pos = {root: (0, 0)} - stack = root.ordered_children[::-1] + stack = children_with_shortcuts(root)[::-1] next_y_pos = defaultdict(int) # x pos -> max y pos children = defaultdict(list) # since AI self-play etc may modify the tree between layout and draw! - children[root] = root.ordered_children + children[root] = [*stack] while stack: move = stack.pop() - x = move.depth - y = max(next_y_pos[x], self.move_pos[move.parent][1]) + if move.shortcut_from: + parent = move.shortcut_from + else: + parent = move.parent + + if parent: + x = self.move_pos[parent][0] + 1 + else: + x = 0 + y = max(next_y_pos[x], self.move_pos[parent][1]) next_y_pos[x] = y + 1 next_y_pos[x - 1] = max(next_y_pos[x], next_y_pos[x - 1]) self.move_pos[move] = (x, y) - children[move] = move.ordered_children - for c in children[move][::-1]: # stack, so push top child last to process first - stack.append(c) + children[move] = children_with_shortcuts(move) + stack += children[move][::-1] # stack, so push top child last to process first - def draw_stone(pos, player): - draw_circle(pos, self.move_size / 2 - 0.5, STONE_COLORS[player]) + def draw_stone(pos, player, special_color=None): + draw_circle(pos, self.move_size / 2 - 0.5, (special_color or STONE_COLORS[player])) Color(*STONE_TEXT_COLORS[player]) Line(circle=(*pos, self.move_size / 2), width=1) @@ -152,7 +185,7 @@ class MoveTreeCanvas(Widget): self.move_xy_pos = {n: xy_pos(x, y) for n, (x, y) in self.move_pos.items()} - special_nodes = {self.menu_selected_node: RED, current_node: YELLOW} + special_nodes = {current_node: YELLOW, self.menu_selected_node: RED} if insert_node: special_nodes[insert_node.parent] = GREEN @@ -176,9 +209,10 @@ class MoveTreeCanvas(Widget): pos=[c - self.move_size / 2 - spacing / 2 for c in self.move_xy_pos[node]], size=(self.move_size + spacing, self.move_size + spacing), ) - draw_stone(pos, node.player) + collapsed_color = LIGHTGREY if node.shortcut_from else None + draw_stone(pos, node.player, collapsed_color) text = str(node.depth) - Color(*STONE_COLORS["W" if node.player == "B" else "B"]) + Color(*STONE_COLORS["W" if node.player == "B" and not node.shortcut_from else "B"]) draw_text(pos=pos, text=text, font_size=self.move_size * 1.75 / (1 + 1 * len(text)), font_name="Roboto") if current_node in self.move_xy_pos: @@ -209,6 +243,9 @@ class MoveTree(ScrollView, BackgroundMixin): def make_selected_node_main_branch(self): self.move_tree_canvas.make_selected_node_main_branch() + def toggle_selected_node_collapse(self): + self.move_tree_canvas.toggle_selected_node_collapse() + def scroll_to_pixel(self, x, y): if not self._viewport: return @@ -249,14 +286,26 @@ Builder.load_string( MoveTreeDropdownItem: text: i18n._("Delete Node") icon: 'img/delete.png' - shortcut: '' + shortcut: 'Ctrl+Del' on_action: root.katrain.controls.move_tree.delete_selected_node() -background_color: LIGHTER_BACKGROUND_COLOR + -height: dp(45) + -width_margin: 1.6 MoveTreeDropdownItem: text: i18n._("Make Main Branch") icon: 'img/Branch.png' - shortcut: '' + shortcut: 'PgUp' on_action: root.katrain.controls.move_tree.make_selected_node_main_branch() -background_color: LIGHTER_BACKGROUND_COLOR + -height: dp(45) + -width_margin: 1.6 + MoveTreeDropdownItem: + text: i18n._("Toggle Collapse Branch") + icon: 'img/Collapse.png' + shortcut: 'c' + on_action: root.katrain.controls.move_tree.toggle_selected_node_collapse() + -background_color: LIGHTER_BACKGROUND_COLOR + -height: dp(45) + -width_margin: 1.6 """ ) diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 8459233..b7e88de 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 c7c47a9..55d9acf 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -753,3 +753,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index fc8bb2b..a848e15 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 69bd3c8..51d9eea 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -805,3 +805,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index e994249..cadb555 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 9d3a04c..eed3f21 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -118,6 +118,16 @@ msgid "analysis:region:start" msgstr "" "Drag to select the region of interest. Selecting a single point removes it." +#. Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. Status message +msgid "playtoend:status" +msgstr "" +"Playing the game quickly to the end, please wait ... {num_moves} moved played." +"moves played." + #. Keep improving current position analysis when there is nothing else to do msgid "analysis:continuous" msgstr "Toggle continuous analysis" @@ -759,6 +769,10 @@ msgstr "Number of visits per move" msgid "Delete Node" msgstr "Delete Node" +#. on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + #. on move tree editing msgid "Make Main Branch" msgstr "Make Main Branch" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 202c0f6..f36f7d9 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 1164e3d..45344fd 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -812,3 +812,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 00a6d1e..12c5a25 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 0b97b3c..3d1aae3 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -839,3 +839,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo index 3b93629..9367683 100644 Binary files a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po index 4efa6ca..33ee94f 100644 --- a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po @@ -796,3 +796,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 0a42567..aa42ac6 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 161c88f..5d2bd99 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -766,3 +766,17 @@ msgstr "Drag to select the region of interest." #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index abe12d6..852ca80 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 0beb047..773c3ba 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -802,3 +802,17 @@ msgstr "" #. TODO - Restrict analysis to a specific region msgid "analysis:region" msgstr "Set region of interest" + +#. TODO - on move tree editing +msgid "Toggle Collapse Branch" +msgstr "Expand/Collapse" + +#. TODO - Menu option +msgid "analysis:playtoend" +msgstr "Fast playout to game end" + +#. TODO - Status message +msgid "playtoend:status" +msgstr "" +"Playing game to the end with policy strategy, please wait ... {num_moves} " +"moves played." diff --git a/katrain/img/Collapse.png b/katrain/img/Collapse.png new file mode 100644 index 0000000..1acb2c9 Binary files /dev/null and b/katrain/img/Collapse.png differ diff --git a/katrain/img/Finish.png b/katrain/img/Finish.png new file mode 100644 index 0000000..e7397a4 Binary files /dev/null and b/katrain/img/Finish.png differ diff --git a/katrain/img/swap.png b/katrain/img/swap.png deleted file mode 100644 index 830fb67..0000000 Binary files a/katrain/img/swap.png and /dev/null differ