Skip to main content

chess/
square.rs

1use core::str::FromStr;
2
3use crate::board::Bitboard;
4
5use File::*;
6use Rank::*;
7
8mod geometry;
9mod moves;
10
11pub use geometry::Direction;
12pub use moves::SliderSights;
13
14crate::finite_set!(
15    File,
16    FileTable {
17        A = 0 as a,
18        B = 1 as b,
19        C = 2 as c,
20        D = 3 as d,
21        E = 4 as e,
22        F = 5 as f,
23        G = 6 as g,
24        H = 7 as h,
25    },
26    FileCursor
27);
28
29crate::finite_set!(
30    Rank,
31    RankTable {
32        One = 0 as "1",
33        Two = 1 as "2",
34        Three = 2 as "3",
35        Four = 3 as "4",
36        Five = 4 as "5",
37        Six = 5 as "6",
38        Seven = 6 as "7",
39        Eight = 7 as "8",
40    }
41);
42
43crate::finite_set!(
44    /// The squares of a chess board.
45    Square,
46    SquareTable {
47        A1 = 0 as a1,
48        B1 = 1 as b1,
49        C1 = 2 as c1,
50        D1 = 3 as d1,
51        E1 = 4 as e1,
52        F1 = 5 as f1,
53        G1 = 6 as g1,
54        H1 = 7 as h1,
55        A2 = 8 as a2,
56        B2 = 9 as b2,
57        C2 = 10 as c2,
58        D2 = 11 as d2,
59        E2 = 12 as e2,
60        F2 = 13 as f2,
61        G2 = 14 as g2,
62        H2 = 15 as h2,
63        A3 = 16 as a3,
64        B3 = 17 as b3,
65        C3 = 18 as c3,
66        D3 = 19 as d3,
67        E3 = 20 as e3,
68        F3 = 21 as f3,
69        G3 = 22 as g3,
70        H3 = 23 as h3,
71        A4 = 24 as a4,
72        B4 = 25 as b4,
73        C4 = 26 as c4,
74        D4 = 27 as d4,
75        E4 = 28 as e4,
76        F4 = 29 as f4,
77        G4 = 30 as g4,
78        H4 = 31 as h4,
79        A5 = 32 as a5,
80        B5 = 33 as b5,
81        C5 = 34 as c5,
82        D5 = 35 as d5,
83        E5 = 36 as e5,
84        F5 = 37 as f5,
85        G5 = 38 as g5,
86        H5 = 39 as h5,
87        A6 = 40 as a6,
88        B6 = 41 as b6,
89        C6 = 42 as c6,
90        D6 = 43 as d6,
91        E6 = 44 as e6,
92        F6 = 45 as f6,
93        G6 = 46 as g6,
94        H6 = 47 as h6,
95        A7 = 48 as a7,
96        B7 = 49 as b7,
97        C7 = 50 as c7,
98        D7 = 51 as d7,
99        E7 = 52 as e7,
100        F7 = 53 as f7,
101        G7 = 54 as g7,
102        H7 = 55 as h7,
103        A8 = 56 as a8,
104        B8 = 57 as b8,
105        C8 = 58 as c8,
106        D8 = 59 as d8,
107        E8 = 60 as e8,
108        F8 = 61 as f8,
109        G8 = 62 as g8,
110        H8 = 63 as h8,
111    }
112);
113
114impl File {
115    #[inline]
116    pub const fn from_char(c: char) -> Option<Self> {
117        if ('a' <= c && c <= 'h') || ('A' <= c && c <= 'H') {
118            Some(Self::panicky_from_char(c))
119        } else {
120            None
121        }
122    }
123
124    #[inline]
125    #[track_caller]
126    pub(crate) const fn panicky_from_char(c: char) -> Self {
127        let index = if 'a' <= c && c <= 'h' {
128            c as u8 - b'a'
129        } else {
130            assert!('A' <= c && c <= 'H');
131            c as u8 - b'A'
132        };
133        unsafe { core::mem::transmute(index) }
134    }
135
136    #[inline]
137    pub const fn lower(self) -> char {
138        match self {
139            A => 'a',
140            B => 'b',
141            C => 'c',
142            D => 'd',
143            E => 'e',
144            F => 'f',
145            G => 'g',
146            H => 'h',
147        }
148    }
149
150    #[inline]
151    pub const fn upper(self) -> char {
152        match self {
153            A => 'A',
154            B => 'B',
155            C => 'C',
156            D => 'D',
157            E => 'E',
158            F => 'F',
159            G => 'G',
160            H => 'H',
161        }
162    }
163}
164
165impl Rank {
166    #[inline]
167    pub const fn from_char(c: char) -> Option<Self> {
168        if '1' <= c && c <= '8' { Some(Self::panicky_from_char(c)) } else { None }
169    }
170
171    #[track_caller]
172    #[inline]
173    pub(crate) const fn panicky_from_char(c: char) -> Self {
174        assert!('1' <= c && c <= '8');
175        unsafe { core::mem::transmute(c as u8 - b'1') }
176    }
177
178    #[inline]
179    pub const fn char(self) -> char {
180        match self {
181            One => '1',
182            Two => '2',
183            Three => '3',
184            Four => '4',
185            Five => '5',
186            Six => '6',
187            Seven => '7',
188            Eight => '8',
189        }
190    }
191}
192
193impl Square {
194    #[inline]
195    pub const fn new(file: File, rank: Rank) -> Self {
196        Self::panicky_from_index(((rank as u8) << 3) | (file as u8))
197    }
198
199    /// A8, B8, ..., H8, A7, ..., H1.
200    pub fn rank_rev_iter() -> impl Iterator<Item = Square> {
201        Rank::iter_rev().flat_map(|rank| File::iter().map(move |file| Square::new(file, rank)))
202    }
203
204    #[inline]
205    pub const fn file(self) -> File {
206        File::panicky_from_index((self as u8) & 0x7)
207    }
208
209    #[inline]
210    pub const fn rank(self) -> Rank {
211        Rank::panicky_from_index((self as u8) >> 3)
212    }
213
214    #[inline]
215    pub const fn coordinates(self) -> (File, Rank) {
216        (self.file(), self.rank())
217    }
218
219    // Add step vector to square, union into a bitboard.
220    const fn checked_add_vector_const(self, directions: &[Direction]) -> Bitboard {
221        let mut attacks = Bitboard::EMPTY;
222        let mut i = 0;
223        while i < directions.len() {
224            if let Some(target) = self.checked_add(directions[i]) {
225                attacks.append(Bitboard::from_square(target));
226            }
227            i += 1;
228        }
229        attacks
230    }
231}
232
233#[derive(Debug, thiserror::Error)]
234pub enum SquareError {
235    #[error("invalid square: {0}")]
236    Invalid(String),
237}
238
239impl FromStr for Square {
240    type Err = SquareError;
241
242    fn from_str(square: &str) -> Result<Self, Self::Err> {
243        let mut chars = square.chars();
244
245        let Some(file) = chars.next().and_then(File::from_char) else {
246            return Err(SquareError::Invalid(square.to_string()));
247        };
248        let Some(rank) = chars.next().and_then(Rank::from_char) else {
249            return Err(SquareError::Invalid(square.to_string()));
250        };
251        if chars.next().is_some() {
252            return Err(SquareError::Invalid(square.to_string()));
253        }
254
255        Ok(Square::new(file, rank))
256    }
257}
258
259#[test]
260fn display_square() {
261    assert_eq!(Square::A7.to_string(), "a7".to_string());
262    assert_eq!(Square::B3.to_string(), "b3".to_string());
263    assert_eq!("e4".parse::<Square>().unwrap(), Square::E4);
264}