const std = @import("std");
const Board = @import("Board.zig");
const zobrist = @import("zobrist.zig");
pub const Perft = struct {
nodes: usize = 0,
gs: Board.GameState,
pub fn perftDriver(self: *@This(), depth: usize) !void {
if (depth == 0) {
// count nodes
self.nodes += 1;
return;
}
var ml = try Board.MoveList.init(0);
try self.gs.generateMoves(&ml);
for (ml.slice()) |mp| {
var bk: Board.GameState = undefined;
self.gs.backup(&bk);
if (!self.gs.makeMove(mp.move, .all)) {
self.gs.restore(&bk);
continue;
}
// only for testing incremental Zobrist hash updates!
// {
// const orig = self.gs.hash;
// zobrist.updateHash(&self.gs);
// if (orig != self.gs.hash) {
// std.log.err("{any}", .{mp.move});
// bk.show();
// self.gs.show();
// return error.HashMismatch;
// }
// }
try self.perftDriver(depth - 1);
self.gs.restore(&bk);
}
}
fn perftTest(self: *@This(), depth: usize) !void {
var ml = try Board.MoveList.init(0);
try self.gs.generateMoves(&ml);
for (ml.slice()) |mp| {
var bk: Board.GameState = undefined;
self.gs.backup(&bk);
if (!self.gs.makeMove(mp.move, .all)) {
self.gs.restore(&bk);
continue;
}
const cummulative_nodes = self.nodes;
try self.perftDriver(depth - 1);
const old_nodes = self.nodes - cummulative_nodes;
self.gs.restore(&bk);
mp.move.show();
std.debug.print("nodes: {d}\n", .{old_nodes});
}
}
};
test "Perft" {
var gs = try Board.GameState.init(
std.testing.allocator,
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -",
);
defer gs.deinit();
{
var p = Perft{ .gs = gs };
try p.perftDriver(3);
try std.testing.expectEqual(@as(usize, 2812), p.nodes);
}
{
var p = Perft{ .gs = gs };
try p.perftDriver(4);
try std.testing.expectEqual(@as(usize, 43238), p.nodes);
}
}
test "PerfTest" {
var gs = try Board.GameState.init(
std.testing.allocator,
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - ",
);
defer gs.deinit();
gs.show();
var p = Perft{ .gs = gs };
var timer = try std.time.Timer.start();
try p.perftTest(5);
std.debug.print("Checked {d} nodes in {d} ms\n", .{ p.nodes, timer.read() / std.time.ns_per_ms });
}