const std = @import("std");
const Chess = @import("Chess.zig");
const Board = @import("Board.zig");
// Scoring layout:
// -infinity, -MATE_VALUE, -CHECKMATE_SCORE, score, CHECKMATE_SCORE, MATE_VALUE, infinity
pub const INFINITY = 50_000;
pub const MATE_VALUE = 49_000;
pub const CHECKMATE_SCORE = 48000;
pub const STALEMATE_SCORE = 0;
// WARNING: this must be in sync with Chess.PE!
pub const MATERIAL_SCORE: [12]i16 = .{
100,
300,
350,
500,
1000,
10_000,
-100,
-300,
-350,
-500,
-1000,
-10_000,
};
pub const PAWN_SCORE: [64]i8 = .{
// zig fmt: off
90, 90, 90, 90, 90, 90, 90, 90,
30, 30, 30, 40, 40, 30, 30, 30,
20, 20, 20, 30, 30, 30, 20, 20,
10, 10, 10, 20, 20, 10, 10, 10,
5, 5, 10, 20, 20, 5, 5, 5,
0, 0, 0, 5, 5, 0, 0, 0,
0, 0, 0, -10, -10, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// zig fmt: on
pub const KNIGHT_SCORE: [64]i8 = .{
// zig fmt: off
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 10, 10, 0, 0, -5,
-5, 5, 20, 20, 20, 20, 5, -5,
-5, 10, 20, 30, 30, 20, 10, -5,
-5, 10, 20, 30, 30, 20, 10, -5,
-5, 5, 20, 10, 10, 20, 5, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, -10, 0, 0, 0, 0, -10, -5
};
// zig fmt: on
pub const BISHOP_SCORE: [64]i8 = .{
// zig fmt: off
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 10, 10, 0, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 10, 0, 0, 0, 0, 10, 0,
0, 30, 0, 0, 0, 0, 30, 0,
0, 0, -10, 0, 0, -10, 0, 0
};
// zig fmt: on
pub const ROOK_SCORE: [64]i8 = .{
// zig fmt: off
50, 50, 50, 50, 50, 50, 50, 50,
50, 50, 50, 50, 50, 50, 50, 50,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 10, 20, 20, 10, 0, 0,
0, 0, 0, 20, 20, 0, 0, 0
};
// zig fmt: on
pub const KING_SCORE: [64]i8 = .{
// zig fmt: off
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 5, 5, 5, 0, 0,
0, 5, 5, 10, 10, 5, 5, 0,
0, 5, 10, 20, 20, 10, 5, 0,
0, 5, 10, 20, 20, 10, 5, 0,
0, 0, 5, 10, 10, 5, 0, 0,
0, 5, 5, -5, -5, 0, 5, 0,
0, 0, 5, 0, -15, 0, 10, 0
};
// zig fmt: on
// mirror positional score tables for opposite side
pub const MIRROR_SCORE: [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
pub fn evaluate(gs: *const Board.GameState) isize {
var score: isize = 0;
for (gs.bitboards, 0..) |bb, idx| {
var board = @as(Board.BoardType, @bitCast(bb));
const bits = @popCount(board);
score += MATERIAL_SCORE[idx] * bits;
while (board != 0) {
const bit = @ctz(board);
score += switch (@as(Chess.PE, @enumFromInt(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[@intFromEnum(MIRROR_SCORE[bit])],
.n => -KNIGHT_SCORE[@intFromEnum(MIRROR_SCORE[bit])],
.b => -BISHOP_SCORE[@intFromEnum(MIRROR_SCORE[bit])],
.r => -ROOK_SCORE[@intFromEnum(MIRROR_SCORE[bit])],
.k => -KING_SCORE[@intFromEnum(MIRROR_SCORE[bit])],
.q => break,
else => 0,
};
board ^= @as(Board.BoardType, 1) << @as(u6, @intCast(bit));
}
}
switch (gs.side) {
.white => return score,
.black => return -score,
}
}
test "evaluate" {
{
// remove white queen
var gs = try Board.GameState.init(
std.testing.allocator,
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR w KQkq - 0 1",
);
defer gs.deinit();
try std.testing.expectEqual(@as(isize, -1000), evaluate(&gs));
}
{
// remove white queen, but black comes
var gs = try Board.GameState.init(
std.testing.allocator,
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR b KQkq - 0 1",
);
defer gs.deinit();
try std.testing.expectEqual(@as(isize, 1000), evaluate(&gs));
}
{
// Pe2e4
var gs = try Board.GameState.init(
std.testing.allocator,
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1",
);
defer gs.deinit();
try std.testing.expectEqual(@as(isize, 30), evaluate(&gs));
}
{
// Pe2e4 pe7e5 Ng1f3
var gs = try Board.GameState.init(
std.testing.allocator,
"rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1",
);
defer gs.deinit();
// gs.show();
try std.testing.expectEqual(@as(isize, 30), evaluate(&gs));
}
}