Devlog 0x3 - General Improvements and Printing Current Game State
Devlog 0x3. Today, I implemented a printBoard function that iterates through the list of piece bitboards and prints a representation of the current game state, along with general improvements to the popBit() and setBit() functions.
Improved Bit Manipulation Functions
Today, I refined the way bitboards are manipulated in my chess engine. Instead of returning a new bitboard value (and requiring the caller to store or handle it), I now pass a pointer to the existing bitboard and modify it directly in place. This approach improves both readability and performance by minimizing unnecessary copies or assignments.
pub fn setBit(bitboard: *u64, square: u6) void {
bitboard.* |= (@as(u64, 1) << square);
}
Previously, I returned a u64 after setting the bit, forcing me assign that value back to the bitboard. Now, the bitboard is updated directly.
pub fn popBit(bitboard: *u64, square: u6) void {
if (getBit(bitboard.*, square) != 0) {
bitboard.* ^= (@as(u64, 1) << square);
}
}
Here as well, the bit is cleared (“popped”) right in the provided bitboard’s memory address. No need for an intermediate variable to hold a modified return value. These changes help to make the code more straightforward and far less verbose.
printBoard() function
The printBoard() function iterates through a list of piece bitboards and prints out a readable chessboard layout in the console. This function is almost purely for debugging purposes and won't be used by the engine in any meaningful way.
pub fn printBoard() void {
std.debug.print("\n", .{});
for (0..8) |rank| {
for (0..8) |file| {
const square: u6 = @intCast(rank * 8 + file);
if (file == 0) std.debug.print(" {d} ", .{8 - rank});
var piece: i5 = -1;
for (0..12) |bitboardPiece| {
if (getBit(bb.bitboards[bitboardPiece], square) != 0) {
piece = @intCast(bitboardPiece);
break;
} else {
piece = -1;
}
}
if (piece == -1) {
std.debug.print(" .", .{});
} else {
std.debug.print(" {s}", .{bb.unicodePieces[@intCast(piece)]});
}
}
std.debug.print("\n", .{});
}
std.debug.print("\n a b c d e f g h\n\n", .{});
const side = if (bb.sideToMove == 0) "white" else "black";
std.debug.print("Side: {s}", .{side});
}
Essentially, if a set bit is found within a given pieces bitboard, the appropriate unicode piece representation is printed. If no set bit is found at a square, we print a dot, indicating an empty space. This function allows us to easily look at the current state without needing a separate GUI.
Next Steps
- Move Generation: Now that bitboard manipulation is streamlined, I will further develop my move-generation functions.
- Performance & Cleanliness: Explore further opportunities for optimization, keeping the codebase clear and efficient.
Thanks for reading, and I'll see you tomorrow :)