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}