hanafi.dev

< back

Writing a Chess AI from Scratch in Python (Part 1)

During quarantine I, like many others, picked up chess as a hobby, a new obsession. I loved the game, but as a programmer, chess engines especially piqued my curiosity. Conceptually, the way they worked seemed simple enough – try every move, play the best one – but they were so intricate and optimized that I knew there had to be more to it. I decided to try to write my own in Python – an interesting challenge that would combine my love for programming with my newfound love for chess. I aim to continually update and release these posts, so that I can document my progress and development of the project as I work (current code found here).

0. Why Python?

Now, for those who know a thing or two about programming languages, you might be wondering: why Python? Well, my goal was never to build an AI that would destroy the best engines like Stockfish or be the first to break the 4000 Elo rating, so the speed of the language wasn’t a top priority. 

Python is a language I’m highly proficient in, and is extremely readable and quick for me to use, so it seemed only logical to use it for this challenge. That said, I may revisit this project in the future using a faster language.

 

1. Let's Play Chess

Before I could dive into creating an AI that could play chess, I needed a chess game for the AI to interact with. I didn’t want to rely on any external chess libraries for this project as my objective was to build everything from scratch. So, the first step was to create a playable chess game from scratch as well.

To do this, I started by downloading these standard chess piece icons:

as well as getting the black and white tile colors from the standard Chess.com chessboard.

I then wrote up a generic Piece class that all the specific piece classes would inherit from which would contain the piece’s information (color, location, etc.), initialized all of the starting pieces, and added a method that would display the piece with its icon in the appropriate square:

Next was setting up movement. I wrote a get_legal_moves() method for each piece that returns a list of the valid target squares for a piece on that move. For example, here is the method found in the Pawn class:

def get_legal_moves(self, board):
    moves = []
    step = -1 if self.color == WHITE else 1
    home_row = 6 if self.color == WHITE else 1
    
    # can move one step forward
    if not board.has_piece(self.col, self.row+step):
        moves.append( (self.col, self.row+step) )
    
    # if hasn't moved yet, two steps forward
        if self.row == home_row and not board.has_piece(self.col, self.row+2*step):
            moves.append( (self.col, self.row+2*step) )

    # if opponent piece diagonal to it, diagonal move
    [...]

    # if en passant pawn adjacent, diagonal move
    [...]

    # return move list
    return moves