Skip to main content

atomic_movegen/
attacks.rs

1use crate::types::*;
2
3// On x86_64: runtime dispatch between PEXT (BMI2) and magic-multiply.
4// On other architectures: direct re-export of the magic fallback.
5
6#[cfg(target_arch = "x86_64")]
7mod sliding_dispatch {
8    use crate::types::*;
9    use core::sync::atomic::{AtomicU8, Ordering};
10
11    // 0 = uninit, 1 = Magic, 2 = Pext
12    static IMPL: AtomicU8 = AtomicU8::new(0);
13
14    pub(crate) fn force_magic() {
15        IMPL.store(1, Ordering::Relaxed);
16    }
17
18    pub(crate) fn force_pext() {
19        IMPL.store(2, Ordering::Relaxed);
20    }
21
22    #[inline(always)]
23    pub fn bishop_attacks(sq: Square, occupied: Bitboard) -> Bitboard {
24        // After init(), IMPL is stable and threadsafe to read.
25        if IMPL.load(Ordering::Relaxed) == 2 {
26            crate::pext::bishop_attacks_pext(sq, occupied)
27        } else {
28            crate::magic::bishop_attacks(sq, occupied)
29        }
30    }
31
32    #[inline(always)]
33    pub fn rook_attacks(sq: Square, occupied: Bitboard) -> Bitboard {
34        if IMPL.load(Ordering::Relaxed) == 2 {
35            crate::pext::rook_attacks_pext(sq, occupied)
36        } else {
37            crate::magic::rook_attacks(sq, occupied)
38        }
39    }
40
41    #[inline(always)]
42    pub fn queen_attacks(sq: Square, occupied: Bitboard) -> Bitboard {
43        bishop_attacks(sq, occupied) | rook_attacks(sq, occupied)
44    }
45}
46
47#[cfg(target_arch = "x86_64")]
48pub use sliding_dispatch::{bishop_attacks, queen_attacks, rook_attacks};
49
50#[cfg(not(target_arch = "x86_64"))]
51pub use crate::magic::{bishop_attacks, queen_attacks, rook_attacks};
52
53// Leaper attack tables computed at compile time (no lazy init).
54
55/// Compute king attacks for all 64 squares at compile time.
56const fn compute_king_attacks() -> [Bitboard; 64] {
57    let mut attacks = [Bitboard(0); 64];
58    let mut sq: u8 = 0;
59    while sq < 64 {
60        let f = sq % 8;
61        let r = sq / 8;
62        let mut atk = 0u64;
63
64        // North
65        if r < 7 {
66            atk |= 1u64 << ((r + 1) * 8 + f);
67        }
68        // South
69        if r > 0 {
70            atk |= 1u64 << ((r - 1) * 8 + f);
71        }
72        // East
73        if f < 7 {
74            atk |= 1u64 << (r * 8 + f + 1);
75        }
76        // West
77        if f > 0 {
78            atk |= 1u64 << (r * 8 + f - 1);
79        }
80        // North-East
81        if r < 7 && f < 7 {
82            atk |= 1u64 << ((r + 1) * 8 + f + 1);
83        }
84        // North-West
85        if r < 7 && f > 0 {
86            atk |= 1u64 << ((r + 1) * 8 + f - 1);
87        }
88        // South-East
89        if r > 0 && f < 7 {
90            atk |= 1u64 << ((r - 1) * 8 + f + 1);
91        }
92        // South-West
93        if r > 0 && f > 0 {
94            atk |= 1u64 << ((r - 1) * 8 + f - 1);
95        }
96
97        attacks[sq as usize] = Bitboard(atk);
98        sq += 1;
99    }
100    attacks
101}
102
103/// Compute knight attacks for all 64 squares at compile time.
104const fn compute_knight_attacks() -> [Bitboard; 64] {
105    let mut attacks = [Bitboard(0); 64];
106    let mut sq: u8 = 0;
107    while sq < 64 {
108        let f = sq as i8 % 8;
109        let r = sq as i8 / 8;
110        let mut atk = 0u64;
111        // Knight offsets: (df,dr) pairs where |df|+|dr|=3 and min(|df|,|dr|)=1
112        let offsets: [i8; 8] = [17, 15, 10, 6, -6, -10, -15, -17];
113        let mut i: u8 = 0;
114        while i < 8 {
115            let to = sq as i8 + offsets[i as usize];
116            if to >= 0 && to < 64 {
117                let tf = to % 8;
118                let tr = to / 8;
119                let df = tf - f;
120                let dr = tr - r;
121                // Valid knight move: (|df|, |dr|) is (1,2) or (2,1).
122                // Squared-distance == 5 avoids abs() dependency.
123                if (df == 1 || df == -1) && (dr == 2 || dr == -2)
124                    || (df == 2 || df == -2) && (dr == 1 || dr == -1)
125                {
126                    atk |= 1u64 << to;
127                }
128            }
129            i += 1;
130        }
131        attacks[sq as usize] = Bitboard(atk);
132        sq += 1;
133    }
134    attacks
135}
136
137/// Compute pawn attacks for all 64 squares at compile time.
138/// Returns a 2D array indexed by [square][color].
139const fn compute_pawn_attacks() -> [[Bitboard; 2]; 64] {
140    let mut attacks = [[Bitboard(0); 2]; 64];
141    let mut sq: u8 = 0;
142    while sq < 64 {
143        let f = sq % 8;
144        let r = sq / 8;
145        let mut white_atk = 0u64;
146        let mut black_atk = 0u64;
147
148        // White pawns attack north-west and north-east
149        if r < 7 {
150            if f > 0 {
151                white_atk |= 1u64 << ((r + 1) * 8 + f - 1);
152            }
153            if f < 7 {
154                white_atk |= 1u64 << ((r + 1) * 8 + f + 1);
155            }
156        }
157        // Black pawns attack south-west and south-east
158        if r > 0 {
159            if f > 0 {
160                black_atk |= 1u64 << ((r - 1) * 8 + f - 1);
161            }
162            if f < 7 {
163                black_atk |= 1u64 << ((r - 1) * 8 + f + 1);
164            }
165        }
166
167        attacks[sq as usize] = [Bitboard(white_atk), Bitboard(black_atk)];
168        sq += 1;
169    }
170    attacks
171}
172
173/// Precomputed king attacks for all 64 squares (compile-time constant).
174const KING_ATTACKS: [Bitboard; 64] = compute_king_attacks();
175
176/// Precomputed knight attacks for all 64 squares (compile-time constant).
177const KNIGHT_ATTACKS: [Bitboard; 64] = compute_knight_attacks();
178
179/// Precomputed pawn attacks for all 64 squares (compile-time constant).
180const PAWN_ATTACKS: [[Bitboard; 2]; 64] = compute_pawn_attacks();
181
182/// Compute between-squares table for all 64×64 square pairs at compile time.
183const fn compute_between_bb() -> [[Bitboard; 64]; 64] {
184    let mut table = [[Bitboard(0); 64]; 64];
185    let mut s1: u8 = 0;
186    while s1 < 64 {
187        let mut s2: u8 = 0;
188        while s2 < 64 {
189            let f1 = s1 % 8;
190            let r1 = s1 / 8;
191            let f2 = s2 % 8;
192            let r2 = s2 / 8;
193
194            if s1 != s2
195                && (f1 == f2
196                    || r1 == r2
197                    || (f1 as i8 - f2 as i8).abs() == (r1 as i8 - r2 as i8).abs())
198            {
199                let mut b = 0u64;
200                let df = (f2 as i8 - f1 as i8).signum();
201                let dr = (r2 as i8 - r1 as i8).signum();
202                let mut f = f1 as i8 + df;
203                let mut r = r1 as i8 + dr;
204                while f != f2 as i8 || r != r2 as i8 {
205                    b |= 1u64 << ((r as u8) * 8 + (f as u8));
206                    f += df;
207                    r += dr;
208                }
209                table[s1 as usize][s2 as usize] = Bitboard(b);
210            }
211            s2 += 1;
212        }
213        s1 += 1;
214    }
215    table
216}
217
218/// Compute line-squares table for all 64×64 square pairs at compile time.
219const fn compute_line_bb() -> [[Bitboard; 64]; 64] {
220    let mut table = [[Bitboard(0); 64]; 64];
221    let mut s1: u8 = 0;
222    while s1 < 64 {
223        let mut s2: u8 = 0;
224        while s2 < 64 {
225            let f1 = s1 % 8;
226            let r1 = s1 / 8;
227            let f2 = s2 % 8;
228            let r2 = s2 / 8;
229
230            if s1 != s2
231                && (f1 == f2
232                    || r1 == r2
233                    || (f1 as i8 - f2 as i8).abs() == (r1 as i8 - r2 as i8).abs())
234            {
235                let mut b = 0u64;
236                let df = (f2 as i8 - f1 as i8).signum();
237                let dr = (r2 as i8 - r1 as i8).signum();
238                let mut f = f1 as i8;
239                let mut r = r1 as i8;
240                while f >= 0 && f < 8 && r >= 0 && r < 8 {
241                    b |= 1u64 << ((r as u8) * 8 + (f as u8));
242                    f += df;
243                    r += dr;
244                }
245                table[s1 as usize][s2 as usize] = Bitboard(b);
246            }
247            s2 += 1;
248        }
249        s1 += 1;
250    }
251    table
252}
253
254/// Precomputed between-squares table: `BETWEEN_BB[s1][s2]` gives the
255/// bitboard of squares strictly between `s1` and `s2`, or `Bitboard::EMPTY`
256/// when `s1` and `s2` are not on the same rank, file, or diagonal.
257pub(crate) static BETWEEN_BB: [[Bitboard; 64]; 64] = compute_between_bb();
258
259/// Precomputed line-squares table: `LINE_BB[s1][s2]` gives the bitboard
260/// of all squares on the same rank, file, or diagonal as `s1` and `s2`
261/// (including `s1` and `s2` themselves), or `Bitboard::EMPTY` when the
262/// two squares are not aligned.
263pub(crate) static LINE_BB: [[Bitboard; 64]; 64] = compute_line_bb();
264
265/// Return the attack bitboard for a king on the given square.
266#[inline(always)]
267pub fn king_attacks(sq: Square) -> Bitboard {
268    KING_ATTACKS[sq as usize]
269}
270
271/// Return the attack bitboard for a knight on the given square.
272#[inline(always)]
273pub fn knight_attacks(sq: Square) -> Bitboard {
274    KNIGHT_ATTACKS[sq as usize]
275}
276
277/// Return the attack bitboard for a pawn of the given color on the given square.
278#[inline(always)]
279pub fn pawn_attacks(c: Color, sq: Square) -> Bitboard {
280    PAWN_ATTACKS[sq as usize][c as usize]
281}
282
283/// Initialize all attack tables (magic and PEXT).
284///
285/// Must be called before any call to `bishop_attacks`, `rook_attacks`, or
286/// `queen_attacks`. Safe to call multiple times — subsequent calls are no-ops.
287pub fn init() {
288    crate::magic::init();
289    #[cfg(target_arch = "x86_64")]
290    {
291        if crate::pext::has_bmi2() {
292            crate::pext::init();
293            sliding_dispatch::force_pext();
294        } else {
295            sliding_dispatch::force_magic();
296        }
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_knight_attacks_center() {
306        let atk = knight_attacks(Square::D4);
307        assert!(atk & Bitboard::square_bb(Square::C2) != Bitboard::EMPTY);
308        assert!(atk & Bitboard::square_bb(Square::E2) != Bitboard::EMPTY);
309        assert!(atk & Bitboard::square_bb(Square::B3) != Bitboard::EMPTY);
310        assert!(atk & Bitboard::square_bb(Square::F3) != Bitboard::EMPTY);
311        assert!(atk & Bitboard::square_bb(Square::B5) != Bitboard::EMPTY);
312        assert!(atk & Bitboard::square_bb(Square::F5) != Bitboard::EMPTY);
313        assert!(atk & Bitboard::square_bb(Square::C6) != Bitboard::EMPTY);
314        assert!(atk & Bitboard::square_bb(Square::E6) != Bitboard::EMPTY);
315        assert_eq!(atk.count(), 8);
316    }
317
318    #[test]
319    fn test_knight_attacks_corner() {
320        let atk = knight_attacks(Square::A1);
321        assert_eq!(atk.count(), 2);
322        assert!(atk & Bitboard::square_bb(Square::B3) != Bitboard::EMPTY);
323        assert!(atk & Bitboard::square_bb(Square::C2) != Bitboard::EMPTY);
324    }
325
326    #[test]
327    fn test_king_attacks_center() {
328        let atk = king_attacks(Square::D4);
329        assert_eq!(atk.count(), 8);
330    }
331
332    #[test]
333    fn test_king_attacks_corner() {
334        let atk = king_attacks(Square::A1);
335        assert_eq!(atk.count(), 3);
336    }
337
338    #[test]
339    fn test_bishop_attacks() {
340        crate::attacks::init();
341        let atk = bishop_attacks(Square::D4, Bitboard::EMPTY);
342        assert!(atk & Bitboard::square_bb(Square::A1) != Bitboard::EMPTY);
343        assert!(atk & Bitboard::square_bb(Square::G7) != Bitboard::EMPTY);
344        assert!((atk & Bitboard::square_bb(Square::D4)).is_empty());
345    }
346
347    #[test]
348    fn test_rook_attacks() {
349        crate::attacks::init();
350        let atk = rook_attacks(Square::D4, Bitboard::EMPTY);
351        assert!(atk & Bitboard::square_bb(Square::D1) != Bitboard::EMPTY);
352        assert!(atk & Bitboard::square_bb(Square::D8) != Bitboard::EMPTY);
353        assert!(atk & Bitboard::square_bb(Square::A4) != Bitboard::EMPTY);
354        assert!(atk & Bitboard::square_bb(Square::H4) != Bitboard::EMPTY);
355    }
356
357    #[test]
358    fn test_bishop_blocked() {
359        crate::attacks::init();
360        let blocker = Bitboard::square_bb(Square::E5);
361        let atk = bishop_attacks(Square::D4, blocker);
362        assert!(
363            atk & Bitboard::square_bb(Square::E5) != Bitboard::EMPTY,
364            "blocker square should be attackable"
365        );
366        assert!(
367            (atk & Bitboard::square_bb(Square::F6)).is_empty(),
368            "beyond blocker should be blocked"
369        );
370    }
371
372    #[test]
373    fn test_rook_blocked() {
374        crate::attacks::init();
375        let blocker = Bitboard::square_bb(Square::D5);
376        let atk = rook_attacks(Square::D4, blocker);
377        assert!(atk & Bitboard::square_bb(Square::D5) != Bitboard::EMPTY);
378        assert!((atk & Bitboard::square_bb(Square::D6)).is_empty());
379    }
380
381    #[test]
382    fn test_queen_equals_bishop_plus_rook() {
383        crate::attacks::init();
384        let occ = Bitboard::square_bb(Square::E5);
385        let queen = queen_attacks(Square::D4, occ);
386        let bishop = bishop_attacks(Square::D4, occ);
387        let rook = rook_attacks(Square::D4, occ);
388        assert_eq!(queen, bishop | rook);
389    }
390}