return std.fmt.bufPrint(&buf, "bestmove {s}{s}{s}\n", .{
@tagName(self.best_move.source),
@tagName(self.best_move.target),
if (self.best_move.prom != .none) @tagName(self.best_move.prom) else "",
}) catch unreachable;
}
fn negamax(self: *@This(), alpha_orig: isize, beta_orig: isize, depth: usize) !isize {
if (depth == 0) return self.evaluate();
var alpha = alpha_orig;
var beta = beta_orig;
// TODO
// nodes += 1;
var best_sofar: BitMove = undefined;
var ml = try MoveList.init(0);
try self.generateMoves(&ml);
for (ml.slice()) |move, idx| {
var bck: GameState = undefined;
self.backup(&bck);
self.ply += 1;
if (!self.makeMove(move, .all)) {
self.ply -= 1;
self.restore(&bck);
continue;
}
const score = -try self.negamax(-beta, -alpha, depth - 1);
self.ply -= 1;
self.restore(&bck);
// fail-hard beta cutoff
if (score >= beta) {
// node (move) fails high
return beta;
}
// found a better move
if (score > alpha) {
// PV (principal validation) node
alpha = score;
// if root move
if (self.ply == 0) {
best_sofar = ml.get(idx);
}
}
}
if (alpha_orig != alpha) {
// init best move
self.best_move = best_sofar;
}
// node (move) fails low
return alpha;
}