Devlog 0x0 - Relevant Bit Masks

Initial Devlog. This edition goes over creating relevant bit masks for the rook and bishop.

Relevant Bit Masks

To start, I almost certainly don't understand this as well as the people over on the Chess Programming Wiki who have an excellent article on Magic Bitboards. If you're looking to get a better understanding of magic bitboards, I would start there. Also, my devlogs will assume a certain familiarity with my codebase for brevity, but know that the entire source code is available on Github if you ever find yourself wondering how a certain function works.

For my implementation of relevant bit masks, I am using a bitboard that represents the relevant occupancy bit count for a piece on a given square. That's a bit cumbersome and wordy so I will try and explain it in a more simple and visual way.

These are the bitboards that I am talking about:

Bishop Relevant Bit Mask            Rook Relevant Bit Mask
    6 5 5 5 5 5 5 6                 12 11 11 11 11 11 11 12
    5 5 5 5 5 5 5 5                 11 10 10 10 10 10 10 11
    5 5 7 7 7 7 5 5                 11 10 10 10 10 10 10 11
    5 5 7 9 9 7 5 5                 11 10 10 10 10 10 10 11
    5 5 7 9 9 7 5 5                 11 10 10 10 10 10 10 11
    5 5 7 7 7 7 5 5                 11 10 10 10 10 10 10 11
    5 5 5 5 5 5 5 5                 11 10 10 10 10 10 10 11
    6 5 5 5 5 5 5 6                 12 11 11 11 11 11 11 12

To get our target bitboard, we need to use a few functions already existing within the engine. We have the countBits function, which takes in a bitboard and returns the number of occupied bits within it; as well as the maskRookAttacks and maskBishopAttacks functions, which take a square and outputs a bit mask of the squares in which the rook or bishop could attack from that given square. Here's an example of a bit mask of a rook on the A1 square, as well as the result of the count bits function for this position:

Bits in mask: 12
  8   0  0  0  0  0  0  0  0
  7   1  0  0  0  0  0  0  0
  6   1  0  0  0  0  0  0  0
  5   1  0  0  0  0  0  0  0
  4   1  0  0  0  0  0  0  0
  3   1  0  0  0  0  0  0  0
  2   1  0  0  0  0  0  0  0
  1   0  1  1  1  1  1  1  0

      a  b  c  d  e  f  g  h

      Bitboard: 9079539427579068672

And here are the relevant functions in obtaining these "magic" bitboards:

pub fn countBits(bitboard: u64) u6 {
    var bitboardCopy = bitboard;
    var bits_set: usize = 0;
    while (bitboardCopy != 0) : (bits_set += 1) {
        bitboardCopy &= bitboardCopy - 1;
    }
    return @as(u6, @truncate(bits_set));
}
pub fn maskRookAttacks(square: u6) u64 {
    var attacks: u64 = @as(u64, 0);

    const targetRank: i8 = square / 8;
    const targetFile: i8 = square % 8;

    var rank: i8 = targetRank + 1;

    while (rank <= 6) {
        const result: u6 = @intCast(rank * 8 + targetFile);
        attacks |= @as(u64, 1) << result;
        rank += 1;
    }

    rank = targetRank - 1;

    while (rank >= 1) {
        const result: u6 = @intCast(rank * 8 + targetFile);
        attacks |= @as(u64, 1) << result;
        rank -= 1;
    }

    var file: i8 = targetFile + 1;

    while (file <= 6) {
        const result: u6 = @intCast(targetRank * 8 + file);
        attacks |= @as(u64, 1) << result;
        file += 1;
    }

    file = targetFile - 1;

    while (file >= 1) {
        const result: u6 = @intCast(targetRank * 8 + file);
        attacks |= @as(u64, 1) << result;
        file -= 1;
    }

    return attacks;
}

And finally, to get our desired output, we need to use the result of the count bits function on each of the bit mask bitboards, and this will tell us how many squares are able to be attacked from a given position (excluding the board edge, for magic bitboard reasons). This is as simple as a for loop that prints our desired output, and then copying it into our program for later use. It's as simple as this:

for (0..8) |rank| {
        for (0..8) |file| {
            const square: u6 = @intCast(rank * 8 + file);
            std.debug.print(" {d}", .{utils.countBits(attacks.maskRookAttacks(square))});
        }
        std.debug.print("\n", .{});
    }

These bitboard will be essential in the future for move generation on the fly. Thanks for reading! Here are some useful sources I used: