Esempio n. 1
0
def test_pieces_can_capture_opponent_pieces():
    board = Board(initial_pieces={'a8': King('black'), 'e5': Pawn('black'), 'f3': Knight('white')})
    assert board.pieces_quantity() == 3

    knight = board.get_piece('f3')
    board.move('f3', 'e5')
    assert board.get_piece('e5') is knight
    assert board.pieces_quantity() == 2
Esempio n. 2
0
def test_king_can_do_castling_to_left():
    board = Board(initial_pieces={'e1': King('white'), 'a1': Rook('white')})
    king = board.get_piece('e1')
    rook = board.get_piece('a1')
    board.move('e1', 'c1')
    assert board.get_piece('c1') == king
    assert board.get_piece('d1') == rook
    assert board.get_piece('e1') is None
    assert board.get_piece('a1') is None
Esempio n. 3
0
def test_pieces_can_capture_opponent_pieces():
    board = Board(initial_pieces={
        'a8': King('black'),
        'e5': Pawn('black'),
        'f3': Knight('white')
    })
    assert board.pieces_quantity() == 3

    knight = board.get_piece('f3')
    board.move('f3', 'e5')
    assert board.get_piece('e5') is knight
    assert board.pieces_quantity() == 2
Esempio n. 4
0
def test_king_can_moves():
    king = King('white')
    #
    # I have to pass a second king because the board look for a second
    # king when you set the first one. Maybe a have to add an attribute
    # to the board class to say wether the match is tutorial or not.
    #
    board = Board(initial_pieces={'f5': king, 'h1': King('black')})
    board.move('f5', 'e5')
    assert board.get_piece('e5') is king
    assert board.get_piece('f5') is None
Esempio n. 5
0
def test_pawn_can_moves():
    board = Board()
    pawn = board.get_piece('e2')
    board.move('e2', 'e3')
    assert board.get_piece('e3') is pawn
    assert board.get_piece('e2') is None
Esempio n. 6
0
def test_rook_can_moves():
    rook = Rook('white')
    board = Board(initial_pieces={'d5': rook})
    board.move('d5', 'd8')
    assert board.get_piece('d8') is rook
    assert board.get_piece('d5') is None
Esempio n. 7
0
class Game:
    players: dict = {
        '1': HumanPlayer(color='white', name='Human'),
        # '1': RandomPlayer(color='white'),
        '2': MiniMaxPlayer(color='black', max_depth=3)
    }

    def __init__(self):
        Game.load_pieces()

        self.running: bool = True

        pygame.init()
        pygame.display.set_caption('Chess')
        self.board = Board(self.players)
        self.players.get('1').set_enemy(self.board)
        self.players.get('2').set_enemy(self.board)
        self.display = pygame.display.set_mode((8 * SIZE, 8 * SIZE))
        self.player: int = 1

        self.human_moves: list[tuple] = []

        self.__draw()

        while self.running:
            current_player = self.players.get(str(self.player))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running: bool = False

                elif event.type == pygame.KEYDOWN:
                    # start a new game
                    if event.key == pygame.K_n:
                        self.board = Board(self.players)
                        self.players.get('1').set_enemy(self.board)
                        self.players.get('2').set_enemy(self.board)
                        self.player: int = 1

                    # undoes the last move
                    elif event.key == pygame.K_r:
                        # sets the player to 1 if move_log is empty
                        if self.board.undo_move():
                            self.player = (self.player % 2) + 1
                        else:
                            self.player = 1

                    # removes the selection
                    elif event.key == pygame.K_d:
                        self.human_moves = []

                    self.__draw()

                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if isinstance(current_player, HumanPlayer):
                        location: tuple = pygame.mouse.get_pos()
                        column: int = location[0] // SIZE
                        row: int = location[1] // SIZE

                        chosen_piece: object = self.board.get_piece(
                            row, column)

                        if len(self.human_moves) == 0 and (chosen_piece.player
                                                           == current_player):
                            moves = chosen_piece.legal_moves(self.board)

                            if len(moves) != 0:
                                start_column = moves[0].start_column
                                start_row = moves[0].start_row

                                self.human_moves = moves
                                self.highlight(self.human_moves,
                                               (start_row, start_column))
                            else:
                                self.highlight([], (row, column), color='red')

                        if len(self.human_moves) != 0:
                            for human_move in self.human_moves:
                                if (human_move.end_row
                                        == row) and (human_move.end_column
                                                     == column):
                                    current_player.set_move(human_move)
                                    self.human_moves = []

            next_move = current_player.best_move(self.board)

            if next_move is not None:
                if isinstance(current_player, ComputerizedPlayer):
                    moving_piece = next_move.moved_piece
                    start = (next_move.start_row, next_move.start_column)
                    self.highlight(moving_piece.legal_moves(self.board), start)
                    sleep(1)
                elif isinstance(current_player, HumanPlayer):
                    current_player.set_move(None)

                if self.running:
                    self.animate_move(next_move)
                    self.board.move_piece(next_move)

                self.player = (self.player % 2) + 1
                self.__draw()
                sleep(0.5)

    def highlight(self, valid_moves, selected, color='green'):
        self.__draw_board()

        if selected != ():
            r, c = selected

            # initial setup for surfaces
            square = pygame.Surface((SIZE, SIZE))
            square.set_alpha(100)

            # highlight selected square
            square.fill(pygame.Color(COLORS.get(color)))
            self.display.blit(square, (c * SIZE, r * SIZE))

            # highlight squares which valid moves
            square.fill(COLORS.get('yellow'))

            if len(valid_moves) != 0:
                for valid_move in valid_moves:
                    if (valid_move.start_row == r) and (valid_move.start_column
                                                        == c):
                        column = (valid_move.end_column * SIZE)
                        row = (valid_move.end_row * SIZE)
                        self.display.blit(square, (column, row))

        self.__draw_pieces()

    def animate_move(self, selected_move: object):
        clock = pygame.time.Clock()

        start_column = selected_move.start_column
        start_row = selected_move.start_row
        end_column = selected_move.end_column
        end_row = selected_move.end_row

        # to disable the moved piece, otherwise it's visible twice
        blank = p.Blank(start_row, start_column)
        self.board.set_piece(start_row, start_column, blank)

        direction_c = end_column - start_column
        direction_r = end_row - start_row

        frames_per_square = 6
        frame_count = (abs(direction_r) + abs(direction_c)) * frames_per_square

        for frame in range(frame_count + 1):
            column = (start_column + (direction_c * frame / frame_count))
            row = (start_row + (direction_r * frame / frame_count))

            self.__draw_board()
            self.__draw_pieces()

            end_square = pygame.Rect(end_column * SIZE, end_row * SIZE, SIZE,
                                     SIZE)

            captured_piece = selected_move.captured_piece
            if not isinstance(captured_piece, p.Blank):
                self.display.blit(IMAGES[captured_piece.load_image()],
                                  end_square)

            moved_piece = selected_move.moved_piece
            self.display.blit(
                IMAGES[moved_piece.load_image()],
                pygame.Rect(column * SIZE, row * SIZE, SIZE, SIZE))

            pygame.display.flip()
            clock.tick(60)

    def __draw(self):
        self.__draw_board()
        self.__draw_pieces()

    def __draw_board(self):
        for r in range(8):
            for c in range(8):
                color: str = 'white' if ((r + c) % 2 == 0) else 'gray'
                pygame.draw.rect(
                    self.display, COLORS.get(color),
                    pygame.Rect((c * SIZE), (r * SIZE), SIZE, SIZE))

    def __draw_pieces(self):
        for r in range(8):
            for c in range(8):
                piece = self.board.get_piece(row=r, column=c)
                if type(piece) != p.Blank:
                    self.display.blit(
                        IMAGES[piece.load_image()],
                        pygame.Rect((c * SIZE), (r * SIZE), SIZE, SIZE))
        pygame.display.flip()

    @staticmethod
    def load_pieces():
        image_names = [
            'b_rook', 'b_knight', 'b_bishop', 'b_queen', 'b_king', 'b_pawn',
            'w_rook', 'w_knight', 'w_bishop', 'w_queen', 'w_king', 'w_pawn'
        ]
        for image_name in image_names:
            loader = pygame.image.load(f'./chess/pieces/{image_name}.png')
            IMAGES[image_name] = pygame.transform.scale(loader, (SIZE, SIZE))
Esempio n. 8
0
class Game:
    def __init__(self, win):
        self.win = win
        self.color_value = {(255, 255, 255): "White", (0, 0, 0): "Black"}
        self._init()

    def _init(self):
        self.selected = None
        self.board = Board()
        self.turn = WHITE
        self.valid_moves = []
        self.check_mate = False
        self.move_x, self.move_y = 805, 40
        self.__draw_static_text()

    def __draw_static_text(self):
        self.win.fill(BLACK)
        display_text(self.win, SMALL_FONT, 'WHITE | BLACK | WHITE | BLACK',
                     800, 20, (255, 255, 255))

    def update(self):
        self.board.draw(self.win)
        if self.selected:
            self.draw_valid_moves(self.valid_moves)
        if self.check_mate:
            display_text(
                self.win, LARGE_FONT,
                f"Check Mate!! {self.color_value.get(self.turn)} Wins!!", 250,
                350, (255, 0, 0))
            display_text(self.win, LARGE_FONT, "Press R to restart", 300, 450,
                         (255, 0, 0))
        pygame.display.update()

    def __display_moves(self, move):
        if self.turn == WHITE:
            display_text(self.win, SMALL_FONT, move, self.move_x, self.move_y,
                         (255, 255, 255))
        else:
            display_text(self.win, SMALL_FONT, move, self.move_x + 50,
                         self.move_y, (255, 255, 255))
            self.move_y += 20

        if self.move_y > 780:
            self.move_x = 905

    def reset(self):
        self._init()

    def __get_opposing_color(self, color):
        return BLACK if color == WHITE else WHITE

    def select(self, coordinate):
        """
        Takes in a coordinate, if a piece is selected it will make the move on a temporary board to ensure it is valid.
        Otherwise a piece will be selected and its valid moves will be calculated
        :param coordinate: integer representing the selected board coordinate
        :return: boolean representing whether or not a piece has been selected
        """
        if self.selected:
            destination_coordinate = coordinate
            temp_board = self.simulate_move(self.selected,
                                            destination_coordinate,
                                            self.board.get_board().copy())
            if self.is_in_check(temp_board,
                                self.__get_opposing_color(self.turn)):
                self.selected = None
                self.select(coordinate)

            result = self.__move(coordinate)
            if not result:
                self.selected = None
                self.select(coordinate)
            else:
                self.update()
        else:
            piece = self.board.get_piece(coordinate)
            if piece != 0 and piece.color == self.turn:
                self.selected = piece
                self.valid_moves = piece.calculate_legal_moves(
                    self.board.get_board())
                return True

        return False

    def get_king_position(self, current_board, color):
        for i in range(len(current_board)):
            if str(current_board[i]
                   ) == 'King' and current_board[i].color == color:
                return i
        return -1  # Should not happen

    def is_in_check(self, current_board, color):
        temp_king_pos = self.get_king_position(
            current_board, self.__get_opposing_color(color))
        enemy_pieces = self.board.get_player_pieces(color, current_board)
        current_enemy_moves = self.board.get_player_moves(
            enemy_pieces, current_board)

        return temp_king_pos in current_enemy_moves

    def is_check_mate(self, current_board, color):
        player_pieces = self.board.get_player_pieces(color, current_board)
        for piece in player_pieces:
            current_player_moves = piece.calculate_legal_moves(current_board)
            for move in current_player_moves:
                updated_board = self.simulate_move(piece, move,
                                                   current_board.copy())
                # if one of the moves does not result in check i.e. there is a valid move, return false
                if not self.is_in_check(updated_board,
                                        self.__get_opposing_color(color)):
                    return False
        return True

    def simulate_move(self, piece, destination_coordinate, simulated_board):
        piece_position = piece.tile_index
        simulated_board[destination_coordinate] = piece
        simulated_board[piece_position] = 0
        return simulated_board

    def __move(self, coordinate):
        if self.selected and coordinate in self.valid_moves:
            self.board.move(self.selected, coordinate)
            self.__display_moves(self.selected.notation +
                                 ALGEBRAIC_NOTATION[coordinate])
            self.change_turn()
        else:
            return False
        return True

    def draw_valid_moves(self, moves):
        for move in moves:
            row = move // ROWS
            col = move % ROWS
            self.win.blit(GREEN_BOX, (col * SQUARE_SIZE, row * SQUARE_SIZE))

    def change_turn(self):
        self.selected = None
        self.turn = WHITE if self.turn == BLACK else BLACK

        if self.is_check_mate(self.board.get_board(), self.turn):
            self.check_mate = True

    def get_current_player(self):
        return self.turn

    def get_board(self):
        return self.board

    def ai_move(self, board):
        current_board = self.board.get_board()
        piece = None
        coordinate = None

        # Find the Piece that was moved and its coordinate
        for i in range(len(board)):
            if current_board[i] != board[i] and board[i] != 0:
                piece = board[i]
                coordinate = i
                break
        if piece:
            self.__display_moves(piece.notation +
                                 ALGEBRAIC_NOTATION[coordinate])
            self.board.move(piece, coordinate)
        else:
            print("COULDN'T FIND PIECE")
        self.change_turn()

    def evaluate(self, depth, current_board):
        """
        Evaluates a given board using the helper functions below
        :param depth: integer representing how deep the minimax algorithm went
        :param current_board:
        :return: inteer representing the evaluated score
        """
        return self.__check(current_board) + self.__checkmate(
            depth, current_board) + self.__mobility() + self.__piece_value(
                current_board)

    def __checkmate(self, depth, current_board):
        return CHECK_MATE_BONUS * self.__depth_bonus(
            depth) if self.is_check_mate(
                current_board, self.__get_opposing_color(self.turn)) else 0

    def __depth_bonus(self, depth):
        return 1 if depth == 0 else DEPTH_BONUS * depth

    def __check(self, current_board):
        return CHECK_BONUS if self.is_in_check(
            current_board, self.__get_opposing_color(self.turn)) else 0

    def __mobility(self):
        return len(self.valid_moves)

    def __piece_value(self, current_board):
        piece_value_score = 0
        pieces = self.board.get_player_pieces(self.turn, current_board)
        for piece in pieces:
            piece_value_score += piece.piece_value
        return piece_value_score
Esempio n. 9
0
def test_knight_can_moves():
    board = Board()
    knight = board.get_piece('b1')
    board.move('b1', 'c3')
    assert board.get_piece('c3') is knight
    assert board.get_piece('b1') is None
Esempio n. 10
0
def test_queen_can_moves_as_rook():
    queen = Queen('white')
    board = Board(initial_pieces={'d5': queen})
    board.move('d5', 'd8')
    assert board.get_piece('d8') is queen
    assert board.get_piece('d5') is None
Esempio n. 11
0
def test_queen_can_moves_as_bishop():
    queen = Queen('white')
    board = Board(initial_pieces={'f1': queen})
    board.move('f1', 'g2')
    assert board.get_piece('g2') is queen
    assert board.get_piece('f1') is None
Esempio n. 12
0
def test_bishop_can_moves():
    bishop = Bishop('White')
    board = Board(initial_pieces={'f1': bishop})
    board.move('f1', 'h3')
    assert board.get_piece('h3') is bishop
    assert board.get_piece('f1') is None
Esempio n. 13
0
class Game:
    def __init__(self, win, skill_level):
        self.win = win
        self.board = Board()
        self._initialize()
        self.engine = Engine(skill_level)
        self.buttons = [
            Button(SQUARE_SIZE * 9, SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE // 2,
                   RED, "queen"),
            Button(SQUARE_SIZE * 9, SQUARE_SIZE * 2, SQUARE_SIZE,
                   SQUARE_SIZE // 2, BLUE, "bishop"),
            Button(SQUARE_SIZE * 9, SQUARE_SIZE * 3, SQUARE_SIZE,
                   SQUARE_SIZE // 2, GREEN, "knight"),
            Button(SQUARE_SIZE * 9, SQUARE_SIZE * 4, SQUARE_SIZE,
                   SQUARE_SIZE // 2, ORANGE, "rook")
        ]

    def _initialize(self):

        self.selected = None
        self.turn = WHITE
        self.valid_moves = {}
        self.winner = None
        self.moves_since_pawn_move_or_capture = 0
        self.past_positions = {self.board.get_position(): 1}
        self.move_count = 0
        self.promotion_move = False

    def update(self):
        self.board.draw(self.win)
        self.draw_valid_moves()
        if self.promotion_move:
            for button in self.buttons:
                button.draw(self.win)

    def draw_valid_moves(self):
        for move in self.valid_moves:
            row, col = move
            pygame.draw.circle(
                self.win, LIGHT_BLUE,
                (col * SQUARE_SIZE + SQUARE_SIZE // 2 + BOARD_EDGE,
                 row * SQUARE_SIZE + SQUARE_SIZE // 2 + BOARD_EDGE),
                POSSIBLE_MOVE_RADIUS)

    def make_engine_move(self):
        self.engine.set_position(self.get_current_fen())
        move = self.engine.get_move(ENGINE_TIME_PER_MOVE)
        promotion = False
        if len(move) == 5:
            start_col, start_row, end_col, end_row, promotion = move
            start_col, end_col = ord(start_col) - ord('a'), ord(end_col) - ord(
                'a')
            start_row, end_row = ROWS - int(start_row), ROWS - int(end_row)
        else:
            start_col, start_row, end_col, end_row = move
            start_col, end_col = ord(start_col) - ord('a'), ord(end_col) - ord(
                'a')
            start_row, end_row = ROWS - int(start_row), ROWS - int(end_row)
        piece = self.board.get_piece(start_row, start_col)
        self.selected = piece
        self.valid_moves = piece.find_legal_moves(self.board.board)
        if promotion:
            self.board.move(self.selected, end_row, end_col, self.board.board)
            if promotion == "q":
                self.board.board[end_row][end_col] = piece.promote_to_queen()
            elif promotion == 'n':
                self.board.board[end_row][end_col] = piece.promote_to_knight()
            elif promotion == 'r':
                self.board.board[end_row][end_col] = piece.promote_to_rook()
            elif promotion == 'b':
                self.board.board[end_row][end_col] = piece.promote_to_bishop()
            self.board.remove(self.board.board, (start_row, start_col))
            self.moves_since_pawn_move_or_capture = 0
            current_position = self.board.get_position()
            self.update_past_positions()
            self.change_turn(current_position)
        else:
            self._move(end_row, end_col)
        time.sleep(1)

    def select(self, position):
        pos = self.get_position(position)
        if pos and not self.promotion_move:
            row, col = pos
            if self.selected:
                result = self._move(row, col)
                if not result:
                    self.selected = None
                    self.select(position)
                    self.valid_moves = {}
            piece = self.board.get_piece(row, col)
            if piece is not None and piece.get_color() == self.turn:
                self.selected = piece
                self.valid_moves = self.board.find_legal_moves(piece)
                return True
        elif self.promotion_move:
            row, col = self.promotion_move
            piece = self.board.get_piece(row, col)
            for button in self.buttons:
                if button.clicked(position):
                    piece_type = button.get_name()
                    piece = self.board.get_piece(row, col)
                    if piece_type == "queen":
                        self.board.board[row][col] = piece.promote_to_queen()
                    elif piece_type == "bishop":
                        self.board.board[row][col] = piece.promote_to_bishop()
                    elif piece_type == "knight":
                        self.board.board[row][col] = piece.promote_to_knight()
                    elif piece_type == "rook":
                        self.board.board[row][col] = piece.promote_to_rook()
                    self.promotion_move = False
                    self.board.promotion_move = False
                    self.update_past_positions()
                    board_pos = self.board.get_position()
                    self.change_turn(board_pos)
                    self.moves_since_pawn_move_or_capture = 0
                    return True
        return False

    @staticmethod
    def get_position(position):
        x, y = position
        if BOARD_EDGE < x < WIDTH + BOARD_EDGE and BOARD_EDGE < y < HEIGHT + BOARD_EDGE:
            row = (y - BOARD_EDGE) // SQUARE_SIZE
            col = (x - BOARD_EDGE) // SQUARE_SIZE
            return row, col
        else:
            return None

    def _move(self, row, col):
        if self.selected and (row, col) in self.valid_moves:
            self.board.move(self.selected, row, col, self.board.board)
            capture = self.valid_moves[(row, col)]
            if capture:
                self.moves_since_pawn_move_or_capture = 0
                capture_row, capture_col = capture
                if capture_row != row:  # en passant
                    self.board.remove(self.board.board, capture)
            elif isinstance(self.selected, Pawn):
                self.moves_since_pawn_move_or_capture = 0
            else:
                self.moves_since_pawn_move_or_capture += 1
            position = self.board.get_position()
            self.promotion_move = self.board.promotion_move
            if not self.promotion_move:
                self.update_past_positions()
                self.change_turn(position)
        else:
            return False
        return True

    def change_turn(self, position):
        self.valid_moves = {}
        self.board.stop_en_passant(self.turn)
        self.selected = None
        if self.turn == WHITE:
            self.turn = BLACK
            self.move_count += 1
        else:
            self.turn = WHITE
        self.board.find_all_possible_moves(self.turn)
        if self.moves_since_pawn_move_or_capture == 100:
            self.winner = "Draw - 50 move rule"
        elif self.past_positions[position] == 3:
            self.winner = "Draw - 3 fold repetition"
        else:
            self.winner = self.board.checkmate_or_stalemate(self.turn)

    def reset(self):
        self.board.reset()
        self._initialize()

    def get_winner(self):
        return self.winner

    def check_promotion(self):
        piece = self.board.promotion_move
        if piece:
            self.board.promotion_move = False
            return piece
        return False

    def update_past_positions(self):
        position = self.board.get_position()
        if position in self.past_positions:
            self.past_positions[position] += 1
        else:
            self.past_positions[position] = 1

    def get_current_fen(self):
        return self.board.get_fen(self.turn,
                                  self.moves_since_pawn_move_or_capture,
                                  self.move_count)