chess/
rank.rs

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