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}