refactor move navigation
This commit is contained in:
1 parent
69ef5aeb12
commit
d371802fa0
9 files changed
+86
-65
No files matched your search
@@ -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).
|
||||
|
||||
|
||||
|
||||
+11
-3
@@ -225,7 +225,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
def _do_new_game(self, move_tree=None, analyze_fast=False):
|
||||
mode = self.play_analyze_mode
|
||||
if (move_tree is None and mode != MODE_PLAY) or (move_tree is None and mode != MODE_ANALYZE):
|
||||
self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
|
||||
self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
|
||||
self.board_gui.animating_pv = None
|
||||
self.engine.on_new_game() # clear queries
|
||||
self.game = Game(self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast)
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,23 +125,24 @@ class ScoreGraph(BackgroundMixin):
|
||||
self.winrate_dot_pos = winrate_dot_point
|
||||
|
||||
def update_value(self, node):
|
||||
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
|
||||
while self.nodes[backfill] is None:
|
||||
self.nodes[backfill] = node
|
||||
backfill -= 1
|
||||
node = node.parent
|
||||
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, bfnode = index - 1, node.parent
|
||||
while self.nodes[backfill] is None:
|
||||
self.nodes[backfill] = bfnode
|
||||
backfill -= 1
|
||||
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
|
||||
if index == len(self.nodes) - 1: # possibly just switched branch
|
||||
while node.children: # add children back
|
||||
node = node.children[0]
|
||||
self.nodes.append(node)
|
||||
Clock.schedule_once(self.update_graph, 0)
|
||||
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
|
||||
if index == len(self.nodes) - 1: # possibly just switched branch
|
||||
while node.children: # add children back
|
||||
node = node.children[0]
|
||||
self.nodes.append(node)
|
||||
Clock.schedule_once(self.update_graph, 0)
|
||||
|
||||
|
||||
Builder.load_string(
|
||||
|
||||
@@ -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!
|
||||
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
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user