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 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 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 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}