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
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
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
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
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
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
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))
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
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
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
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
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
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)