Esempio n. 1
0
class StefGammon(arcade.View):
    """

    ...
    Atributes
    ---------
    animation_iteration: int
        Keeps track of checker movement animation frames
    is_cpu_game: bool
        Informs if the game is played against cpu
    background_image: str
        Image resource for play board
    background_left:
        Image resource for left player stats background
    background_right:
        Image resource for right player stats background
    red_begins_button:
        Image resource for button displayed when red begins
    white_begins_button:
        Image resource for button displayed when white begins
    roll_again_button:
        Image resource for button displayed when rolls coincide
    tick_button:
        Image resource for button that ends player turn
    bear_off_table:
        Image resource for the place where beard off checkers go
    main_menu:
        Image resource for button that loads menu view
    play_again:
        Image resource for button that resets the game
    choose_turns_button: str
        Image resource for button that initiates turn choosing
    checker_list:
        List with all checkers in play
    point_list:
        List of all points on table
    dead_checker_list:
        List of checkers that have been removed from play and wait to be reentered
    selected_checker:
        Checker currently being interacted with
    selected_checker_origin:
        Position of selected_checker
    turn:
        Player id of who is moving
    dice: Dice
        Object that simulates a dice
    game_state: str
        A string that defines the state of the game
    white_roll: int
        Beginning roll for white player
    red_roll: int
        Beginning roll for red player
    rolls: List
        List of all rolls for current turn
    used_rolls: List
        List of all rolls that were previously used in the current turn
    beared_off_white: str
        Image resource for checker in beared off pile
    beared_off_red: str
        Image resource for checker in beared off pile
    nr_of_beared_off: List
        Number of beared off checkers for each player
    selected_checker_destination: List
        Position of selected checker destination
    checker_state: str
        String that informs what operation was performed on a checker: remove, add, missplaced
    dead_checker_origin: List
        Position of top checker of removed checker pile
    dead_rolls: List
        List of rolls that no checker can move
    current_roll:
        Roll that was used for current move
    winner: bool
        Id of winner
    player_can_move: bool
        Informs if a player has any moves left
    score: List
        Score of each player
    bottom_message: str
        Message to be displayed in the hint bar
    bottom_message_time:
        Duration of message in frames
    red_avatar: Sprite
        Image representing a player
    white_avatar: Sprite
        Image representing a player


    Methods
    -------
    setup():
        Called to set/reset the game
    on_draw():
        Renders the screen
    on_update():
        Called every frame
    on_mouse_press()
        Called when the user presses a mouse button
    on_mouse_release()
        Called when the user releases a mouse button
    place_selected_checker()
        Adds a checker to it's destination checker pile depending on it's state:
        moved, missplaced, bear off
    set_selected_checker()
        Removes a checker from it's pile and sets the selected checker attribute
    get_bear_off_destination()
        Returns position of next checker to be added to bear off pile
    valid_move_exists()
        Determines if a player still has available moves
    is_valid_move()
        Determines if a move is valid
    on_mouse_motion()
        Called when user moves mouse
    set_points()
        Creates all point objects and sets them to their positions
    set_checkers()
        Creates all checkers and places them in their initial positions
    bring_sprite_to front()
        Brings a sprite to the top of the list in order to be rendered on top
    ready_for_bearoff()
        Determines if a player is allowed to bear off checkers
    """
    def __init__(self, is_cpu_game, red_avatar, white_avatar):
        super().__init__()
        self.animation_iteration = None
        self.is_cpu_game = is_cpu_game
        self.background_image = None
        self.background_left = None
        self.background_right = None
        self.main_menu = None
        self.play_again = None
        self.checker_list = None
        self.point_list = None
        self.dead_checker_list = None
        self.selected_checker = None
        self.selected_checker_origin = None
        self.turn = None
        self.dice = Dice()
        self.game_state = None
        self.white_roll = None
        self.red_roll = None
        self.rolls = None
        self.used_rolls = None
        self.choose_turns_button = None
        self.red_begins_button = None
        self.white_begins_button = None
        self.roll_again_button = None
        self.tick_button = None
        self.beared_off_white = None
        self.beared_off_red = None
        self.nr_of_beared_off = None
        self.bear_off_table = None
        self.selected_checker_destination = None
        self.checker_state = None
        self.dead_checker_origin = None
        self.dead_rolls = None
        self.current_roll = None
        self.winner = None
        self.player_can_move = None
        self.score = [0, 0]
        self.bottom_message = None
        self.bottom_message_time = None
        self.red_avatar = red_avatar
        self.white_avatar = white_avatar

    def setup(self):
        """ Set up the game here. Call this function to restart the game. """
        self.background_image = arcade.load_texture("resources/sg_board.png")
        self.background_left = arcade.load_texture("resources/sg_side_l.png")
        self.background_right = arcade.load_texture("resources/sg_side_r.png")
        self.dice.load_textures()

        self.choose_turns_button = arcade.load_texture(
            "resources/choose_turns_button.png")
        self.white_begins_button = arcade.load_texture(
            "resources/white_begins_button.png")
        self.red_begins_button = arcade.load_texture(
            "resources/red_begins_button.png")
        self.roll_again_button = arcade.load_texture(
            "resources/roll_again_button.png")
        self.tick_button = arcade.load_texture("resources/tick_button.png")
        self.beared_off_white = arcade.load_texture(
            "resources/beared_off_white.png")
        self.beared_off_red = arcade.load_texture(
            "resources/beared_off_red.png")
        self.bear_off_table = arcade.load_texture(
            "resources/beare_off_table.png")
        self.main_menu = arcade.load_texture("resources/main_menu_button.png")
        self.play_again = arcade.load_texture(
            "resources/play_again_button.png")

        self.checker_list = arcade.SpriteList()
        self.point_list = arcade.SpriteList()
        self.dead_checker_list = [arcade.SpriteList(), arcade.SpriteList()]
        self.dead_checker_list = [[], []]
        self.rolls = []
        self.used_rolls = []
        self.nr_of_beared_off = [0, 0]
        self.set_points()
        self.set_checkers()
        self.animation_iteration = 0
        self.selected_checker_destination = None
        self.selected_checker_origin = None
        self.checker_state = None
        self.dead_checker_origin = None
        self.dead_rolls = []
        self.winner = None
        self.selected_checker = None
        self.game_state = "before_choose_turns"
        self.player_can_move = True
        self.bottom_message = ""
        self.bottom_message_time = 0

        self.red_avatar.center_y = SCREEN_HEIGHT - 100
        self.white_avatar.center_y = SCREEN_HEIGHT - 100

    def on_draw(self):
        """ Render the screen. """
        arcade.start_render()
        arcade.draw_lrwh_rectangle_textured(200, 0, BOARD_WIDTH, SCREEN_HEIGHT,
                                            self.background_image)
        arcade.draw_lrwh_rectangle_textured(0, 0, 200, SCREEN_HEIGHT,
                                            self.background_left)

        arcade.draw_lrwh_rectangle_textured(1252, 0, 200, SCREEN_HEIGHT,
                                            self.background_right)
        arcade.draw_texture_rectangle(
            SCREEN_WIDTH - PLAYER_STAT_WIDTH / 4,
            BEAR_OFF_BEGIN_Y - BEAR_OFF_CHECKER_HEIGHT / 2 + 287 / 2, 90, 307,
            self.bear_off_table)
        arcade.draw_texture_rectangle(
            PLAYER_STAT_WIDTH / 4,
            BEAR_OFF_BEGIN_Y - BEAR_OFF_CHECKER_HEIGHT / 2 + 287 / 2, 90, 307,
            self.bear_off_table)
        arcade.draw_text(str(self.score[0]),
                         SCREEN_WIDTH - PLAYER_STAT_WIDTH / 2,
                         480,
                         arcade.color.WHITE_SMOKE,
                         anchor_x="center",
                         anchor_y="center",
                         font_size=40)
        arcade.draw_text(str(self.score[1]),
                         PLAYER_STAT_WIDTH / 2,
                         480,
                         arcade.color.WHITE,
                         anchor_x="center",
                         anchor_y="center",
                         font_size=40)
        self.red_avatar.draw()
        self.white_avatar.draw()
        self.checker_list.draw()

        if self.game_state == "before_choose_turns":
            arcade.draw_texture_rectangle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2,
                                          200, 50, self.choose_turns_button)
        elif self.game_state == "choosing_turns":
            arcade.draw_texture_rectangle(SCREEN_WIDTH / 2 - BOARD_WIDTH / 4,
                                          SCREEN_HEIGHT / 2, 50, 50,
                                          self.dice.get_face(self.red_roll))
            arcade.draw_texture_rectangle(SCREEN_WIDTH / 2 + BOARD_WIDTH / 4,
                                          SCREEN_HEIGHT / 2, 50, 50,
                                          self.dice.get_face(self.white_roll))
            if self.white_roll == self.red_roll:
                arcade.draw_texture_rectangle(SCREEN_WIDTH / 2,
                                              SCREEN_HEIGHT / 2, 200, 50,
                                              self.roll_again_button)
            elif self.white_roll > self.red_roll:
                arcade.draw_texture_rectangle(SCREEN_WIDTH / 2,
                                              SCREEN_HEIGHT / 2, 200, 50,
                                              self.white_begins_button)
            else:
                arcade.draw_texture_rectangle(SCREEN_WIDTH / 2,
                                              SCREEN_HEIGHT / 2, 200, 50,
                                              self.red_begins_button)
        elif self.game_state == "started":
            # tick buttons
            if self.turn == 0:
                arcade.draw_texture_rectangle(
                    SCREEN_WIDTH - PLAYER_STAT_WIDTH / 2, SCREEN_HEIGHT / 2,
                    70, 70, self.tick_button)
            elif not self.is_cpu_game:
                arcade.draw_texture_rectangle(PLAYER_STAT_WIDTH / 2,
                                              SCREEN_HEIGHT / 2, 70, 70,
                                              self.tick_button)

            # dice
            sign = -1
            not_used_rolls = list_difference(self.rolls, self.used_rolls)
            for index, roll in enumerate(not_used_rolls):
                if self.turn == 0:
                    arcade.draw_texture_rectangle(
                        SCREEN_WIDTH - PLAYER_STAT_WIDTH - BOARD_WIDTH / 4 +
                        DICE_X[index], SCREEN_HEIGHT / 2, 50, 50,
                        self.dice.get_face(roll))
                else:
                    arcade.draw_texture_rectangle(
                        PLAYER_STAT_WIDTH + BOARD_WIDTH / 4 + DICE_X[index],
                        SCREEN_HEIGHT / 2, 50, 50, self.dice.get_face(roll))
                sign = -sign

            # bear_off checkers
            for count in range(self.nr_of_beared_off[0]):
                arcade.draw_texture_rectangle(
                    SCREEN_WIDTH - PLAYER_STAT_WIDTH / 4,
                    BEAR_OFF_BEGIN_Y + count * (BEAR_OFF_CHECKER_HEIGHT + 2),
                    70, 15, self.beared_off_white)
            for count in range(self.nr_of_beared_off[1]):
                arcade.draw_texture_rectangle(
                    PLAYER_STAT_WIDTH / 4,
                    BEAR_OFF_BEGIN_Y + count * (BEAR_OFF_CHECKER_HEIGHT + 2),
                    70, 15, self.beared_off_red)

            # hint messages
            if self.bottom_message_time > 0:
                arcade.draw_text(self.bottom_message,
                                 SCREEN_WIDTH / 2,
                                 17,
                                 color=arcade.color.WHITE,
                                 font_size=20,
                                 anchor_x="center",
                                 anchor_y="center")
                self.bottom_message_time -= 1

            # game over screen
            if self.winner is not None:
                arcade.draw_texture_rectangle(SCREEN_WIDTH / 2,
                                              SCREEN_HEIGHT / 2 + 27, 200, 50,
                                              self.play_again)
                arcade.draw_texture_rectangle(SCREEN_WIDTH / 2,
                                              SCREEN_HEIGHT / 2 - 27, 200, 50,
                                              self.main_menu)
                if self.winner == 1:
                    if self.is_cpu_game:
                        message = "CPU won"
                    else:
                        message = "Red won"
                else:
                    message = "You won"
                arcade.draw_text(message,
                                 SCREEN_WIDTH / 2,
                                 17,
                                 color=arcade.color.WHITE,
                                 font_size=20,
                                 anchor_x="center",
                                 anchor_y="center")

    def on_update(self, delta_time):
        """Called every frame"""
        if self.is_cpu_game and self.turn == 1 and self.selected_checker_destination is None:
            if len(self.rolls) != len(self.used_rolls):
                # sleep(2)
                roll_index = random.randint(0, len(self.rolls) - 1)
                avalable_rolls = list_difference(self.rolls, self.used_rolls)
                while True:
                    if len(avalable_rolls) == 0:
                        break
                    self.current_roll = avalable_rolls[roll_index %
                                                       len(avalable_rolls)]
                    if self.current_roll in self.dead_rolls:
                        break
                    if len(self.dead_checker_list[1]) == 0:
                        bear_off_point = self.get_point_by_id(
                            self.current_roll)
                        if self.ready_for_bearoff(
                                1) and bear_off_point.checker_color == 1:
                            self.set_selected_checker(
                                bear_off_point.get_top_checker())
                            self.selected_checker_destination = self.get_bear_off_destination(
                                1)
                            self.selected_checker_origin = self.selected_checker.position
                            self.checker_state = "bear_off"
                            break
                        else:
                            checker_index = random.randint(
                                0,
                                len(self.checker_list) - 1)
                            for index in range(
                                    checker_index,
                                    checker_index + len(self.checker_list)):
                                random_checker = self.checker_list[index % len(
                                    self.checker_list)]
                                if random_checker.colorr == 1 and random_checker.is_selectable:
                                    destination_point = self.get_point_by_id(
                                        random_checker.point.id -
                                        self.current_roll)
                                    if destination_point:
                                        if self.is_valid_move(
                                                random_checker,
                                                destination_point, self.rolls,
                                                self.used_rolls):
                                            self.set_selected_checker(
                                                random_checker)
                                            destination_point.arrange_pile(
                                                for_what="add")
                                            self.selected_checker_origin = self.selected_checker.position
                                            self.selected_checker_destination = \
                                                destination_point.get_checker_destination(
                                                    self.selected_checker)
                                            self.checker_state = "valid_move"
                                            break
                            if self.selected_checker_destination:
                                break
                            else:
                                if self.ready_for_bearoff(1):
                                    for point_id in reversed(
                                            range(1, self.current_roll)):
                                        bear_off_point = self.get_point_by_id(
                                            point_id)
                                        if bear_off_point.checker_color == 1:
                                            self.set_selected_checker(
                                                bear_off_point.get_top_checker(
                                                ))
                                            self.selected_checker_destination = self.get_bear_off_destination(
                                                1)
                                            self.selected_checker_origin = self.selected_checker.position
                                            self.checker_state = "bear_off"
                                            break
                                    if self.selected_checker_destination:
                                        break
                                self.dead_rolls.append(self.current_roll)
                                if len(self.rolls) == 4:
                                    break

                    # cpu has to revive dead checkers
                    else:
                        for checker in self.dead_checker_list[1]:
                            if checker.is_selectable:
                                destination_point = self.get_point_by_id(
                                    25 - self.current_roll)
                                if self.is_valid_move(checker,
                                                      destination_point,
                                                      self.rolls,
                                                      self.used_rolls):
                                    self.set_selected_checker(checker)
                                    destination_point.arrange_pile(
                                        for_what="add")
                                    self.selected_checker_origin = self.selected_checker.position
                                    self.selected_checker_destination = destination_point.get_checker_destination(
                                        self.selected_checker)
                                    self.checker_state = "valid_move"
                                    break
                        if self.selected_checker_destination:
                            break
                        else:
                            self.dead_rolls.append(self.current_roll)
                            if len(self.rolls) == 4:
                                break
                    roll_index += 1
                if len(self.dead_rolls) > 0:
                    self.bottom_message = "CPU is out of moves. Your turn."
                    self.bottom_message_time = BOTTOM_MSG_TIME
                    self.used_rolls = list(self.rolls)

            else:
                self.rolls = self.dice.double_roll()
                self.used_rolls.clear()
                self.dead_rolls.clear()
                self.turn = 1 - self.turn
                if self.winner is None:
                    self.player_can_move = self.valid_move_exists(self.turn)

        if self.selected_checker_destination is not None:

            if self.animation_iteration < CHECKER_SPEED:
                # self.last_x_distance = abs(self.animation_checker.center_x - self.selected_checker_destination[0])
                self.selected_checker.center_x += (
                    self.selected_checker_destination[0] -
                    self.selected_checker_origin[0]) / CHECKER_SPEED
                self.selected_checker.center_y += (
                    self.selected_checker_destination[1] -
                    self.selected_checker_origin[1]) / CHECKER_SPEED
                self.animation_iteration += 1

            else:
                self.place_selected_checker(self.current_roll)
                self.selected_checker.position = self.selected_checker_destination
                self.animation_iteration = 0
                self.selected_checker = None
                self.selected_checker_destination = None
                if self.nr_of_beared_off[self.turn] == 17:
                    self.used_rolls = list(self.rolls)
                    self.winner = self.turn
                    if self.nr_of_beared_off[1 - self.turn] == 0:
                        if len(self.dead_checker_list[1 - self.turn]) > 0:
                            self.score[self.winner] += 3
                        else:
                            self.score[self.winner] += 2
                    else:
                        self.score[self.winner] += 1

                if self.winner is None and (self.turn == 0 or
                                            (self.turn == 1
                                             and not self.is_cpu_game)):
                    self.player_can_move = self.valid_move_exists(self.turn)

    def set_selected_checker(self, checker):
        """Removes a checker from it's pile and sets the selected checker attribute"""
        self.bring_sprite_to_front(checker)
        if checker.is_dead:
            self.dead_checker_list[checker.colorr].remove(checker)
            if len(self.dead_checker_list[checker.colorr]) > 0:
                self.dead_checker_list[checker.colorr][-1].is_selectable = True
            self.dead_checker_origin = checker.position
            self.selected_checker = checker

        elif len(self.dead_checker_list[self.turn]) == 0:
            checker.point.arrange_pile(for_what="remove")
            checker.remove()

            self.selected_checker = checker

    def on_mouse_press(self, x, y, button, key_modifiers):
        """ Called when the user presses a mouse button. """
        if self.game_state == "before_choose_turns":
            if SCREEN_WIDTH / 2 - 100 <= x <= SCREEN_WIDTH / 2 + 100 and \
                    SCREEN_HEIGHT / 2 - 25 <= y <= SCREEN_HEIGHT / 2 + 25:
                self.game_state = "choosing_turns"
                self.white_roll = self.dice.roll()
                self.red_roll = self.dice.roll()

        elif self.game_state == "choosing_turns":
            if SCREEN_WIDTH / 2 - 100 <= x <= SCREEN_WIDTH / 2 + 100 and \
                    SCREEN_HEIGHT / 2 - 25 <= y <= SCREEN_HEIGHT / 2 + 25:
                if self.white_roll != self.red_roll:
                    self.game_state = "started"
                    self.rolls = self.dice.double_roll()
                    if self.white_roll > self.red_roll:
                        self.turn = 0
                    else:
                        self.turn = 1
                else:
                    self.white_roll = self.dice.roll()
                    self.red_roll = self.dice.roll()

        elif self.game_state == "started":
            if self.selected_checker_destination is None:
                if self.winner is None:
                    # if len(self.used_rolls) == len(self.rolls):
                    if not self.player_can_move:
                        if 0 <= SCREEN_WIDTH / 2 + BOARD_WIDTH / 2 + PLAYER_STAT_WIDTH / 2 + 35 - x <= 70 and \
                                0 <= SCREEN_HEIGHT / 2 + 35 - y <= 70 and self.turn == 0:
                            self.rolls = self.dice.double_roll()
                            self.used_rolls.clear()
                            self.turn = 1 - self.turn
                            self.player_can_move = self.valid_move_exists(
                                self.turn)

                        elif 0 <= PLAYER_STAT_WIDTH / 2 + 35 - x <= 70 and 0 <= SCREEN_HEIGHT / 2 + 35 - y <= 70 and \
                                self.turn == 1 and not self.is_cpu_game:
                            self.rolls = self.dice.double_roll()
                            self.turn = 1 - self.turn
                            self.used_rolls.clear()
                            self.player_can_move = self.valid_move_exists(
                                self.turn)
                        else:
                            self.bottom_message = "Out of moves. You have to seize your turn."
                            self.bottom_message_time = BOTTOM_MSG_TIME

                    else:
                        clicked_checkers = arcade.get_sprites_at_point(
                            (x, y), self.checker_list)
                        for checker in clicked_checkers:
                            if checker.colorr == self.turn and checker.is_selectable:
                                self.set_selected_checker(checker)

                                if self.selected_checker:
                                    break

                else:
                    if SCREEN_WIDTH / 2 - 100 <= x <= SCREEN_WIDTH / 2 + 100 and \
                            SCREEN_HEIGHT / 2 + 2 <= y <= SCREEN_HEIGHT / 2 + 52:
                        self.setup()
                    if SCREEN_WIDTH / 2 - 100 <= x <= SCREEN_WIDTH / 2 + 100 and \
                            SCREEN_HEIGHT / 2 - 2 >= y >= SCREEN_HEIGHT / 2 - 52:
                        menu_view = MenuView()
                        self.window.show_view(menu_view)

    def place_selected_checker(self, bear_off_roll=None):
        """Adds a checker to it's destination checker pile depending on it's state:
        moved, missplaced, bear off
        """
        if self.checker_state == "valid_move":
            destination_point, dist = arcade.get_closest_sprite(
                self.selected_checker, self.point_list)
            placed_checker, used_roll, dead_checker = destination_point.add_checker(
                self.selected_checker)
            self.used_rolls.append(used_roll)
            if dead_checker is not None:
                self.bring_sprite_to_front(dead_checker)
                dead_checker.position = SCREEN_WIDTH / 2 + DEAD_CHECKER_PILE_DIRECTION[
                    dead_checker.colorr] * (
                        BOARD_WIDTH / 2 +
                        PLAYER_STAT_WIDTH / 4), CHECKER_RADIUS + 5 + 50 * len(
                            self.dead_checker_list[dead_checker.colorr])
                if len(self.dead_checker_list[dead_checker.colorr]) > 0:
                    self.dead_checker_list[
                        dead_checker.colorr][-1].is_selectable = False
                self.dead_checker_list[dead_checker.colorr].append(
                    dead_checker)

        elif self.checker_state == "missplaced":
            self.selected_checker.place_back_to_origin(self.dead_checker_list)
        elif self.checker_state == "bear_off":
            self.used_rolls.append(bear_off_roll)
            self.checker_list.remove(self.selected_checker)
            self.nr_of_beared_off[self.turn] += 1

    def get_bear_off_destination(self, turn):
        """Returns position of next checker to be added to bear off pile"""
        position = []
        if turn == 0:
            position.append(SCREEN_WIDTH - PLAYER_STAT_WIDTH / 4)
        else:
            position.append(PLAYER_STAT_WIDTH / 4)
        position.append(BEAR_OFF_BEGIN_Y + self.nr_of_beared_off[turn] *
                        (BEAR_OFF_CHECKER_HEIGHT + 2))
        return position

    def on_mouse_release(self, x: float, y: float, button: int,
                         modifiers: int):
        """ Called when the user presses a mouse button. """
        if self.game_state == "started" and self.winner is None and self.selected_checker_destination is None:

            if self.selected_checker is not None and self.selected_checker_destination is None:
                if x > SCREEN_WIDTH - PLAYER_STAT_WIDTH and self.ready_for_bearoff(
                        0) or x < PLAYER_STAT_WIDTH and self.ready_for_bearoff(
                            1):
                    if self.is_valid_move(self.selected_checker, None,
                                          self.rolls, self.used_rolls):
                        self.checker_state = "bear_off"
                        self.selected_checker_origin = x, y
                        self.selected_checker_destination = self.get_bear_off_destination(
                            self.turn)
                    else:
                        self.selected_checker.point.arrange_pile(
                            for_what="add")
                        self.selected_checker_destination = self.selected_checker.point.get_checker_destination(
                            self.selected_checker)
                        self.selected_checker_origin = x, y
                        self.checker_state = "missplaced"

                else:
                    closest_point, dist = arcade.get_closest_sprite(
                        self.selected_checker, self.point_list)
                    if self.is_valid_move(self.selected_checker, closest_point,
                                          self.rolls, self.used_rolls):
                        closest_point.arrange_pile(for_what="add")
                        self.selected_checker_origin = x, y
                        self.selected_checker_destination = closest_point.get_checker_destination(
                            self.selected_checker)
                        self.checker_state = "valid_move"
                    # wrong move -> checker must be put back
                    else:
                        # checker is in play
                        if self.selected_checker.point is not None:
                            self.selected_checker.point.arrange_pile(
                                for_what="add")
                            self.selected_checker_destination = self.selected_checker.point.get_checker_destination(
                                self.selected_checker)
                            self.selected_checker_origin = x, y
                            self.checker_state = "missplaced"
                        # checker is dead
                        else:
                            self.selected_checker_destination = self.dead_checker_origin
                            self.selected_checker_origin = x, y
                            self.checker_state = "missplaced"

    def valid_move_exists(self, turn):
        """Determines if a player still has available moves"""
        available_rolls = list_difference(self.rolls, self.used_rolls)
        if len(available_rolls) == 0:
            return False
        # if all checkers are in play
        if len(self.dead_checker_list[turn]) == 0:
            if self.ready_for_bearoff(turn):
                # if there is a checker on roll position it can be beared_off
                for roll in available_rolls:
                    point_id = 25 - roll if turn == 0 else roll
                    if self.get_point_by_id(point_id).checker_color == turn:
                        return True
                # if there is a roll bigger than the max checker position, that checker can be beared_off
                max_roll = max(available_rolls)
                low = 19 if self.turn == 0 else 7
                high = 25 if self.turn == 0 else 1
                step = 1 if self.turn == 0 else -1
                for point_id in range(low, high, step):
                    max_checker = self.get_point_by_id(
                        point_id).get_top_checker()
                    if max_checker is not None:
                        if max_checker.colorr == self.turn:
                            break
                point_position = max_checker.point.id if self.turn == 1 else 25 - max_checker.point.id
                if max_roll > point_position:
                    return True

            # if a checker can be moved
            for point in self.point_list:
                top_checker = point.get_top_checker()
                if top_checker is not None and top_checker.colorr == turn:
                    for roll in available_rolls:
                        potential_destinations = [
                            self.get_point_by_id(point.id + roll),
                            self.get_point_by_id(point.id - roll)
                        ]
                        for potential_destination in potential_destinations:
                            if potential_destination:
                                if self.is_valid_move(top_checker,
                                                      potential_destination,
                                                      self.rolls,
                                                      self.used_rolls):
                                    return True
        # if there's a dead checker
        else:
            # find the selectable dead_checker
            for checker in self.dead_checker_list[turn]:
                if checker.is_selectable:
                    dead_checker = checker
                    break
            # check if it can be re-entered in play
            point_id = 1 if turn == 0 else 19
            while point_id != 7 and point_id != 25:
                point = self.get_point_by_id(point_id)
                if self.is_valid_move(dead_checker, point, self.rolls,
                                      self.used_rolls):
                    return True
                point_id += 1
        return False

    def is_valid_move(self, checker, point, rolls, used_rolls):
        """Determines if a move is valid"""
        # bear_off
        if point is None:
            self.current_roll = checker.point.id if checker.colorr == 1 else 25 - checker.point.id
        else:
            self.current_roll = (
                point.id if checker.colorr == 0 else 25 -
                point.id) if checker.point is None else abs(checker.point.id -
                                                            point.id)
        available_rolls = list_difference(self.rolls, self.used_rolls)
        if self.current_roll not in available_rolls:
            if point is not None:
                # return False
                pass
            # checker can be beared_off is no checker can be moved and is on point with highest id
            else:
                max_roll = max(available_rolls)
                point_position = checker.point.id if self.turn == 1 else 25 - checker.point.id
                if max_roll > point_position:
                    low = 19 if self.turn == 0 else checker.point.id + 1
                    high = checker.point.id if self.turn == 0 else 7
                    for point_id in range(low, high):
                        if len(self.get_point_by_id(
                                point_id).checker_pile) > 0:
                            return False
                else:
                    return False
                self.current_roll = max_roll
                return True

        if point is not None:
            if len(point.checker_pile
                   ) > 1 and checker.colorr == 1 - point.checker_color:
                return False
            # if checker is in play (not dead) => verify if move direction is valid
            if checker.point is not None:
                if checker.colorr == 0 and point.id - checker.point.id < 0:
                    return False
                if checker.colorr == 1 and point.id - checker.point.id > 0:
                    return False
            # if checker is dead
            else:
                # if dead checker placed outside house
                if self.current_roll > 6:
                    return False
        return True

    def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
        """ User moves mouse """
        if self.game_state == "started":
            if self.selected_checker is not None and self.selected_checker_destination is None:
                self.selected_checker.center_x += dx
                self.selected_checker.center_y += dy

    def set_points(self):
        """Creates all point objects and sets them to their positions"""
        color = 1  # red
        id = 1
        for y, x in POINTS_POSITIONS:
            point = Point(color, 25 - id)
            point.position = x, y
            point.direction = 1
            self.point_list.append(point)
            point = Point(1 - color, id, True)
            id += 1
            point.position = x, SCREEN_HEIGHT - y
            point.direction = -1
            self.point_list.append(point)
            color = 1 - color

    def set_checkers(self):
        """Creates all checkers and places them in their initial positions"""
        for index, (y, x) in enumerate(CHECKER_POSITIONS):
            point_low = arcade.get_sprites_at_point((x, y), self.point_list)[0]
            point_low.checker_color = CHECKER_COLORS[index]
            point_top = arcade.get_sprites_at_point((x, SCREEN_HEIGHT - y),
                                                    self.point_list)[0]
            point_top.checker_color = 1 - CHECKER_COLORS[index]
            for checker_count in range(CHECKER_PILES[index]):
                checker = Checker(CHECKER_COLORS[index], 1)
                checker.position = x, y + checker_count * CHECKER_PILE_OFFSET[
                    CHECKER_PILES[index]]
                checker.point = point_low
                if CHECKER_PILES[index] == checker_count + 1:
                    checker.is_selectable = True
                point_low.checker_pile.append(checker)
                self.checker_list.append(checker)
                checker = Checker(1 - CHECKER_COLORS[index], 1)
                checker.position = x, SCREEN_HEIGHT - y - checker_count * CHECKER_PILE_OFFSET[
                    CHECKER_PILES[index]]
                checker.point = point_top
                if CHECKER_PILES[index] == checker_count + 1:
                    checker.is_selectable = True
                point_top.checker_pile.append(checker)
                self.checker_list.append(checker)

    def bring_sprite_to_front(self, sprite):
        """Brings a sprite to the top of the list in order to be rendered on top"""
        if isinstance(sprite, Checker):
            self.checker_list.remove(sprite)
            self.checker_list.append(sprite)
            self.checker_list.draw()

    def ready_for_bearoff(self, player):
        """Determines if a player is allowed to bear off checkers"""
        if player == 0:
            for checker in self.checker_list:
                if checker.colorr == player and (checker.point is None
                                                 or checker.point.id <= 18):
                    return False
        else:
            for checker in self.checker_list:
                if checker.colorr == player and (checker.point is None
                                                 or checker.point.id > 6):
                    return False
        return True

    def get_point_by_id(self, idd):
        if idd < 1 or idd > 25:
            return None
        for point in self.point_list:
            if point.id == idd:
                return point