Mon 17 March 2025
Chess Programming Part 1
While exploring an interesting game engine which compiles python code to WASM; I thought, there's no better way to have fun than to write a chess engine. This was the start of a one month long project which led me to create my own chess AI.
Chess programming is a topic that has been written about extensively, it has been a proving ground for some of the first AI researchers and even today it is still used as a benchmark for techniques in applied research.
This article is the first part in a series of three in which I attempt to distill some insight into the application of algorithms in software, namely chess programming. In the hopes that it may inspire how you approach solving similar problems in the future.
Bit Operations
If you've ever done some leetcoding or any other live algorithm style interview prep in software engineering you might have come across the odd question on bit operations and wondered if you'll ever need to actually do these in your day to day job. I'm not here to answer that, but I am here to show you how it applies to chess programming.
Firstly what are bit operations:
a bitwise operation operates on a bit string, a bit array or a binary numeral at the level of its individual bits. It is a fast and simple action, basic to the higher-level arithmetic operations and directly supported by the processor.
Essentially bitwise operations are physical operations that are embedded into the processor, they consist of your logical AND, OR, XOR and bit shifts. The mathematical operations that you're familiar with (such as Add, Subtract etc.) are all built upon bitwise operations.
Here's an example of a NOT
operation:
NOT 0111 (decimal 7)
= 1000 (decimal 8)
And an example of an AND
operation:
1010 (decimal 5)
AND 1100 (decimal 12)
= 1000 (decimal 8)
We can see these operations supported in python:
>>> 0b1100 & 0b1010
8
>>> bin(0b1100 & 0b1010)
'0b1000'
There's two ways we can shift bits. (Left and right) Here's an example in python:
>>> # Left
>>> bin(0b0010 << 1)
'0b100'
>>> # Right
>>> bin(0b0010 >> 1)
'0b1'
Explaining how the processor does addition is out of scope of this blog post, but put together some of these bit operators and you've got yourself a function that can do addition.
Bitboards
Back to the chess:
We are doing all of this essentially to get as close to the processor as possible, since we want the most performance out of a computer as we can by avoiding any unnecessary operations.
In chess programming we will use a single number to keep
track of all the positions of pieces on the board. This is
what we call a "bitboard". We will have a few bitboards
to maintain the game's state. The first one will be 'board'
which tracks all the pieces. At the start of the game
this number will be: 0xFFFF00000000FFFF
. Which is
hexadecimal for 18446462598732906495
. If we print the
binary form of this number and format it over 8 rows we get
the following:
Bitboard:
8 1 1 1 1 1 1 1 1
7 1 1 1 1 1 1 1 1
6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0
2 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
a b c d e f g h
There are a few other bitboards to track during a game of chess. There will be 2 boards for each colour (white and black). And there will be a bitboard for each type (6). Which gives us a total of 9 bitboards, or 9 numbers to cover the entire game state of chess. Here's two more examples of bitboards, one for pawns and one for white pieces:
>>> pawns = 0x00FF00000000FF00
>>> white = 0x000000000000FFFF
Now we can uses these three numbers to give us the answer to some questions for example; Where are all the white pawns?
>>> board & pawns & white
65280
Bitboard (65280):
8 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0
2 1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 0 0
a b c d e f g h
During a game of chess you'll use these to calculate which
pieces are under attack. (e.g. given:
knightsmove & theirpieces
the result will be 0
if
there's no piece under threat). We can also use these
bitboards workout where the valid moves are with
moves & ~board
. (Moves and not board).
A chess AI that's looking 4 moves ahead will have ~5mil positions to consider, so having these operations done on a low level can help boost the performance of the software. The more you squeeze out of performance the more accurate your chess AI will be, because this improves it's ability to look into the future.
Beyond Chess
In summary learning about how bitboards are applied to chess programming has allowed me to understand how bit operations can be applied practically in software. Before I dived into learning how chess is programmed I had only imagined keeping track of the board state using several objects and an array, however I now see there's more than one way to crack an egg; the board state egg.
I hope the article helps to providing insight into practical applications of bit operators and leaves them to be considered as valuable for solving technical problems or at least reminds software engineers that we have them in our toolbelt.