Devlog 0xE - Debugging Move Generation Part 3
Today, I took a deeper look into move generation to attempt to find the missing moves for kiwiPete depth 2 and found one of the errors mentioned in Devlog 0xC, a missing castling move.
Debugging Successes
If you read Devlog 0xC, you'll see that we were testing the position known as kiwiPete by Peter McKenzie and getting a few errors. Here's the debug output from our last session:
Total for depth 2:
Captures: 350
En passants: 1
Castles: 90
Promotions: 0
Checks: 0
Total nodes: 2038
Perft(2) found 2038 nodes in 2ms
For this position at depth 2, we expect the following output per Chess Programming's Perft Testing Results page:
Total for depth 2:
Captures: 351
En passants: 1
Castles: 91
Promotions: 0
Checks: 3
Total nodes: 2039
Last time, I suspected that the error was being too restrictive on castling moves, because we generated one less castling move than was expected. I was correct, in my previous implementation I had the following code which checked if the square on b1 or b8 was attacked, while that is not a valid check for generating possible castling moves:
// Check if path is clear
if (utils.getBit(board.occupancy[2], d1) == 0 and
utils.getBit(board.occupancy[2], c1) == 0 and
utils.getBit(board.occupancy[2], b1) == 0 and
utils.getBit(board.bitboard[@intFromEnum(bitboard.Piece.R)], a1) == 1)
{
// Check that squares king moves through are not attacked
if (!atk.isSquareAttacked(@intCast(d1), side, board, attackTable) and
!atk.isSquareAttacked(@intCast(c1), side, board, attackTable) and
!atk.isSquareAttacked(@intCast(b1), side, board, attackTable)) {
...
This code isn't properly checking for all castling moves because it doesn't matter if the square on b1 is attacked. You do need to check if the square on b1 is occupied, because the king and the rook can't castle through pieces, but it doesn't matter if a castling move would theoretically put a piece on b1 in an attacked position.
Here's the updated version with the check for isSquareAttacked on the square b1 removed:
// Check if path is clear
if (utils.getBit(board.occupancy[2], d1) == 0 and
utils.getBit(board.occupancy[2], c1) == 0 and
utils.getBit(board.occupancy[2], b1) == 0 and
utils.getBit(board.bitboard[@intFromEnum(bitboard.Piece.R)], a1) == 1)
{
// Check that squares king moves through are not attacked
if (!atk.isSquareAttacked(@intCast(d1), side, board, attackTable) and
!atk.isSquareAttacked(@intCast(c1), side, board, attackTable)) {
...
I also checked my perft test implementation - when perft testing, you want to add one capture move and one enpassant move for any given enpassant move. This is because any EP move is a capture, so appropriately we need to label it as such. Previously, I was only adding capture moves for non-EP moves. After adjusting my approach, we are now getting the correct number of nodes for both depth 2 and depth 3. Check out the new debug output:
Total for depth 2:
Captures: 351
En passants: 1
Castles: 91
Promotions: 0
Checks: 3
Quiet moves: 1597
Total nodes: 2039
Perft(2) found 2039 nodes in 1ms
We're making great progress toward an accurate and functioning move generator in Zig! Unfortunately, we're not done debugging yet, as the perft test at depth 4 for kiwiPete is off by several thousand nodes.
But still, significant progress and I am optimistic that sometime next week we will be able to begin working on our evaluation algorithm. Thanks a bunch for reading and I'll see you tomorrow :)