Esempio n. 1
0
 def draw_stone(self, x, y, player, alpha=1, innercol=None, evalcol=None, evalscale=1.0, scale=1.0):
     stone_size = self.stone_size * scale
     Color(1, 1, 1, alpha)
     Rectangle(
         pos=(self.gridpos_x[x] - stone_size, self.gridpos_y[y] - stone_size),
         size=(2 * stone_size, 2 * stone_size),
         texture=cached_texture(Theme.STONE_TEXTURE[player]),
     )
     if evalcol:
         eval_radius = math.sqrt(evalscale)  # scale area by evalscale
         evalsize = self.stone_size * (
             Theme.EVAL_DOT_MIN_SIZE + eval_radius * (Theme.EVAL_DOT_MAX_SIZE - Theme.EVAL_DOT_MIN_SIZE)
         )
         Color(*evalcol)
         Rectangle(
             pos=(self.gridpos_x[x] - evalsize, self.gridpos_y[y] - evalsize),
             size=(2 * evalsize, 2 * evalsize),
             texture=cached_texture(Theme.EVAL_DOT_TEXTURE),
         )
     if innercol:
         Color(*innercol)
         inner_size = stone_size * 0.8
         Rectangle(
             pos=(self.gridpos_x[x] - inner_size, self.gridpos_y[y] - inner_size),
             size=(2 * inner_size, 2 * inner_size),
             texture=cached_texture(Theme.LAST_MOVE_TEXTURE),
         )
Esempio n. 2
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")
Esempio n. 3
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")
Esempio n. 4
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",
                    )