Exemplo n.º 1
0
    def draw_pv(self, pv, node, up_to_move):
        katrain = self.katrain
        next_last_player = [node.next_player, node.player]
        stone_color = STONE_COLORS
        cn = katrain.game.current_node
        if node != cn and node.parent != cn:
            hide_node = cn
            while hide_node and hide_node.move and hide_node != node:
                if not hide_node.move.is_pass:
                    self.draw_stone(*hide_node.move.coords, [0.85, 0.68, 0.40, 0.8])  # board coloured dot
                hide_node = hide_node.parent
        for i, gtpmove in enumerate(pv):
            if i > up_to_move:
                return
            move_player = next_last_player[i % 2]
            opp_player = next_last_player[1 - i % 2]
            coords = Move.from_gtp(gtpmove).coords
            if coords is None:  # tee-hee
                sizefac = katrain.board_controls.pass_btn.size[1] / 2 / self.stone_size
                board_coords = [
                    katrain.board_controls.pass_btn.pos[0]
                    + katrain.board_controls.pass_btn.size[0]
                    + self.stone_size * sizefac,
                    katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
                ]
            else:
                board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[1]])
                sizefac = 1

            draw_circle(board_coords, self.stone_size * sizefac, stone_color[move_player])
            Color(*stone_color[opp_player])
            draw_text(pos=board_coords, text=str(i + 1), font_size=self.grid_size * sizefac / 1.45, font_name="Roboto")
Exemplo n.º 2
0
    def draw_stone(self,
                   x,
                   y,
                   col,
                   outline_col=None,
                   innercol=None,
                   evalcol=None,
                   evalscale=1.0,
                   scale=1.0):
        stone_size = self.stone_size * scale
        draw_circle((self.gridpos_x[x], self.gridpos_y[y]), stone_size, col)
        if outline_col:
            Color(*outline_col)
            Line(circle=(self.gridpos_x[x], self.gridpos_y[y], stone_size),
                 width=min(2, 0.035 * stone_size))
        if evalcol:
            eval_radius = math.sqrt(evalscale)  # scale area by evalscale
            evalsize = self.stone_size * (
                EVAL_DOT_MIN_SIZE + eval_radius *
                (EVAL_DOT_MAX_SIZE - EVAL_DOT_MIN_SIZE))
            draw_circle((self.gridpos_x[x], self.gridpos_y[y]), evalsize,
                        evalcol)

        if innercol:
            Color(*innercol)
            Line(circle=(self.gridpos_x[x], self.gridpos_y[y],
                         stone_size * 0.475 / 0.85),
                 width=0.1 * stone_size)
Exemplo n.º 3
0
    def draw_pv(self, pv, node, up_to_move):
        katrain = self.katrain
        next_last_player = [
            node.next_player,
            Move.opponent_player(node.next_player)
        ]
        cn = katrain.game.current_node
        if node != cn and node.parent != cn:
            hide_node = cn
            while hide_node and hide_node.move and hide_node != node:
                if not hide_node.move.is_pass:
                    pos = (self.gridpos_x[hide_node.move.coords[0]],
                           self.gridpos_y[hide_node.move.coords[1]])
                    draw_circle(pos, self.stone_size, [0.85, 0.68, 0.40, 0.8])
                hide_node = hide_node.parent
        for i, gtpmove in enumerate(pv):
            if i > up_to_move:
                return
            move_player = next_last_player[i % 2]
            coords = Move.from_gtp(gtpmove).coords
            if coords is None:  # tee-hee
                sizefac = katrain.board_controls.pass_btn.size[
                    1] / 2 / self.stone_size
                board_coords = [
                    katrain.board_controls.pass_btn.pos[0] +
                    katrain.board_controls.pass_btn.size[0] +
                    self.stone_size * sizefac,
                    katrain.board_controls.pass_btn.pos[1] +
                    katrain.board_controls.pass_btn.size[1] / 2,
                ]
            else:
                board_coords = (self.gridpos_x[coords[0]],
                                self.gridpos_y[coords[1]])
                sizefac = 1

            stone_size = self.stone_size * sizefac
            Color(1, 1, 1, 1)
            Rectangle(  # not sure why the -1 here, but seems to center better
                pos=(board_coords[0] - stone_size - 1,
                     board_coords[1] - stone_size),
                size=(2 * stone_size + 1, 2 * stone_size + 1),
                texture=cached_texture(Theme.STONE_TEXTURE[move_player]),
            )
            Color(*Theme.PV_TEXT_COLORS[move_player])
            draw_text(pos=board_coords,
                      text=str(i + 1),
                      font_size=self.grid_size * sizefac / 1.45,
                      font_name="Roboto")
Exemplo n.º 4
0
 def draw_stone(pos, player, special_color=None):
     draw_circle(pos, self.move_size / 2 - 0.5,
                 (special_color or Theme.STONE_COLORS[player]))
     Color(*Theme.MOVE_TREE_STONE_OUTLINE_COLORS[player])
     Line(circle=(*pos, self.move_size / 2), width=1)
Exemplo n.º 5
0
    def draw_board_contents(self, *_args):
        if not (self.katrain and self.katrain.game):
            return
        stone_color = STONE_COLORS
        outline_color = OUTLINE_COLORS
        katrain = self.katrain
        board_size_x, board_size_y = katrain.game.board_size
        lock_ai = self.trainer_config[
            "lock_ai"] and self.katrain.play_analyze_mode == MODE_PLAY
        show_n_eval = self.trainer_config["eval_off_show_last"]

        self.canvas.clear()
        with self.canvas:
            # stones
            current_node = katrain.game.current_node
            game_ended = katrain.game.ended
            full_eval_on = katrain.analysis_controls.eval.active
            has_stone = {}
            drawn_stone = {}
            for m in katrain.game.stones:
                has_stone[m.coords] = m.player

            show_dots_for = {
                p: self.trainer_config["eval_show_ai"]
                or katrain.players_info[p].human
                for p in Move.PLAYERS
            }
            show_dots_for_class = self.trainer_config["show_dots"]
            nodes = katrain.game.current_node.nodes_from_root
            realized_points_lost = None

            katrain.config("trainer/show_dots")
            for i, node in enumerate(nodes[::-1]):  # reverse order!
                points_lost = node.points_lost
                evalsize = 1
                if points_lost and realized_points_lost:
                    if points_lost <= 0.5 and realized_points_lost <= 1.5:
                        evalsize = 0
                    else:
                        evalsize = min(
                            1, max(0, realized_points_lost / points_lost))
                for m in node.move_with_placements:
                    if has_stone.get(m.coords) and not drawn_stone.get(
                            m.coords):  # skip captures, last only for
                        move_eval_on = show_dots_for.get(
                            m.player) and (i < show_n_eval or full_eval_on)
                        if move_eval_on and points_lost is not None:
                            evalcol = self.eval_color(points_lost,
                                                      show_dots_for_class)
                        else:
                            evalcol = None
                        inner = stone_color[m.opponent] if i == 0 else None
                        drawn_stone[m.coords] = m.player
                        self.draw_stone(
                            m.coords[0],
                            m.coords[1],
                            stone_color[m.player],
                            outline_color[m.player],
                            inner,
                            evalcol,
                            evalsize,
                        )
                realized_points_lost = node.parent_realized_points_lost

            if katrain.game.current_node.is_root and katrain.debug_level >= 3:  # secret ;)
                for y in range(0, board_size_y):
                    evalcol = self.eval_color(16 * y / board_size_y)
                    self.draw_stone(0, y, stone_color["B"], outline_color["B"],
                                    None, evalcol, y / (board_size_y - 1))
                    self.draw_stone(1, y, stone_color["B"], outline_color["B"],
                                    stone_color["W"], evalcol, 1)
                    self.draw_stone(2, y, stone_color["W"], outline_color["W"],
                                    None, evalcol, y / (board_size_y - 1))
                    self.draw_stone(3, y, stone_color["W"], outline_color["W"],
                                    stone_color["B"], evalcol, 1)
                    self.draw_stone(4, y, [*evalcol[:3], 0.5], scale=0.8)

            # ownership - allow one move out of date for smooth animation
            ownership = current_node.ownership or (
                current_node.parent and current_node.parent.ownership)
            if katrain.analysis_controls.ownership.active and ownership and not lock_ai:
                ownership_grid = var_to_grid(ownership,
                                             (board_size_x, board_size_y))
                rsz = self.grid_size * 0.2
                for y in range(board_size_y - 1, -1, -1):
                    for x in range(board_size_x):
                        ix_owner = "B" if ownership_grid[y][x] > 0 else "W"
                        if ix_owner != (has_stone.get((x, y), -1)):
                            Color(*stone_color[ix_owner],
                                  abs(ownership_grid[y][x]))
                            Rectangle(pos=(self.gridpos_x[x] - rsz / 2,
                                           self.gridpos_y[y] - rsz / 2),
                                      size=(rsz, rsz))

            policy = current_node.policy
            if (not policy and current_node.parent
                    and current_node.parent.policy
                    and katrain.last_player_info.ai
                    and katrain.next_player_info.ai):
                policy = (
                    current_node.parent.policy
                )  # in the case of AI self-play we allow the policy to be one step out of date

            pass_btn = katrain.board_controls.pass_btn
            pass_btn.canvas.after.clear()
            if katrain.analysis_controls.policy.active and policy and not lock_ai:
                policy_grid = var_to_grid(policy, (board_size_x, board_size_y))
                best_move_policy = max(*policy)
                for y in range(board_size_y - 1, -1, -1):
                    for x in range(board_size_x):
                        if policy_grid[y][x] > 0:
                            polsize = 1.1 * math.sqrt(policy_grid[y][x])
                            policy_circle_color = (
                                *POLICY_COLOR,
                                GHOST_ALPHA + TOP_MOVE_ALPHA *
                                (policy_grid[y][x] == best_move_policy),
                            )
                            self.draw_stone(x,
                                            y,
                                            policy_circle_color,
                                            scale=polsize)
                polsize = math.sqrt(policy[-1])
                with pass_btn.canvas.after:
                    draw_circle(
                        (pass_btn.pos[0] + pass_btn.width / 2,
                         pass_btn.pos[1] + pass_btn.height / 2),
                        polsize * pass_btn.height / 2,
                        POLICY_COLOR,
                    )

            # pass circle
            passed = len(nodes) > 1 and current_node.is_pass
            if passed:
                if game_ended:
                    text = katrain.game.manual_score or i18n._(
                        "board-game-end")
                else:
                    text = i18n._("board-pass")
                Color(0.45, 0.05, 0.45, 0.7)
                center = (self.gridpos_x[int(board_size_x / 2)],
                          self.gridpos_y[int(board_size_y / 2)])
                size = min(self.width, self.height) * 0.22
                Ellipse(pos=(center[0] - size / 2, center[1] - size / 2),
                        size=(size, size))
                Color(0.85, 0.85, 0.85)
                draw_text(pos=center,
                          text=text,
                          font_size=size * 0.25,
                          halign="center",
                          outline_color=[0.95, 0.95, 0.95])

        self.draw_hover_contents()
Exemplo n.º 6
0
    def draw_board(self, *_args):
        if not (self.katrain and self.katrain.game):
            return
        katrain = self.katrain
        board_size_x, board_size_y = katrain.game.board_size
        max_board_size = max(board_size_x, board_size_y)
        self.canvas.before.clear()
        with self.canvas.before:
            # set up margins and grid lines
            grid_spaces_margin_x = [1.5, 0.75]  # left, right
            grid_spaces_margin_y = [1.5, 0.75]  # bottom, top
            x_grid_spaces = board_size_x - 1 + sum(grid_spaces_margin_x)
            y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y)
            self.grid_size = min(self.width / x_grid_spaces,
                                 self.height / y_grid_spaces)
            board_width_with_margins = x_grid_spaces * self.grid_size
            board_height_with_margins = y_grid_spaces * self.grid_size
            extra_px_margin_x = (self.width - board_width_with_margins) / 2
            extra_px_margin_y = (self.height - board_height_with_margins) / 2
            self.stone_size = self.grid_size * STONE_SIZE

            self.gridpos_x = [
                self.pos[0] + extra_px_margin_x +
                math.floor((grid_spaces_margin_x[0] + i) * self.grid_size +
                           0.5) for i in range(board_size_x)
            ]
            self.gridpos_y = [
                self.pos[1] + extra_px_margin_y +
                math.floor((grid_spaces_margin_y[0] + i) * self.grid_size +
                           0.5) for i in range(board_size_y)
            ]

            Color(*BOARD_COLOR)
            Rectangle(
                pos=(self.gridpos_x[0] - self.grid_size * 1.5,
                     self.gridpos_y[0] - self.grid_size * 1.5),
                size=(self.grid_size * x_grid_spaces,
                      self.grid_size * y_grid_spaces),
            )

            Color(*LINE_COLOR)
            for i in range(board_size_x):
                Line(points=[(
                    self.gridpos_x[i],
                    self.gridpos_y[0]), (self.gridpos_x[i],
                                         self.gridpos_y[-1])])
            for i in range(board_size_y):
                Line(points=[(
                    self.gridpos_x[0],
                    self.gridpos_y[i]), (self.gridpos_x[-1],
                                         self.gridpos_y[i])])

            # star points
            def star_point_coords(size):
                star_point_pos = 3 if size <= 11 else 4
                if size < 7:
                    return []
                return [star_point_pos - 1, size - star_point_pos] + (
                    [int(size / 2)] if size % 2 == 1 and size > 7 else [])

            starpt_size = self.grid_size * STARPOINT_SIZE
            for x in star_point_coords(board_size_x):
                for y in star_point_coords(board_size_y):
                    draw_circle((self.gridpos_x[x], self.gridpos_y[y]),
                                starpt_size, LINE_COLOR)

            # coordinates
            Color(0.25, 0.25, 0.25)
            coord_offset = self.grid_size * 1.5 / 2
            for i in range(board_size_x):
                draw_text(
                    pos=(self.gridpos_x[i], self.gridpos_y[0] - coord_offset),
                    text=Move.GTP_COORD[i],
                    font_size=self.grid_size / 1.5,
                    font_name="Roboto",
                )
            for i in range(board_size_y):
                draw_text(
                    pos=(self.gridpos_x[0] - coord_offset, self.gridpos_y[i]),
                    text=str(i + 1),
                    font_size=self.grid_size / 1.5,
                    font_name="Roboto",
                )
Exemplo n.º 7
0
    def draw_hover_contents(self, *_args):
        ghost_alpha = Theme.GHOST_ALPHA
        katrain = self.katrain
        game_ended = katrain.game.end_result
        current_node = katrain.game.current_node
        next_player = current_node.next_player

        board_size_x, board_size_y = katrain.game.board_size
        if len(self.gridpos_x) < board_size_x or len(self.gridpos_y) < board_size_y:
            return  # race condition

        with self.canvas.after:
            self.canvas.after.clear()

            self.active_pv_moves = []
            # hints or PV
            hint_moves = []
            if (
                katrain.analysis_controls.hints.active
                and not katrain.analysis_controls.policy.active
                and not game_ended
            ):
                hint_moves = current_node.candidate_moves
            elif katrain.controls.status_state[1] == STATUS_TEACHING:  # show score hint for teaching  undo
                hint_moves = [
                    m
                    for m in current_node.candidate_moves
                    for c in current_node.children
                    if c.move and c.auto_undo and c.move.gtp() == m["move"]
                ]

            top_move_coords = None
            child_moves = {c.move.gtp() for c in current_node.children if c.move}
            if hint_moves:
                low_visits_threshold = katrain.config("trainer/low_visits", 25)
                top_moves_show = [
                    opt
                    for opt in [
                        katrain.config("trainer/top_moves_show"),
                        katrain.config("trainer/top_moves_show_secondary"),
                    ]
                    if opt in TOP_MOVE_OPTIONS and opt != TOP_MOVE_NOTHING
                ]
                for move_dict in hint_moves:
                    move = Move.from_gtp(move_dict["move"])
                    if move.coords is not None:
                        engine_best_move = move_dict.get("order", 99) == 0
                        scale = Theme.HINT_SCALE
                        text_on = True
                        alpha = Theme.HINTS_ALPHA
                        if (
                            move_dict["visits"] < low_visits_threshold
                            and not engine_best_move
                            and not move_dict["move"] in child_moves
                        ):
                            scale = Theme.UNCERTAIN_HINT_SCALE
                            text_on = False
                            alpha = Theme.HINTS_LO_ALPHA

                        if "pv" in move_dict:
                            self.active_pv_moves.append((move.coords, move_dict["pv"], current_node))
                        else:
                            katrain.log(f"PV missing for move_dict {move_dict}", OUTPUT_DEBUG)
                        evalsize = self.stone_size * scale
                        evalcol = self.eval_color(move_dict["pointsLost"])
                        if text_on and top_moves_show:  # remove grid lines using a board colored circle
                            draw_circle(
                                (self.gridpos_x[move.coords[0]], self.gridpos_y[move.coords[1]]),
                                self.stone_size * scale * 0.98,
                                Theme.APPROX_BOARD_COLOR,
                            )

                        Color(*evalcol[:3], alpha)
                        Rectangle(
                            pos=(self.gridpos_x[move.coords[0]] - evalsize, self.gridpos_y[move.coords[1]] - evalsize),
                            size=(2 * evalsize, 2 * evalsize),
                            texture=cached_texture(Theme.TOP_MOVE_TEXTURE),
                        )
                        if text_on and top_moves_show:  # TODO: faster if not sized?
                            keys = {"size": self.grid_size / 3, "smallsize": self.grid_size / 3.33}
                            player_sign = current_node.player_sign(next_player)
                            if len(top_moves_show) == 1:
                                fmt = "[size={size:.0f}]{" + top_moves_show[0] + "}[/size]"
                            else:
                                fmt = (
                                    "[size={size:.0f}]{"
                                    + top_moves_show[0]
                                    + "}[/size]\n[size={smallsize:.0f}]{"
                                    + top_moves_show[1]
                                    + "}[/size]"
                                )

                            keys[TOP_MOVE_DELTA_SCORE] = (
                                "0.0" if -0.05 < move_dict["pointsLost"] < 0.05 else f"{-move_dict['pointsLost']:+.1f}"
                            )
                            #                           def fmt_maybe_missing(arg,sign,digits=1):
                            #                               return str(round(sign*arg,digits)) if arg is not None else "N/A"

                            keys[TOP_MOVE_SCORE] = f"{player_sign * move_dict['scoreLead']:.1f}"
                            winrate = move_dict["winrate"] if player_sign == 1 else 1 - move_dict["winrate"]
                            keys[TOP_MOVE_WINRATE] = f"{winrate*100:.1f}"
                            keys[TOP_MOVE_DELTA_WINRATE] = f"{-move_dict['winrateLost']:+.1%}"
                            keys[TOP_MOVE_VISITS] = format_visits(move_dict["visits"])
                            #                            keys[TOP_MOVE_UTILITY] = fmt_maybe_missing( move_dict.get('utility'),player_sign,2)
                            #                            keys[TOP_MOVE_UTILITYLCB] = fmt_maybe_missing(move_dict.get('utilityLcb'),player_sign,2)
                            #                            keys[TOP_MOVE_SCORE_STDDEV] =fmt_maybe_missing(move_dict.get('scoreStdev'),1)
                            Color(*Theme.HINT_TEXT_COLOR)
                            draw_text(
                                pos=(self.gridpos_x[move.coords[0]], self.gridpos_y[move.coords[1]]),
                                text=fmt.format(**keys),
                                font_name="Roboto",
                                markup=True,
                                line_height=0.85,
                                halign="center",
                            )

                        if engine_best_move:
                            top_move_coords = move.coords
                            Color(*Theme.TOP_MOVE_BORDER_COLOR)
                            Line(
                                circle=(
                                    self.gridpos_x[move.coords[0]],
                                    self.gridpos_y[move.coords[1]],
                                    self.stone_size - dp(1.2),
                                ),
                                width=dp(1.2),
                            )

            # children of current moves in undo / review
            if katrain.analysis_controls.show_children.active:
                for child_node in current_node.children:
                    move = child_node.move
                    if move and move.coords is not None:
                        if child_node.analysis_exists:
                            self.active_pv_moves.append(
                                (move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node)
                            )

                        if move.coords != top_move_coords:  # for contrast
                            dashed_width = 18
                            Color(*Theme.NEXT_MOVE_DASH_CONTRAST_COLORS[child_node.player])
                            Line(
                                circle=(
                                    self.gridpos_x[move.coords[0]],
                                    self.gridpos_y[move.coords[1]],
                                    self.stone_size - dp(1.2),
                                ),
                                width=dp(1.2),
                            )
                        else:
                            dashed_width = 10
                        Color(*Theme.STONE_COLORS[child_node.player])
                        for s in range(0, 360, 30):
                            Line(
                                circle=(
                                    self.gridpos_x[move.coords[0]],
                                    self.gridpos_y[move.coords[1]],
                                    self.stone_size - dp(1.2),
                                    s,
                                    s + dashed_width,
                                ),
                                width=dp(1.2),
                            )

            if self.selecting_region_of_interest and len(self.region_of_interest) == 4:
                x1, x2, y1, y2 = self.region_of_interest
                self.draw_roi_box([min(x1, x2), max(x1, x2), min(y1, y2), max(y1, y2)], width=dp(2))
            else:
                # hover next move ghost stone
                if self.ghost_stone:
                    self.draw_stone(*self.ghost_stone, next_player, alpha=ghost_alpha)

                animating_pv = self.animating_pv
                if animating_pv:
                    pv, node, start_time, _ = animating_pv
                    up_to_move = self.get_animate_pv_index()
                    self.draw_pv(pv, node, up_to_move)

                if getattr(self.katrain.game, "region_of_interest", None):
                    self.draw_roi_box(self.katrain.game.region_of_interest, width=dp(1.25))

            # pass circle
            if current_node.is_pass or game_ended:
                if game_ended:
                    text = game_ended
                    katrain.controls.timer.paused = True
                else:
                    text = i18n._("board-pass")
                Color(*Theme.PASS_CIRCLE_COLOR)
                center = (self.gridpos_x[int(board_size_x / 2)], self.gridpos_y[int(board_size_y / 2)])
                size = min(self.width, self.height) * 0.227
                Ellipse(pos=(center[0] - size / 2, center[1] - size / 2), size=(size, size))
                Color(*Theme.PASS_CIRCLE_TEXT_COLOR)
                draw_text(pos=center, text=text, font_size=size * 0.25, halign="center")
Exemplo n.º 8
0
    def draw_board_contents(self, *_args):
        if not (self.katrain and self.katrain.game):
            return
        katrain = self.katrain
        board_size_x, board_size_y = katrain.game.board_size
        if len(self.gridpos_x) < board_size_x or len(self.gridpos_y) < board_size_y:
            return  # race condition
        show_n_eval = self.trainer_config.get("eval_on_show_last", 3)

        with self.canvas:
            self.canvas.clear()
            # stones
            current_node = katrain.game.current_node
            all_dots_off = not katrain.analysis_controls.eval.active
            has_stone = {}
            drawn_stone = {}
            for m in katrain.game.stones:
                has_stone[m.coords] = m.player

            show_dots_for = {
                p: self.trainer_config["eval_show_ai"] or katrain.players_info[p].human for p in Move.PLAYERS
            }
            show_dots_for_class = self.trainer_config["show_dots"]
            nodes = katrain.game.current_node.nodes_from_root
            realized_points_lost = None

            for i, node in enumerate(nodes[::-1]):  # reverse order!
                points_lost = node.points_lost
                evalscale = 1
                if points_lost and realized_points_lost:
                    if points_lost <= 0.5 and realized_points_lost <= 1.5:
                        evalscale = 0
                    else:
                        evalscale = min(1, max(0, realized_points_lost / points_lost))
                placements = node.placements
                for m in node.moves + placements:
                    if has_stone.get(m.coords) and not drawn_stone.get(m.coords):  # skip captures, last only for
                        move_eval_on = not all_dots_off and show_dots_for.get(m.player) and i < show_n_eval
                        if move_eval_on and points_lost is not None:
                            evalcol = self.eval_color(points_lost, show_dots_for_class)
                        else:
                            evalcol = None
                        inner = Theme.STONE_COLORS[m.opponent] if i == 0 and m not in placements else None
                        drawn_stone[m.coords] = m.player
                        self.draw_stone(
                            x=m.coords[0],
                            y=m.coords[1],
                            player=m.player,
                            innercol=inner,
                            evalcol=evalcol,
                            evalscale=evalscale,
                        )
                realized_points_lost = node.parent_realized_points_lost

            if katrain.game.current_node.is_root and katrain.debug_level >= 3:  # secret ;)
                for y in range(0, board_size_y):
                    evalcol = self.eval_color(16 * y / board_size_y)
                    self.draw_stone(0, y, "B", evalcol=evalcol, evalscale=y / (board_size_y - 1))
                    self.draw_stone(1, y, "B", innercol=Theme.STONE_COLORS["W"], evalcol=evalcol)
                    self.draw_stone(2, y, "W", evalcol=evalcol, evalscale=y / (board_size_y - 1))
                    self.draw_stone(3, y, "W", innercol=Theme.STONE_COLORS["B"], evalcol=evalcol)

            # ownership - allow one move out of date for smooth animation
            ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
            if katrain.analysis_controls.ownership.active and ownership:
                rsz = self.grid_size * 0.2
                if (
                    current_node.children
                    and katrain.controls.status_state[1] == STATUS_TEACHING
                    and current_node.children[-1].auto_undo
                    and current_node.children[-1].ownership
                ):  # loss
                    loss_grid = var_to_grid(
                        [a - b for a, b in zip(current_node.children[-1].ownership, ownership)],
                        (board_size_x, board_size_y),
                    )

                    for y in range(board_size_y - 1, -1, -1):
                        for x in range(board_size_x):
                            loss = max(0, (-1 if current_node.children[-1].move.player == "B" else 1) * loss_grid[y][x])
                            if loss > 0:
                                Color(*Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss)
                                Rectangle(
                                    pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz)
                                )
                else:
                    ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
                    for y in range(board_size_y - 1, -1, -1):
                        for x in range(board_size_x):
                            ix_owner = "B" if ownership_grid[y][x] > 0 else "W"
                            if ix_owner != (has_stone.get((x, y), -1)):
                                Color(*Theme.STONE_COLORS[ix_owner][:3], abs(ownership_grid[y][x]))
                                Rectangle(
                                    pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz)
                                )

            policy = current_node.policy
            if (
                not policy
                and current_node.parent
                and current_node.parent.policy
                and katrain.last_player_info.ai
                and katrain.next_player_info.ai
            ):
                policy = (
                    current_node.parent.policy
                )  # in the case of AI self-play we allow the policy to be one step out of date

            pass_btn = katrain.board_controls.pass_btn
            pass_btn.canvas.after.clear()
            if katrain.analysis_controls.policy.active and policy:
                policy_grid = var_to_grid(policy, (board_size_x, board_size_y))
                best_move_policy = max(*policy)
                colors = Theme.EVAL_COLORS[self.trainer_config["theme"]]
                text_lb = 0.01 * 0.01
                for y in range(board_size_y - 1, -1, -1):
                    for x in range(board_size_x):
                        move_policy = policy_grid[y][x]
                        if move_policy < 0:
                            continue
                        pol_order = max(0, 5 + int(math.log10(max(1e-9, move_policy - 1e-9))))
                        if move_policy > text_lb:
                            draw_circle(
                                (self.gridpos_x[x], self.gridpos_y[y]),
                                self.stone_size * Theme.HINT_SCALE * 0.98,
                                Theme.APPROX_BOARD_COLOR,
                            )
                            scale = 0.95
                        else:
                            scale = 0.5
                        draw_circle(
                            (self.gridpos_x[x], self.gridpos_y[y]),
                            Theme.HINT_SCALE * self.stone_size * scale,
                            (*colors[pol_order][:3], Theme.POLICY_ALPHA),
                        )
                        if move_policy > text_lb:
                            Color(*Theme.HINT_TEXT_COLOR)
                            draw_text(
                                pos=(self.gridpos_x[x], self.gridpos_y[y]),
                                text=f"{100 * move_policy :.2f}"[:4] + "%",
                                font_name="Roboto",
                                font_size=self.grid_size / 4,
                                halign="center",
                            )
                        if move_policy == best_move_policy:
                            Color(*Theme.TOP_MOVE_BORDER_COLOR[:3], Theme.POLICY_ALPHA)
                            Line(
                                circle=(
                                    self.gridpos_x[x],
                                    self.gridpos_y[y],
                                    self.stone_size - dp(1.2),
                                ),
                                width=dp(2),
                            )

                with pass_btn.canvas.after:
                    move_policy = policy[-1]
                    pol_order = 5 - int(-math.log10(max(1e-9, move_policy - 1e-9)))
                    if pol_order >= 0:
                        draw_circle(
                            (pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2),
                            pass_btn.height / 2,
                            (*colors[pol_order][:3], Theme.GHOST_ALPHA),
                        )

        self.redraw_hover_contents_trigger()
Exemplo n.º 9
0
    def draw_board(self, *_args):
        if not (self.katrain and self.katrain.game):
            return
        katrain = self.katrain
        board_size_x, board_size_y = katrain.game.board_size

        with self.canvas.before:
            self.canvas.before.clear()
            # set up margins and grid lines
            if self.draw_coords_enabled:
                grid_spaces_margin_x = [1.5, 0.75]  # left, right
                grid_spaces_margin_y = [1.5, 0.75]  # bottom, top
            else:  # no coordinates means remove the offset
                grid_spaces_margin_x = [0.75, 0.75]  # left, right
                grid_spaces_margin_y = [0.75, 0.75]  # bottom, top
            x_grid_spaces = board_size_x - 1 + sum(grid_spaces_margin_x)
            y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y)
            self.grid_size = min(self.width / x_grid_spaces, self.height / y_grid_spaces)
            board_width_with_margins = x_grid_spaces * self.grid_size
            board_height_with_margins = y_grid_spaces * self.grid_size
            extra_px_margin_x = (self.width - board_width_with_margins) / 2
            extra_px_margin_y = (self.height - board_height_with_margins) / 2
            self.stone_size = self.grid_size * Theme.STONE_SIZE

            self.gridpos_x = [
                self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * self.grid_size + 0.5)
                for i in range(board_size_x)
            ]
            self.gridpos_y = [
                self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * self.grid_size + 0.5)
                for i in range(board_size_y)
            ]

            if katrain.game.insert_mode:
                Color(*Theme.INSERT_BOARD_COLOR_TINT)  # dreamy
            else:
                Color(*Theme.BOARD_COLOR_TINT)  # image is a bit too light
            Rectangle(
                pos=(
                    self.gridpos_x[0] - self.grid_size * grid_spaces_margin_x[0],
                    self.gridpos_y[0] - self.grid_size * grid_spaces_margin_y[0],
                ),
                size=(self.grid_size * x_grid_spaces, self.grid_size * y_grid_spaces),
                texture=cached_texture(Theme.BOARD_TEXTURE),
            )

            Color(*Theme.LINE_COLOR)
            for i in range(board_size_x):
                Line(points=[(self.gridpos_x[i], self.gridpos_y[0]), (self.gridpos_x[i], self.gridpos_y[-1])])
            for i in range(board_size_y):
                Line(points=[(self.gridpos_x[0], self.gridpos_y[i]), (self.gridpos_x[-1], self.gridpos_y[i])])

            # star points
            def star_point_coords(size):
                star_point_pos = 3 if size <= 11 else 4
                if size < 7:
                    return []
                return [star_point_pos - 1, size - star_point_pos] + (
                    [int(size / 2)] if size % 2 == 1 and size > 7 else []
                )

            starpt_size = self.grid_size * Theme.STARPOINT_SIZE
            for x in star_point_coords(board_size_x):
                for y in star_point_coords(board_size_y):
                    draw_circle((self.gridpos_x[x], self.gridpos_y[y]), starpt_size, Theme.LINE_COLOR)

            # coordinates
            if self.draw_coords_enabled:
                Color(0.25, 0.25, 0.25)
                coord_offset = self.grid_size * 1.5 / 2
                for i in range(board_size_x):
                    draw_text(
                        pos=(self.gridpos_x[i], self.gridpos_y[0] - coord_offset),
                        text=Move.GTP_COORD[i],
                        font_size=self.grid_size / 1.5,
                        font_name="Roboto",
                    )
                for i in range(board_size_y):
                    draw_text(
                        pos=(self.gridpos_x[0] - coord_offset, self.gridpos_y[i]),
                        text=str(i + 1),
                        font_size=self.grid_size / 1.5,
                        font_name="Roboto",
                    )
Exemplo n.º 10
0
    def draw_hover_contents(self, *_args):
        ghost_alpha = GHOST_ALPHA
        katrain = self.katrain
        game_ended = katrain.game.ended
        current_node = katrain.game.current_node
        player, next_player = current_node.player, current_node.next_player

        board_size_x, board_size_y = katrain.game.board_size
        if len(self.gridpos_x) < board_size_x or len(
                self.gridpos_y) < board_size_y:
            return  # race condition

        with self.canvas.after:
            self.canvas.after.clear()
            self.active_pv_moves = []

            # children of current moves in undo / review
            alpha = GHOST_ALPHA
            if katrain.analysis_controls.show_children.active:
                for child_node in current_node.children:
                    points_lost = child_node.points_lost
                    move = child_node.move
                    if move and move.coords is not None:
                        if points_lost is None:
                            evalcol = None
                        else:
                            evalcol = copy.copy(self.eval_color(points_lost))
                            evalcol[3] = alpha
                        if child_node.analysis_ready:
                            self.active_pv_moves.append(
                                (move.coords, [move.gtp()] +
                                 child_node.candidate_moves[0]["pv"],
                                 current_node))
                        scale = CHILD_SCALE
                        self.draw_stone(
                            move.coords[0],
                            move.coords[1],
                            move.player,
                            alpha=alpha,
                            evalcol=evalcol,
                            evalscale=scale,
                            scale=scale,
                        )

            # hints or PV
            if katrain.analysis_controls.hints.active and not game_ended:
                hint_moves = current_node.candidate_moves
                for i, move_dict in enumerate(hint_moves):
                    move = Move.from_gtp(move_dict["move"])
                    if move.coords is not None:
                        alpha, scale = GHOST_ALPHA, 1.0
                        if move_dict["visits"] < VISITS_FRAC_SMALL * hint_moves[
                                0]["visits"]:
                            scale = 0.8
                        if "pv" in move_dict:
                            self.active_pv_moves.append(
                                (move.coords, move_dict["pv"], current_node))
                        else:
                            katrain.log(
                                f"PV missing for move_dict {move_dict}",
                                OUTPUT_DEBUG)
                        draw_circle(
                            (self.gridpos_x[move.coords[0]],
                             self.gridpos_y[move.coords[1]]),
                            col=[
                                *self.eval_color(move_dict["pointsLost"])[:3],
                                alpha
                            ],
                            r=self.stone_size * scale,
                        )
                        if i == 0:
                            Color(*TOP_MOVE_BORDER_COLOR)
                            Line(
                                circle=(
                                    self.gridpos_x[move.coords[0]],
                                    self.gridpos_y[move.coords[1]],
                                    self.stone_size * scale - 1.2,
                                ),
                                width=dp(1.2),
                            )

            # hover next move ghost stone
            if self.ghost_stone:
                self.draw_stone(*self.ghost_stone,
                                next_player,
                                alpha=ghost_alpha)

            animating_pv = self.animating_pv
            if animating_pv:
                pv, node, start_time, _ = animating_pv
                delay = self.trainer_config.get("anim_pv_time", 0.5)
                up_to_move = (time.time() - start_time) / delay
                self.draw_pv(pv, node, up_to_move)
Exemplo n.º 11
0
 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)
Exemplo n.º 12
0
    def draw_board_contents(self, *_args):
        if not (self.katrain and self.katrain.game):
            return
        katrain = self.katrain
        board_size_x, board_size_y = katrain.game.board_size
        if len(self.gridpos_x) < board_size_x or len(self.gridpos_y) < board_size_y:
            return  # race condition
        show_n_eval = self.trainer_config["eval_off_show_last"]

        with self.canvas:
            self.canvas.clear()
            # stones
            current_node = katrain.game.current_node
            game_ended = katrain.game.end_result
            full_eval_on = katrain.analysis_controls.eval.active
            has_stone = {}
            drawn_stone = {}
            for m in katrain.game.stones:
                has_stone[m.coords] = m.player

            show_dots_for = {
                p: self.trainer_config["eval_show_ai"] or katrain.players_info[p].human for p in Move.PLAYERS
            }
            show_dots_for_class = self.trainer_config["show_dots"]
            nodes = katrain.game.current_node.nodes_from_root
            realized_points_lost = None

            for i, node in enumerate(nodes[::-1]):  # reverse order!
                points_lost = node.points_lost
                evalscale = 1
                if points_lost and realized_points_lost:
                    if points_lost <= 0.5 and realized_points_lost <= 1.5:
                        evalscale = 0
                    else:
                        evalscale = min(1, max(0, realized_points_lost / points_lost))
                placements = node.placements
                for m in node.moves + placements:
                    if has_stone.get(m.coords) and not drawn_stone.get(m.coords):  # skip captures, last only for
                        move_eval_on = show_dots_for.get(m.player) and (i < show_n_eval or full_eval_on)
                        if move_eval_on and points_lost is not None:
                            evalcol = self.eval_color(points_lost, show_dots_for_class)
                        else:
                            evalcol = None
                        inner = STONE_COLORS[m.opponent] if i == 0 and not m in placements else None
                        drawn_stone[m.coords] = m.player
                        self.draw_stone(
                            x=m.coords[0],
                            y=m.coords[1],
                            player=m.player,
                            innercol=inner,
                            evalcol=evalcol,
                            evalscale=evalscale,
                        )
                realized_points_lost = node.parent_realized_points_lost

            if katrain.game.current_node.is_root and katrain.debug_level >= 3:  # secret ;)
                for y in range(0, board_size_y):
                    evalcol = self.eval_color(16 * y / board_size_y)
                    self.draw_stone(0, y, "B", evalcol=evalcol, evalscale=y / (board_size_y - 1))
                    self.draw_stone(1, y, "B", innercol=STONE_COLORS["W"], evalcol=evalcol)
                    self.draw_stone(2, y, "W", evalcol=evalcol, evalscale=y / (board_size_y - 1))
                    self.draw_stone(3, y, "W", innercol=STONE_COLORS["B"], evalcol=evalcol)

            # ownership - allow one move out of date for smooth animation
            ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
            if katrain.analysis_controls.ownership.active and ownership:
                rsz = self.grid_size * 0.2
                if (
                    current_node.children
                    and katrain.controls.status_state[1] == STATUS_TEACHING
                    and current_node.children[-1].auto_undo
                    and current_node.children[-1].ownership
                ):  # loss
                    loss_grid = var_to_grid(
                        [a - b for a, b in zip(current_node.children[-1].ownership, ownership)],
                        (board_size_x, board_size_y),
                    )

                    for y in range(board_size_y - 1, -1, -1):
                        for x in range(board_size_x):
                            loss = max(0, (-1 if current_node.children[-1].move.player == "B" else 1) * loss_grid[y][x])
                            if loss > 0:
                                Color(*EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss)
                                Rectangle(
                                    pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz)
                                )
                else:
                    ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
                    for y in range(board_size_y - 1, -1, -1):
                        for x in range(board_size_x):
                            ix_owner = "B" if ownership_grid[y][x] > 0 else "W"
                            if ix_owner != (has_stone.get((x, y), -1)):
                                Color(*STONE_COLORS[ix_owner][:3], abs(ownership_grid[y][x]))
                                Rectangle(
                                    pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz)
                                )

            policy = current_node.policy
            if (
                not policy
                and current_node.parent
                and current_node.parent.policy
                and katrain.last_player_info.ai
                and katrain.next_player_info.ai
            ):
                policy = (
                    current_node.parent.policy
                )  # in the case of AI self-play we allow the policy to be one step out of date

            pass_btn = katrain.board_controls.pass_btn
            pass_btn.canvas.after.clear()
            if katrain.analysis_controls.policy.active and policy:
                policy_grid = var_to_grid(policy, (board_size_x, board_size_y))
                best_move_policy = max(*policy)
                for y in range(board_size_y - 1, -1, -1):
                    for x in range(board_size_x):
                        if policy_grid[y][x] > 0:
                            polsize = 1.1 * math.sqrt(policy_grid[y][x])
                            policy_circle_color = (
                                *POLICY_COLOR,
                                POLICY_ALPHA + TOP_POLICY_ALPHA * (policy_grid[y][x] == best_move_policy),
                            )
                            draw_circle(
                                (self.gridpos_x[x], self.gridpos_y[y]), polsize * self.stone_size, policy_circle_color
                            )
                polsize = math.sqrt(policy[-1])
                with pass_btn.canvas.after:
                    draw_circle(
                        (pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2),
                        polsize * pass_btn.height / 2,
                        POLICY_COLOR,
                    )

            # pass circle
            passed = len(nodes) > 1 and current_node.is_pass
            if passed or game_ended:
                if game_ended:
                    text = game_ended
                    katrain.controls.timer.paused = True
                else:
                    text = i18n._("board-pass")
                Color(0.45, 0.05, 0.45, 0.7)
                center = (self.gridpos_x[int(board_size_x / 2)], self.gridpos_y[int(board_size_y / 2)])
                size = min(self.width, self.height) * 0.227
                Ellipse(pos=(center[0] - size / 2, center[1] - size / 2), size=(size, size))
                Color(0.85, 0.85, 0.85)
                draw_text(
                    pos=center, text=text, font_size=size * 0.25, halign="center", outline_color=[0.95, 0.95, 0.95]
                )

        self.draw_hover_contents()