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
64 changes: 64 additions & 0 deletions scripts/GetAttacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import chess

# A diverse set of FEN positions for testing
fen_positions = [
"8/8/8/8/8/8/8/8 w - - ",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1",
"rnbqkb1r/pp1p1pPp/8/2p1pP2/1P1P4/3P3P/P1P1P3/RNBQKBNR w KQkq e6 0 1",
"r2q1rk1/ppp2ppp/2n1bn2/2b1p3/3pP3/3P1NPP/PPP1NPB1/R1BQ1RK1 b - - 0 9 ",
"r3k2r/8/8/8/3pPp2/8/8/R3K1RR b KQkq e3 0 1",
"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1",
"8/7p/p5pb/4k3/P1pPn3/8/P5PP/1rB2RK1 b - d3 0 28",
"8/3K4/2p5/p2b2r1/5k2/8/8/1q6 b - - 1 67",
"rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3",
"n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1",
"r3k2r/p6p/8/B7/1pp1p3/3b4/P6P/R3K2R w KQkq - 0 1",
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1",
"r6r/1b2k1bq/8/8/7B/8/8/R3K2R b KQ - 3 2",
"8/8/8/2k5/2pP4/8/B7/4K3 b - d3 0 3",
"r1bqkbnr/pppppppp/n7/8/8/P7/1PPPPPPP/RNBQKBNR w KQkq - 2 2",
"r3k2r/p1pp1pb1/bn2Qnp1/2qPN3/1p2P3/2N5/PPPBBPPP/R3K2R b KQkq - 3 2",
"2kr3r/p1ppqpb1/bn2Qnp1/3PN3/1p2P3/2N5/PPPBBPPP/R3K2R b KQ - 3 2",
"rnb2k1r/pp1Pbppp/2p5/q7/2B5/8/PPPQNnPP/RNB1K2R w KQ - 3 9",
]


def to_zig_hex(bb: int) -> str:
"""Converts a python-chess bitboard (big-endian) to a little-endian hex string for Zig."""
raw_be = bb.to_bytes(8, byteorder="big")
raw_le = raw_be[::-1]
zig_int = int.from_bytes(raw_le, byteorder="big")
return f"0x{zig_int:016x}"


white_attacks_bb_arr = []
black_attacks_bb_arr = []

for fen in fen_positions:
board = chess.Board(fen)
print(f"FEN: {fen}")
print(board)

white_attacks_bb = 0
black_attacks_bb = 0

# Iterate over all 64 squares to build the attack maps
for sq in chess.SQUARES:
# Check if the square is attacked by white
if board.is_attacked_by(chess.WHITE, sq):
white_attacks_bb |= 1 << sq

# Check if the square is attacked by black
if board.is_attacked_by(chess.BLACK, sq):
black_attacks_bb |= 1 << sq

white_attacks_bb_arr.append(white_attacks_bb)
black_attacks_bb_arr.append(black_attacks_bb)

print(f"White Attacks: {to_zig_hex(white_attacks_bb)}")
print(f"Black Attacks: {to_zig_hex(black_attacks_bb)}")
print("-" * 40)

print(f"White Attacks list: {white_attacks_bb_arr} \n")
print(f"Black Attacks list: {black_attacks_bb_arr} \n")
127 changes: 99 additions & 28 deletions src/attacks.zig
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const print = std.debug.print;
const types = @import("types.zig");
const std = @import("std");
const print = std.debug.print;
const tabele = @import("tabeles.zig");
const util = @import("util.zig");
const bitboard = @import("bitboard.zig");

// generate knight attacks tabele
pub inline fn knight_attacks_from_bitboard(bb: types.Bitboard) types.Bitboard {
return (((bb << 17) & ~@intFromEnum(types.MaskFile.AFILE)) |
Expand Down Expand Up @@ -40,25 +41,21 @@ pub var pawn_attacks: [2][64]u64 = undefined;
pub var pseudo_legal_attacks: [6][64]u64 = undefined;

pub fn initialise_pseudo_legal() void {
// copy pawn-attack tables directly
pawn_attacks[0] = tabele.White_pawn_attacks_tabele;
pawn_attacks[1] = tabele.Black_pawn_attacks_tabele;

// copy fixed knight & king tables
const knight_i = @intFromEnum(types.PieceType.Knight);
const king_i = @intFromEnum(types.PieceType.King);
pseudo_legal_attacks[knight_i] = tabele.Knight_attackes_tabele;
pseudo_legal_attacks[king_i] = tabele.King_attackes_tabele;

// build rook, bishop, queen on empty board
const rook_i = @intFromEnum(types.PieceType.Rook);
const bishop_i = @intFromEnum(types.PieceType.Bishop);
const queen_i = @intFromEnum(types.PieceType.Queen);

// single pass over all 64 squares
for (types.square_number) |s| {
const sq: u8 = @intCast(s);
const occ = 0; // empty occupancy
const occ = 0;

// sliding attacks
const r_att = get_rook_attacks_for_init(sq, occ);
Expand All @@ -70,7 +67,6 @@ pub fn initialise_pseudo_legal() void {
}
}

/// Returns the pawn-attack mask for square `s` (0..63) and color `c`
pub inline fn pawn_attacks_from_square(s: usize, c: types.Color) u64 {
return pawn_attacks[@intFromEnum(c)][s];
}
Expand Down Expand Up @@ -203,7 +199,10 @@ pub inline fn init_rook_attackes() void {
var idx64: u64 = @as(u64, subset) *% magic;
idx64 = idx64 >> shift;
const idx: usize = @intCast(idx64);
Rook_attacks[square][idx] = get_rook_attacks_for_init(sq6, subset);
Rook_attacks[square][idx] = get_rook_attacks_for_init(
sq6,
subset,
);
if (subset == 0) break;
subset = (subset - 1) & mask;
}
Expand All @@ -216,21 +215,64 @@ pub inline fn get_rook_attacks(square: u6, occ: u64) u64 {
const shift: u6 = @intCast(64 - tabele.Rook_index_bit[square]);
const relevant: u64 = occ & mask;
const idx: usize = @intCast((relevant *% magic) >> shift);

return Rook_attacks[square][idx];
}

pub inline fn get_bishop_attacks_for_init(square: u8, occ: u64) u64 {
const rank_i8: i8 = @intCast(square / 8);
const file_i8: i8 = @intCast(square % 8);
const diag_i: i8 = rank_i8 - file_i8 + 7;
const anti_i: i8 = rank_i8 + file_i8;
const diag_idx: usize = @intCast(diag_i);
const anti_idx: usize = @intCast(anti_i);
const mask1: u64 = types.mask_diagonal_nw_se[diag_idx];
const mask2: u64 = types.mask_anti_diagonal_ne_sw[anti_idx];
const att1 = sliding_attacks(square, occ, mask1);
const att2 = sliding_attacks(square, occ, mask2);
return att1 | att2;
// Correct on-the-fly attack generation for bishops using simple ray-casting.
// This is a one-time cost at initialization.
pub fn get_bishop_attacks_for_init(square: u8, blockers: u64) u64 {
var attacks: u64 = 0;
const r: i8 = @intCast(square / 8);
const f: i8 = @intCast(square % 8);
const one: u64 = 1;

// North-East
var cr = r + 1;
var cf = f + 1;
while (cr <= 7 and cf <= 7) : ({
cr += 1;
cf += 1;
}) {
const s = cr * 8 + cf;
attacks |= (one << @intCast(s));
if ((blockers & (one << @intCast(s))) != 0) break;
}
// South-West
cr = r - 1;
cf = f - 1;
while (cr >= 0 and cf >= 0) : ({
cr -= 1;
cf -= 1;
}) {
const s = cr * 8 + cf;
attacks |= (one << @intCast(s));
if ((blockers & (one << @intCast(s))) != 0) break;
}
// North-West
cr = r + 1;
cf = f - 1;
while (cr <= 7 and cf >= 0) : ({
cr += 1;
cf -= 1;
}) {
const s = cr * 8 + cf;
attacks |= (one << @intCast(s));
if ((blockers & (one << @intCast(s))) != 0) break;
}
// South-East
cr = r - 1;
cf = f + 1;
while (cr >= 0 and cf <= 7) : ({
cr -= 1;
cf += 1;
}) {
const s = cr * 8 + cf;
attacks |= (one << @intCast(s));
if ((blockers & (one << @intCast(s))) != 0) break;
}

return attacks;
}

// bishop magice Bitboards
Expand All @@ -241,16 +283,41 @@ pub inline fn init_bishop_attackes() void {
const mask = tabele.Bishops_attackes_tabele[square];
const relevantBits = tabele.Bishop_index_bit[square];
const magic = tabele.bishop_magics[square];

const shift: u6 = @truncate(64 - relevantBits);
const sq6 = @as(u8, @intCast(square));
var subset: types.Bitboard = mask;

// Clear the table for this square first
const table_size = @as(usize, 1) << @intCast(relevantBits);
@memset(Bishop_attacks[square][0..table_size], 0);

var subset: types.Bitboard = mask;
while (true) {
var idx64: u64 = @as(u64, subset) *% magic;
idx64 = idx64 >> shift;
const idx: usize = @intCast(idx64);
Bishop_attacks[square][idx] = get_bishop_attacks_for_init(sq6, subset);

const correct_attacks = get_bishop_attacks_for_init(
sq6,
subset,
);

// Check for collision
if (Bishop_attacks[square][idx] != 0 and
Bishop_attacks[square][idx] != correct_attacks)
{
std.debug.panic(
"Magic collision detected for bishop square {} at index {}: existing=0x{x}, new=0x{x}\n",
.{
square,
idx,
Bishop_attacks[square][idx],
correct_attacks,
},
);
}

Bishop_attacks[square][idx] = correct_attacks;

if (subset == 0) break;
subset = (subset - 1) & mask;
}
Expand All @@ -262,23 +329,27 @@ pub inline fn get_bishop_attacks(square: u6, occ: u64) u64 {
const magic: u64 = tabele.bishop_magics[square];
const shift: u6 = @intCast(64 - tabele.Bishop_index_bit[square]);
const relevant: u64 = occ & mask;
const raw_idx: u64 = (relevant *% magic) >> shift;
const idx: usize = @intCast(raw_idx);
const idx: usize = @intCast((relevant *% magic) >> shift);
return Bishop_attacks[square][idx];
}

pub inline fn get_queen_attacks(square: u6, occ: u64) u64 {
const queen_attacks: u64 = get_bishop_attacks(square, occ) | get_rook_attacks(square, occ);
const queen_attacks: u64 = get_bishop_attacks(square, occ) |
get_rook_attacks(square, occ);
return queen_attacks;
}

pub fn piece_attacks(square: u6, occ: u64, comptime Piece: types.PieceType) void {
pub fn piece_attacks(
square: u6,
occ: u64,
comptime Piece: types.PieceType,
) types.Bitboard {
if (Piece != types.PieceType.Pawn) {
return switch (Piece) {
types.PieceType.Bishop => get_bishop_attacks(square, occ),
types.PieceType.Rook => get_rook_attacks(square, occ),
types.PieceType.Queen => get_queen_attacks(square, occ),
else => print("pseudo legal attacks", .{}),
else => (&pseudo_legal_attacks)[Piece.toU3()][square],
};
} else {
@panic("don't pass pawns");
Expand Down
Loading
Loading