Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/bitboard.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const types = @import("types.zig");
const attacks = @import("attacks.zig");
const tables = @import("tabeles.zig");
const eval = @import("evaluation.zig");
const zobrist = @import("zobrist.zig");
const print = std.debug.print;

pub fn print_board(bitboard: types.Bitboard) void {
Expand Down Expand Up @@ -287,6 +288,9 @@ pub fn fan_pars(fen: []const u8, board: *types.Board) !void {
} else {
return FenError.InvalidEnPassant;
}

// Compute Zobrist hash from scratch
board.hash = zobrist.compute_hash(board);
}

pub inline fn get_all_attackers(board: *const types.Board, square: u6, occupied: u64) u64 {
Expand Down
111 changes: 93 additions & 18 deletions src/evaluation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ pub const Evaluat = struct {
var king_score = [_]i32{ 0, 0 };
var additional_material_score = [_]i32{ 0, 0 };
var mobility_score = [_]i32{ 0, 0 };
var white_passed_bb: u64 = 0;
var black_passed_bb: u64 = 0;

// check if pawn is passed
const isPassedPawn = struct {
Expand Down Expand Up @@ -576,7 +578,9 @@ pub const Evaluat = struct {
}

// Passed pawn evaluation
if (isPassedPawn(sq, types.Color.White, black_pawns, white_pawns)) {
const is_passed_w = isPassedPawn(sq, types.Color.White, black_pawns, white_pawns);
if (is_passed_w) {
white_passed_bb |= types.squar_bb[sq];
var tmp_sc = get_passed_pawn_score(sq);
pawn_structure_score[0] += tmp_sc[0];
pawn_structure_score[1] += tmp_sc[1];
Expand All @@ -590,19 +594,28 @@ pub const Evaluat = struct {
}

// Pawn is supported?
if ((white_pawn_attacks & types.squar_bb[sq]) != 0) {
const is_supported_w = (white_pawn_attacks & types.squar_bb[sq]) != 0;
if (is_supported_w) {
const tmp_sc = get_supported_pawn_bonus(rank);
pawn_structure_score[0] += tmp_sc[0];
pawn_structure_score[1] += tmp_sc[1];
}

// Pawn phalanx (adjacent pawns on same rank)
if (file < 7 and (white_pawns & types.squar_bb[sq + 1]) != 0) {
const has_phalanx_w = file < 7 and (white_pawns & types.squar_bb[sq + 1]) != 0;
if (has_phalanx_w) {
const tmp_sc = get_phalanx_score(rank);
pawn_structure_score[0] += tmp_sc[0];
pawn_structure_score[1] += tmp_sc[1];
}

// Connected passed pawn bonus: passed + supported or phalanx
if (is_passed_w and (is_supported_w or has_phalanx_w)) {
const tmp_sc = get_connected_passer_bonus(rank);
pawn_structure_score[0] += tmp_sc[0];
pawn_structure_score[1] += tmp_sc[1];
}

// Threats
var b1 = att & black_knight;
if (b1 != 0) {
Expand Down Expand Up @@ -659,7 +672,9 @@ pub const Evaluat = struct {
}

// Passed pawn evaluation
if (isPassedPawn(sq, types.Color.Black, white_pawns, black_pawns)) {
const is_passed_b = isPassedPawn(sq, types.Color.Black, white_pawns, black_pawns);
if (is_passed_b) {
black_passed_bb |= types.squar_bb[sq];
var tmp_sc = get_passed_pawn_score(sq ^ 56); // Flip for black
pawn_structure_score[0] -= tmp_sc[0];
pawn_structure_score[1] -= tmp_sc[1];
Expand All @@ -673,19 +688,28 @@ pub const Evaluat = struct {
}

// Pawn is supported?
if ((black_pawn_attacks & types.squar_bb[sq]) != 0) {
const is_supported_b = (black_pawn_attacks & types.squar_bb[sq]) != 0;
if (is_supported_b) {
const tmp_sc = get_supported_pawn_bonus(7 - rank);
pawn_structure_score[0] -= tmp_sc[0];
pawn_structure_score[1] -= tmp_sc[1];
}

// Pawn phalanx
if (file < 7 and (black_pawns & types.squar_bb[sq + 1]) != 0) {
const has_phalanx_b = file < 7 and (black_pawns & types.squar_bb[sq + 1]) != 0;
if (has_phalanx_b) {
const tmp_sc = get_phalanx_score(7 - rank);
pawn_structure_score[0] -= tmp_sc[0];
pawn_structure_score[1] -= tmp_sc[1];
}

// Connected passed pawn bonus: passed + supported or phalanx
if (is_passed_b and (is_supported_b or has_phalanx_b)) {
const tmp_sc = get_connected_passer_bonus(7 - rank);
pawn_structure_score[0] -= tmp_sc[0];
pawn_structure_score[1] -= tmp_sc[1];
}

// Threats
var b1 = att & white_knight;
if (b1 != 0) {
Expand Down Expand Up @@ -1043,6 +1067,29 @@ pub const Evaluat = struct {
additional_material_score[1] += seventh_rank_bonus[1];
}

// Rook behind passed pawn
{
const passers_on_file = (white_passed_bb | black_passed_bb) & file_mask;
if (passers_on_file != 0) {
const own_passers = white_passed_bb & file_mask;
if (own_passers != 0) {
const lowest_passer_sq: u6 = @intCast(util.lsb_index(own_passers));
if (sq < lowest_passer_sq) {
additional_material_score[0] += 15;
additional_material_score[1] += 25;
}
}
const enemy_passers = black_passed_bb & file_mask;
if (enemy_passers != 0) {
const highest_passer_sq: u6 = @intCast(63 - @clz(enemy_passers));
if (sq > highest_passer_sq) {
additional_material_score[0] += 15;
additional_material_score[1] += 25;
}
}
}
}

// Threats
var b1 = mobility & black_pawns;
if (b1 != 0) {
Expand Down Expand Up @@ -1122,6 +1169,29 @@ pub const Evaluat = struct {
additional_material_score[1] -= second_rank_bonus[1];
}

// Rook behind passed pawn
{
const passers_on_file = (white_passed_bb | black_passed_bb) & file_mask;
if (passers_on_file != 0) {
const own_passers = black_passed_bb & file_mask;
if (own_passers != 0) {
const highest_passer_sq: u6 = @intCast(63 - @clz(own_passers));
if (sq > highest_passer_sq) {
additional_material_score[0] -= 15;
additional_material_score[1] -= 25;
}
}
const enemy_passers = white_passed_bb & file_mask;
if (enemy_passers != 0) {
const lowest_passer_sq: u6 = @intCast(util.lsb_index(enemy_passers));
if (sq < lowest_passer_sq) {
additional_material_score[0] -= 15;
additional_material_score[1] -= 25;
}
}
}
}

// Threats
var b1 = mobility & white_pawns;
if (b1 != 0) {
Expand Down Expand Up @@ -1301,14 +1371,12 @@ pub const Evaluat = struct {
const black_bishop_count = util.popcount(black_bishop);

if (white_bishop_count >= 2) {
const tmp_sc = [_]i32{ 12, 46 };
additional_material_score[0] += tmp_sc[0];
additional_material_score[1] += tmp_sc[1];
additional_material_score[0] += mg_bishop_pair[0];
additional_material_score[1] += eg_bishop_pair[0];
}
if (black_bishop_count >= 2) {
const tmp_sc = [_]i32{ 12, 46 };
additional_material_score[0] -= tmp_sc[0];
additional_material_score[1] -= tmp_sc[1];
additional_material_score[0] -= mg_bishop_pair[0];
additional_material_score[1] -= eg_bishop_pair[0];
}

// Doubled pawns
Expand Down Expand Up @@ -1396,16 +1464,14 @@ pub const Evaluat = struct {

// King + Bishop vs King + Knight
if (total_minors == 2 and
((white_bishop_count == 1 and black_knight_count == 1) or
(white_knight_count == 1 and black_bishop_count == 1)))
((white_bishop_count == 1 and black_knight_count == 1) or (white_knight_count == 1 and black_bishop_count == 1)))
{
return true;
}

// King + two Knights vs King
if (total_minors == 2 and
((white_knight_count == 2 and black_bishop_count == 0 and black_knight_count == 0) or
(black_knight_count == 2 and white_bishop_count == 0 and white_knight_count == 0)))
((white_knight_count == 2 and black_bishop_count == 0 and black_knight_count == 0) or (black_knight_count == 2 and white_bishop_count == 0 and white_knight_count == 0)))
{
return true;
}
Expand Down Expand Up @@ -1512,8 +1578,13 @@ const eg_queen_attacking: [6]i32 = .{ 0, -3, 8, 3, 0, 0 };
const mg_doubled_pawns: [1]i32 = .{-2};
const eg_doubled_pawns: [1]i32 = .{-13};

const mg_bishop_pair: [1]i32 = .{12};
const eg_bishop_pair: [1]i32 = .{46};
// Connected passed pawn bonus by rank: passed pawns that are supported or in phalanx
// These pawns protect each other while advancing, making them especially dangerous
const mg_connected_passer: [8]i32 = .{ 0, 5, 7, 12, 20, 35, 0, 0 };
const eg_connected_passer: [8]i32 = .{ 0, 7, 10, 17, 30, 50, 0, 0 };

const mg_bishop_pair: [1]i32 = .{30};
const eg_bishop_pair: [1]i32 = .{50};

pub inline fn get_passed_pawn_score(sq: u6) [2]i32 {
return .{ mg_passed_score[sq], eg_passed_score[sq] };
Expand Down Expand Up @@ -1560,6 +1631,10 @@ pub inline fn get_phalanx_score(rank: u6) [2]i32 {
return .{ mg_pawn_phalanx[rank], eg_pawn_phalanx[rank] };
}

pub inline fn get_connected_passer_bonus(rank: u6) [2]i32 {
return .{ mg_connected_passer[rank], eg_connected_passer[rank] };
}

pub inline fn get_knight_mobility_score(idx: u7) [2]i32 {
return .{ mg_knight_mobility[idx], eg_knight_mobility[idx] };
}
Expand Down
63 changes: 62 additions & 1 deletion src/move_generation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const lists = @import("lists.zig");
const util = @import("util.zig");
const bitboard = @import("bitboard.zig");
const eval = @import("evaluation.zig");
const zobrist = @import("zobrist.zig");
const print = std.debug.print;

// Define a move
Expand Down Expand Up @@ -707,6 +708,15 @@ pub fn make_move(board: *types.Board, move: Move) bool {
// Determine the moving side based on the piece found
moving_side = if (@intFromEnum(piece_type) < 6) types.Color.White else types.Color.Black;

// Save old castling rights and en passant for hash update
const old_castle = board.castle;
const old_ep = board.enpassant;

// Zobrist: remove piece from source, add to target
const pi = zobrist.piece_index(piece_type);
board.hash ^= zobrist.piece_keys[pi][source_square];
board.hash ^= zobrist.piece_keys[pi][target_square];

// Move the piece
board.pieces[@intFromEnum(piece_type)] = util.clear_bit(board.pieces[@intFromEnum(piece_type)], @enumFromInt(source_square));
board.pieces[@intFromEnum(piece_type)] = util.set_bit(board.pieces[@intFromEnum(piece_type)], @enumFromInt(target_square));
Expand All @@ -723,6 +733,7 @@ pub fn make_move(board: *types.Board, move: Move) bool {
if (util.get_bit(board.pieces[captured_piece_idx], target_square)) {
board.pieces[captured_piece_idx] = util.clear_bit(board.pieces[captured_piece_idx], @enumFromInt(target_square));
const captured_piece: types.Piece = @enumFromInt(captured_piece_idx);
board.hash ^= zobrist.piece_keys[zobrist.piece_index(captured_piece)][target_square];
eval.global_evaluator.remove_piece_phase(captured_piece);
eval.global_evaluator.remove_piece_material(captured_piece);
break;
Expand All @@ -739,6 +750,9 @@ pub fn make_move(board: *types.Board, move: Move) bool {
const promoted_piece = get_promoted_piece(move_flags, moving_side);
board.pieces[@intFromEnum(promoted_piece)] = util.set_bit(board.pieces[@intFromEnum(promoted_piece)], @enumFromInt(target_square));

board.hash ^= zobrist.piece_keys[zobrist.piece_index(pawn_piece)][target_square];
board.hash ^= zobrist.piece_keys[zobrist.piece_index(promoted_piece)][target_square];

eval.global_evaluator.remove_piece_phase(pawn_piece);
eval.global_evaluator.remove_piece_material(pawn_piece);
eval.global_evaluator.put_piece_phase(promoted_piece);
Expand All @@ -754,6 +768,7 @@ pub fn make_move(board: *types.Board, move: Move) bool {

const captured_pawn = if (moving_side == types.Color.White) types.Piece.BLACK_PAWN else types.Piece.WHITE_PAWN;
board.pieces[@intFromEnum(captured_pawn)] = util.clear_bit(board.pieces[@intFromEnum(captured_pawn)], @enumFromInt(captured_pawn_square));
board.hash ^= zobrist.piece_keys[zobrist.piece_index(captured_pawn)][captured_pawn_square];
eval.global_evaluator.remove_piece_phase(captured_pawn);
eval.global_evaluator.remove_piece_material(captured_pawn);
}
Expand All @@ -771,19 +786,65 @@ pub fn make_move(board: *types.Board, move: Move) bool {

// Handle castling moves
if (move_flags == types.MoveFlags.OO or move_flags == types.MoveFlags.OOO) {
const rook_piece = if (moving_side == types.Color.White) types.Piece.WHITE_ROOK else types.Piece.BLACK_ROOK;
const rook_pi = zobrist.piece_index(rook_piece);
switch (target_square) {
@intFromEnum(types.square.g1) => {
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.h1)];
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.f1)];
},
@intFromEnum(types.square.c1) => {
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.a1)];
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.d1)];
},
@intFromEnum(types.square.g8) => {
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.h8)];
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.f8)];
},
@intFromEnum(types.square.c8) => {
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.a8)];
board.hash ^= zobrist.piece_keys[rook_pi][@intFromEnum(types.square.d8)];
},
else => {},
}
handle_castling(board, target_square, moving_side);
}

// Update castling rights using bitboard masks
update_castling_rights(board, source_square, target_square);

// Zobrist: update castling rights (XOR out old, XOR in new)
if (old_castle != board.castle) {
// XOR out old castling keys
if (old_castle & @intFromEnum(types.Castle.WK) != 0) board.hash ^= zobrist.castle_keys[0];
if (old_castle & @intFromEnum(types.Castle.WQ) != 0) board.hash ^= zobrist.castle_keys[1];
if (old_castle & @intFromEnum(types.Castle.BK) != 0) board.hash ^= zobrist.castle_keys[2];
if (old_castle & @intFromEnum(types.Castle.BQ) != 0) board.hash ^= zobrist.castle_keys[3];
// XOR in new castling keys
if (board.castle & @intFromEnum(types.Castle.WK) != 0) board.hash ^= zobrist.castle_keys[0];
if (board.castle & @intFromEnum(types.Castle.WQ) != 0) board.hash ^= zobrist.castle_keys[1];
if (board.castle & @intFromEnum(types.Castle.BK) != 0) board.hash ^= zobrist.castle_keys[2];
if (board.castle & @intFromEnum(types.Castle.BQ) != 0) board.hash ^= zobrist.castle_keys[3];
}

// Zobrist: update en passant
if (old_ep != types.square.NO_SQUARE) {
board.hash ^= zobrist.ep_keys[@intFromEnum(old_ep) % 8];
}
if (board.enpassant != types.square.NO_SQUARE) {
board.hash ^= zobrist.ep_keys[@intFromEnum(board.enpassant) % 8];
}

// Zobrist: flip side to move
board.hash ^= zobrist.side_key;

// Check if move leaves our king in check (illegal move)
const our_king_piece = if (moving_side == types.Color.White) types.Piece.WHITE_KING else types.Piece.BLACK_KING;
const our_king_square: u6 = @intCast(util.lsb_index(board.pieces[@intFromEnum(our_king_piece)]));
const opponent_side = if (moving_side == types.Color.White) types.Color.Black else types.Color.White;

if (bitboard.is_square_attacked(board, our_king_square, opponent_side)) {
// Restore board state - illegal move
// Restore board state - illegal move (hash is restored via BoardState)
board.restore_state(saved_state);
eval.global_evaluator = saved_evaluator;
return false;
Expand Down
15 changes: 7 additions & 8 deletions src/score_moves.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const SCORE_QUIET = 0;
const SCORE_BAD_CAPTURE = -1000000;
const SCORE_KILLER = 90000;
const SCORE_KILLER_2 = 80000;
const SCORE_COUNTERMOVE = 50000;
const MAX_LOOP_COUNT = 32;
const SCORE_PV_MOVE = 10000000;

Expand Down Expand Up @@ -81,7 +82,7 @@ pub inline fn get_next_best_move(move_list: *lists.MoveList, score_list: *lists.
return move_list.moves[i];
}

pub inline fn score_move(board: *types.Board, move_list: *lists.MoveList, score_list: *lists.ScoreList,pv_move: move_gen.Move) void {
pub inline fn score_move(board: *types.Board, move_list: *lists.MoveList, score_list: *lists.ScoreList, pv_move: move_gen.Move, countermove: move_gen.Move) void {
score_list.count = 0;

for (0..move_list.count) |i| {
Expand Down Expand Up @@ -154,8 +155,11 @@ pub inline fn score_move(board: *types.Board, move_list: *lists.MoveList, score_
// score second killer move
} else if (std.meta.eql(move_list.moves[i],search.global_search.killer_moves[1][search.global_search.ply])) {
score = SCORE_KILLER_2;
// score countermove
} else if (!countermove.is_empty() and moves_equal(move, countermove)) {
score = SCORE_COUNTERMOVE;
// score history move
} else{
} else{
score = search.global_search.history_moves[move.from][move.to];
}
}
Expand Down Expand Up @@ -280,13 +284,8 @@ pub fn see(board: *const types.Board, move: move_generation.Move, threshold: i32
// Update the value (negamax style)
value = -value - 1 - attacker_piece_val;

// Pruning - if this capture is good enough, stop
// Pruning - if this side can stand pat with value >= 0, the exchange is decided
if (value >= 0) {
// Check if the opponent still has attackers
const opp_side_mask = if (side == types.Color.White) board.set_white() else board.set_black();
if ((attackers & opp_side_mask) != 0) {
return side != board.get_piece_color_at(move.from).?;
}
return side != board.get_piece_color_at(move.from).?;
}

Expand Down
Loading
Loading