1use crate::chess::Color;
2use crate::{error::Error, utils};
3use std::mem::transmute;
4use std::ops::Deref;
5
6#[derive(PartialEq, Debug)]
7#[repr(u8)]
8pub enum Rank {
9 One,
10 Two,
11 Three,
12 Four,
13 Five,
14 Six,
15 Seven,
16 Eight,
17}
18
19impl From<u8> for Rank {
20 fn from(value: u8) -> Self {
21 unsafe { transmute(value & 7) }
24 }
25}
26
27#[derive(PartialEq, Debug)]
28#[repr(u8)]
29pub enum File {
30 A,
31 B,
32 C,
33 D,
34 E,
35 F,
36 G,
37 H,
38}
39
40impl From<u8> for File {
41 fn from(value: u8) -> Self {
42 unsafe { transmute(value & 7) }
44 }
45}
46
47#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
48pub struct Square(pub u8); impl Square {
51 pub fn new(rank: Rank, file: File) -> Self {
52 Self(16 * (rank as u8) + (file as u8))
53 }
54
55 pub fn add(&self, delta: i8) -> Result<Self, Error> {
56 let new_idx = (self.0 as i16 + delta as i16) as u8;
57
58 Ok(new_idx.try_into()?)
59 }
60
61 pub fn file(&self) -> u8 {
62 return self.0 & 7;
63 }
64
65 pub fn rank(&self) -> u8 {
67 return self.0 >> 4;
68 }
69
70 pub fn get_notation(&self) -> String {
71 format!("{}{}", (self.file() + 97) as char, self.rank() + 1)
72 }
73
74 pub fn color(self) -> Color {
75 match 0x00AA0055_u32.rotate_right(self.0 as u32) & 1 {
76 0 => Color::WHITE,
77 1 => Color::BLACK,
78 _ => panic!("unknown square color"),
79 }
80 }
81}
82
83impl TryFrom<u8> for Square {
84 type Error = Error;
85
86 fn try_from(idx: u8) -> Result<Self, Self::Error> {
87 utils::is_valid_idx(idx)?;
88
89 Ok(Self(idx))
90 }
91}
92
93impl TryFrom<&str> for Square {
94 type Error = Error;
95
96 fn try_from(value: &str) -> Result<Self, Self::Error> {
97 if value.len() != 2 {
98 return Err(Error::InvalidSquareString);
99 }
100
101 let mut value = value.chars();
102 let ascii_val = value.next().unwrap().to_ascii_lowercase() as u8;
103 let file: u8 = if ascii_val >= 97 && ascii_val <= 104 {
104 ascii_val - 97
105 } else {
106 return Err(Error::InvalidSquareString);
107 };
108
109 let Some(rank) = value.next().unwrap().to_digit(10) else {
110 return Err(Error::InvalidSquareString);
111 };
112
113 if rank <= 0 {
114 return Err(Error::InvalidSquareString);
115 }
116
117 Ok(Self::new(((rank - 1) as u8).into(), file.into()))
118 }
119}
120
121impl Deref for Square {
122 type Target = u8;
123
124 fn deref(&self) -> &Self::Target {
125 &self.0
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn square_from_rank_and_file() {
135 assert_eq!(Square::new(Rank::Eight, File::B).0, 0x71);
136 assert_eq!(Square::new(Rank::One, File::A).0, 0x00);
137 assert_eq!(Square::new(Rank::Eight, File::H).0, 0x77);
138 assert_eq!(Square::new(Rank::Five, File::C).0, 0x42);
139 }
140
141 #[test]
142 fn square_from_arbitrary_idx() {
143 assert!(Square::try_from(0x78).is_err());
144 assert!(Square::try_from(0x7F).is_err());
145 assert!(Square::try_from(0x4D).is_err());
146
147 assert!(Square::try_from(0x70).is_ok());
148 assert!(Square::try_from(0x34).is_ok());
149 assert!(Square::try_from(0x00).is_ok());
150 }
151
152 #[test]
153 fn file_and_rank() {
154 assert_eq!(Square::new(Rank::One, File::A).file(), File::A as u8);
155 assert_eq!(Square::new(Rank::One, File::B).file(), File::B as u8);
156 assert_eq!(Square::new(Rank::One, File::C).file(), File::C as u8);
157 assert_eq!(Square::new(Rank::One, File::D).file(), File::D as u8);
158 assert_eq!(Square::new(Rank::One, File::E).file(), File::E as u8);
159 assert_eq!(Square::new(Rank::One, File::F).file(), File::F as u8);
160 assert_eq!(Square::new(Rank::One, File::G).file(), File::G as u8);
161 assert_eq!(Square::new(Rank::One, File::H).file(), File::H as u8);
162
163 assert_eq!(Square::new(Rank::One, File::A).rank(), Rank::One as u8);
164 assert_eq!(Square::new(Rank::Two, File::B).rank(), Rank::Two as u8);
165 assert_eq!(Square::new(Rank::Three, File::C).rank(), Rank::Three as u8);
166 assert_eq!(Square::new(Rank::Four, File::D).rank(), Rank::Four as u8);
167 assert_eq!(Square::new(Rank::Five, File::E).rank(), Rank::Five as u8);
168 assert_eq!(Square::new(Rank::Six, File::F).rank(), Rank::Six as u8);
169 assert_eq!(Square::new(Rank::Seven, File::G).rank(), Rank::Seven as u8);
170 assert_eq!(Square::new(Rank::Eight, File::H).rank(), Rank::Eight as u8);
171
172 let sq = Square::try_from(0x70).unwrap();
173 assert_eq!(sq.rank(), Rank::Eight as u8);
174 assert_eq!(sq.file(), File::A as u8);
175
176 let sq = Square::try_from(0x34).unwrap();
177 assert_eq!(sq.rank(), Rank::Four as u8);
178 assert_eq!(sq.file(), File::E as u8);
179 }
180
181 #[test]
182 fn u8_to_rank_and_file() {
183 let one: Rank = 0.into();
184 let eight: Rank = 7.into();
185 let one1: Rank = 8.into(); let a: File = 0.into();
188 let h: File = 7.into();
189 let a1: File = 8.into();
190
191 assert_eq!(one, Rank::One);
192 assert_eq!(eight, Rank::Eight);
193 assert_eq!(one1, Rank::One);
194
195 assert_eq!(a, File::A);
196 assert_eq!(h, File::H);
197 assert_eq!(a1, File::A);
198 }
199
200 #[test]
201 fn string_to_square() {
202 let a4: Square = "a4".try_into().unwrap();
203 let d1: Square = "d1".try_into().unwrap();
204 let h1: Square = "h1".try_into().unwrap();
205 let e8: Square = "e8".try_into().unwrap();
206 assert_eq!(a4, Square::new(Rank::Four, File::A));
207 assert_eq!(h1, Square::new(Rank::One, File::H));
208 assert_eq!(d1, Square::new(Rank::One, File::D));
209 assert_eq!(e8, Square::new(Rank::Eight, File::E));
210
211 assert!(TryInto::<Square>::try_into("j9").is_err());
212 assert!(TryInto::<Square>::try_into("k2").is_err());
213 assert!(TryInto::<Square>::try_into("n3").is_err());
214 assert!(TryInto::<Square>::try_into("h0").is_err());
215 assert!(TryInto::<Square>::try_into("h23").is_err());
216 }
217
218 #[test]
219 fn square_notation() {
220 assert_eq!(Square::new(Rank::Eight, File::B).get_notation(), "b8");
221 assert_eq!(Square::new(Rank::One, File::A).get_notation(), "a1");
222 assert_eq!(Square::new(Rank::Eight, File::H).get_notation(), "h8");
223 assert_eq!(Square::new(Rank::Five, File::C).get_notation(), "c5");
224 }
225
226 #[test]
227 fn square_color() {
228 struct TestCase {
229 sq: &'static str,
230 color: Color,
231 }
232 let tests = vec![
233 TestCase {
234 sq: "a1",
235 color: Color::BLACK,
236 },
237 TestCase {
238 sq: "a8",
239 color: Color::WHITE,
240 },
241 TestCase {
242 sq: "h1",
243 color: Color::WHITE,
244 },
245 TestCase {
246 sq: "h8",
247 color: Color::BLACK,
248 },
249 TestCase {
250 sq: "a7",
251 color: Color::BLACK,
252 },
253 TestCase {
254 sq: "h7",
255 color: Color::WHITE,
256 },
257 TestCase {
258 sq: "f5",
259 color: Color::WHITE,
260 },
261 TestCase {
262 sq: "c3",
263 color: Color::BLACK,
264 },
265 ];
266
267 for test in tests {
268 let sq: Square = test.sq.try_into().unwrap();
269 assert_eq!(sq.color(), test.color);
270 }
271 }
272}