chessie_types/
bitboard.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7use std::{
8    fmt,
9    ops::{Index, Not},
10    str::FromStr,
11};
12
13use anyhow::{anyhow, bail};
14
15use super::{Color, File, Rank, Square};
16
17/// A [`Bitboard`] represents the game board as a set of bits.
18/// They are used for various computations, such as fetching valid moves or computing move costs.
19///
20/// The internal representation is a 64-bit binary number, so the values will represent the entire board.
21/// They are color-agnostic, with the low order bits representing the "lower" half of the board.
22///
23/// Bit index 0 is the least-significant bit (LSB = 2^0)
24/// Bit index 63 is the most-significant bit (MSB = 2^63)
25///
26/// The internal encoding uses [Little-Endian Rank-File Mapping (LERF)](https://www.chessprogramming.org/Square_Mapping_Considerations#Little-Endian_Rank-File_Mapping),
27/// so a bitboard of first Rank would look like this in binary:
28/// ```text
29/// 00000000
30/// 00000000
31/// 00000000
32/// 00000000
33/// 00000000
34/// 00000000
35/// 00000000
36/// 11111111
37/// ```
38#[derive(Clone, Copy, PartialEq, Eq, Hash)]
39#[repr(transparent)]
40pub struct Bitboard(pub(crate) u64);
41
42impl Bitboard {
43    pub const FILE_A: Self = Self(0x0101010101010101);
44    pub const FILE_B: Self = Self(0x0202020202020202);
45    pub const FILE_C: Self = Self(0x0404040404040404);
46    pub const FILE_D: Self = Self(0x0808080808080808);
47    pub const FILE_E: Self = Self(0x1010101010101010);
48    pub const FILE_F: Self = Self(0x2020202020202020);
49    pub const FILE_G: Self = Self(0x4040404040404040);
50    pub const FILE_H: Self = Self(0x8080808080808080);
51    pub const NOT_FILE_A: Self = Self(0xfefefefefefefefe);
52    pub const NOT_FILE_H: Self = Self(0x7f7f7f7f7f7f7f7f);
53    pub const RANK_1: Self = Self(0x00000000000000FF);
54    pub const RANK_2: Self = Self(0x000000000000FF00);
55    pub const RANK_3: Self = Self(0x0000000000FF0000);
56    pub const RANK_4: Self = Self(0x00000000FF000000);
57    pub const RANK_5: Self = Self(0x000000FF00000000);
58    pub const RANK_6: Self = Self(0x0000FF0000000000);
59    pub const RANK_7: Self = Self(0x00FF000000000000);
60    pub const RANK_8: Self = Self(0xFF00000000000000);
61    pub const A1_H8_DIAG: Self = Self(0x8040201008040201);
62    pub const H1_A8_DIAG: Self = Self(0x0102040810204080);
63    pub const LIGHT_SQUARES: Self = Self(0x55AA55AA55AA55AA);
64    pub const DARK_SQUARES: Self = Self(0xAA55AA55AA55AA55);
65    pub const EMPTY_BOARD: Self = Self(0x0000000000000000);
66    pub const FULL_BOARD: Self = Self(0xFFFFFFFFFFFFFFFF);
67    pub const EDGES: Self = Self(0xFF818181818181FF);
68    pub const CORNERS: Self = Self(0x8100000000000081);
69    pub const CENTER: Self = Self(0x0000001818000000);
70    pub const BACK_RANKS: Self = Self(0xFF000000000000FF);
71    pub const PAWN_RANKS: Self = Self(0x00FF00000000FF00);
72
73    /// Constructs a new [`Bitboard`] from the provided bit pattern.
74    ///
75    /// # Example
76    /// ```
77    /// # use chessie_types::Bitboard;
78    /// let board = Bitboard::new(255);
79    /// assert_eq!(board.to_hex_string(), "0x00000000000000FF");
80    /// ```
81    #[inline(always)]
82    pub const fn new(bits: u64) -> Self {
83        Self(bits)
84    }
85
86    /// Constructs a new [`Bitboard`] from the provided [`Square`].
87    ///
88    /// The resulting [`Bitboard`] will have only a single bit toggled on.
89    ///
90    /// # Example
91    /// ```
92    /// # use chessie_types::{Bitboard, Square};
93    /// let board = Bitboard::from_square(Square::H8);
94    /// assert_eq!(board.to_hex_string(), "0x8000000000000000");
95    /// ```
96    #[inline(always)]
97    pub const fn from_square(square: Square) -> Self {
98        Self(1 << square.index())
99    }
100
101    /// Constructs a new [`Bitboard`] from the provided [`File`].
102    ///
103    /// The resulting [`Bitboard`] will have an entire column of bits toggled on.
104    ///
105    /// # Example
106    /// ```
107    /// # use chessie_types::{Bitboard, File};
108    /// let board = Bitboard::from_file(File::F);
109    /// assert_eq!(board.to_hex_string(), "0x2020202020202020");
110    /// ```
111    #[inline(always)]
112    pub const fn from_file(file: File) -> Self {
113        Self::new(Self::FILE_A.0 << file.0)
114    }
115
116    /// Constructs a new [`Bitboard`] from the provided [`Rank`].
117    ///
118    /// The resulting [`Bitboard`] will have an entire row of bits toggled on.
119    ///
120    /// # Example
121    /// ```
122    /// # use chessie_types::{Bitboard, Rank};
123    /// let board = Bitboard::from_rank(Rank::SEVEN);
124    /// assert_eq!(board.to_hex_string(), "0x00FF000000000000");
125    /// ```
126    #[inline(always)]
127    pub const fn from_rank(rank: Rank) -> Self {
128        Self::new(Self::RANK_1.0 << (rank.0 * 8))
129    }
130
131    /// Returns [`Bitboard::FULL_BOARD`] if `true`, else [`Bitboard::EMPTY_BOARD`].
132    ///
133    /// # Example
134    /// ```
135    /// # use chessie_types::Bitboard;
136    ///
137    /// assert_eq!(Bitboard::from_bool(true), Bitboard::FULL_BOARD);
138    /// assert_eq!(Bitboard::from_bool(false), Bitboard::EMPTY_BOARD);
139    /// ```
140    #[inline(always)]
141    pub const fn from_bool(value: bool) -> Self {
142        Self((value as u64).wrapping_neg() & u64::MAX)
143    }
144
145    /// If `value` is `Some`, this converts the inner `T` using the appropriate [`Bitboard::from`] implementation.
146    ///
147    /// If `value` is `None`, this yields an empty bitboard.
148    ///
149    /// # Example
150    /// ```
151    /// # use chessie_types::{Bitboard, Square};
152    /// assert_eq!(Bitboard::from_option(Some(Square::A1)), Square::A1.bitboard());
153    /// assert_eq!(Bitboard::from_option::<Square>(None), Bitboard::EMPTY_BOARD);
154    /// ```
155    #[inline(always)]
156    pub fn from_option<T>(value: Option<T>) -> Self
157    where
158        Self: From<T>,
159    {
160        value.map(Self::from).unwrap_or_default()
161    }
162
163    /// Returns a [`Bitboard`] of this [`Color`]'s first rank.
164    ///
165    /// # Example
166    /// ```
167    /// # use chessie_types::{Bitboard, Color};
168    ///
169    /// assert_eq!(Bitboard::first_rank(Color::White), Bitboard::RANK_1);
170    /// assert_eq!(Bitboard::first_rank(Color::Black), Bitboard::RANK_8);
171    /// ```
172    #[inline(always)]
173    pub const fn first_rank(color: Color) -> Self {
174        [Self::RANK_1, Self::RANK_8][color.index()]
175    }
176
177    /// Returns a [`Bitboard`] of this [`Color`]'s second rank.
178    ///
179    /// # Example
180    /// ```
181    /// # use chessie_types::{Bitboard, Color};
182    ///
183    /// assert_eq!(Bitboard::second_rank(Color::White), Bitboard::RANK_2);
184    /// assert_eq!(Bitboard::second_rank(Color::Black), Bitboard::RANK_7);
185    /// ```
186    #[inline(always)]
187    pub const fn second_rank(color: Color) -> Self {
188        [Self::RANK_2, Self::RANK_7][color.index()]
189    }
190
191    /// Returns a [`Bitboard`] of this [`Color`]'s third rank.
192    ///
193    /// # Example
194    /// ```
195    /// # use chessie_types::{Bitboard, Color};
196    ///
197    /// assert_eq!(Bitboard::third_rank(Color::White), Bitboard::RANK_3);
198    /// assert_eq!(Bitboard::third_rank(Color::Black), Bitboard::RANK_6);
199    /// ```
200    #[inline(always)]
201    pub const fn third_rank(color: Color) -> Self {
202        [Self::RANK_3, Self::RANK_6][color.index()]
203    }
204
205    /// Returns a [`Bitboard`] of this [`Color`]'s seventh rank.
206    ///
207    /// # Example
208    /// ```
209    /// # use chessie_types::{Bitboard, Color};
210    ///
211    /// assert_eq!(Bitboard::seventh_rank(Color::White), Bitboard::RANK_7);
212    /// assert_eq!(Bitboard::seventh_rank(Color::Black), Bitboard::RANK_2);
213    /// ```
214    #[inline(always)]
215    pub const fn seventh_rank(color: Color) -> Self {
216        [Self::RANK_7, Self::RANK_2][color.index()]
217    }
218
219    /// Returns a [`Bitboard`] of this [`Color`]'s eighth (last) rank.
220    ///
221    /// # Example
222    /// ```
223    /// # use chessie_types::{Bitboard, Color};
224    ///
225    /// assert_eq!(Bitboard::eighth_rank(Color::White), Bitboard::RANK_8);
226    /// assert_eq!(Bitboard::eighth_rank(Color::Black), Bitboard::RANK_1);
227    /// ```
228    #[inline(always)]
229    pub const fn eighth_rank(color: Color) -> Self {
230        [Self::RANK_8, Self::RANK_1][color.index()]
231    }
232
233    /// Returns the inner `u64` of this [`Bitboard`].
234    #[inline(always)]
235    pub const fn inner(&self) -> u64 {
236        self.0
237    }
238
239    /// Creates a [`Square`] from this [`Bitboard`] based on the lowest-index bit that is flipped.
240    ///
241    /// If this [`Bitboard`] contains more than a single flipped bit, it is converted into a [`Square`]
242    /// based on the index of the lowest bit that is flipped.
243    ///
244    /// # Example
245    /// ```
246    /// # use chessie_types::{Bitboard, Square};
247    /// let board = Bitboard::from_square(Square::G2);
248    /// assert_eq!(board.to_square_unchecked(), Square::G2);
249    /// ```
250    #[inline(always)]
251    pub const fn to_square_unchecked(&self) -> Square {
252        Square::from_index_unchecked(self.0.trailing_zeros() as usize)
253    }
254
255    /// Creates a [`Square`] from this [`Bitboard`] based on the lowest-index bit that is flipped.
256    ///
257    /// If this [`Bitboard`] contains more than a single flipped bit, it is converted into a [`Square`]
258    /// based on the index of the lowest bit that is flipped.
259    ///
260    /// # Example
261    /// ```
262    /// # use chessie_types::{Bitboard, Square};
263    /// let board = Bitboard::from_square(Square::G2);
264    /// assert_eq!(board.to_square(), Some(Square::G2));
265    /// let invalid = Bitboard::RANK_1;
266    /// assert_eq!(invalid.to_square(), None);
267    /// ```
268    #[inline(always)]
269    pub const fn to_square(&self) -> Option<Square> {
270        if self.population() == 1 {
271            Some(self.to_square_unchecked())
272        } else {
273            None
274        }
275    }
276
277    /// Reverse this [`Bitboard`], viewing it from the opponent's perspective.
278    ///
279    /// # Example
280    /// ```
281    /// # use chessie_types::Bitboard;
282    /// let board = Bitboard::RANK_7;
283    /// assert_eq!(board.to_hex_string(), "0x00FF000000000000");
284    ///
285    /// let flipped = board.flipped();
286    /// assert_eq!(flipped.to_hex_string(), "0x000000000000FF00");
287    /// ```
288    #[inline(always)]
289    pub const fn flipped(&self) -> Self {
290        Self(self.0.swap_bytes())
291    }
292
293    /// If `color` is Black, flips this [`Bitboard`].
294    /// If `color` is White, does nothing.
295    ///
296    /// See [`Bitboard::flipped`] for more.
297    ///
298    /// # Example
299    /// ```
300    /// # use chessie_types::{Color, Bitboard};
301    /// assert_eq!(Bitboard::RANK_2.relative_to(Color::White), Bitboard::RANK_2);
302    /// assert_eq!(Bitboard::RANK_2.relative_to(Color::Black), Bitboard::RANK_7);
303    /// ```
304    #[inline(always)]
305    pub const fn relative_to(self, color: Color) -> Self {
306        match color {
307            Color::White => self,
308            Color::Black => self.flipped(),
309        }
310    }
311
312    /// Checks if this [`Bitboard`] is empty, meaning all bits are set to `0`.
313    ///
314    /// # Example
315    /// ```
316    /// # use chessie_types::Bitboard;
317    /// let board = Bitboard::new(0x0);
318    /// assert!(board.is_empty());
319    /// ```
320    #[inline(always)]
321    pub const fn is_empty(&self) -> bool {
322        self.0 == 0
323    }
324
325    /// Checks if this [`Bitboard`] is NOT empty, or contains at least one set bit.
326    ///
327    /// # Example
328    /// ```
329    /// # use chessie_types::Bitboard;
330    /// let board = Bitboard::CORNERS;
331    /// assert!(board.is_nonempty());
332    /// ```
333    #[inline(always)]
334    pub const fn is_nonempty(&self) -> bool {
335        self.0 != 0
336    }
337
338    /// Returns `true` if `self` contains *every* bit set in `other`.
339    ///
340    /// # Example
341    /// ```
342    /// # use chessie_types::{Bitboard, Square};
343    /// // Works on single squares
344    /// let e4 = Bitboard::from_square(Square::E4);
345    /// assert!(e4.is_superset(Square::E4));
346    ///
347    /// // ...multiple squares
348    /// assert!(Bitboard::FULL_BOARD.is_superset(Bitboard::CORNERS));
349    ///
350    /// // ...and overlaps
351    /// let rank_1 = Bitboard::RANK_1;
352    /// let rank_5 = Bitboard::RANK_5;
353    /// assert!(rank_1.is_superset(Square::E1));
354    /// assert!(!rank_1.is_superset(rank_5));
355    /// ```
356    #[inline(always)]
357    pub fn is_superset(&self, other: impl Into<Self>) -> bool {
358        let other = other.into();
359        (*self & other) == other
360    }
361
362    /// Returns `true` if `other` contains *every* bit set in `self`.
363    ///
364    /// Wrapper for `other.is_superset(self)`, since those are logically equivalent statements.
365    #[inline(always)]
366    pub fn is_subset(&self, other: impl Into<Self>) -> bool {
367        other.into().is_superset(*self)
368    }
369
370    /// Returns `true` if `self` contains none of the bits set in `other`.
371    ///
372    /// # Example
373    /// ```
374    /// # use chessie_types::{Bitboard, Square};
375    /// // Works on single squares
376    /// let e4 = Bitboard::from_square(Square::E4);
377    /// assert!(e4.is_disjoint(Square::A1));
378    ///
379    /// // ...multiple squares
380    /// assert!(Bitboard::EDGES.is_disjoint(Bitboard::CENTER));
381    ///
382    /// // ...and overlaps
383    /// assert!(Bitboard::RANK_1.is_disjoint(Bitboard::RANK_5));
384    /// assert!(!Bitboard::RANK_1.is_disjoint(Square::A1));
385    /// ```
386    #[inline(always)]
387    pub fn is_disjoint(&self, other: impl Into<Self>) -> bool {
388        (*self & other.into()).is_empty()
389    }
390
391    /// Returns `true` if `self` contains *any* of the bits set in `other`.
392    ///
393    /// # Example
394    /// ```
395    /// # use chessie_types::{Bitboard, Square};
396    /// // Works on single squares
397    /// let e4 = Bitboard::from_square(Square::E4);
398    /// assert!(e4.intersects(Square::E4));
399    ///
400    /// // ...multiple squares
401    /// assert!(Bitboard::FILE_A.intersects(Square::A3));
402    ///
403    /// // ...and overlaps
404    /// assert!(Bitboard::RANK_1.intersects(Bitboard::FILE_A));
405    /// assert!(!Bitboard::RANK_1.intersects(Bitboard::RANK_5));
406    /// ```
407    #[inline(always)]
408    pub fn intersects(&self, other: impl Into<Self>) -> bool {
409        (*self & other.into()).is_nonempty()
410    }
411
412    /// Sets the bit(s) at the location(s) specified by `other` to `1` (on).
413    ///
414    /// # Example
415    /// ```
416    /// # use chessie_types::{Bitboard, Square};
417    /// let mut board = Bitboard::EMPTY_BOARD;
418    /// board.set(Square::G2);
419    /// assert_eq!(board.to_hex_string(), "0x0000000000004000");
420    /// ```
421    #[inline(always)]
422    pub fn set(&mut self, other: impl Into<Self>) {
423        *self |= other.into()
424    }
425
426    /// Toggles (inverts/negates) the bit(s) at the location(s) specified by `other`.
427    ///
428    /// # Example
429    /// ```
430    /// # use chessie_types::{Bitboard, Square};
431    /// let mut board = Bitboard::RANK_1;
432    /// assert_eq!(board.to_hex_string(), "0x00000000000000FF");
433    /// board.toggle(Square::C1);
434    /// assert_eq!(board.to_hex_string(), "0x00000000000000FB");
435    /// board.toggle(Square::C1);
436    /// assert_eq!(board.to_hex_string(), "0x00000000000000FF");
437    /// ```
438    #[inline(always)]
439    pub fn toggle(&mut self, other: impl Into<Self>) {
440        *self ^= other.into()
441    }
442
443    /// Clears the bit(s) at the location(s) specified by `other` to `0` (off).
444    ///
445    /// # Example
446    /// ```
447    /// # use chessie_types::{Bitboard, Square};
448    /// let mut board = Bitboard::RANK_1;
449    /// board.clear(Square::C1);
450    /// assert_eq!(board.to_hex_string(), "0x00000000000000FB");
451    /// ```
452    #[inline(always)]
453    pub fn clear(&mut self, other: impl Into<Self>) {
454        *self &= !other.into()
455    }
456
457    /// Returns the index of the lowest non-zero bit of this [`Bitboard`], as a [`Square`].
458    ///
459    /// If `self` is empty, this yields `None`,
460    #[inline(always)]
461    pub fn lsb(&self) -> Option<Square> {
462        self.is_nonempty()
463            .then(|| Square(self.0.trailing_zeros() as u8))
464    }
465
466    /// Returns the index of the lowest non-zero bit of this [`Bitboard`], as a [`Square`].
467    ///
468    /// It is undefined behavior to call this function when `self` is empty.
469    #[inline(always)]
470    pub fn lsb_unchecked(&self) -> Square {
471        Square(self.0.trailing_zeros() as u8)
472    }
473
474    /// Pops and returns the index of the lowest non-zero bit of this [`Bitboard`], as a [`Square`].
475    ///
476    /// If `self` is empty, this yields `None`,
477    #[inline(always)]
478    pub fn pop_lsb(&mut self) -> Option<Square> {
479        let lsb = self.lsb();
480        self.clear_lsb();
481        lsb
482    }
483
484    /// Clears the lowest non-zero bit from `self`, if there is a square to clear.
485    #[inline(always)]
486    pub fn clear_lsb(&mut self) {
487        self.0 &= self.0.wrapping_sub(1);
488    }
489
490    /// Returns a [`BitboardIter`] to iterate over all of the set bits as [`Square`]s.
491    #[inline(always)]
492    pub const fn iter(&self) -> BitboardIter {
493        BitboardIter { bitboard: *self }
494    }
495
496    /// Returns a [`BitboardSubsetIter`] to iterate over all of the subsets of this bitboard.
497    #[inline(always)]
498    pub const fn subsets(&self) -> BitboardSubsetIter {
499        BitboardSubsetIter {
500            bitboard: *self,
501            subset: Self::EMPTY_BOARD,
502            remaining: 2usize.pow(self.population() as u32),
503        }
504    }
505
506    /// Yields the total number of `1`s in this [`Bitboard`].
507    ///
508    /// In other words, this function determines how many bits are activated.
509    ///
510    /// # Example
511    /// ```
512    /// # use chessie_types::Bitboard;
513    /// let board = Bitboard::RANK_1;
514    /// assert_eq!(board.population(), 8);
515    /// ```
516    #[inline(always)]
517    pub const fn population(&self) -> u8 {
518        self.0.count_ones() as u8
519    }
520
521    /// Shifts this [`Bitboard`] forward by `n`, according to `color`.
522    ///
523    /// If `color` is White, this shifts `n` ranks up. If Black, it shifts by `n` rank down.
524    ///
525    /// Note: This can "wrap" by advancing beyond the end of the board, so be careful!
526    ///
527    /// # Example
528    /// ```
529    /// # use chessie_types::{Bitboard, Color};
530    /// let rank4 = Bitboard::RANK_4;
531    /// assert_eq!(rank4.forward_by(Color::White, 1), Bitboard::RANK_5);
532    /// assert_eq!(rank4.forward_by(Color::Black, 1), Bitboard::RANK_3);
533    /// // Wrapping
534    /// assert_eq!(rank4.forward_by(Color::White, 5), Bitboard::RANK_1);
535    /// ```
536    #[inline(always)]
537    pub const fn forward_by(self, color: Color, n: u32) -> Self {
538        // Black magic: If `color` is White, this rotates left by 8, which is the same as "n ranks up"
539        // If `color` is Black, this rotates left by 496, which is the same as rotating right by 8, or "n ranks down"
540        Self(self.0.rotate_left(n * 8 * (1 + color as u32 * 62)))
541    }
542
543    /// Shifts this [`Bitboard`] backward by `n`, according to `color`.
544    ///
545    /// If `color` is White, this shifts `n` ranks up. If Black, it shifts by `n` ranks down.
546    ///
547    /// Note: This can "wrap" by advancing beyond the end of the board, so be careful!
548    ///
549    /// # Example
550    /// ```
551    /// # use chessie_types::{Bitboard, Color};
552    /// let rank4 = Bitboard::RANK_4;
553    /// assert_eq!(rank4.backward_by(Color::White, 1), Bitboard::RANK_3);
554    /// assert_eq!(rank4.backward_by(Color::Black, 1), Bitboard::RANK_5);
555    /// // Wrapping
556    /// assert_eq!(rank4.backward_by(Color::Black, 5), Bitboard::RANK_1);
557    /// ```
558    #[inline(always)]
559    pub const fn backward_by(self, color: Color, n: u32) -> Self {
560        // Black magic: If `color` is White, this rotates right by 8, which is the same as "n ranks down"
561        // If `color` is Black, this rotates right by 496, which is the same as rotating left by 8, or "n ranks up"
562        Self(self.0.rotate_right(n * 8 * (1 + color as u32 * 62)))
563    }
564
565    /// Shifts this [`Bitboard`] by one rank up.
566    ///
567    /// If already at the final rank (8), returns an empty board.
568    ///
569    /// # Example
570    /// ```
571    /// # use chessie_types::Bitboard;
572    /// assert_eq!(Bitboard::RANK_4.north(), Bitboard::RANK_5);
573    /// assert_eq!(Bitboard::RANK_8.north(), Bitboard::EMPTY_BOARD);
574    /// ```
575    #[inline(always)]
576    pub const fn north(self) -> Self {
577        Self(self.0 << 8)
578    }
579
580    // Rank down
581    /// Shifts this [`Bitboard`] by one rank board.
582    ///
583    /// If already at the first rank (1), returns an empty board.
584    ///
585    /// # Example
586    /// ```
587    /// # use chessie_types::Bitboard;
588    /// assert_eq!(Bitboard::RANK_4.south(), Bitboard::RANK_3);
589    /// assert_eq!(Bitboard::RANK_1.south(), Bitboard::EMPTY_BOARD);
590    /// ```
591    #[inline(always)]
592    pub const fn south(self) -> Self {
593        Self(self.0 >> 8)
594    }
595
596    /// Shifts this [`Bitboard`] by one [`File`] up.
597    ///
598    /// If already at the first file (a), returns an empty board.
599    ///
600    /// # Example
601    /// ```
602    /// # use chessie_types::Bitboard;
603    /// assert_eq!(Bitboard::FILE_C.east(), Bitboard::FILE_D);
604    /// assert_eq!(Bitboard::FILE_H.east(), Bitboard::EMPTY_BOARD);
605    /// ```
606    #[inline(always)]
607    pub const fn east(self) -> Self {
608        // Post-shift mask
609        Self((self.0 << 1) & Self::NOT_FILE_A.0)
610    }
611
612    /// Shifts this [`Bitboard`] by one [`File`] down.
613    ///
614    /// If already at the final file (h), returns an empty board.
615    ///
616    /// # Example
617    /// ```
618    /// # use chessie_types::Bitboard;
619    /// assert_eq!(Bitboard::FILE_C.west(), Bitboard::FILE_B);
620    /// assert_eq!(Bitboard::FILE_A.west(), Bitboard::EMPTY_BOARD);
621    /// ```
622    #[inline(always)]
623    pub const fn west(self) -> Self {
624        // Post-shift mask
625        Self((self.0 >> 1) & Self::NOT_FILE_H.0)
626    }
627
628    /// Combination of [`Bitboard::north`] and [`Bitboard::east`].
629    ///
630    /// If already at the edge, returns an empty board.
631    ///
632    /// This operation is faster than calling [`Bitboard::north`] and [`Bitboard::east`] separately.
633    #[inline(always)]
634    pub const fn northeast(self) -> Self {
635        // Post-shift mask
636        Self((self.0 << 9) & Self::NOT_FILE_A.0)
637    }
638
639    /// Combination of [`Bitboard::south`] and [`Bitboard::east`].
640    ///
641    /// If already at the edge, returns an empty board.
642    ///
643    /// This operation is faster than calling [`Bitboard::south`] and [`Bitboard::east`] separately.
644    #[inline(always)]
645    pub const fn southeast(self) -> Self {
646        // Post-shift mask
647        Self((self.0 >> 7) & Self::NOT_FILE_A.0)
648    }
649
650    /// Combination of [`Bitboard::north`] and [`Bitboard::west`].
651    ///
652    /// If already at the edge, returns an empty board.
653    ///
654    /// This operation is faster than calling [`Bitboard::north`] and [`Bitboard::west`] separately.
655    #[inline(always)]
656    pub const fn northwest(self) -> Self {
657        // Post-shift mask
658        Self((self.0 << 7) & Self::NOT_FILE_H.0)
659    }
660
661    /// Combination of [`Bitboard::south`] and [`Bitboard::west`].
662    ///
663    /// If already at the edge, returns an empty board.
664    ///
665    /// This operation is faster than calling [`Bitboard::south`] and [`Bitboard::west`] separately.
666    #[inline(always)]
667    pub const fn southwest(self) -> Self {
668        // Post-shift mask
669        Self((self.0 >> 9) & Self::NOT_FILE_H.0)
670    }
671
672    /*
673    /// Creates a mask of all squares in front of `square` (according to `color`) that are either directly in front or on the adjacent files.
674    pub fn passed_pawn_mask(square: Square, color: Color) -> Self {
675        let rank = match color {
676            Color::White => square.rank().increase(),
677            Color::Black => square.rank().decrease(),
678        };
679
680        let forward_ranks = Self::FULL_BOARD << rank;
681
682        let file = square.file();
683        let left = Self::from(file.decrease());
684        let right = Self::from(file.increase());
685        let center = Self::from_file(file);
686
687        let files = center | left | right;
688
689        forward_ranks & files
690    }
691     */
692
693    /// `const` analog of [`std::ops::BitAnd::bitand`].
694    ///
695    /// Returns the bitwise AND (`&`) of `self` and `other`.
696    #[inline(always)]
697    pub const fn and(self, other: Self) -> Self {
698        Self(self.0 & other.0)
699    }
700
701    /// `const` analog of [`std::ops::BitOr::bitor`].
702    ///
703    /// Returns the bitwise OR (`|`) of `self` and `other`.
704    #[inline(always)]
705    pub const fn or(self, other: Self) -> Self {
706        Self(self.0 | other.0)
707    }
708
709    /// `const` analog of [`std::ops::BitXor::bitxor`].
710    ///
711    /// Returns the bitwise XOR (`^`) of `self` and `other`.
712    #[inline(always)]
713    pub const fn xor(self, other: Self) -> Self {
714        Self(self.0 ^ other.0)
715    }
716
717    /// `const` analog of [`Not::not`].
718    ///
719    /// Returns the logical negation (`!`) of `self`, flipping all `1`'s to `0`'s and vice versa.
720    #[inline(always)]
721    pub const fn not(self) -> Self {
722        Self(!self.0)
723    }
724
725    /// Formats this [`Bitboard`] as a hexadecimal string.
726    #[inline(always)]
727    pub fn to_hex_string(&self) -> String {
728        format!("0x{:0>16X}", self.0)
729    }
730}
731
732impl FromStr for Bitboard {
733    type Err = anyhow::Error;
734    /// Constructs a new [`Bitboard`] from the provided string.
735    ///
736    /// The string may be a binary or hexadecimal number, and may be proceeded with `0b` or `0x`.
737    ///
738    /// # Example
739    /// ```
740    /// # use chessie_types::Bitboard;
741    /// use std::str::FromStr;
742    /// let board1 = Bitboard::from_str("0x00FF000000000000").unwrap();
743    /// let board2 = Bitboard::from_str("00FF000000000000").unwrap();
744    /// let board3 = Bitboard::from_str("0000000011111111000000000000000000000000000000000000000000000000").unwrap();
745    /// let board4 = Bitboard::from_str("0b0000000011111111000000000000000000000000000000000000000000000000").unwrap();
746    /// assert_eq!(board1, board2);
747    /// assert_eq!(board1, board3);
748    /// assert_eq!(board1, board4);
749    /// assert_eq!(board1.to_hex_string(), "0x00FF000000000000");
750    /// ```
751    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
752        let bits = s.to_lowercase();
753
754        if bits.len() == 64 || bits.len() == 66 {
755            let bits = bits.trim_start_matches("0b");
756            let bits = u64::from_str_radix(bits, 2).map_err(|_| {
757                anyhow!("Invalid Bitboard string: Expected binary digits, got {bits}")
758            })?;
759            Ok(Self::new(bits))
760        } else if bits.len() == 16 || bits.len() == 18 {
761            let bits = bits.trim_start_matches("0x");
762            let bits = u64::from_str_radix(bits, 16).map_err(|_| {
763                anyhow!("Invalid Bitboard string: Expected hexadecimal digits, got {bits}")
764            })?;
765            Ok(Self::new(bits))
766        } else {
767            bail!("Invalid Bitboard string: Invalid length {}. Length must be either 64 (binary) or 16 (hexadecimal)", bits.len())
768        }
769    }
770}
771
772impl FromIterator<Square> for Bitboard {
773    /// A [`Bitboard`] can be created from an iterator over [`Square`]s.
774    fn from_iter<T: IntoIterator<Item = Square>>(iter: T) -> Self {
775        iter.into_iter().fold(Self::default(), |bb, sq| bb | sq)
776    }
777}
778
779macro_rules! impl_bitwise_op {
780    // Impl op and op_assign for Self
781    ($op:tt, $op_assign:tt, $func:ident, $func_assign:ident) => {
782        impl<T> std::ops::$op<T> for Bitboard
783        where
784            Self: From<T>,
785        {
786            type Output = Self;
787            #[inline(always)]
788            fn $func(self, rhs: T) -> Self::Output {
789                Self(self.0.$func(Self::from(rhs).0))
790            }
791        }
792
793        impl<T> std::ops::$op_assign<T> for Bitboard
794        where
795            Self: From<T>,
796        {
797            #[inline(always)]
798            fn $func_assign(&mut self, rhs: T) {
799                self.0.$func_assign(Self::from(rhs).0);
800            }
801        }
802    };
803}
804
805impl_bitwise_op!(BitAnd, BitAndAssign, bitand, bitand_assign);
806impl_bitwise_op!(BitOr, BitOrAssign, bitor, bitor_assign);
807impl_bitwise_op!(BitXor, BitXorAssign, bitxor, bitxor_assign);
808
809impl Not for Bitboard {
810    type Output = Self;
811    #[inline(always)]
812    fn not(self) -> Self::Output {
813        Self(!self.0)
814    }
815}
816
817impl<T: Into<Bitboard>> Index<T> for Bitboard {
818    type Output = bool;
819
820    /// Wrapper over [`Bitboard::intersects`].
821    #[inline(always)]
822    fn index(&self, index: T) -> &Self::Output {
823        if self.intersects(index) {
824            &true
825        } else {
826            &false
827        }
828    }
829}
830
831impl<T> From<Option<T>> for Bitboard
832where
833    Self: From<T>,
834{
835    /// Wrapper for [`Bitboard::from_option`].
836    #[inline(always)]
837    fn from(value: Option<T>) -> Self {
838        Self::from_option(value)
839    }
840}
841
842impl From<Square> for Bitboard {
843    #[inline(always)]
844    /// Wrapper for [`Bitboard::from_square`].
845    fn from(value: Square) -> Self {
846        Self::from_square(value)
847    }
848}
849
850impl From<File> for Bitboard {
851    #[inline(always)]
852    /// Wrapper for [`Bitboard::from_file`].
853    fn from(value: File) -> Self {
854        Self::from_file(value)
855    }
856}
857
858impl From<Rank> for Bitboard {
859    #[inline(always)]
860    /// Wrapper for [`Bitboard::from_rank`].
861    fn from(value: Rank) -> Self {
862        Self::from_rank(value)
863    }
864}
865
866impl From<u64> for Bitboard {
867    #[inline(always)]
868    /// Wrapper for [`Bitboard::new`].
869    fn from(value: u64) -> Self {
870        Self::new(value)
871    }
872}
873
874impl From<bool> for Bitboard {
875    #[inline(always)]
876    /// Wrapper for [`Bitboard::from_bool`].
877    fn from(value: bool) -> Self {
878        Self::from_bool(value)
879    }
880}
881
882impl fmt::UpperHex for Bitboard {
883    /// Formats this [`Bitboard`] as a 16-character uppercase hexadecimal string, not including the `0x` prefix.
884    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885        write!(f, "0X{:0>16X}", self.0)
886    }
887}
888
889impl fmt::LowerHex for Bitboard {
890    /// Formats this [`Bitboard`] as a 16-character lowercase hexadecimal string, not including the `0x` prefix.
891    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
892        write!(f, "0x{:0>16x}", self.0)
893    }
894}
895
896impl fmt::Binary for Bitboard {
897    /// Formats this [`Bitboard`] as a 64-character binary string, not including the `0b` prefix.
898    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
899        write!(f, "0b{:0>64b}", self.0)
900    }
901}
902
903impl Default for Bitboard {
904    #[inline(always)]
905    fn default() -> Self {
906        Self::EMPTY_BOARD
907    }
908}
909
910impl fmt::Display for Bitboard {
911    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912        // Allocate just enough capacity
913        let mut board = String::with_capacity(136);
914
915        for rank in Rank::iter().rev() {
916            for file in File::iter() {
917                let square = Square::new(file, rank);
918                let occupant = if self.intersects(square) { 'X' } else { '.' };
919
920                board += &format!("{occupant} ");
921            }
922            board += "\n";
923        }
924
925        write!(f, "{board}")
926    }
927}
928
929impl fmt::Debug for Bitboard {
930    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931        // Allocate just enough capacity
932        let mut board = String::with_capacity(198);
933
934        for rank in Rank::iter().rev() {
935            board += &format!("{rank}| ");
936
937            for file in File::iter() {
938                let square = Square::new(file, rank);
939                let occupant = if self.intersects(square) { 'X' } else { '.' };
940
941                board += &format!("{occupant} ");
942            }
943            board += "\n";
944        }
945        board += " +";
946        for _ in File::iter() {
947            board += "--";
948        }
949        board += "\n   ";
950        for file in File::iter() {
951            board += &format!("{file} ");
952        }
953
954        write!(f, "{board}")
955    }
956}
957
958/// An iterator over all set bits in a [`Bitboard`].
959///
960/// See [`Bitboard::iter`].
961pub struct BitboardIter {
962    bitboard: Bitboard,
963}
964
965impl Iterator for BitboardIter {
966    type Item = Square;
967    #[inline(always)]
968    fn next(&mut self) -> Option<Self::Item> {
969        self.bitboard.pop_lsb()
970    }
971
972    fn size_hint(&self) -> (usize, Option<usize>) {
973        let size = self.bitboard.population() as usize;
974        (size, Some(size))
975    }
976}
977
978impl ExactSizeIterator for BitboardIter {
979    #[inline(always)]
980    fn len(&self) -> usize {
981        self.bitboard.population() as usize
982    }
983}
984
985impl IntoIterator for Bitboard {
986    type Item = Square;
987    type IntoIter = BitboardIter;
988    #[inline(always)]
989    fn into_iter(self) -> Self::IntoIter {
990        BitboardIter { bitboard: self }
991    }
992}
993
994impl IntoIterator for &Bitboard {
995    type Item = Square;
996    type IntoIter = BitboardIter;
997    #[inline(always)]
998    fn into_iter(self) -> Self::IntoIter {
999        BitboardIter { bitboard: *self }
1000    }
1001}
1002
1003impl IntoIterator for &mut Bitboard {
1004    type Item = Square;
1005    type IntoIter = BitboardIter;
1006    #[inline(always)]
1007    fn into_iter(self) -> Self::IntoIter {
1008        BitboardIter { bitboard: *self }
1009    }
1010}
1011
1012/// An iterator over all possible subsets of a [`Bitboard`].
1013///
1014/// See [`Bitboard::subsets`].
1015///
1016/// This is primarily used in magic bitboard generation, but may also be useful for other purposes, so it is made public.
1017pub struct BitboardSubsetIter {
1018    /// The original bitboard whose subsets to iterate over.
1019    bitboard: Bitboard,
1020
1021    /// The current subset, which will be the result of `.next()`.
1022    subset: Bitboard,
1023
1024    /// How many subsets we have left to iterate.
1025    remaining: usize,
1026}
1027
1028impl Iterator for BitboardSubsetIter {
1029    type Item = Bitboard;
1030    fn next(&mut self) -> Option<Self::Item> {
1031        if self.remaining == 0 {
1032            None
1033        } else {
1034            // By saving and returning the original subset, we make the iterator return
1035            // an empty set as the first element and the full subset as the last.
1036            let subset = self.subset;
1037
1038            // Performs a Carry-Rippler operation: https://www.chessprogramming.org/Traversing_Subsets_of_a_Set#All_Subsets_of_any_Set
1039            self.subset.0 = self.subset.0.wrapping_sub(self.bitboard.0) & self.bitboard.0;
1040            self.remaining -= 1;
1041
1042            Some(subset)
1043        }
1044    }
1045
1046    #[inline(always)]
1047    fn size_hint(&self) -> (usize, Option<usize>) {
1048        (self.remaining, Some(self.remaining))
1049    }
1050}
1051
1052impl ExactSizeIterator for BitboardSubsetIter {
1053    #[inline(always)]
1054    fn len(&self) -> usize {
1055        self.remaining
1056    }
1057}
1058
1059#[cfg(test)]
1060mod test {
1061    use super::*;
1062
1063    #[test]
1064    fn test_bitboard_to_string() {
1065        let expected = ". . . . . . . X \n\
1066                              . . . . . . X . \n\
1067                              . . . . . X . . \n\
1068                              . . . . X . . . \n\
1069                              . . . X . . . . \n\
1070                              . . X . . . . . \n\
1071                              . X . . . . . . \n\
1072                              X . . . . . . . \n";
1073        assert_eq!(Bitboard::A1_H8_DIAG.to_string(), expected);
1074
1075        let expected = ". . . . . . . . \n\
1076                              . . . . . . . . \n\
1077                              . . . . . . . . \n\
1078                              . . . . . . . . \n\
1079                              . . . . . . . . \n\
1080                              . . . . . . . . \n\
1081                              X X X X X X X X \n\
1082                              . . . . . . . . \n";
1083        assert_eq!(Bitboard::RANK_2.to_string(), expected);
1084
1085        let board = Bitboard::RANK_2 | Bitboard::FILE_C;
1086        let expected = ". . X . . . . . \n\
1087                              . . X . . . . . \n\
1088                              . . X . . . . . \n\
1089                              . . X . . . . . \n\
1090                              . . X . . . . . \n\
1091                              . . X . . . . . \n\
1092                              X X X X X X X X \n\
1093                              . . X . . . . . \n";
1094        assert_eq!(board.to_string(), expected);
1095    }
1096
1097    #[test]
1098    fn test_bitboard_masking() {
1099        let file_a = Bitboard::FILE_A;
1100        let full_board = Bitboard::FULL_BOARD;
1101        let expected = Bitboard::NOT_FILE_A;
1102
1103        assert_eq!(file_a ^ full_board, expected);
1104    }
1105
1106    #[test]
1107    fn test_bitboard_from_str() {
1108        let bits = "0x0101010101010101";
1109        let board = Bitboard::from_str(&bits).unwrap();
1110        assert_eq!(board, Bitboard::FILE_A);
1111
1112        let bits = "0101010101010101";
1113        let board = Bitboard::from_str(&bits).unwrap();
1114        assert_eq!(board, Bitboard::FILE_A);
1115
1116        let bits = "0b0000000100000001000000010000000100000001000000010000000100000001";
1117        let board = Bitboard::from_str(&bits).unwrap();
1118        assert_eq!(board, Bitboard::FILE_A);
1119
1120        let bits = "0000000100000001000000010000000100000001000000010000000100000001";
1121        let board = Bitboard::from_str(&bits).unwrap();
1122        assert_eq!(board, Bitboard::FILE_A);
1123
1124        let bits = "0b0000000200000002000000020000000200000002000000010000000100000001";
1125        let board = Bitboard::from_str(bits);
1126        assert!(board.is_err());
1127
1128        let bits = "0000000200000002000000020000000200000002000000010000000100000001";
1129        let board = Bitboard::from_str(bits);
1130        assert!(board.is_err());
1131
1132        let bits = "x0awdk";
1133        let board = Bitboard::from_str(bits);
1134        assert!(board.is_err());
1135
1136        let bits = "";
1137        let board = Bitboard::from_str(bits);
1138        assert!(board.is_err());
1139    }
1140
1141    #[test]
1142    fn test_bitboard_constructors() {
1143        assert_eq!(Bitboard::RANK_4, Bitboard::from_rank(Rank::FOUR));
1144    }
1145}