G4HJL4QLASCZBWYGCEXYYRBYL7UVX6ENELHRRBFB5UAXXGVVGZGQC
5VEZOTVGONDXPL6WC7UWJ4UZXAVJDKOAPC6ATCLSWDDH7AML57UQC
WZNDXPQM23LLYDZ3ZCTB4GOVRJLL7PMSEPSYBVFX6AOTJOZ5YYZQC
A46B5KNQFPTZEIL2JZKD2CQELGU3COE6FGTV2ABS5U7DVTVDGEBQC
GLTWBA5NKQIHAL23SWKCFOZQH62W6B3LX4RC5GFQUEQUIYN4OKSAC
FOORIA7SEZCLKDBNMV6KEDTQOJJVAH57BQFBRSQMET6FERJPHCVQC
BL3ZR4OWJM54HFXUNMUZKB5YQYVBT7ETFIXCOXWL6S5SZFM6IFDQC
DKK2G2S3X7KFEJBHGLUEVTBBW6UBR36XR7ZBUWG5GO6JYXWGJOOAC
TB4YBE4CMWCLSKJ43QF6IU5HVYUUO33BLXVG7XDRLJS3IFIBQLYAC
THYIOCFC72V4ZMTLMGRGRIBARXT2LPSQCL2XNOIBI22QRVYUMIYQC
NYVHTMLLUMBKBQSWO6SQDQ5KFACQLNCBRDDM5BH3OU6WWT54KC2AC
LWF4H4TMNZYUKTCVDALXQLBE3U5AXQJ3LLNUJJ7YGTLG6T2O33JAC
6S6HHKOXBQ5ARL7S7PKMPDIAH3VTPOAPWWSMGY37RP2R2U662FBAC
const std = @import("std");
const Chess = @import("Chess.zig");
const Board = @import("Board.zig");
const nnue = @cImport({
@cInclude("nnue.h");
});
// https://tests.stockfishchess.org/nns
const NNUE_FILE = "nn-62ef826d1a6d.nnue";
test "NNUE" {
nnue.nnue_init(NNUE_FILE);
try std.testing.expectEqual(
@as(c_int, 57),
nnue.nnue_evaluate_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"),
);
}
// convert our piece codes to Stockfish NNUE codes
const NNUE_PIECES: [12]c_int = .{ 6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7 };
// convert our square indices to Stockfish NNUE indices
const NNUE_SQUARES: [64]Board.Square = .{
// zig fmt: off
.a1, .b1, .c1, .d1, .e1, .f1, .g1, .h1,
.a2, .b2, .c2, .d2, .e2, .f2, .g2, .h2,
.a3, .b3, .c3, .d3, .e3, .f3, .g3, .h3,
.a4, .b4, .c4, .d4, .e4, .f4, .g4, .h4,
.a5, .b5, .c5, .d5, .e5, .f5, .g5, .h5,
.a6, .b6, .c6, .d6, .e6, .f6, .g6, .h6,
.a7, .b7, .c7, .d7, .e7, .f7, .g7, .h7,
.a8, .b8, .c8, .d8, .e8, .f8, .g8, .h8,
};
// zig fmt: on
test "NNUE_SQUARES" {
{
const square = @enumToInt(Board.Square.a8);
const nnue_square_enum = NNUE_SQUARES[square];
std.debug.assert(nnue_square_enum == Board.Square.a1);
}
{
const square = @enumToInt(Board.Square.f3);
const nnue_square_enum = NNUE_SQUARES[square];
std.debug.assert(nnue_square_enum == Board.Square.f6);
}
}
// 2*(pawns(8), knights(2), bishops(2), rooks(2), queen, king)
// +1 for the ending empty piece that Stockfish nnue expects
const PIECE_COUNT = 2 * 8 + 2 * 2 + 2 * 2 + 2 * 2 + 2 + 2 + 1;
var nnue_initialized = false;
pub fn evaluate(gs: *const Board.GameState) isize {
std.debug.assert(PIECE_COUNT == 33);
var sf_pieces: [PIECE_COUNT]c_int = undefined;
var sf_squares: [PIECE_COUNT]c_int = undefined;
var index: usize = 2; // 0 and 1 are reserved for the two kings
var bitboard: Board.BoardType = undefined;
for (gs.bitboards) |bb, idx| {
bitboard = @bitCast(Board.BoardType, bb);
while (bitboard != 0) {
const piece = @intToEnum(Chess.PE, idx);
const square = @ctz(bitboard);
switch (piece) {
.K => {
sf_pieces[0] = NNUE_PIECES[@enumToInt(piece)];
sf_squares[0] = @enumToInt(NNUE_SQUARES[square]);
},
.k => {
sf_pieces[1] = NNUE_PIECES[@enumToInt(piece)];
sf_squares[1] = @enumToInt(NNUE_SQUARES[square]);
},
else => {
sf_pieces[index] = NNUE_PIECES[@enumToInt(piece)];
sf_squares[index] = @enumToInt(NNUE_SQUARES[square]);
index += 1;
},
}
bitboard ^= @as(Board.BoardType, 1) << @intCast(Board.SquareType, square);
}
}
// zero shows Stockfish nnue that there are no more pieces.
sf_pieces[index] = 0;
sf_squares[index] = 0;
if (!nnue_initialized) {
nnue.nnue_init(NNUE_FILE);
nnue_initialized = true;
}
return @intCast(isize, nnue.nnue_evaluate(@enumToInt(gs.side), &sf_pieces, &sf_squares));
}
test "nnue.evaluate" {
var gs = try Board.GameState.init(null);
try std.testing.expectEqual(@as(isize, 57), evaluate(&gs));
}
score += MATERIAL_SCORE[idx] * bits;
while (board != 0) {
const bit = @ctz(board);
score += switch (@intToEnum(Chess.PE, idx)) {
.P => PAWN_SCORE[bit],
.N => KNIGHT_SCORE[bit],
.B => BISHOP_SCORE[bit],
.R => ROOK_SCORE[bit],
.K => KING_SCORE[bit],
.Q => break,
.p => -PAWN_SCORE[@enumToInt(MIRROR_SCORE[bit])],
.n => -KNIGHT_SCORE[@enumToInt(MIRROR_SCORE[bit])],
.b => -BISHOP_SCORE[@enumToInt(MIRROR_SCORE[bit])],
.r => -ROOK_SCORE[@enumToInt(MIRROR_SCORE[bit])],
.k => -KING_SCORE[@enumToInt(MIRROR_SCORE[bit])],
.q => break,
else => 0,
};
board ^= @as(Board.BoardType, 1) << @intCast(u6, bit);
}
}
switch (gs.side) {
.white => return score,
.black => return -score,
}
}
test "evaluate" {
{
// remove white queen
var gs = try Board.GameState.init("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR w KQkq - 0 1");
try std.testing.expectEqual(@as(isize, -1000), evaluate(&gs));
}
{
// remove white queen, but black comes
var gs = try Board.GameState.init("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR b KQkq - 0 1");
try std.testing.expectEqual(@as(isize, 1000), evaluate(&gs));
}
{
// Pe2e4
var gs = try Board.GameState.init("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1");
try std.testing.expectEqual(@as(isize, 30), evaluate(&gs));
}
{
// Pe2e4 pe7e5 Ng1f3
var gs = try Board.GameState.init("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1");
// gs.show();
try std.testing.expectEqual(@as(isize, 30), evaluate(&gs));
}
}
fn evaluate(self: @This()) isize {
var score: isize = 0;
for (self.bitboards) |bb, idx| {
var board = @bitCast(BoardType, bb);
const bits = @popCount(board);
score += eval.MATERIAL_SCORE[idx] * bits;
while (board != 0) {
const bit = @ctz(board);
score += switch (@intToEnum(Chess.PE, idx)) {
.P => eval.PAWN_SCORE[bit],
.N => eval.KNIGHT_SCORE[bit],
.B => eval.BISHOP_SCORE[bit],
.R => eval.ROOK_SCORE[bit],
.K => eval.KING_SCORE[bit],
.Q => break,
.p => -eval.PAWN_SCORE[@enumToInt(eval.MIRROR_SCORE[bit])],
.n => -eval.KNIGHT_SCORE[@enumToInt(eval.MIRROR_SCORE[bit])],
.b => -eval.BISHOP_SCORE[@enumToInt(eval.MIRROR_SCORE[bit])],
.r => -eval.ROOK_SCORE[@enumToInt(eval.MIRROR_SCORE[bit])],
.k => -eval.KING_SCORE[@enumToInt(eval.MIRROR_SCORE[bit])],
.q => break,
else => 0,
};
board ^= @as(BoardType, 1) << @intCast(u6, bit);
}
}
test "evaluate" {
{
// remove white queen
var gs = try GameState.init("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR w KQkq - 0 1");
try std.testing.expectEqual(@as(isize, -1000), gs.evaluate());
}
{
// remove white queen, but black comes
var gs = try GameState.init("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR b KQkq - 0 1");
try std.testing.expectEqual(@as(isize, 1000), gs.evaluate());
}
{
// Pe2e4
var gs = try GameState.init("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1");
try std.testing.expectEqual(@as(isize, 30), gs.evaluate());
}
{
// Pe2e4 pe7e5 Ng1f3
var gs = try GameState.init("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1");
// gs.show();
try std.testing.expectEqual(@as(isize, 30), gs.evaluate());
}
}