Skip to main content

timecat/chess/
castle.rs

1use super::*;
2
3#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4#[repr(u8)]
5#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
6pub enum CastleRights {
7    None = 0,
8    KingSide = 1,
9    QueenSide = 2,
10    Both = 3,
11}
12
13impl CastleRights {
14    /// Can I castle kingside?
15    #[inline]
16    pub const fn has_kingside(self) -> bool {
17        self.to_index() & 1 == 1
18    }
19
20    /// Can I castle queenside?
21    #[inline]
22    pub const fn has_queenside(self) -> bool {
23        self.to_index() & 2 == 2
24    }
25
26    #[inline]
27    pub fn square_to_castle_rights(color: Color, square: Square) -> Self {
28        *get_item_unchecked!(
29            const {
30                let mut array = [[Self::None; 64]; 2];
31                array[0][63] = Self::KingSide;
32                array[1][7] = Self::KingSide;
33                array[0][56] = Self::QueenSide;
34                array[1][0] = Self::QueenSide;
35                array[0][60] = Self::Both;
36                array[1][4] = Self::Both;
37                array
38            },
39            color.to_index(),
40            square.to_index()
41        )
42    }
43
44    /// What squares need to be empty to castle kingside?
45    #[inline]
46    pub fn kingside_squares(self, color: Color) -> BitBoard {
47        *get_item_unchecked!(
48            const { [BitBoard::new(6917529027641081856), BitBoard::new(96)] },
49            color.to_index(),
50        )
51    }
52
53    /// What squares need to be empty to castle queenside?
54    #[inline]
55    pub fn queenside_squares(self, color: Color) -> BitBoard {
56        *get_item_unchecked!(
57            const { [BitBoard::new(1008806316530991104), BitBoard::new(14)] },
58            color.to_index(),
59        )
60    }
61
62    /// Remove castle rights, and return a new `CastleRights`.
63    #[inline]
64    pub fn remove(self, remove: Self) -> Self {
65        unsafe { Self::from_int(self.to_int() & !remove.to_int()) }
66    }
67
68    /// Convert `CastleRights` to `u8` for table lookups
69    #[inline]
70    pub const fn to_int(self) -> u8 {
71        self as u8
72    }
73
74    /// Convert `CastleRights` to `usize` for table lookups
75    #[inline]
76    pub const fn to_index(self) -> usize {
77        self as usize
78    }
79
80    /// Convert `usize` to `CastleRights`.  Panic if invalid number.
81    #[inline]
82    pub const unsafe fn from_int(i: u8) -> Self {
83        std::mem::transmute(i)
84    }
85
86    /// Convert `usize` to `CastleRights`.  Panic if invalid number.
87    #[inline]
88    pub const unsafe fn from_index(i: usize) -> Self {
89        Self::from_int(i as u8)
90    }
91
92    /// Which rooks can we "guarantee" we haven't moved yet?
93    #[inline]
94    pub fn unmoved_rooks(self, color: Color) -> BitBoard {
95        // TODO: match can be removed.
96        match self {
97            Self::None => BitBoard::EMPTY,
98            Self::KingSide => BitBoard::from_rank_and_file(color.to_my_backrank(), File::H),
99            Self::QueenSide => BitBoard::from_rank_and_file(color.to_my_backrank(), File::A),
100            Self::Both => {
101                let my_backrank = color.to_my_backrank();
102                BitBoard::from_rank_and_file(my_backrank, File::A)
103                    ^ BitBoard::from_rank_and_file(my_backrank, File::H)
104            }
105        }
106    }
107
108    #[inline]
109    pub fn to_string(self, color: Color) -> &'static str {
110        get_item_unchecked!(
111            const { ["", "k", "q", "kq", "", "K", "Q", "KQ"] },
112            color.to_index() << 2 | self.to_index()
113        )
114    }
115}
116
117impl Add for CastleRights {
118    type Output = Self;
119
120    #[expect(clippy::suspicious_arithmetic_impl)]
121    #[inline]
122    fn add(self, rhs: Self) -> Self::Output {
123        unsafe { Self::from_int(self.to_int() | rhs.to_int()) }
124    }
125}
126
127impl AddAssign for CastleRights {
128    #[inline]
129    fn add_assign(&mut self, rhs: Self) {
130        *self = *self + rhs;
131    }
132}
133
134impl Sub for CastleRights {
135    type Output = Self;
136
137    #[inline]
138    fn sub(self, rhs: Self) -> Self::Output {
139        self.remove(rhs)
140    }
141}
142
143impl SubAssign for CastleRights {
144    #[inline]
145    fn sub_assign(&mut self, rhs: Self) {
146        *self = *self - rhs;
147    }
148}
149
150impl TryFrom<char> for CastleRights {
151    type Error = TimecatError;
152
153    fn try_from(value: char) -> Result<Self> {
154        match value {
155            'K' | 'k' => Ok(Self::KingSide),
156            'Q' | 'q' => Ok(Self::QueenSide),
157            _ => Err(TimecatError::InvalidCastleRightsString {
158                s: value.to_string().into(),
159            }),
160        }
161    }
162}
163
164impl FromStr for CastleRights {
165    type Err = TimecatError;
166
167    fn from_str(s: &str) -> Result<Self> {
168        match s.trim() {
169            "" => Ok(Self::None),
170            "K" | "k" => Ok(Self::KingSide),
171            "Q" | "q" => Ok(Self::QueenSide),
172            "KQ" | "kq" | "QK" | "qk" => Ok(Self::Both),
173            _ => Err(TimecatError::InvalidCastleRightsString {
174                s: s.to_string().into(),
175            }),
176        }
177    }
178}