monster_chess/bitboard/
util.rs

1/// `BitBoard<T>` is essentially a wrapper around a `[u128; T]`.
2/// It has multiple `u128`s so it can serve as essentially a bigger unsigned integer for `T > 1`.
3/// It handles bit operations and even some mathematical operations to operate essentially as if it was any other integer type.
4/// It also has methods specifically related to its use as a BitBoard, like moving it up, down, right, left, etc.
5/// For `T = 1` (a BitBoard with only one `u128`), it should compile down to esssentially a single `u128`, and should be very fast.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord)]
7pub struct BitBoard<const T: usize> {
8    pub bits: [u128; T],
9}
10
11impl<const T: usize> BitBoard<T> {
12    /// Create a `BitBoard` from a backing array.
13    pub fn from_data(data: [u128; T]) -> BitBoard<T> {
14        BitBoard { bits: data }
15    }
16
17    /// Create a `BitBoard` from a single `u128`.
18    pub fn from_element(el: u128) -> BitBoard<T> {
19        let mut arr = [0; T];
20        arr[T - 1] = el;
21        BitBoard { bits: arr }
22    }
23
24    /// Create a `BitBoard` with a single bit from the index of that bit from the LSB.
25    pub fn from_lsb(bit: u16) -> BitBoard<T> {
26        BitBoard::<T>::from_element(1) << bit
27    }
28
29    /// Create a `BitBoard` with a single bit from the index of that bit from the MSB.
30    pub fn from_msb(bit: u16) -> BitBoard<T> {
31        !(BitBoard::<T>::max() >> 1) >> bit
32    }
33
34    /// Create a `BitBoard` starting at a given bit, and continuing for a given length.
35    pub fn starting_at_lsb(bit: u16, length: u16) -> BitBoard<T> {
36        (BitBoard::<T>::from_lsb(length) - BitBoard::<T>::from_element(1)) << bit
37    }
38
39    /// Check if a `BitBoard` has a given bit.
40    pub fn has_bit(self, bit: u16) -> bool {
41        (self & (BitBoard::<T>::from_element(1) << bit)).is_set()
42    }
43
44    /// Check if a `BitBoard` is empty (if that `BitBoard` is `0`.)
45    pub fn is_empty(&self) -> bool {
46        if T == 1 {
47            return self.bits[0] == 0;
48        }
49
50        self.bits.iter().all(|el| *el == 0)
51    }
52
53    /// Check if a `BitBoard` is set (if that `BitBoard` isn't `0`, if it has at least one set bit.)
54    pub fn is_set(&self) -> bool {
55        if T == 1 {
56            return self.bits[0] != 0;
57        }
58
59        self.bits.iter().any(|el| *el != 0)
60    }
61
62    /// Get the highest possible value for a given `BitBoard`.
63    pub fn max() -> BitBoard<T> {
64        BitBoard::<T>::from_data([u128::MAX; T])
65    }
66
67    /// Initialize a new, empty `BitBoard`.
68    pub fn new() -> BitBoard<T> {
69        BitBoard::<T>::from_data([0; T])
70    }
71
72    /// A utility method primarily for internally handling bit operations.
73    /// Apply a function to each of the `u128`s of two `BitBoards`.
74    /// For instance, `bitboard.apply(rhs, |el| el.0 | el.1)` would get a `BitBoard` for the `or` bit operation.
75    #[inline(always)]
76    pub fn apply(self, rhs: BitBoard<T>, apply: impl Fn((&u128, u128)) -> u128) -> Self {
77        if T == 1 {
78            return BitBoard {
79                bits: [apply((&self.bits[0], rhs.bits[0])); T],
80            };
81        }
82
83        BitBoard {
84            bits: self
85                .bits
86                .iter()
87                .zip(rhs.bits)
88                .map(apply)
89                .collect::<Vec<_>>()
90                .try_into()
91                .expect(&format!("Could not convert BitBoard data vector into an array when applying operation with `apply`."))
92        }
93    }
94
95    /// A utility method primarily for internally handling bit operations.
96    /// Apply a function to each of the `u128`s of two `BitBoard`s, mutating the first of those `BitBoard`s.
97    /// For instance, `bitboard.effect(rhs, |el| el.0 | el.1)` would apply the `or` operation to this `BitBoard`.
98    #[inline(always)]
99    pub fn effect(&mut self, rhs: BitBoard<T>, apply: impl Fn((&u128, u128)) -> u128) {
100        if T == 1 {
101            self.bits = [apply((&self.bits[0], rhs.bits[0])); T];
102            return;
103        }
104
105        self.bits = self
106            .bits
107            .iter()
108            .zip(rhs.bits)
109            .map(apply)
110            .collect::<Vec<_>>()
111            .try_into()
112            .expect(&format!("Could not convert BitBoard data vector into an array when applying operation with `effect`."));
113    }
114
115    /// Count the number of zero bits in a given `BitBoard`.
116    pub fn count_zeros(&self) -> u32 {
117        if T == 1 {
118            self.bits[0].count_zeros()
119        } else {
120            self.bits.iter().map(|el| el.count_zeros()).sum()
121        }
122    }
123
124    /// Count the number of one bits in a given `BitBoard`.
125    pub fn count_ones(&self) -> u32 {
126        if T == 1 {
127            self.bits[0].count_ones()
128        } else {
129            self.bits.iter().map(|el| el.count_ones()).sum()
130        }
131    }
132
133    /// Outputs an array of all bits, with `0` if they're off, and `1` otherwise.
134    /// Not a well optimized method; avoid using in hot loops.
135    pub fn get_bits(&self) -> Vec<u128> {
136        let mut bits: Vec<u128> = Vec::with_capacity(128 * T);
137        for container in self.bits.iter().rev() {
138            for i in 0..128 {
139                bits.push((container >> i) & 1); // Get `i`th bit of `container` and check if it is toggled on (equal to 1)
140            }
141        }
142        bits
143    }
144
145    /// Iterates over all set bits in the `BitBoard`.
146    pub fn iter_set_bits(self, end: u16) -> BitIterator<T> {
147        BitIterator(self, end)
148    }
149
150    /// Displays a `BitBoard`, showing ⬛ if the bit is off, and ⬜ if it's on.
151    /// Consider using this if you need to debug a `BitBoard`.
152    pub fn display(&self, rows: usize, cols: usize) -> String {
153        let mut chunks = Vec::<String>::with_capacity(rows);
154        for (ind, row) in self.get_bits().chunks(cols).enumerate() {
155            if ind == rows {
156                break;
157            }
158
159            let chunk = row
160                .iter()
161                .map(|i| if i == &0 { "⬛" } else { "⬜" })
162                .collect::<Vec<_>>()
163                .join("");
164            chunks.push(chunk);
165        }
166
167        chunks.join("\n")
168    }
169}
170
171/// `BitIterator` is used to efficiently iterate over all of the bits in a `BitBoard` with `BitBoard.iter_set_bits()`.
172pub struct BitIterator<const T: usize>(pub BitBoard<T>, u16);
173
174impl<const T: usize> Iterator for BitIterator<T> {
175    type Item = u16;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        if self.0.is_set() {
179            let bit = self.0.bitscan_forward();
180            if bit >= self.1 {
181                return None;
182            }
183            self.0 &= self.0 - BitBoard::from_element(1);
184            Some(bit)
185        } else {
186            None
187        }
188    }
189}