Skip to main content

chess/board/
bitboard.rs

1use core::{fmt, ops};
2
3use crate::{
4    Square,
5    square::{Direction, File, Rank},
6};
7
8use File::*;
9
10#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
11pub struct Bitboard(pub u64);
12
13/// Bitboard Construction API.
14impl Bitboard {
15    #[inline]
16    pub const fn from_file(file: File) -> Bitboard {
17        Bitboard(Bitboard::FILE_A.0 << file as u32)
18    }
19
20    #[inline]
21    pub const fn from_rank(rank: Rank) -> Bitboard {
22        Bitboard(Self::RANKS[rank as usize])
23    }
24
25    #[inline]
26    pub const fn from_square(square: Square) -> Bitboard {
27        Bitboard(1 << square as u32)
28    }
29
30    #[inline]
31    pub const fn from_squares<const N: usize>(squares: [Square; N]) -> Bitboard {
32        let mut bitboard = Bitboard::EMPTY;
33        let mut i = 0;
34        while i < N {
35            bitboard.append(Bitboard::from_square(squares[i]));
36            i += 1;
37        }
38        bitboard
39    }
40}
41
42/// Bitboard Set API.
43impl Bitboard {
44    /// Appends `squares`.
45    #[inline]
46    pub const fn append(&mut self, squares: Bitboard) {
47        self.0 |= squares.0;
48    }
49
50    #[inline]
51    pub const fn clear(&mut self) {
52        self.0 = 0;
53    }
54
55    #[inline]
56    pub const fn contains(self, square: Square) -> bool {
57        !self.intersection(Bitboard::from_square(square)).is_empty()
58    }
59
60    #[inline]
61    pub const fn difference(self, squares: Bitboard) -> Bitboard {
62        Bitboard(self.0 & !squares.0)
63    }
64
65    #[inline]
66    pub const fn eq(self, other: Self) -> bool {
67        self.0 == other.0
68    }
69
70    #[inline]
71    pub const fn first(self) -> Option<Square> {
72        if self.is_empty() {
73            None
74        } else {
75            Some(Square::panicky_from_index(self.0.trailing_zeros() as u8))
76        }
77    }
78
79    #[inline]
80    pub const fn insert(&mut self, square: Square) -> bool {
81        if self.contains(square) {
82            false
83        } else {
84            self.append(Bitboard::from_square(square));
85            true
86        }
87    }
88
89    #[inline]
90    pub const fn intersection(self, squares: Bitboard) -> Bitboard {
91        Bitboard(self.0 & squares.0)
92    }
93
94    #[inline]
95    pub const fn is_disjoint(self, other: Bitboard) -> bool {
96        self.intersection(other).is_empty()
97    }
98
99    #[inline]
100    pub const fn is_empty(self) -> bool {
101        self.0 == 0
102    }
103
104    #[inline]
105    pub const fn is_subset(self, other: Bitboard) -> bool {
106        self.difference(other).is_empty()
107    }
108
109    #[inline]
110    pub const fn is_superset(self, other: Bitboard) -> bool {
111        other.is_subset(self)
112    }
113
114    #[inline]
115    pub const fn iter(self) -> IntoIter {
116        IntoIter(self)
117    }
118
119    #[inline]
120    pub const fn last(self) -> Option<Square> {
121        if let Some(index) = self.0.checked_ilog2() {
122            Some(Square::panicky_from_index(index as u8))
123        } else {
124            None
125        }
126    }
127
128    #[inline]
129    pub const fn len(self) -> usize {
130        self.0.count_ones() as usize
131    }
132
133    #[inline]
134    pub const fn pop_first(&mut self) -> Option<Square> {
135        let square = self.first();
136        self.discard_first();
137        square
138    }
139
140    #[inline]
141    pub const fn pop_last(&mut self) -> Option<Square> {
142        let square = self.last();
143        self.discard_last();
144        square
145    }
146
147    #[inline]
148    pub const fn remove(&mut self, square: Square) -> bool {
149        if self.contains(square) {
150            *self = self.difference(Bitboard::from_square(square));
151            true
152        } else {
153            false
154        }
155    }
156
157    #[inline]
158    pub const fn symmetric_difference(self, squares: Bitboard) -> Bitboard {
159        Bitboard(self.0 ^ squares.0)
160    }
161
162    #[inline]
163    pub const fn union(self, squares: Bitboard) -> Bitboard {
164        Bitboard(self.0 | squares.0)
165    }
166
167    #[inline]
168    const fn discard_first(&mut self) {
169        *self = self.without_first();
170    }
171
172    #[inline]
173    const fn discard_last(&mut self) {
174        *self = self.without_last();
175    }
176
177    #[inline]
178    const fn without_last(self) -> Bitboard {
179        let Bitboard(mask) = self;
180        Bitboard(mask & !((1u64 << 63).wrapping_shr(mask.leading_zeros())))
181    }
182}
183
184/// Set extensions
185impl Bitboard {
186    #[inline]
187    pub const fn more_than_one(self) -> bool {
188        !self.without_first().is_empty()
189    }
190
191    /// Returns the next subset of `self` after `set`, wrapping back to empty.
192    ///
193    /// `set` is expected to be a subset of `self`.
194    #[inline]
195    pub const fn next_subset(self, set: Bitboard) -> Bitboard {
196        // This numerical trick cycles through the powerset of m:
197        //
198        // s := 0
199        // s -> s.wrapping_sub(m) & m
200        //
201        // The operation is conjugate to ordinary increment on a dense counter.
202        // Let the set bits of m be at positions:
203        // p0 < p1 < ... < p(n-1)
204        //
205        // Define pack(s) as taking a subset s and compressing the mask bits into an n-bit integer:
206        // bit(i) of pack(s) = bit(p_i) of s
207        //
208        // Then pack(next(s)) = pack(s) + 1 mod 2^n.
209        //
210        // Example:
211        // m bits:      positions 1,2,4
212        // s bitboard:  00000 00010 00100 00110 10000 ...
213        // pack(s):     000   001   010   011   100 ...
214        //
215        // So this walks through dense counter values 0, 1, 2, ..., 2^n - 1,
216        // embedded into the sparse positions of m.
217        Bitboard(set.0.wrapping_sub(self.0) & self.0)
218    }
219
220    #[inline]
221    // Non-standard
222    pub const fn set(&mut self, square: Square, set: bool) {
223        if set {
224            self.append(Bitboard::from_square(square));
225        } else {
226            *self = self.difference(Bitboard::from_square(square));
227        }
228    }
229
230    #[inline]
231    pub const fn with(self, square: Square) -> Bitboard {
232        self.union(Bitboard::from_square(square))
233    }
234
235    #[inline]
236    pub const fn without_first(self) -> Bitboard {
237        let Bitboard(mask) = self;
238        Bitboard(mask & mask.wrapping_sub(1))
239    }
240}
241
242/// Bitboard Geometry API.
243impl Bitboard {
244    #[inline]
245    pub const fn checked_shift(self, direction: Direction) -> Bitboard {
246        use Direction::*;
247
248        match direction {
249            North => self.wrapping_shift(North),
250            South => self.wrapping_shift(South),
251            East => self.without_h_file().wrapping_shift(East),
252            West => self.without_a_file().wrapping_shift(West),
253            NorthNorth => self.wrapping_shift(NorthNorth),
254            SouthSouth => self.wrapping_shift(SouthSouth),
255            NorthWest => self.without_a_file().wrapping_shift(NorthWest),
256            SouthWest => self.without_a_file().wrapping_shift(SouthWest),
257            NorthEast => self.without_h_file().wrapping_shift(NorthEast),
258            SouthEast => self.without_h_file().wrapping_shift(SouthEast),
259            KnightNorthWest => self.without_a_file().wrapping_shift(KnightNorthWest),
260            KnightSouthWest => self.without_a_file().wrapping_shift(KnightSouthWest),
261            KnightNorthEast => self.without_h_file().wrapping_shift(KnightNorthEast),
262            KnightSouthEast => self.without_h_file().wrapping_shift(KnightSouthEast),
263            KnightWestNorth => self.without_ab_files().wrapping_shift(KnightWestNorth),
264            KnightWestSouth => self.without_ab_files().wrapping_shift(KnightWestSouth),
265            KnightEastNorth => self.without_gh_files().wrapping_shift(KnightEastNorth),
266            KnightEastSouth => self.without_gh_files().wrapping_shift(KnightEastSouth),
267        }
268    }
269
270    #[inline]
271    /// Mirror at h1-a8 diagonal
272    pub const fn flip_anti_diagonal(self) -> Bitboard {
273        // https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#Anti-Diagonal
274        let k1 = 0xaa00_aa00_aa00_aa00;
275        let k2 = 0xcccc_0000_cccc_0000;
276        let k4 = 0xf0f0_f0f0_0f0f_0f0f;
277        let mut x = self.0;
278        let t = x ^ (x << 36);
279        x ^= k4 & (t ^ (x >> 36));
280        let t = k2 & (x ^ (x << 18));
281        x ^= t ^ (t >> 18);
282        let t = k1 & (x ^ (x << 9));
283        x ^= t ^ (t >> 9);
284        Bitboard(x)
285    }
286
287    #[inline]
288    /// Mirror at a1-h8 diagonal
289    pub const fn flip_diagonal(self) -> Bitboard {
290        // https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#Diagonal
291        let k1 = 0x5500_5500_5500_5500;
292        let k2 = 0x3333_0000_3333_0000;
293        let k4 = 0x0f0f_0f0f_0000_0000;
294        let mut x = self.0;
295        let t = k4 & (x ^ (x << 28));
296        x ^= t ^ (t >> 28);
297        let t = k2 & (x ^ (x << 14));
298        x ^= t ^ (t >> 14);
299        let t = k1 & (x ^ (x << 7));
300        x ^= t ^ (t >> 7);
301        Bitboard(x)
302    }
303
304    #[inline]
305    // Non-standard
306    pub const fn flip_horizontal(self) -> Bitboard {
307        // https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#Horizontal
308        let k1 = 0x5555_5555_5555_5555;
309        let k2 = 0x3333_3333_3333_3333;
310        let k4 = 0x0f0f_0f0f_0f0f_0f0f;
311        let x = self.0;
312        let x = ((x >> 1) & k1) | ((x & k1) << 1);
313        let x = ((x >> 2) & k2) | ((x & k2) << 2);
314        let x = ((x >> 4) & k4) | ((x & k4) << 4);
315        Bitboard(x)
316    }
317
318    #[inline]
319    // Non-standard
320    pub const fn flip_vertical(self) -> Bitboard {
321        Bitboard(self.0.swap_bytes())
322    }
323
324    #[inline]
325    // Rotate 180 degrees (clockwise)
326    pub const fn rotate_180(self) -> Bitboard {
327        Bitboard(self.0.reverse_bits())
328    }
329
330    #[inline]
331    // Rotate 270 degrees clockwise
332    pub const fn rotate_270(self) -> Bitboard {
333        self.flip_vertical().flip_diagonal()
334    }
335
336    #[inline]
337    // Rotate 90 degrees clockwise
338    pub const fn rotate_90(self) -> Bitboard {
339        self.flip_diagonal().flip_vertical()
340    }
341
342    #[inline]
343    pub const fn without_a_file(self) -> Bitboard {
344        self.difference(Bitboard::FILE_A)
345    }
346
347    #[inline]
348    pub const fn without_ab_files(self) -> Bitboard {
349        self.without_a_file().difference(Bitboard::from_file(B))
350    }
351
352    #[inline]
353    pub const fn without_gh_files(self) -> Bitboard {
354        self.without_h_file().difference(Bitboard::from_file(G))
355    }
356
357    #[inline]
358    pub const fn without_h_file(self) -> Bitboard {
359        self.difference(Bitboard::from_file(H))
360    }
361
362    #[inline]
363    const fn wrapping_shift(self, direction: Direction) -> Bitboard {
364        let offset = direction as i8;
365        if offset >= 0 {
366            Bitboard(self.0 << offset as u32)
367        } else {
368            Bitboard(self.0 >> -offset as u32)
369        }
370    }
371}
372
373impl fmt::Debug for Bitboard {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        use fmt::Write as _;
376
377        for rank in Rank::iter_rev() {
378            for file in File::iter() {
379                let square = Square::new(file, rank);
380                f.write_char(if self.contains(square) { '1' } else { '.' })?;
381                f.write_char(if file < H { ' ' } else { '\n' })?;
382            }
383        }
384
385        Ok(())
386    }
387}
388
389impl fmt::UpperHex for Bitboard {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        fmt::UpperHex::fmt(&self.0, f)
392    }
393}
394
395impl fmt::LowerHex for Bitboard {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        fmt::LowerHex::fmt(&self.0, f)
398    }
399}
400
401impl fmt::Octal for Bitboard {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        fmt::Octal::fmt(&self.0, f)
404    }
405}
406
407impl fmt::Binary for Bitboard {
408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409        fmt::Binary::fmt(&self.0, f)
410    }
411}
412
413impl From<Square> for Bitboard {
414    #[inline]
415    fn from(sq: Square) -> Bitboard {
416        Bitboard::from_square(sq)
417    }
418}
419
420impl From<Rank> for Bitboard {
421    #[inline]
422    fn from(rank: Rank) -> Bitboard {
423        Bitboard::from_rank(rank)
424    }
425}
426
427impl From<File> for Bitboard {
428    #[inline]
429    fn from(file: File) -> Bitboard {
430        Bitboard::from_file(file)
431    }
432}
433
434impl From<u64> for Bitboard {
435    #[inline]
436    fn from(bitboard: u64) -> Bitboard {
437        Bitboard(bitboard)
438    }
439}
440
441impl From<Bitboard> for u64 {
442    #[inline]
443    fn from(bitboard: Bitboard) -> u64 {
444        bitboard.0
445    }
446}
447
448impl<T> ops::BitAnd<T> for Bitboard
449where
450    T: Into<Bitboard>,
451{
452    type Output = Bitboard;
453
454    #[inline]
455    fn bitand(self, rhs: T) -> Bitboard {
456        let Bitboard(rhs) = rhs.into();
457        Bitboard(self.0 & rhs)
458    }
459}
460
461impl<T> ops::BitAndAssign<T> for Bitboard
462where
463    T: Into<Bitboard>,
464{
465    #[inline]
466    fn bitand_assign(&mut self, rhs: T) {
467        let Bitboard(rhs) = rhs.into();
468        self.0 &= rhs;
469    }
470}
471
472impl<T> ops::BitOr<T> for Bitboard
473where
474    T: Into<Bitboard>,
475{
476    type Output = Bitboard;
477
478    #[inline]
479    fn bitor(self, rhs: T) -> Bitboard {
480        let Bitboard(rhs) = rhs.into();
481        Bitboard(self.0 | rhs)
482    }
483}
484
485impl<T> ops::BitOrAssign<T> for Bitboard
486where
487    T: Into<Bitboard>,
488{
489    #[inline]
490    fn bitor_assign(&mut self, rhs: T) {
491        let Bitboard(rhs) = rhs.into();
492        self.0 |= rhs;
493    }
494}
495
496impl<T> ops::BitXor<T> for Bitboard
497where
498    T: Into<Bitboard>,
499{
500    type Output = Bitboard;
501
502    #[inline]
503    fn bitxor(self, rhs: T) -> Bitboard {
504        let Bitboard(rhs) = rhs.into();
505        Bitboard(self.0 ^ rhs)
506    }
507}
508
509impl<T> ops::BitXorAssign<T> for Bitboard
510where
511    T: Into<Bitboard>,
512{
513    #[inline]
514    fn bitxor_assign(&mut self, rhs: T) {
515        let Bitboard(rhs) = rhs.into();
516        self.0 ^= rhs;
517    }
518}
519
520impl ops::Not for Bitboard {
521    type Output = Bitboard;
522
523    #[inline]
524    fn not(self) -> Bitboard {
525        Bitboard(!self.0)
526    }
527}
528
529impl FromIterator<Square> for Bitboard {
530    fn from_iter<T>(iter: T) -> Bitboard
531    where
532        T: IntoIterator<Item = Square>,
533    {
534        let mut bitboard = Bitboard(0);
535        bitboard.extend(iter);
536        bitboard
537    }
538}
539
540impl Extend<Square> for Bitboard {
541    fn extend<T: IntoIterator<Item = Square>>(&mut self, iter: T) {
542        for square in iter {
543            self.insert(square);
544        }
545    }
546}
547
548impl IntoIterator for Bitboard {
549    type Item = Square;
550    type IntoIter = IntoIter;
551
552    #[inline]
553    fn into_iter(self) -> IntoIter {
554        IntoIter(self)
555    }
556}
557
558/// Iterator over the squares of a [`Bitboard`].
559#[derive(Clone, Debug, Default)]
560pub struct IntoIter(Bitboard);
561
562impl Iterator for IntoIter {
563    type Item = Square;
564
565    #[inline]
566    fn next(&mut self) -> Option<Square> {
567        self.0.pop_first()
568    }
569
570    #[inline]
571    fn count(self) -> usize {
572        self.0.len()
573    }
574
575    #[inline]
576    fn size_hint(&self) -> (usize, Option<usize>) {
577        let len = self.0.len();
578        (len, Some(len))
579    }
580
581    #[inline]
582    fn last(self) -> Option<Square> {
583        self.0.last()
584    }
585
586    #[inline]
587    fn fold<B, F>(self, init: B, mut f: F) -> B
588    where
589        F: FnMut(B, Square) -> B,
590    {
591        let mut accum = init;
592        let Bitboard(mut mask) = self.0;
593        while mask != 0 {
594            accum = f(accum, Square::panicky_from_index(mask.trailing_zeros() as u8));
595            mask = mask & mask.wrapping_sub(1);
596        }
597        accum
598    }
599}
600
601impl ExactSizeIterator for IntoIter {
602    #[inline]
603    fn len(&self) -> usize {
604        self.0.len()
605    }
606}
607
608impl DoubleEndedIterator for IntoIter {
609    #[inline]
610    fn next_back(&mut self) -> Option<Square> {
611        self.0.pop_last()
612    }
613}
614
615impl core::iter::FusedIterator for IntoIter {}
616
617/// Constants
618impl Bitboard {
619    // Full h1-a8 antidiagonal.
620    pub const ANTIDIAGONAL: Bitboard = Bitboard(0x0102_0408_1020_4080);
621    pub const BACKRANKS: Bitboard = Bitboard(0xff00_0000_0000_00ff);
622    pub const BLACK: Bitboard = Bitboard(0xaa55_aa55_aa55_aa55);
623    pub const CENTER: Bitboard = Bitboard(0x0000_0018_1800_0000);
624    pub const CORNERS: Bitboard = Bitboard(0x8100_0000_0000_0081);
625    // Full a1-h8 diagonal.
626    pub const DIAGONAL: Bitboard = Bitboard(0x8040_2010_0804_0201);
627    pub const EAST: Bitboard = Bitboard(0xf0f0_f0f0_f0f0_f0f0);
628    pub const EMPTY: Bitboard = Bitboard(0);
629    pub const FILE_A: Bitboard = Bitboard(0x0101_0101_0101_0101);
630    pub const FULL: Bitboard = Bitboard(!0);
631    pub const FULL_RAYS: [[Bitboard; 64]; 64] = {
632        let mut table = [[Bitboard::EMPTY; 64]; 64];
633        let mut a = 0i32;
634        while a < 64 {
635            let file_a = a & 7;
636            let rank_a = a >> 3;
637            let diagonal_a = rank_a - file_a;
638            let antidiagonal_a = rank_a + file_a - 7;
639
640            let mut b = 0i32;
641            while b < 64 {
642                let file_b = b & 7;
643                let rank_b = b >> 3;
644                let diagonal_b = rank_b - file_b;
645                let antidiagonal_b = rank_b + file_b - 7;
646
647                table[a as usize][b as usize] = Bitboard(if a == b {
648                    0
649                } else if file_a == file_b {
650                    Bitboard::FILE_A.0 << file_a
651                } else if rank_a == rank_b {
652                    0xff << (8 * rank_a)
653                } else if diagonal_a == diagonal_b {
654                    if diagonal_a >= 0 {
655                        Bitboard::DIAGONAL.0 << (8 * diagonal_a)
656                    } else {
657                        Bitboard::DIAGONAL.0 >> (8 * -diagonal_a)
658                    }
659                } else if antidiagonal_a == antidiagonal_b {
660                    if antidiagonal_a >= 0 {
661                        Bitboard::ANTIDIAGONAL.0 << (8 * antidiagonal_a)
662                    } else {
663                        Bitboard::ANTIDIAGONAL.0 >> (8 * -antidiagonal_a)
664                    }
665                } else {
666                    0
667                });
668                b += 1;
669            }
670            a += 1;
671        }
672        table
673    };
674    pub const NORTH: Bitboard = Bitboard(0xffff_ffff_0000_0000);
675    const RANKS: [u64; 8] = {
676        let mut masks = [0; 8];
677        let mut i = 0;
678        while i < 8 {
679            masks[i] = 0xff << (i * 8);
680            i += 1;
681        }
682        masks
683    };
684    pub const SOUTH: Bitboard = Bitboard(0x0000_0000_ffff_ffff);
685    pub const WEST: Bitboard = Bitboard(0x0f0f_0f0f_0f0f_0f0f);
686    pub const WHITE: Bitboard = Bitboard(0x55aa_55aa_55aa_55aa);
687}
688
689#[cfg(test)]
690mod tests {
691    use super::*;
692
693    #[test]
694    fn bitboard_constants() {
695        println!("{:?}", Bitboard::NORTH);
696        println!("{:?}", Bitboard::EAST);
697        println!("{:?}", Bitboard::SOUTH);
698        println!("{:?}", Bitboard::WEST);
699    }
700}