hexe_core/square/mod.rs
1//! A chess board square and its components.
2//!
3//! A chess board is comprised of sixty-four squares ranging from A1 through H8.
4//! Each square has two components:
5//!
6//! - File: a column represented by a letter from A through F
7//! - Rank: a row represented by a number from 1 through 8
8//!
9//! # Examples
10//!
11//! Basic usage:
12//!
13//! ```
14//! use hexe_core::square::{Square, File, Rank};
15//!
16//! let f = File::B;
17//! let r = Rank::Seven;
18//! let sq = Square::B7;
19//!
20//! assert_eq!(sq, Square::new(f, r));
21//! ```
22//!
23//! [`Square`] is an `enum` so that we can safely and conveniently index into
24//! tables of sixty-four elements. Because the optimizer knows that the index
25//! will **never** be greater than 64, the bounds check gets removed, thus
26//! making lookups fast.
27//!
28//! ```
29//! # use hexe_core::prelude::*;
30//! # type T = ();
31//! static TABLE: [T; 64] = [
32//! /* ... */
33//! # (); 64
34//! ];
35//!
36//! pub fn get_value(sq: Square) -> T {
37//! // Will never panic
38//! TABLE[sq as usize]
39//! }
40//! ```
41//!
42//! [`Square`]: enum.Square.html
43
44use core::{fmt, ops, str};
45use prelude::*;
46use uncon::*;
47
48#[cfg(feature = "serde")]
49use serde::*;
50
51#[cfg(all(test, nightly))]
52mod benches;
53mod tables;
54
55impl_rand!(u8 => Square, File, Rank);
56
57/// A square on a chess board.
58#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, FromUnchecked)]
59#[uncon(impl_from, other(u16, u32, u64, usize))]
60#[repr(u8)]
61#[allow(missing_docs)]
62pub enum Square {
63 A1, B1, C1, D1, E1, F1, G1, H1,
64 A2, B2, C2, D2, E2, F2, G2, H2,
65 A3, B3, C3, D3, E3, F3, G3, H3,
66 A4, B4, C4, D4, E4, F4, G4, H4,
67 A5, B5, C5, D5, E5, F5, G5, H5,
68 A6, B6, C6, D6, E6, F6, G6, H6,
69 A7, B7, C7, D7, E7, F7, G7, H7,
70 A8, B8, C8, D8, E8, F8, G8, H8,
71}
72
73impl fmt::Debug for Square {
74 #[inline]
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 fmt::Display::fmt(self, f)
77 }
78}
79
80impl fmt::Display for Square {
81 #[inline]
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 self.map_str(|s| s.fmt(f))
84 }
85}
86
87impl From<(File, Rank)> for Square {
88 #[inline]
89 fn from((file, rank): (File, Rank)) -> Square {
90 Square::new(file, rank)
91 }
92}
93
94define_from_str_error! { Square;
95 /// The error returned when `Square::from_str` fails.
96 "failed to parse a string as a square"
97}
98
99impl str::FromStr for Square {
100 type Err = FromStrError;
101
102 fn from_str(s: &str) -> Result<Square, FromStrError> {
103 let bytes = s.as_bytes();
104 if bytes.len() != 2 { Err(FromStrError(())) } else {
105 // Gets better optimized as a macro for some strange reason
106 macro_rules! convert {
107 ($lo:expr, $hi:expr, $b:expr) => {
108 match $b {
109 $lo...$hi => unsafe { ($b - $lo).into_unchecked() },
110 _ => return Err(FromStrError(())),
111 }
112 }
113 }
114 Ok(Square::new(convert!(b'a', b'h', bytes[0] | 32),
115 convert!(b'1', b'8', bytes[1])))
116 }
117 }
118}
119
120#[cfg(feature = "serde")]
121impl Serialize for Square {
122 fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
123 self.map_str(|s| ser.serialize_str(s))
124 }
125}
126
127const FILE_BITS: u8 = 7;
128const RANK_BITS: u8 = FILE_BITS << RANK_SHIFT;
129const RANK_SHIFT: usize = 3;
130
131const TRIANGLE_LEN: usize = 64 * 65 / 2;
132
133impl Square {
134 /// Initializes a `Square` from a `File` and `Rank`.
135 ///
136 /// # Examples
137 ///
138 /// Basic usage:
139 ///
140 /// ```
141 /// # use hexe_core::square::*;
142 /// let s = Square::new(File::B, Rank::Five);
143 ///
144 /// assert_eq!(s.file(), File::B);
145 /// assert_eq!(s.rank(), Rank::Five);
146 /// ```
147 #[inline]
148 pub fn new(file: File, rank: Rank) -> Square {
149 (((rank as u8) << RANK_SHIFT) | (file as u8)).into()
150 }
151
152 #[inline]
153 pub(crate) fn between(self, other: Square) -> Bitboard {
154 use self::tables::*;
155 Bitboard(TABLES[BETWEEN_START..][self as usize][other as usize])
156 }
157
158 #[inline]
159 pub(crate) fn line(self, other: Square) -> Bitboard {
160 use self::tables::*;
161 Bitboard(TABLES[LINE_START..][self as usize][other as usize])
162 }
163
164 /// Returns the `File` for `self`.
165 #[inline]
166 pub fn file(self) -> File {
167 ((self as u8) & FILE_BITS).into()
168 }
169
170 /// Returns the `Rank` for `self`.
171 #[inline]
172 pub fn rank(self) -> Rank {
173 ((self as u8) >> RANK_SHIFT).into()
174 }
175
176 /// Reverses the file of `self`.
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// # use hexe_core::prelude::*;
182 /// assert_eq!(Square::B2.rev_file(), Square::G2);
183 /// ```
184 #[inline]
185 pub fn rev_file(self) -> Square {
186 (FILE_BITS ^ self as u8).into()
187 }
188
189 /// Reverses the rank of `self`.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// # use hexe_core::prelude::*;
195 /// assert_eq!(Square::B2.rev_rank(), Square::B7);
196 /// ```
197 #[inline]
198 pub fn rev_rank(self) -> Square {
199 (RANK_BITS ^ self as u8).into()
200 }
201
202 /// Combines the file of `self` with the rank of `other`.
203 ///
204 /// # Examples
205 ///
206 /// Basic usage:
207 ///
208 /// ```
209 /// # use hexe_core::prelude::*;
210 /// let s1 = Square::B5;
211 /// let s2 = Square::C7;
212 ///
213 /// assert_eq!(s1.combine(s2), Square::B7);
214 /// assert_eq!(s2.combine(s1), Square::C5);
215 /// ```
216 #[inline]
217 pub fn combine(self, other: Square) -> Square {
218 ((FILE_BITS & self as u8) | (RANK_BITS & other as u8)).into()
219 }
220
221 /// Returns the `Color` for `self`.
222 ///
223 /// # Examples
224 ///
225 /// Basic usage:
226 ///
227 /// ```
228 /// # use hexe_core::prelude::*;
229 /// let a = Square::A1;
230 /// assert_eq!(a.color(), Color::Black);
231 ///
232 /// let b = Square::B5;
233 /// assert_eq!(b.color(), Color::White);
234 /// ```
235 #[inline]
236 pub fn color(self) -> Color {
237 const BLACK: usize = Bitboard::BLACK.0 as usize;
238 const MOD: usize = ::consts::PTR_SIZE * 8;
239 (BLACK >> (self as usize % MOD)).into()
240 }
241
242 /// Returns whether `self` and `other` are equal in color.
243 #[inline]
244 pub fn color_eq(self, other: Square) -> bool {
245 let bits = self as u8 ^ other as u8;
246 ((bits >> RANK_SHIFT) ^ bits) & 1 == 0
247 }
248
249 /// Returns whether `self` is aligned with two other squares along a file,
250 /// rank, or diagonal.
251 ///
252 /// # Examples
253 ///
254 /// Square A3 lies on the same diagonal as C5 and F8:
255 ///
256 /// ```
257 /// # use hexe_core::prelude::*;
258 /// assert!(Square::A3.is_aligned(Square::C5, Square::F8));
259 /// ```
260 #[inline]
261 pub fn is_aligned(self, a: Square, b: Square) -> bool {
262 a.line(b).contains(self)
263 }
264
265 /// Returns whether `self` is between two other squares along a file, rank,
266 /// or diagonal.
267 ///
268 /// # Examples
269 ///
270 /// Square D4 lies between B2 and G7 along a diagonal:
271 ///
272 /// ```
273 /// # use hexe_core::prelude::*;
274 /// assert!(Square::D4.is_between(Square::B2, Square::G7));
275 /// ```
276 #[inline]
277 pub fn is_between(self, a: Square, b: Square) -> bool {
278 a.between(b).contains(self)
279 }
280
281 /// Calculates the [Chebyshev distance][wiki] between `self` and `other`.
282 ///
283 /// The result is the number of steps required to move a king from one
284 /// square to the other.
285 ///
286 /// # Examples
287 ///
288 /// It takes a king two moves to travel the same distance as a knight:
289 ///
290 /// ```
291 /// # use hexe_core::prelude::*;
292 /// for s1 in Square::ALL {
293 /// for s2 in s1.knight_attacks() {
294 /// assert_eq!(s1.distance(s2), 2);
295 /// }
296 /// }
297 /// ```
298 ///
299 /// [wiki]: https://en.wikipedia.org/wiki/Chebyshev_distance
300 #[inline]
301 pub fn distance(self, other: Square) -> usize {
302 tables::DISTANCE[self as usize][other as usize] as usize
303 }
304
305 /// Calculates the [Manhattan distance][wiki] between `self` and `other`.
306 ///
307 /// The result is the distance when strictly taking a horizontal/vertical
308 /// path from one square to the other.
309 ///
310 /// # Examples
311 ///
312 /// The knight's path always traverses three squares:
313 ///
314 /// ```
315 /// # use hexe_core::prelude::*;
316 /// for s1 in Square::ALL {
317 /// for s2 in s1.knight_attacks() {
318 /// assert_eq!(s1.man_distance(s2), 3);
319 /// }
320 /// }
321 /// ```
322 ///
323 /// [wiki]: https://en.wiktionary.org/wiki/Manhattan_distance
324 #[inline]
325 pub fn man_distance(self, other: Square) -> usize {
326 self.file().distance(other.file()) + self.rank().distance(other.rank())
327 }
328
329 /// Calculates the [Chebyshev distance][wiki] between `self` and the center
330 /// of the board.
331 ///
332 /// # Examples
333 ///
334 /// It takes a king three moves to travel to the center of the board from
335 /// any edge:
336 ///
337 /// ```
338 /// # use hexe_core::prelude::*;
339 /// let edges = File::A | File::H | Rank::One | Rank::Eight;
340 ///
341 /// for sq in edges {
342 /// assert_eq!(sq.center_distance(), 3);
343 /// }
344 /// ```
345 ///
346 /// [wiki]: https://en.wikipedia.org/wiki/Chebyshev_distance
347 #[inline]
348 pub fn center_distance(self) -> usize {
349 use self::tables::*;
350 DISTANCE[CHEBYSHEV_INDEX][self as usize] as usize
351 }
352
353 /// Calculates the [Manhattan distance][wiki] between `self` and the center
354 /// of the board.
355 ///
356 /// [wiki]: https://en.wiktionary.org/wiki/Manhattan_distance
357 #[inline]
358 pub fn center_man_distance(self) -> usize {
359 use self::tables::*;
360 DISTANCE[MANHATTAN_INDEX][self as usize] as usize
361 }
362
363 /// Returns the [triangular index][wiki] for `self` and `other`.
364 ///
365 /// This allows indexing into tables of size 2080, which is slightly greater
366 /// than 2048 (64 × 64 ÷ 2).
367 ///
368 /// # Tradeoffs
369 /// While this allows for using 50.78% as much memory as a table of size
370 /// 4096 (64 × 64), computing the index takes a considerable amount of time
371 /// compared to indexing into a 64 × 64 table.
372 ///
373 /// # Safety
374 /// The result index has been thoroughly tested to **always** return a value
375 /// less than 2080. Because of this, it is safe to call [`get_unchecked`] on
376 /// arrays/slices of that size or greater.
377 ///
378 /// [wiki]: https://chessprogramming.wikispaces.com/Square+Attacked+By#Legality%20Test-In%20Between-Triangular%20Lookup
379 /// [`get_unchecked`]: https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked
380 #[inline]
381 pub fn tri_index(self, other: Square) -> usize {
382 let mut a = self as isize;
383 let mut b = other as isize;
384 let mut d = a - b;
385 d &= d >> 31;
386 b += d;
387 a -= d;
388 b *= b ^ 127;
389 ((b >> 1) + a) as usize
390 }
391
392 /// Returns a shared reference to an element from the table via triangular
393 /// index.
394 #[inline]
395 pub fn tri<T>(self, other: Square, table: &[T; TRIANGLE_LEN]) -> &T {
396 // tri index < TRIANGLE_LEN
397 unsafe { table.get_unchecked(self.tri_index(other)) }
398 }
399
400 /// Returns a mutable reference to an element from the table via triangular
401 /// index.
402 #[inline]
403 pub fn tri_mut<T>(self, other: Square, table: &mut [T; TRIANGLE_LEN]) -> &mut T {
404 unsafe { table.get_unchecked_mut(self.tri_index(other)) }
405 }
406
407 /// Returns the result of applying a function to a mutable string
408 /// representation of `self`.
409 ///
410 /// This is a _much_ preferred way of getting the string representation of
411 /// a square, especially in when using `#![no_std]`. The alternative would
412 /// be to use `to_string` or `format!`, which perform a heap allocation
413 /// whereas this uses a stack-allocated string.
414 ///
415 /// # Examples
416 ///
417 /// The string's lifetime is for the duration of the closure's execution:
418 ///
419 /// ```
420 /// # use hexe_core::prelude::*;
421 /// Square::A5.map_str(|s| assert_eq!(s, "A5"));
422 /// ```
423 #[inline]
424 pub fn map_str<T, F: FnOnce(&mut str) -> T>(self, f: F) -> T {
425 let mut buf = [char::from(self.file()) as u8,
426 char::from(self.rank()) as u8];
427 unsafe { f(str::from_utf8_unchecked_mut(&mut buf)) }
428 }
429
430 /// Returns the pawn attacks for `self` and `color`.
431 #[inline]
432 pub fn pawn_attacks(self, color: Color) -> Bitboard {
433 Bitboard(tables::TABLES[color as usize][self as usize])
434 }
435
436 /// Returns the knight attacks for `self`.
437 #[inline]
438 pub fn knight_attacks(self) -> Bitboard {
439 Bitboard(tables::TABLES[2][self as usize])
440 }
441
442 /// Returns the rook attacks for `self` and `occupied`.
443 ///
444 /// Whether or not `occupied` contains `self` does not matter.
445 ///
446 /// # Examples
447 ///
448 /// Basic usage:
449 ///
450 /// ```
451 /// # use hexe_core::prelude::*;
452 /// let start = Square::A1;
453 ///
454 /// let occ = Square::A3 | Square::C1;
455 /// let exp = Square::A2 | Square::B1 | occ;
456 ///
457 /// assert_eq!(start.rook_attacks(occ), exp);
458 /// ```
459 #[inline]
460 pub fn rook_attacks(self, occupied: Bitboard) -> Bitboard {
461 ::magic::rook_attacks(self, occupied)
462 }
463
464 /// Returns the bishop attacks for `self` and `occupied`.
465 ///
466 /// Whether or not `occupied` contains `self` does not matter.
467 ///
468 /// # Examples
469 ///
470 /// Basic usage:
471 ///
472 /// ```
473 /// # use hexe_core::prelude::*;
474 /// let start = Square::A1;
475 ///
476 /// let occ = Square::C3;
477 /// let exp = Square::B2 | occ;
478 ///
479 /// assert_eq!(start.bishop_attacks(occ.into()), exp);
480 /// ```
481 #[inline]
482 pub fn bishop_attacks(self, occupied: Bitboard) -> Bitboard {
483 ::magic::bishop_attacks(self, occupied)
484 }
485
486 /// Returns the king attacks for `self`.
487 #[inline]
488 pub fn king_attacks(self) -> Bitboard {
489 Bitboard(tables::TABLES[3][self as usize])
490 }
491
492 /// Returns the queen attacks for `self` and `occupied`.
493 ///
494 /// This works the same as combining the results of `rook_attacks` and
495 /// `bishop_attacks`.
496 #[inline]
497 pub fn queen_attacks(self, occupied: Bitboard) -> Bitboard {
498 self.rook_attacks(occupied) | self.bishop_attacks(occupied)
499 }
500}
501
502/// A file (or column) for a chess board.
503#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, FromUnchecked)]
504#[uncon(impl_from, other(u16, u32, u64, usize))]
505#[repr(u8)]
506#[allow(missing_docs)]
507pub enum File { A, B, C, D, E, F, G, H }
508
509impl File {
510 /// Returns a file from the parsed character.
511 #[inline]
512 pub fn from_char(ch: char) -> Option<File> {
513 match 32 | ch as u8 {
514 b @ b'a' ... b'f' => unsafe {
515 Some((b - b'a').into_unchecked())
516 },
517 _ => None,
518 }
519 }
520
521 /// Returns the adjacent mask for `self`, containing all squares on the
522 /// files directly to the left and right of `self`.
523 ///
524 /// # Examples
525 ///
526 /// Basic usage:
527 ///
528 /// ```
529 /// # use hexe_core::prelude::*;
530 /// let val = File::C;
531 /// let adj = File::B | File::D;
532 ///
533 /// assert_eq!(val.adjacent_mask(), adj);
534 /// ```
535 #[inline]
536 pub fn adjacent_mask(&self) -> Bitboard {
537 Bitboard(tables::ADJACENT[0][*self as usize])
538 }
539}
540
541/// A rank (or row) for a chess board.
542#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, FromUnchecked)]
543#[uncon(impl_from, other(u16, u32, u64, usize))]
544#[repr(u8)]
545#[allow(missing_docs)]
546pub enum Rank { One, Two, Three, Four, Five, Six, Seven, Eight }
547
548impl Rank {
549 /// Returns the first rank for `color`.
550 #[inline]
551 pub fn first(color: Color) -> Rank {
552 match color {
553 Color::White => Rank::One,
554 Color::Black => Rank::Eight,
555 }
556 }
557
558 /// Returns the last rank for `color`.
559 #[inline]
560 pub fn last(color: Color) -> Rank {
561 Rank::first(!color)
562 }
563
564 /// Returns a rank from the parsed character.
565 #[inline]
566 pub fn from_char(ch: char) -> Option<Rank> {
567 match ch as u8 {
568 b @ b'1' ... b'8' => unsafe {
569 Some((b - b'1').into_unchecked())
570 },
571 _ => None,
572 }
573 }
574
575 /// Returns the adjacent mask for `self`, containing all squares on the
576 /// ranks directly ahead and behind `self`.
577 ///
578 /// # Examples
579 ///
580 /// Basic usage:
581 ///
582 /// ```
583 /// # use hexe_core::prelude::*;
584 /// let val = Rank::Five;
585 /// let adj = Rank::Four | Rank::Six;
586 ///
587 /// assert_eq!(val.adjacent_mask(), adj);
588 /// ```
589 #[inline]
590 pub fn adjacent_mask(&self) -> Bitboard {
591 Bitboard(tables::ADJACENT[1][*self as usize])
592 }
593
594 /// Returns the remaining distance for `color` to reach the end of the board
595 /// from `self`.
596 ///
597 /// This is useful for finding the number of moves a pawn must make to be
598 /// promoted.
599 ///
600 /// # Examples
601 ///
602 /// ```
603 /// # use hexe_core::prelude::*;
604 /// let rank = Rank::Three;
605 ///
606 /// assert_eq!(rank.rem_distance(Color::White), 5);
607 /// assert_eq!(rank.rem_distance(Color::Black), 2);
608 /// ```
609 #[inline]
610 pub fn rem_distance(self, color: Color) -> usize {
611 (0b111 * !color as usize) ^ self as usize
612 }
613}
614
615macro_rules! impl_components {
616 ($($t:ty, $c:expr, $m:expr;)+) => { $(
617 impl From<$t> for char {
618 #[inline]
619 fn from(val: $t) -> char {
620 ($c + val as u8) as char
621 }
622 }
623
624 impl ops::Not for $t {
625 type Output = Self;
626
627 #[inline]
628 fn not(self) -> Self {
629 (7 - self as u8).into()
630 }
631 }
632
633 #[cfg(feature = "serde")]
634 impl Serialize for $t {
635 fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
636 ser.serialize_char((*self).into())
637 }
638 }
639
640 #[cfg(feature = "serde")]
641 impl<'de> Deserialize<'de> for $t {
642 fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
643 Self::from_char(char::deserialize(de)?).ok_or_else(|| {
644 de::Error::custom($m)
645 })
646 }
647 }
648
649 impl $t {
650 /// Returns the distance between `self` and `other`.
651 #[inline]
652 pub fn distance(self, other: Self) -> usize {
653 (self as isize - other as isize).abs() as usize
654 }
655 }
656 )+ }
657}
658
659impl_components! {
660 File, b'A', "failed to parse board file";
661 Rank, b'1', "failed to parse board rank";
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use rand::{Rng, thread_rng};
668
669 macro_rules! sliding_attacks {
670 ($($fn:ident)*) => {
671 $(#[test]
672 fn $fn() {
673 let mut rng = thread_rng();
674 for occupied in (0..20_000).map(|_| Bitboard(rng.gen())) {
675 for square in Square::ALL {
676 let exp = Bitboard::from(square).$fn(!occupied);
677 let res = square.$fn(occupied);
678 if exp != res {
679 panic!(
680 "Square: {}\n\
681 Occupied: {1:?}\n{1}\n\
682 Expected: {2:?}\n{2}\n\
683 Generated: {3:?}\n{3}",
684 square,
685 occupied,
686 exp,
687 res,
688 );
689 }
690 }
691 }
692 })*
693 }
694 }
695
696 macro_rules! jump_attacks {
697 ($($fn:ident)*) => {
698 $(#[test]
699 fn $fn() {
700 for square in Square::ALL {
701 let exp = Bitboard::from(square).$fn();
702 let res = square.$fn();
703 assert_eq!(exp, res);
704 }
705 })*
706 }
707 }
708
709 sliding_attacks! { rook_attacks bishop_attacks queen_attacks }
710
711 jump_attacks! { knight_attacks king_attacks }
712
713 #[test]
714 fn distance() {
715 fn square(a: Square, b: Square) -> usize {
716 use core::cmp::max;
717 max(a.file().distance(b.file()), a.rank().distance(b.rank()))
718 }
719
720 for s1 in Square::ALL {
721 for s2 in Square::ALL {
722 assert_eq!(square(s1, s2), s1.distance(s2));
723 }
724 }
725 }
726
727 #[test]
728 fn tri_index() {
729 for s1 in Square::ALL {
730 for s2 in Square::ALL {
731 let idx = s1.tri_index(s2);
732 assert_eq!(idx, s2.tri_index(s1));
733 assert!(idx < TRIANGLE_LEN);
734 }
735 }
736 }
737
738 #[test]
739 fn pawn_attacks() {
740 for &color in &[Color::White, Color::Black] {
741 for square in Square::ALL {
742 let exp = Bitboard::from(square).pawn_attacks(color);
743 let res = square.pawn_attacks(color);
744 assert_eq!(exp, res);
745 }
746 }
747 }
748
749 #[test]
750 fn file_from_char() {
751 for ch in b'A'..(b'F' + 1) {
752 for &ch in &[ch, ch | 32] {
753 assert!(File::from_char(ch as _).is_some());
754 }
755 }
756 }
757
758 #[test]
759 fn rank_from_char() {
760 for ch in b'1'..(b'8' + 1) {
761 assert!(Rank::from_char(ch as _).is_some());
762 }
763 }
764
765 #[test]
766 fn square_color() {
767 for s1 in Square::ALL {
768 for s2 in Square::ALL {
769 assert_eq!(s1.color() == s2.color(), s1.color_eq(s2));
770 }
771 }
772 for &(b, c) in &[(Bitboard::WHITE, Color::White),
773 (Bitboard::BLACK, Color::Black)] {
774 for s in b {
775 assert_eq!(s.color(), c);
776 }
777 }
778 }
779}