Chess Engine

Description

The chess engine is an interface to any chess AI chosen. This project uses sunfish by thomasahle as the chess AI of choice. The chess engine spins up an instance of the sunfish program, and then sends the users plays to the AI. The AI then responds with what it decides should be the computer/robots move. To do this, some small modifications had to be made to the sunfish source code:

  • Add a wait loop for the command queue to be filled with the user’s response. Inform the engine if the move is invalid or not.
        pass_number = 1  # We set the first pass number before entering the loop
        while move not in pos.gen_moves():
            if pass_number > 1:  # if on the second pass, the previous must've been invalid
                valid_queue.put(0)  # report to engine that the input was invalid

            command = command_queue.get(block=True)  # get a command from engine if available

            match = re.match('([a-h][1-8])'*2, command)
            if match:
                move = parse(match.group(1)), parse(match.group(2))
            else:
                # Inform the user when invalid input (e.g. "help") is entered
                print("Please enter a move like g8f6")

            pass_number += 1

  • Inform the game engine if the user’s move caused them to win.

        # After our move we rotate the board and print it again.
        # This allows us to see the effect of our move.
        print_pos(pos.rotate())

        if pos.score <= -MATE_LOWER:
            print("You won")
            valid_queue.put(1)  # inform engine that the move was accepted and user won
            break

  • Send whether the computer won or not. Also, send the computers move back to the chess engine through a seperate queue.
            print("Checkmate!")
            valid_queue.put(2)  # inform engine that the move was accepted and computer won
        else:
            valid_queue.put(3)  # inform engine that the move was accepted and sunfish will reply
        # The black player moves from a rotated position, so we have to
        # 'back rotate' the move before printing it.
        computer_move = render(119-move[0]) + render(119-move[1])
        reply_queue.put(computer_move, block=True)  # reply to engine
        print("My move:", computer_move)
        pos = pos.move(move)

These minimal changes mean that any AI that takes a users input as a chess command (e.g. ‘a2a4’) can be modified with minimal code to work with our chess engine.

Design

The CE (chess engine) is written by the team, it can provide all the chess functionality needed.

Tip

Lower case letters represent black pieces (p,r,n,b,k,q), and upper case letters represent white pieces (P,R,N,B,K,Q). This follows the model used in sunfish.

The BWE matrix provided by the Perception module is taken. This Black-White-Empty matrix is simply a list of strings which represent each square on the board. The elements on the list correspond to the positions on the board as [A8, B7, C7, ..., F1, G1, H1]. For the image below, this would be: ['B','B','B',...,'W','W','W'].

_images/chess-board.jpg

This matrix is compared with an internally stored matrix in the CE. This means the CE can understand:

  • where the piece moved from
  • where it moved to

and construct a chess command from it. This BWE matrix is of course checked for logical inconsistencies by measuring the change in number of black, white or empty squares in a single turn. Now that a potential chess move has been obtained, it is added to the command queue, a shared resource that both the CE and the chess AI (sunfish) have access to.

Hint

Moving pawn piece A2 to A4 at the beginning of the game would require command ‘a2a4’

_images/chess_engine_user_move.png

This move is trialled internally on the AI, and it responds with a number of options.

  • The move is invalid, in which case reporting of an illegal move back to the user needs to be carried out
  • The move is valid and has caused the user to win, in which case the user should be told
  • The move is valid and the computer responds with move (which may be a checkmate)

If the computer replies with a move, it will put it on the reply queue which is shared with the CE. The CE must now understand exactly what the chess AI is asking of it. It will have received a command such as ‘n, b1c4’ (i.e. knight b1 to c4). The CE then splits this command into the start and end position of the move. It then converts these positions into indices, which it uses to search its internally stored board for the type of piece that is moving. In addition it checks if there is already a piece existing at the end position.

The CE first updates its internal board to remember the move the computer just made, then returns the following information to the caller (function):

  • Firstly, if there is a piece to be killed, its location and type.
  • Then, the start location of the piece moving, and its type.
  • Finally, the end location of the piece moving.

Limitations

The chess engine has limitations, which could be implemented in later versions:

  • No support for pawn piece conversion (bringing pieces back onto the board).
  • No support for special chess moves such as castling.

Implementation

Example usage:

from chess.engine import ChessEngine

bwe_list  # Get BWE from the camera

engine = ChessEngine()
code, result = engine.input_bwe(bwe_list)

if code == -1:
    print("There was a problem with the BWE matrix")
elif code == 0:
    print("Invalid move by user: ", result)
elif code == 1:
    print("The user won the game")
elif code == 2:
    print("The computer has won the game", result)
    franka_move(result)
    franka_celebrate()
elif code == 3:
    print("The computer's move is: ", result)
    franka_move(result)
else:
  print("Error code not recognised: ", code)

Documentation:

This engine is designed to interface with the modified sunfish file to provide a specialised interface between the other modules in this project and the chess logic underneath.

class chess.engine.ChessEngine(debug=False, suppress_sunfish=True)[source]

Engine that manages communication between main program and chess AI Sunfish (running in separate process).

It’s main purpose is to take a BWE matrix as the user’s potential move and provide an analysis of this move by either reporting back its invalidity or the AI’s response.

input_bwe(bwe)[source]

Takes in the latest BWE and tries to input that to Sunfish AI.

Returns:
code : int
  • -1, BWE was invalid
  • 0, move was invalid
  • 1, user won the game with move
  • 2, computer won the game with checkmate
  • 3, computer did not win and
result

Depending on code, this will vary:

  • -1, None
  • 0, Move that was invalid as string
  • 1, None
  • 2, Move that computer is playing to win as tuple
  • 3, Move that computer is playing as tuple

Computer move is a list of tuples in the form:

  • [ (piece_to_move, move) ], or
  • [ (piece_to_kill, location), (piece_to_move, move) ]

where,

  • Pieces are single character strings.
  • Locations are two character strings e.g. ‘a2’
  • Moves are 4 character strings e.g. ‘a2a4’
start_sunfish_process()[source]

Spins up external process for Sunfish AI.

Process communicates with three queues, the command queue (for user moves), the valid queue for confirming if the user queue is valid, and the reply queue for the Sunfish computer move response.

test()[source]

This method is only used when debugging and developing the engine. It should not be called from other modules.

class chess.engine.ChessState(debug=False)[source]

Class holding the ongoing state of the chess board.

compare_bwe(new_bwe)[source]

Takes a BWE list and compares it to the existing game state. Return tuple of (move_from, move_to) indices for the single move that’s detected. Does not verify if move is a legal one.

convert_to_index(chess_pos)[source]

Takes an board position (e.g. ‘a2’) and converts to the corresponding board index ( 0-63).

convert_to_pos(index_num)[source]

Takes an board index (0-63) and converts to the corresponding board position (e.g. ‘a2’).

get_bwe()[source]

Returns the current game state as a BWE list.

get_bwe_move(bwe)[source]

Takes a BWE list and returns the move that was made.

Attributes:
  • bwe: A list of single character strings either ‘B’,’W’, or ‘E’.
Returns:
  • piece (str): Type of piece that moved e.g. 'P'
  • move (str): Move recognised in BWE e.g. 'a2a4'
update_board(bwe_matrix)[source]

Updates the game state with the latest BWE matrix after the user has played their turn. The BWE has been checked and the move has been checked with Sunfish.

exception chess.engine.EngineError(message)[source]

Exception raised for errors in the game engine.

Attributes:
message – explanation of the error
exception chess.engine.Error[source]

Base class for exceptions in this module.

class chess.engine.HiddenPrints[source]

Context manager for suppressing the print output of functions within its scope.