from collections import defaultdict from kivy.graphics.context_instructions import Color from kivy.graphics.vertex_instructions import Line, Rectangle from kivy.lang import Builder from kivy.properties import ObjectProperty, Clock, DictProperty, NumericProperty from kivy.uix.scrollview import ScrollView from kivy.uix.widget import Widget from kivymd.app import MDApp from katrain.core.game_node import GameNode from katrain.gui.kivyutils import BackgroundMixin, draw_circle, draw_text from katrain.gui.style import WHITE, STONE_COLORS, LIGHTGREY, BACKGROUND_COLOR, OUTLINE_COLORS, LIGHTER_BACKGROUND_COLOR, YELLOW, STONE_TEXT_COLORS class MoveTreeCanvas(Widget): move_xy_pos = DictProperty({}) move_size = NumericProperty(5) move_tree = ObjectProperty(None) def on_touch_up(self, touch): if touch.button=='left': node, (x,y) = min(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() def draw_move_tree(self, current_node): if not self.move_tree: return spacing = 5 moves_vert = 3 self.move_size = (self.move_tree.height - (moves_vert+1) * spacing) / moves_vert root = current_node.root move_pos = {root: (0,0)} stack = GameNode.order_children( root.children )[::-1] next_y_pos = defaultdict(int) # x pos -> max y pos while stack: move = stack.pop() x = move.depth y = max(next_y_pos[x], 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) for c in GameNode.order_children( move.children )[::-1]: # stack, so push top child last to process first stack.append(c) def draw_stone(pos, player): draw_circle(pos,self.move_size/2-0.5,STONE_COLORS[player]) Color(*STONE_TEXT_COLORS[player]) Line(circle=(*pos, self.move_size/2), width=1) 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())) 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.canvas.clear() with self.canvas: Color(*YELLOW) Rectangle(pos=[c-self.move_size/2-spacing/2 for c in self.move_xy_pos[current_node]],size=(self.move_size+spacing,self.move_size+spacing)) Color(*LIGHTGREY) for node, (x,y) in self.move_xy_pos.items(): for ci, c in enumerate(GameNode.order_children(node.children)): cx,cy = self.move_xy_pos[c] Line(points=[x,y,x,cy,cx,cy],width=1) for node, pos in self.move_xy_pos.items(): draw_stone(pos, node.player) text = str(node.depth) 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]) class MoveTree(ScrollView,BackgroundMixin): current_node = ObjectProperty(None) def __init__(self, **kwargs): super().__init__(**kwargs) self.redraw_tree_trigger = Clock.create_trigger(lambda _dt: self.move_tree_canvas.draw_move_tree(self.current_node)) self.bind(current_node=self.redraw_tree_trigger,size=self.redraw_tree_trigger) def scroll_to_pixel(self, x, y): if not self._viewport: return vp = self._viewport if vp.width > self.width: sx = (x - self.width/2) / (vp.width - self.width) self.scroll_x =max(0, min(1, sx)) if vp.height > self.height: sy = (y - self.height/2) / (vp.height - self.height) self.scroll_y = max(0, min(1, sy)) Builder.load_string( """ : background_color: BOX_BACKGROUND_COLOR move_tree_canvas: move_tree_canvas MoveTreeCanvas: move_tree: root id: move_tree_canvas size_hint: None, None """)