chess/
file.rs

1use std::str::FromStr;
2
3use crate::{Error, BOARD_SIZE};
4
5/// Describe a file (column) on a chess board.
6#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
7#[repr(u8)]
8pub enum File {
9    A,
10    B,
11    C,
12    D,
13    E,
14    F,
15    G,
16    H,
17}
18
19/// Numbers of [`File`].
20pub const NUM_FILES: usize = BOARD_SIZE.1 as usize;
21
22/// Enumerate all files.
23pub const ALL_FILES: [File; NUM_FILES] = [
24    File::A,
25    File::B,
26    File::C,
27    File::D,
28    File::E,
29    File::F,
30    File::G,
31    File::H,
32];
33
34impl File {
35    /// Gets a [`File`] from an integer index.
36    ///
37    /// > **Note**: If index is not in the range 0..=7, wrap around.
38    #[inline]
39    pub fn new(index: usize) -> Self {
40        ALL_FILES[index % NUM_FILES]
41    }
42
43    /// Convert this [`File`] into a [`usize`].
44    #[inline]
45    pub fn to_index(&self) -> usize {
46        *self as usize
47    }
48
49    /// Go one file to the left.
50    ///
51    /// > **Note**: If impossible, wrap around.
52    #[inline]
53    pub fn left(&self) -> Self {
54        File::new(self.to_index().wrapping_sub(1))
55    }
56
57    /// Go one file to the right.
58    ///
59    /// > **Note**: If impossible, wrap around.
60    #[inline]
61    pub fn right(&self) -> Self {
62        File::new(self.to_index() + 1)
63    }
64
65    /// Distance between two [`File`].
66    #[inline]
67    pub fn distance(&self, other: File) -> u32 {
68        self.to_index().abs_diff(other.to_index()) as u32
69    }
70
71    /// Verify if the [`File`] is between two other (i.e. lower <= self <= upper).
72    ///
73    /// Assume that lower_bound <= upper_bound.
74    #[inline]
75    pub fn between(&self, lower_bound: File, upper_bound: File) -> bool {
76        lower_bound <= *self && *self <= upper_bound
77    }
78}
79
80impl FromStr for File {
81    type Err = Error;
82
83    /// Only lowercase from a to h (inclusive).
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        if s.is_empty() {
86            return Err(Error::InvalidFile);
87        }
88        match s.chars().next().unwrap() {
89            'a' => Ok(File::A),
90            'b' => Ok(File::B),
91            'c' => Ok(File::C),
92            'd' => Ok(File::D),
93            'e' => Ok(File::E),
94            'f' => Ok(File::F),
95            'g' => Ok(File::G),
96            'h' => Ok(File::H),
97            _ => Err(Error::InvalidFile),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn to_index() {
108        assert_eq!(File::A.to_index(), 0);
109        assert_eq!(File::B.to_index(), 1);
110        assert_eq!(File::C.to_index(), 2);
111        assert_eq!(File::D.to_index(), 3);
112        assert_eq!(File::E.to_index(), 4);
113        assert_eq!(File::F.to_index(), 5);
114        assert_eq!(File::G.to_index(), 6);
115        assert_eq!(File::H.to_index(), 7);
116    }
117
118    #[test]
119    fn right() {
120        assert_eq!(File::A.right(), File::B);
121        assert_eq!(File::B.right(), File::C);
122        assert_eq!(File::C.right(), File::D);
123        assert_eq!(File::D.right(), File::E);
124        assert_eq!(File::E.right(), File::F);
125        assert_eq!(File::F.right(), File::G);
126        assert_eq!(File::G.right(), File::H);
127        assert_eq!(File::H.right(), File::A);
128    }
129
130    #[test]
131    fn left() {
132        assert_eq!(File::A.left(), File::H);
133        assert_eq!(File::B.left(), File::A);
134        assert_eq!(File::C.left(), File::B);
135        assert_eq!(File::D.left(), File::C);
136        assert_eq!(File::E.left(), File::D);
137        assert_eq!(File::F.left(), File::E);
138        assert_eq!(File::G.left(), File::F);
139        assert_eq!(File::H.left(), File::G);
140    }
141
142    #[test]
143    fn distance() {
144        assert_eq!(File::A.distance(File::A), 0);
145        assert_eq!(File::A.distance(File::D), 3);
146        assert_eq!(File::A.distance(File::H), 7);
147    }
148
149    #[test]
150    fn between() {
151        // expect true
152        assert!(File::A.between(File::A, File::H));
153        assert!(File::H.between(File::A, File::H));
154        assert!(File::A.between(File::A, File::A));
155        // expect false
156        assert!(!File::A.between(File::B, File::H));
157        assert!(!File::H.between(File::A, File::G));
158        assert!(!File::B.between(File::C, File::A));
159    }
160
161    #[test]
162    fn from_str() {
163        assert_eq!(File::from_str("a"), Ok(File::A));
164        assert_eq!(File::from_str("b"), Ok(File::B));
165        assert_eq!(File::from_str("c"), Ok(File::C));
166        assert_eq!(File::from_str("d"), Ok(File::D));
167        assert_eq!(File::from_str("e"), Ok(File::E));
168        assert_eq!(File::from_str("f"), Ok(File::F));
169        assert_eq!(File::from_str("g"), Ok(File::G));
170        assert_eq!(File::from_str("h"), Ok(File::H));
171    }
172
173    #[test]
174    fn from_str_error() {
175        assert_eq!(File::from_str(""), Err(Error::InvalidFile));
176        assert_eq!(File::from_str(" a"), Err(Error::InvalidFile));
177        assert_eq!(File::from_str("A"), Err(Error::InvalidFile));
178    }
179}