RNEXG5IFDKMHSUR6RMNTI3Y32ORLVMZ6UJYKHLV2XBMT2QONBTVQC
WSRRWTTVBE2K6NKPS54CXMY3QYK5YTDRGEH3YJ53VQ2XW5UM5O2QC
G4HJL4QLASCZBWYGCEXYYRBYL7UVX6ENELHRRBFB5UAXXGVVGZGQC
A46B5KNQFPTZEIL2JZKD2CQELGU3COE6FGTV2ABS5U7DVTVDGEBQC
BL3ZR4OWJM54HFXUNMUZKB5YQYVBT7ETFIXCOXWL6S5SZFM6IFDQC
TB4YBE4CMWCLSKJ43QF6IU5HVYUUO33BLXVG7XDRLJS3IFIBQLYAC
DKK2G2S3X7KFEJBHGLUEVTBBW6UBR36XR7ZBUWG5GO6JYXWGJOOAC
THYIOCFC72V4ZMTLMGRGRIBARXT2LPSQCL2XNOIBI22QRVYUMIYQC
6S6HHKOXBQ5ARL7S7PKMPDIAH3VTPOAPWWSMGY37RP2R2U662FBAC
NYVHTMLLUMBKBQSWO6SQDQ5KFACQLNCBRDDM5BH3OU6WWT54KC2AC
5VEZOTVGONDXPL6WC7UWJ4UZXAVJDKOAPC6ATCLSWDDH7AML57UQC
const std = @import("std");
const Str = []const u8;
const Chess = @import("Chess.zig");
const BoardType = @import("Board.zig").BoardType;
const BitMove = @import("Board.zig").BitMove;
const BitMoveType = @import("Board.zig").BitMoveType;
const GameState = @import("Board.zig").GameState;
const MoveList = @import("Board.zig").MoveList;
const SquareType = @import("Board.zig").SquareType;
const eval = @import("eval.zig");
const nnue = @import("nnue.zig");
var buf: [16]u8 = undefined;
fn searchPosition(gs: *GameState, depth: usize) Str {
const ALPHA = -5000;
const BETA = 5000;
const score = negamax(gs, ALPHA, BETA, depth) catch unreachable;
std.debug.print("info score cp {d} depth {d}\n", .{ score, depth });
if (@bitCast(BitMoveType, gs.best_move) != 0) {
return std.fmt.bufPrint(&buf, "bestmove {s}{s}{s}\n", .{
@tagName(gs.best_move.source),
@tagName(gs.best_move.target),
if (gs.best_move.prom != .none) @tagName(gs.best_move.prom) else "",
}) catch unreachable;
} else return "";
}
test "searchPosition" {
var gs = try GameState.init(null);
gs.show();
std.debug.print("{s}", .{searchPosition(&gs, 4)});
}
fn negamax(gs: *GameState, alpha_orig: isize, beta: isize, depth: usize) !isize {
if (depth == 0) return try quiescenceSearch(gs, alpha_orig, beta);
var alpha = alpha_orig;
// TODO
// nodes += 1;
const king = switch (gs.side) {
.white => @intCast(SquareType, @ctz(@bitCast(BoardType, gs.bitboards[@enumToInt(Chess.PE.K)]))),
.black => @intCast(SquareType, @ctz(@bitCast(BoardType, gs.bitboards[@enumToInt(Chess.PE.k)]))),
};
var in_check = gs.isSquareAttacked(king, gs.side.enemy());
var legal_moves: usize = 0;
var best_sofar: BitMove = @bitCast(BitMove, @as(BitMoveType, 0));
var ml = try MoveList.init(0);
try gs.generateMoves(&ml);
for (ml.slice()) |move, idx| {
var bck: GameState = undefined;
gs.backup(&bck);
gs.ply += 1;
if (!gs.makeMove(move, .all)) {
gs.ply -= 1;
gs.restore(&bck);
continue;
}
legal_moves += 1;
const score = -try negamax(gs, -beta, -alpha, depth - 1);
gs.ply -= 1;
gs.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 (gs.ply == 0) {
best_sofar = ml.get(idx);
}
}
}
// Detect check- and stalemate
if (legal_moves == 0) {
if (in_check) {
// Adding ply helps the engine finding fastest mates.
return eval.CHECKMATE_SCORE + @intCast(isize, gs.ply);
} else {
return eval.STALEMATE_SCORE;
}
}
if (alpha_orig != alpha) {
// init best move
gs.best_move = best_sofar;
}
// node (move) fails low
return alpha;
}
// quiescenceSearch helps the engine not thow away its pieces because of
// a temporary seemingly good move. (Horizon effect)
fn quiescenceSearch(gs: *GameState, alpha_orig: isize, beta: isize) !isize {
var alpha = alpha_orig;
const evaluation = eval.evaluate(gs);
// fail-hard beta cutoff
if (evaluation >= beta) {
// node (move) fails high
return beta;
}
// found a better move
if (evaluation > alpha) {
// PV (principal validation) node
alpha = evaluation;
}
var ml = try MoveList.init(0);
try gs.generateMoves(&ml);
for (ml.slice()) |move| {
var bck: GameState = undefined;
gs.backup(&bck);
gs.ply += 1;
if (!gs.makeMove(move, .captures)) {
gs.ply -= 1;
gs.restore(&bck);
continue;
}
const score = -try quiescenceSearch(gs, -beta, -alpha);
gs.ply -= 1;
gs.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;
}
}
// node (move) fails low
return alpha;
}
}
fn searchPosition(self: *@This(), depth: usize) Str {
const ALPHA = -5000;
const BETA = 5000;
const score = self.negamax(ALPHA, BETA, depth) catch unreachable;
std.debug.print("info score cp {d} depth {d}\n", .{ score, depth });
if (@bitCast(BitMoveType, self.best_move) != 0) {
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;
} else return "";
fn negamax(self: *@This(), alpha_orig: isize, beta: isize, depth: usize) !isize {
if (depth == 0) return try self.quiescenceSearch(alpha_orig, beta);
var alpha = alpha_orig;
// TODO
// nodes += 1;
const king = switch (self.side) {
.white => @intCast(SquareType, @ctz(@bitCast(BoardType, self.bitboards[@enumToInt(Chess.PE.K)]))),
.black => @intCast(SquareType, @ctz(@bitCast(BoardType, self.bitboards[@enumToInt(Chess.PE.k)]))),
};
var in_check = self.isSquareAttacked(king, self.side.enemy());
var legal_moves: usize = 0;
var best_sofar: BitMove = @bitCast(BitMove, @as(BitMoveType, 0));
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;
}
legal_moves += 1;
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);
}
}
}
// Detect check- and stalemate
if (legal_moves == 0) {
if (in_check) {
// Adding ply helps the engine finding fastest mates.
return eval.CHECKMATE_SCORE + @intCast(isize, self.ply);
} else {
return eval.STALEMATE_SCORE;
}
}
if (alpha_orig != alpha) {
// init best move
self.best_move = best_sofar;
}
// node (move) fails low
return alpha;
}
// quiescenceSearch helps the engine not thow away its pieces because of
// a temporary seemingly good move.
fn quiescenceSearch(self: *@This(), alpha_orig: isize, beta: isize) !isize {
var alpha = alpha_orig;
const evaluation = nnue.evaluate(self);
// fail-hard beta cutoff
if (evaluation >= beta) {
// node (move) fails high
return beta;
}
// found a better move
if (evaluation > alpha) {
// PV (principal validation) node
alpha = evaluation;
}
var ml = try MoveList.init(0);
try self.generateMoves(&ml);
for (ml.slice()) |move| {
var bck: GameState = undefined;
self.backup(&bck);
self.ply += 1;
if (!self.makeMove(move, .captures)) {
self.ply -= 1;
self.restore(&bck);
continue;
}
const score = -try self.quiescenceSearch(-beta, -alpha);
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;
}
}
// node (move) fails low
return alpha;
}