refactor move navigation

This commit is contained in:
Sander Land committed 2020-06-13 11:30:24 +02:00
1 parent 69ef5aeb12
commit d371802fa0
9 files changed
+71 -50

No files matched your search

+1 -1
View File
@@ -141,7 +141,7 @@ In addition to shortcuts mentioned above and those shown in the main menu:
* I am looking for contributors of more translations of both this manual and the program itself. The best way to help with this is to contact me on discord.
* You also can contact me on [discord](https://discord.gg/AjTPFpN) (Sander#3278), [KakaoTalk](https://open.kakao.com/o/gTsMJCac)
or [Reddit](http://reddit.com/u/sanderbaduk) to get help, discuss improvements, or simply show your appreciation.
* Donations for improving and promoting KaTrain are taken supported through [Liberapay](https://liberapay.com/KaTrain/).
* Donations for improving and promoting KaTrain are taken supported through [Liberapay](https://liberapay.com/KaTrain/) or [Github Sponsors](https://github.com/sponsors/sanderland).
+10 -2
View File
@@ -253,9 +253,13 @@ class KaTrainGui(Screen, KaTrainBase):
self.board_gui.animating_pv = None
self.game.redo(n_times)
def _do_cycle_children(self, *args):
self.board_gui.animating_pv = None
self.game.cycle_children(*args)
def _do_switch_branch(self, *args):
self.board_gui.animating_pv = None
self.game.switch_branch(*args)
self.controls.move_tree.switch_branch(*args)
def _do_play(self, coords):
self.board_gui.animating_pv = None
@@ -367,7 +371,11 @@ class KaTrainGui(Screen, KaTrainBase):
self.log("Imported game from clipboard.", OUTPUT_INFO)
def on_touch_up(self, touch):
if self.board_gui.collide_point(*touch.pos) or self.board_controls.collide_point(*touch.pos):
if (
self.board_gui.collide_point(*touch.pos)
or self.board_controls.collide_point(*touch.pos)
or self.controls.move_tree.collide_point(*touch.pos)
):
if touch.button == "scrollup":
self("redo")
elif touch.button == "scrolldown":
+7 -10
View File
@@ -86,7 +86,9 @@ class Game:
try:
for node in self.current_node.nodes_from_root:
for m in node.move_with_placements:
self._validate_move_and_update_chains(m, True) # ignore ko since we didn't know if it was forced
self._validate_move_and_update_chains(
m, True
) # ignore ko since we didn't know if it was forced
except IllegalMoveException as e:
raise Exception(f"Unexpected illegal move ({str(e)})")
@@ -163,7 +165,6 @@ class Game:
cn = self.current_node # avoid race conditions
for _ in range(n_times):
if not cn.is_root:
cn.parent.set_favourite_child(cn)
cn = cn.parent
self.set_current_node(cn)
@@ -171,18 +172,14 @@ class Game:
cn = self.current_node # avoid race conditions
for _ in range(n_times):
if cn.children:
cn = cn.favourite_child
cn = cn.order_children[0]
self.set_current_node(cn)
def switch_branch(self, direction, cycle=False):
def cycle_children(self, direction):
cn = self.current_node # avoid race conditions
if cn.parent and len(cn.parent.children) > 1:
ordered_children = GameNode.order_children(cn.parent.children)
ix = ordered_children.index(cn) + direction
if cycle:
ix = (ix + len(ordered_children)) % len(ordered_children)
elif ix < 0 or ix >= len(ordered_children):
return
ordered_children = cn.parent.ordered_children
ix = (ordered_children.index(cn) + len(ordered_children) + direction) % len(ordered_children)
self.set_current_node(ordered_children[ix])
def place_handicap_stones(self, n_handicaps):
+1 -14
View File
@@ -22,10 +22,7 @@ class GameNode(SGFNode):
self.move_number = 0
self.time_used = 0
self.analysis_visits_requested = 0
self.undo_threshold = (
random.random()
) # for fractional undos, store the random threshold in the move itself for consistency
self._favourite_child = None
self.undo_threshold = random.random() # for fractional undos
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None):
properties = copy.copy(super().sgf_properties())
@@ -70,16 +67,6 @@ class GameNode(SGFNode):
children, key=lambda c: 0.5 if c.auto_undo is None else int(c.auto_undo)
) # analyzed/not undone main, non-teach second, undone last
def set_favourite_child(self, c):
self._favourite_child = c
@property
def favourite_child(self) -> Optional["GameNode"]:
if self._favourite_child:
return self._favourite_child
elif self.children:
return self.children[0]
# 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:
+5 -1
View File
@@ -91,6 +91,10 @@ class SGFNode:
"""For hooking into in a subclass and overriding branch order."""
return children
@property
def ordered_children(self):
return self.order_children(self.children)
@staticmethod
def _escape_value(value):
return re.sub(r"([\]\\])", r"\\\1", value) if isinstance(value, str) else value # escape \ and ]
@@ -122,7 +126,7 @@ class SGFNode:
if len(item.children) == 1:
stack.append(item.children[0])
elif item.children:
stack += sum([[")", c, "("] for c in self.order_children(item.children)[::-1]], [])
stack += sum([[")", c, "("] for c in item.ordered_children[::-1]], [])
return sgf_str
def add_list_property(self, property: str, values: List):
+1 -1
View File
@@ -236,7 +236,7 @@
NavIconButton:
id: branch
icon: 'img/Branch.png'
on_press: root.katrain('switch-branch',1,True)
on_press: root.katrain('cycle-children',1)
disabled: True
ClickableLabel:
pos_hint: {'center_x': 0.85,'center_y':0.5}
+1 -1
View File
@@ -310,7 +310,7 @@ class AIPopup(QuickConfigGui):
class ConfigPopup(QuickConfigGui):
def build_and_set_properties(self, *_args):
super().build_and_set_properties()
#self.check_models()
# self.check_models()
def check_models(self, *args): # WIP
try:
+6 -3
View File
@@ -5,6 +5,7 @@ from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Cloc
from kivymd.app import MDApp
from katrain.gui.kivyutils import BackgroundMixin
import threading
class ScoreGraph(BackgroundMixin):
@@ -31,6 +32,7 @@ class ScoreGraph(BackgroundMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._lock = threading.Lock()
self.bind(pos=self.update_graph, size=self.update_graph)
def on_touch_down(self, touch):
@@ -123,15 +125,16 @@ class ScoreGraph(BackgroundMixin):
self.winrate_dot_pos = winrate_dot_point
def update_value(self, node):
with self._lock:
self.highlighted_index = index = node.depth
self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1)))
self.nodes[index] = node
if index > 1 and node.parent: # sometimes things go so fast
backfill, node = index-1, node.parent
backfill, bfnode = index - 1, node.parent
while self.nodes[backfill] is None:
self.nodes[backfill] = node
self.nodes[backfill] = bfnode
backfill -= 1
node = node.parent
bfnode = bfnode.parent
if index + 1 < len(self.nodes) and (node is None or self.nodes[index + 1] not in node.children):
self.nodes = self.nodes[: index + 1] # on branch switching, don't show history from other branch
+39 -17
View File
@@ -23,9 +23,18 @@ from katrain.gui.style import (
class MoveTreeCanvas(Widget):
move_xy_pos = DictProperty({})
scroll_view_widget = ObjectProperty(None)
move_size = NumericProperty(5)
move_tree = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.move_pos = {}
self.move_xy_pos = {}
def set_game_node(self, node):
katrain = MDApp.get_running_app().gui
katrain.game.set_current_node(node)
katrain.update_state()
def on_touch_up(self, touch):
if touch.button == "left":
@@ -33,31 +42,40 @@ class MoveTreeCanvas(Widget):
self.move_xy_pos.items(), key=lambda n_xy: abs(n_xy[1][0] - touch.x) + abs(n_xy[1][1] - touch.y)
)
if max(abs(x - touch.x), abs(y - touch.y)) <= (self.move_size / 2):
katrain = MDApp.get_running_app().gui
katrain.game.set_current_node(node)
katrain.update_state()
self.set_game_node(node)
def switch_branch(self, direction=1):
pos = self.move_pos.get(self.scroll_view_widget.current_node)
if not self.scroll_view_widget:
return
same_x_moves = sorted([(y, n) for n, (x, y) in self.move_pos.items() if x == pos[0]])
new_index = next((i for i, (y, n) in enumerate(same_x_moves) if y == pos[1]), 0) + direction
if new_index < 0 or new_index >= len(same_x_moves):
return
self.set_game_node(same_x_moves[new_index][1])
def draw_move_tree(self, current_node):
if not self.move_tree:
if not self.scroll_view_widget:
return
spacing = 5
moves_vert = 3
self.move_size = (self.move_tree.height - (moves_vert + 1) * spacing) / moves_vert
self.move_size = (self.scroll_view_widget.height - (moves_vert + 1) * spacing) / moves_vert
root = current_node.root
move_pos = {root: (0, 0)}
stack = GameNode.order_children(root.children)[::-1]
self.move_pos = {root: (0, 0)}
stack = root.ordered_children[::-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!
while stack:
move = stack.pop()
x = move.depth
y = max(next_y_pos[x], move_pos[move.parent][1])
y = max(next_y_pos[x], self.move_pos[move.parent][1])
next_y_pos[x] = y + 1
next_y_pos[x - 1] = max(next_y_pos[x], next_y_pos[x - 1])
move_pos[move] = (x, y)
children[move] = GameNode.order_children(move.children)
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)
@@ -69,13 +87,13 @@ class MoveTreeCanvas(Widget):
def coord_pos(coord):
return (coord + 0.5) * (spacing + self.move_size) + spacing / 2
self.width = coord_pos(max(x + 0.5 for x, y in move_pos.values()))
self.height = coord_pos(max(y + 0.5 for x, y in move_pos.values()))
self.width = coord_pos(max(x + 0.5 for x, y in self.move_pos.values()))
self.height = coord_pos(max(y + 0.5 for x, y in self.move_pos.values()))
def xy_pos(x, y):
return coord_pos(x), self.height - coord_pos(y)
self.move_xy_pos = {n: xy_pos(x, y) for n, (x, y) in move_pos.items()}
self.move_xy_pos = {n: xy_pos(x, y) for n, (x, y) in self.move_pos.items()}
with self.canvas:
self.canvas.clear()
@@ -96,7 +114,7 @@ class MoveTreeCanvas(Widget):
Color(*STONE_COLORS["W" if node.player == "B" else "B"])
draw_text(pos=pos, text=text, font_size=self.move_size * 1.75 / (1 + 1 * len(text)), font_name="Roboto")
self.move_tree.scroll_to_pixel(*self.move_xy_pos[current_node])
self.scroll_view_widget.scroll_to_pixel(*self.move_xy_pos[current_node])
class MoveTree(ScrollView, BackgroundMixin):
@@ -109,6 +127,9 @@ class MoveTree(ScrollView, BackgroundMixin):
)
self.bind(current_node=self.redraw_tree_trigger, size=self.redraw_tree_trigger)
def switch_branch(self, direction):
self.move_tree_canvas.switch_branch(direction)
def scroll_to_pixel(self, x, y):
if not self._viewport:
return
@@ -126,8 +147,9 @@ Builder.load_string(
<MoveTree>:
background_color: BOX_BACKGROUND_COLOR
move_tree_canvas: move_tree_canvas
scroll_distance: 0 # scroll wheel is for forward/backward
MoveTreeCanvas:
move_tree: root
scroll_view_widget: root
id: move_tree_canvas
size_hint: None, None
"""