Search code examples
pythonpython-3.xpython-chess

Trying to create an endgame chess engine fully on my own in Python. Keeping getting AssertionError: san() and lan() expect move to be legal or null


I'm trying to create an endgame chess engine fully on my own and in Python. I'm keeping getting an error as below. I'm unable to get rid of it. Can someone kindly fix it or give me a hint how to fix it? Or, where is the problem.

import chess
import time

def ddfs(board, depth, is_white_turn):
    if board.is_checkmate():
        return True, []
    if depth == 0 or board.is_game_over():
        return False, []
    legal_moves = list(board.legal_moves)
    for move in legal_moves:
        board.push(move)
        found_mate, mate_path = ddfs(board, depth - 1, not is_white_turn)
        board.pop()
        if found_mate:
            return True, [move] + mate_path
    return False, []

def iterative_deepening_dfs(board):
    depth = 0
    while True:
        found_mate, mate_sequence = ddfs(board.copy(), depth, board.turn == chess.WHITE)
        if found_mate:
            return depth, mate_sequence
        depth += 1

def print_move_sequence(board, move_sequence):
    print("Initial board state:")
    print(board, "\n")
    for move in move_sequence:
        if board.is_legal(move):
            board.push(move)
            print(f"Move {board.san(move)}:")
            print(board, "\n")
        else:
            print("An illegal move found in the sequence, which should not happen.")
            break

def main():
    initial_fen = "6k1/8/5K2/2Q5/8/8/8/8 w - - 0 1"
    board = chess.Board(initial_fen)

    start_time = time.time()
    mate_depth, mate_sequence = iterative_deepening_dfs(board)
    elapsed_time = time.time() - start_time

    if mate_depth is not None:
        print(f"Mate found in {mate_depth} move(s), time taken: {elapsed_time:.2f} seconds.")
        board.reset()
        board.set_fen(initial_fen)  # Reset the board to initial state
        print_move_sequence(board, mate_sequence)
    else:
        print("No mate found.")

if __name__ == "__main__":
    main()

I'm getting this error and cannot get rid of it

Mate found in 3 move(s), time taken: 0.00 seconds.

Initial board state:
     . . . . . . k .
     . . . . . . . .
     . . . . . K . .
     . . Q . . . . .
     . . . . . . . .
     . . . . . . . .
     . . . . . . . .
     . . . . . . . . 
    ---------------------------------------------------------------------------
    AssertionError                            Traceback (most recent call last)
    Cell In[19], line 55
         52         print("No mate found.")
         54 if __name__ == "__main__":
    ---> 55     main()
    
    Cell In[19], line 50, in main()
         48     board.reset()
         49     board.set_fen(initial_fen)  # Reset the board to initial state
    ---> 50     print_move_sequence(board, mate_sequence)
         51 else:
         52     print("No mate found.")
    
    Cell In[19], line 32, in print_move_sequence(board, move_sequence)
         30 if board.is_legal(move):
         31     board.push(move)
    ---> 32     print(f"Move {board.san(move)}:")
         33     print(board, "\n")
         34 else:
    
    File ~\myenvGPU32\Lib\site-packages\chess\__init__.py:2866, in Board.san(self, move)
       2861 def san(self, move: Move) -> str:
       2862     """
       2863     Gets the standard algebraic notation of the given move in the context
       2864     of the current position.
       2865     """
    -> 2866     return self._algebraic(move)
    
    File ~\myenvGPU32\Lib\site-packages\chess\__init__.py:2879, in Board._algebraic(self, move, long)
       2878 def _algebraic(self, move: Move, *, long: bool = False) -> str:
    -> 2879     san = self._algebraic_and_push(move, long=long)
       2880     self.pop()
       2881     return san
    
    File ~\myenvGPU32\Lib\site-packages\chess\__init__.py:2884, in Board._algebraic_and_push(self, move, long)
       2883 def _algebraic_and_push(self, move: Move, *, long: bool = False) -> str:
    -> 2884     san = self._algebraic_without_suffix(move, long=long)
       2886     # Look ahead for check or checkmate.
       2887     self.push(move)
    
    File ~\myenvGPU32\Lib\site-packages\chess\__init__.py:2920, in Board._algebraic_without_suffix(self, move, long)
       2917         return "O-O"
       2919 piece_type = self.piece_type_at(move.from_square)
    -> 2920 assert piece_type, f"san() and lan() expect move to be legal or null, but got {move} in {self.fen()}"
       2921 capture = self.is_capture(move)
       2923 if piece_type == PAWN:
    
    AssertionError: san() and lan() expect move to be legal or null, but got f6g6 in 6k1/8/6K1/2Q5/8/8/8/8 b - - 1 1

Solution

  • TL;DR

    Print the move (board.san()) before you push that move the board with board.push().


    Just make board.san() line before board.push.

    So the print_move_sequence(board, move_sequence) function would be like this:

    def print_move_sequence(board, move_sequence):
        print("Initial board state:")
        print(board, "\n")
        for move in move_sequence:
            if board.is_legal(move):
                print(f"Move {board.san(move)}:") # This line should be first.
                board.push(move) # This line shuold be second.
                print(board, "\n")
            else:
                print("An illegal move found in the sequence, which should not happen.")
                break
    

    The issue was that when you push a move to the board, that move is actually made on the board, and it might sometimes not become legal after it's already executed. In your example, when f6g6 move is executed, it becomes black's turn instead of white. Thus, when doing board.san() to see the SAN representation of the move, you won't be able to because it's no longer legal. So the better approach to this is to print the SAN representation before making the move.