play to end/collapse

This commit is contained in:
Sander Land committed 2020-12-05 20:21:02 +01:00
1 parent a02697c783
commit b958975628
23 files changed
+281 -28

No files matched your search

+1
View File
@@ -7,6 +7,7 @@ For on related licenses for these binaries and libraries see https://github.com/
- 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
- 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.
+10 -1
View File
@@ -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:
+49 -2
View File
@@ -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, visits=1,
)
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
+30 -1
View File
@@ -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 []
+9 -3
View File
@@ -650,9 +650,10 @@
<MenuItem>
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 @@
<AnalysisMenuItem@MenuItem>:
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/ai.png'
shortcut: 'l'
on_action: root.katrain("play-to-end")
<AnalysisControls>:
+70 -21
View File
@@ -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
"""
)
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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 game to the end with policy strategy, please wait ... {num_moves} "
"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"
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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."
Binary file not shown.
@@ -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."
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB