def __init__(self): """The class constructor. Args: self (Director): an instance of Director. """ self._console = Console() self._keep_playing = True self._move = Move() self._roster = Roster() self._logic = Logic()
def test_execute(self, move_data, should_promote): move = Move(*move_data) from_piece_pre_move = self.test_board.get_piece(move.from_x, move.from_y) move.execute(self.test_board) from_piece_post_move = self.test_board.get_piece(move.from_x, move.from_y) to_piece_post_move = self.test_board.get_piece(move.to_x, move.to_y) self.assertEqual(from_piece_post_move, pieces.Blank) if should_promote: self.assertEqual(from_piece_pre_move, pieces.Pawn) self.assertEqual(to_piece_post_move, pieces.Queen) else: self.assertEqual(from_piece_pre_move, to_piece_post_move)
def __init__(self): """The class constructor. Args: self (Director): an instance of Director. """ self._board = Board() self._console = Console() self.keep_playing = True self.code = self._board.numbers_to_guess self._moves = [Move(self.code, '----'), Move(self.code, '----')] self._roster = Roster()
def test_black_start_move_and_last_move(): self.assertEqual( test_enemy_pawn_to_take_on_the_aisle.possible_moves(), [Coordinate(0, 5), Coordinate(0, 4)]) self.desk.set_last_move( Move(test_enemy_pawn_to_take_on_the_aisle, Coordinate(0, 4))) test_move(test_enemy_pawn_to_take_on_the_aisle, Coordinate(0, 4))
def possible_moves(self, controller, pieces_pos=None): if (pieces_pos is None): #if a piece list is supplied, take their word for it pieces_pos = self.pieces_of(controller) moves = [] for pos in pieces_pos: moves.extend([Move(controller, pos, x) for x in \ map(lambda i: (pos[0] + i[0], pos[1] + i[1]), Move.DELTAS_JUMP + Move.DELTAS_MOVE) if Move._in_board(x)]) try: # really hackish way to consider the exit moves, may change later. moves.append(Move(controller, pos, None)) except AssertionError: pass return list(filter(lambda move: self.valid_move(move), moves))
def createMoves(self, game: Game): moves = [] for board in self.getBoardsForMove(game): for field in board.getUnmarkedFields(): move = Move(field.x, field.y, board.id) violations = game.validateMove(move) if violations.isEmpty(): moves.append(move) return moves
def update_moves(cls, x: int, y: int, board: list[list[str]], color: str) -> list[Move]: if color == 'white': y0 = [12, 13] y1 = 8 direction = -1 else: y0 = [2, 3] y1 = 7 direction = 1 moves = [] piece = None if Blank.is_piece(board[y + direction][x]): moves.append(Move(x, y, x, y + direction, cls, cls.points, color)) if y + direction == y1: moves[-1].points += cls.promote if y in y0 and Blank.is_piece(board[y + 2 * direction][x]): moves.append( Move( x, y, x, y + 2 * direction, cls, cls.points, color, )) if x > 0 and cls.is_opponent(piece := board[y + direction][x - 1], color): moves.append( Move( x, y, x - 1, y + direction, cls, cls.points + cls.get_piece(piece)[0].points, color, )) if y + direction == y1: moves[-1].points += cls.promote
def make_safe_move(self, move: Union[Move, str]): """ Same as Board.make_move, but validates if the move is legal first (slower). """ if isinstance(move, str): move = Move.from_uci(move) if move in self.legal_moves: self.make_move(move) else: raise IllegalMove(f"Move {move} is illegal.")
def _pseudo_legal_moves(self, colour: Colour) -> Iterable[Move]: """Generates possible moves without taking into account check and the safety of the King.""" # Generic piece moves non_pawns = self.occupied_colour[colour] & ~self.pawns[colour] for from_square in bitboard_to_squares(non_pawns): moves = self._moves_from_square(from_square, colour) if moves: moves &= ~self.occupied_colour[ colour] # Cannot take our own pieces for to_square in bitboard_to_squares(moves): yield Move(from_square, to_square) # Handle pawns specifically so we can assign promotions to moves pawns = self.pawns[colour] for from_square in bitboard_to_squares(pawns): moves = self._moves_from_square(from_square, colour) if moves: moves &= ~self.occupied_colour[colour] bb_sq = BB_SQUARES[from_square] for to_square in bitboard_to_squares(moves): if self.pawns[colour] & bb_sq and to_square.rank in ( 0, 7): # Promotion for piece_type in (QUEEN, ROOK, BISHOP, KNIGHT): yield Move(from_square, to_square, promotion=piece_type) else: yield Move(from_square, to_square) # Castling moves if self.castling_rights: from_square = Square(msb( self.kings[colour])) # Is a move for the King for rook_sq in bitboard_to_squares(self.castling_rights[colour]): if not (BB_BETWEEN[from_square][rook_sq] & self.occupied): # Check no pieces in-between castle_sq = rook_sq + 2 if rook_sq.file == 0 else rook_sq - 1 yield Move(from_square, castle_sq, is_castling=True)
def _get_inputs(self): """Gets the inputs at the beginning of each round of play. For this game, gets move from current player. Args: self(Director): An instance of Director. """ player = self._roster.get_current() self._console.write(f"{player.get_name()}'s turn") prompt = "Please enter your guess (1000-9999): " guess = self._console.read_number(prompt) while True: if (guess < 1000): self._console.write("Please enter a guess over 1000.") guess = self._console.read_number(prompt) elif (guess > 9999): self._console.write("Please enter a guess under 9999.") guess = self._console.read_number(prompt) else: break move = Move(guess) player.set_move(move.get_guess())
def move(self, board, player, other_player): max_points = 0 best_move = None for row in range(0, board.size()): for column in range(0, board.size()): if not board.is_field_used(row, column): move = Move(row, column) board.apply_move(move, player) points = PointsCalculator.calculate(board, move) if points >= max_points: best_move = move max_points = points board.revert_move(move) return best_move
def test_undo(self, move_data): move = Move(*move_data) from_piece_pre_move = self.test_board.get_piece(move.from_x, move.from_y) to_piece_pre_move = self.test_board.get_piece(move.to_x, move.to_y) move.execute(self.test_board) move.undo(self.test_board) from_piece_post_move = self.test_board.get_piece(move.from_x, move.from_y) to_piece_post_move = self.test_board.get_piece(move.to_x, move.to_y) self.assertEqual(from_piece_pre_move, from_piece_post_move) self.assertEqual(to_piece_pre_move, to_piece_post_move)
def _get_inputs(self): """Gets the inputs at the beginning of each round of play. In this case, that means getting the move from the current player. Args: self (Director): An instance of Director. """ # display the game board board = self._board.to_string() self._console.write(board) # get next player's move player = self._roster.get_current() self._console.write(f"{player.get_name()}'s turn:") pile = self._console.read_number("What pile to remove from? ") stones = self._console.read_number("How many stones to remove? ") move = Move(stones, pile) player.set_move(move)
def _alpha_beta(self, board, player, other_player, maximizing_player, depth): best_move = None max_points = -inf # Return if there are no empty fields left if board.count_empty_fields() == 0: return 0, None # Return if max depth is reached if depth == 0: return 0, None # For each empty field for row in range(0, board.size()): for column in range(0, board.size()): if not board.is_field_used(row, column): # Calculate points for move move = Move(row, column) board.apply_move(move, player) points = PointsCalculator.calculate(board, move) # Go deeper if this node is promising if points > max_points: (deeper_points, deeper_move) = self._alpha_beta(board, other_player, player, not maximizing_player, depth - 1) points += deeper_points # Check if it's best move if points > max_points: max_points = points best_move = move # Revert move to check others board.revert_move(move) if maximizing_player: # Return positive points if it's maximizing player return max_points, best_move else: # Return negative points if it's minimizing player return -max_points, best_move
def test_is_valid(self, move_data, expected): move = Move(*move_data) self.assertEqual(move.is_valid(), expected)
def play(self, board, color, moves_left): if color == 'black': return Move(2, 2, 2, 3, pieces.Pawn, 10, 'black') else: return Move(2, 13, 2, 12, pieces.Pawn, 10, 'white')
def pick(self, board: Board, color: str, maximize: bool, depth: int, moves_left: int, alfa: int = None, beta: int = None): moves = board.get_moves(color) if len(moves) == 1: return moves[0].points, moves[0] elif len(moves) == 0: return float('inf') if maximize else float('-inf'), Move() moves.sort(key=lambda x: x.points, reverse=True) candidate = None max_score = float('-inf') min_score = float('+inf') min_points = 0 max_points = 0 if not alfa: alfa = float('-inf') if not beta: beta = float('+inf') for move in moves: move_heuristic = self.heuristic(board, move, color, moves_left) if move_heuristic < 0: continue if depth == 0 or moves_left == 1: if maximize: if move_heuristic > max_score: max_score = move_heuristic candidate = move max_points = move.points else: if move_heuristic < min_score: min_score = move_heuristic candidate = move min_points = -move.points else: move.execute(board) if maximize: score, _ = self.pick( board, self.invert_color(color), False, depth - 1, moves_left - 1, alfa, beta, ) move.undo(board) score += move_heuristic if score > max_score: max_score = score candidate = move max_points = score - move_heuristic + move.points alfa = max(alfa, score) if alfa >= beta: break else: score, _ = self.pick( board, self.invert_color(color), True, depth - 1, moves_left - 1, alfa, beta, ) move.undo(board) score -= move_heuristic if score < min_score: min_score = score candidate = move min_points = score + move_heuristic - move.points beta = min(beta, score) if beta <= alfa: break if candidate is None: if maximize: return moves[0].points, moves[0] else: return -moves[0].points, moves[0] if maximize: return max_points, candidate else: return min_points, candidate
class Director: """A code template for a person who directs the game. The responsibility of this class of objects is to control the sequence of play. Attributes: console (Console): An instance of the class of objects known as Console. roster (Roster): An instance of the class of objects known as Roster. """ def __init__(self): """The class constructor. Args: self (Director): an instance of Director. """ self._console = Console() self._keep_playing = True self._move = Move() self._roster = Roster() self._logic = Logic() def start_game(self): """Starts the game loop to control the sequence of play. Args: self (Director): an instance of Director. """ self._get_name() while self._keep_playing: self._get_inputs() self._do_updates() self._do_outputs() def _get_name(self): """Prepares the game before it begins. In this case, that means getting the player names and adding them to the roster. Args: self (Director): An instance of Director. """ for n in range(2): name = self._console.read(f"Enter a name for player {n + 1}: ") player = Player(name) self._roster.add_player(player) def _get_inputs(self): """Gets the inputs at the beginning of each round of play. In this case, that means getting the move from the current player. Args: self (Director): An instance of Director. """ # display the game board self._console._print_board(self._roster._the_roster[0],self._roster._the_roster[1]) # get next player's move player = self._roster.get_current() self._console.write(f"{player.player_name}'s turn:") guess = self._console.read_number("What is your next guess? ") #could send to player or logic. Who controls the numbers? self._roster.get_current().guess = guess def _do_updates(self): """Updates the important game information for each round of play. In this case, that means updating the logic/roster with the current move. Args: self (Director): An instance of Director. """ player = self._roster.get_current() self._logic.check_number(str(player.guess), player) self._roster.get_current().hint = "".join(self._logic.result) self._move.as_string(str(self._roster.get_current().guess),str(self._roster.get_current().hint)) def _do_outputs(self): """Outputs the important game information for each round of play. In this case, that means checking if there are stones left and declaring the winner. Args: self (Director): An instance of Director. """ if self._roster.get_current().win: winner = self._roster.get_current().player_name print(f"\n{winner} won!") self._keep_playing = False self._roster.next_player()
def move(self, board, player, other_player): while True: row = int(input("Row (0-%d): " % (board.size() - 1))) column = int(input("Column (0-%d): " % (board.size() - 1))) if not board.is_field_used(row, column): return Move(row, column)
def update_moves(cls, x: int, y: int, board: list[list[str]], color: str) -> list[Move]: return [Move()]
def update_moves(cls, x: int, y: int, board: list[list[str]], color: str) -> list[Move]: moves = [] if y + 2 <= 15: if x - 1 >= 0: piece = board[y + 2][x - 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x - 1, y + 2, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if x + 1 <= 15: piece = board[y + 2][x + 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 1, y + 2, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if y - 2 >= 0: if x - 1 >= 0: piece = board[y - 2][x - 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x - 1, y - 2, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if x + 1 <= 15: piece = board[y - 2][x + 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 1, y - 2, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if x + 2 <= 15: if y - 1 >= 0: piece = board[y - 1][x + 2] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 2, y - 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if y + 1 <= 15: piece = board[y + 1][x + 2] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 2, y + 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if x - 2 >= 0: if y - 1 >= 0: piece = board[y - 1][x - 2] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x - 2, y - 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if y + 1 <= 15: piece = board[y + 1][x - 2] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x - 2, y + 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 return moves
def update_moves(cls, x: int, y: int, board: list[list[str]], color: str) -> list[Move]: skip_lt = False skip_ld = False skip_rt = False skip_rd = False skip_l = False skip_r = False skip_t = False skip_d = False moves = [] for z in range(1, 16): lcheck = x - z >= 0 rcheck = x + z <= 15 tcheck = y + z <= 15 dcheck = y - z >= 0 if lcheck: if not skip_l: piece = board[y][x - z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x - z, y, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_l = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if tcheck and not skip_lt: piece = board[y + z][x - z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x - z, y + z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_lt = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if dcheck and not skip_ld: piece = board[y - z][x - z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x - z, y - z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_ld = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if rcheck: if not skip_r: piece = board[y][x + z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x + z, y, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_r = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if tcheck and not skip_rt: piece = board[y + z][x + z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x + z, y + z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_rt = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if dcheck and not skip_rd: piece = board[y - z][x + z] if Blank.is_piece(piece) or Piece.is_opponent( piece, color): moves.append( Move( x, y, x + z, y - z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_rd = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece( piece)[0].points * 10 if tcheck and not skip_t: piece = board[y + z][x] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x, y + z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_t = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if dcheck and not skip_d: piece = board[y - z][x] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x, y - z, cls, cls.points, color, )) if not Blank.is_piece(piece): skip_d = True if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 return moves
def badMove1() -> Move: return Move(3, 4, 1)
def outOfBoardMove() -> Move: return Move(-1, 2, 1)
def goodMove() -> Move: return Move(1, 2, 1)
def update_moves(cls, x: int, y: int, board: list[list[str]], color: str) -> list[Move]: moves = [] lcheck = x - 1 >= 0 rcheck = x + 1 <= 15 tcheck = y + 1 <= 15 dcheck = y - 1 >= 0 if lcheck: piece = board[y][x - 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x - 1, y, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if tcheck: piece = board[y + 1][x - 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move(x, y, x - 1, y + 1, cls, cls.points, color)) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if dcheck: piece = board[y - 1][x - 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x - 1, y - 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if rcheck: piece = board[y][x + 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x + 1, y, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if tcheck: piece = board[y + 1][x + 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 1, y + 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if dcheck: piece = board[y - 1][x + 1] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append( Move( x, y, x + 1, y - 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if tcheck: piece = board[y + 1][x] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x, y + 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 if dcheck: piece = board[y - 1][x] if Blank.is_piece(piece) or Piece.is_opponent(piece, color): moves.append(Move( x, y, x, y - 1, cls, cls.points, color, )) if Piece.is_opponent(piece, color): moves[-1].points += cls.get_piece(piece)[0].points * 10 return moves
def move_piece(self, from_xy, to_xy): """Moves a piece if the move is legal, ignoring checks.""" if not self.is_legal_move(from_xy, to_xy): return False piece = self.get_piece(from_xy[0], from_xy[1]) captured_piece = self.get_piece(to_xy[0], to_xy[1]) en_passant = False promoted_to_piece = None castling_rights_revoked = [] castling_side = 0 if piece.name.upper() == "P": if not captured_piece and from_xy[0] != to_xy[0]: captured_piece = self.get_piece(to_xy[0], from_xy[1]) self.remove_piece(to_xy[0], from_xy[1]) en_passant = True elif to_xy[1] == 0 or to_xy[1] == self.width - 1: promoted_to_piece = self.get_piece_by_name( self.promotion_piece if piece.is_white( ) else self.promotion_piece.lower()) elif piece.name.upper() == "K": delta_x = to_xy[0] - from_xy[0] if abs(delta_x) == 2: castling_side = delta_x for side in [False, True]: if self.revoke_castling_right(piece.is_white(), side): castling_rights_revoked.append((piece.is_white(), side)) elif piece.name.upper() == "R": if self.contains_rook_in_initial_position(from_xy[0], from_xy[1]): if self.revoke_castling_right(piece.is_white(), from_xy[0] != 0): castling_rights_revoked.append( (piece.is_white(), from_xy[0] != 0)) # Revoke castling rights from captured rooks in case of promotion. if captured_piece and captured_piece.name.upper() == "R": if self.contains_rook_in_initial_position(to_xy[0], to_xy[1]): if self.revoke_castling_right(captured_piece.is_white(), to_xy[0] != 0): castling_rights_revoked.append( (captured_piece.is_white(), to_xy[0] != 0)) move = Move( piece, from_xy[0], from_xy[1], captured_piece, en_passant, to_xy[0], to_xy[1], promoted_to_piece, castling_side, castling_rights_revoked, ) self.remove_piece(move.from_x, move.from_y) self.en_passant_target_xy.append(None) if promoted_to_piece: piece = promoted_to_piece self.set_piece(move.to_x, move.to_y, piece) if castling_side: rook_x = self.width - 1 if castling_side > 0 else 0 rook = self.get_piece(rook_x, to_xy[1]) self.remove_piece(rook_x, to_xy[1]) self.set_piece(to_xy[0] + (-1 if castling_side > 0 else 1), to_xy[1], rook) self.moves.append(move) return True
def move(self, board, player, other_player): while True: row = randrange(board.size()) column = randrange(board.size()) if not board.is_field_used(row, column): return Move(row, column)
def make_move(self, move: Union[Move, str]): """ Moves a piece on the game. Warning: moves are not checked for legality in this function, this is for speed. The consumer of this API should enforce legality by checking Bitboard.legal_moves. """ if isinstance(move, str): move = Move.from_uci(move) self._save() piece = self.piece_at(move.from_square) captured_piece = self.piece_at(move.to_square) if not piece: raise IllegalMove(f"No piece at {move.from_square}") if piece.colour != self.turn: raise IllegalMove(f"Can't move that piece, it's not your turn.") backrank = 7 if self.turn == WHITE else 0 # Castling if a king is moving more than 1 square if piece.type == KING and abs(move.from_square.file - move.to_square.file) > 1: # Move King self.remove_piece(move.from_square) self.place_piece(move.to_square, piece.type, piece.colour) # Move Rook rook_shift = 1 if move.to_square.file < move.from_square.file else -1 # For Queen/Kingside if rook_shift > 0: # Queenside self.remove_piece(Square.from_file_rank( 0, move.to_square.rank)) else: self.remove_piece(Square.from_file_rank( 7, move.to_square.rank)) self.place_piece( Square.from_file_rank(move.to_square.file + rook_shift, move.from_square.rank), ROOK, piece.colour, ) self.repetitions = [] # Reset repetitions when castling elif piece.type == PAWN and move.to_square == self.en_passant_sq: # Take piece by en_passant shift = -8 if piece.colour == WHITE else 8 capture_sq = self.en_passant_sq + shift captured_piece = self.remove_piece(capture_sq) self.remove_piece(move.from_square) self.place_piece(move.to_square, piece.type, piece.colour) elif piece.type == PAWN and move.to_square.rank == backrank: # Promotion self.remove_piece(move.from_square) self.place_piece(move.to_square, move.promotion, piece.colour) # Assume queen for now else: # Regular piece move self.remove_piece(move.from_square) self.place_piece(move.to_square, piece.type, piece.colour) # Set En Passant square if piece.type == PAWN: distance = move.to_square.rank - move.from_square.rank if abs(distance) == 2: if distance > 0: # White pawn goes from low rank to higher self.en_passant_sq = Square( move.from_square + 8) # En Passant square is 1 rank behind else: # Black pawn self.en_passant_sq = Square(move.from_square - 8) else: self.en_passant_sq = None else: self.en_passant_sq = None # Update castling rights if the king or rook move if piece.type in (KING, ROOK): self._update_castling_rights() # Reset halfmove clock if a pawn moved or a piece was captured if piece.type == PAWN or captured_piece: self.halfmove_clock = 0 self.repetitions = [] else: self.halfmove_clock += 1 if self.track_repetitions: self.repetitions.append( self._short_fen) # Imperfect repetition tracking if self.turn == BLACK: # Increment full moves after Black's turn self.fullmoves += 1 self.move_history.append(move) self.turn = not self.turn