Skip to main content

hieu_chess/
square.rs

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        // https://docs.rs/chess/latest/src/chess/rank.rs.html#38
22        // reinterpret the u8 bits as a Rank enum. bitwise-AND 7 to wrap around
23        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        // reinterpret the u8 bits as a File enum. bitwise-AND 7 to wrap around
43        unsafe { transmute(value & 7) }
44    }
45}
46
47#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
48pub struct Square(pub u8); // square index is guaranteed to be valid
49
50impl 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    // go from 0 to 7
66    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(); // should wrap to 1
186
187        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}