open_pql/base/
rank.rs

1use super::{Display, FromStr, Hash, N_RANKS, ParseError, mem};
2
3/// Enum for Ranks
4///
5/// Represents the rank (value) of a playing card from 2 to Ace.
6/// Ranks are ordered from lowest (2) to highest (Ace) for poker hand evaluation.
7/// Each rank has a unique numeric value (0-12) and character representation.
8///
9/// # Examples
10///
11/// ```
12/// use open_pql::{Rank, Rank::*};
13///
14/// let rank = RA; // Ace
15/// assert_eq!(rank.to_string(), "A");
16/// assert_eq!(rank as u8, 12);
17///
18/// let parsed: Rank = "K".parse().unwrap();
19/// assert_eq!(parsed, RK);
20/// ```
21#[derive(
22    Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd, Hash, Display, Default,
23)]
24pub enum Rank {
25    /// Duece
26    #[default]
27    #[display("2")]
28    R2 = 0,
29    /// Three
30    #[display("3")]
31    R3,
32    /// Four
33    #[display("4")]
34    R4,
35    /// Five
36    #[display("5")]
37    R5,
38    /// Six
39    #[display("6")]
40    R6,
41    /// Seven
42    #[display("7")]
43    R7,
44    /// Eight
45    #[display("8")]
46    R8,
47    /// Nine
48    #[display("9")]
49    R9,
50    /// Ten
51    #[display("T")]
52    RT,
53    /// Jack
54    #[display("J")]
55    RJ,
56    /// Queen
57    #[display("Q")]
58    RQ,
59    /// King
60    #[display("K")]
61    RK,
62    /// Ace
63    #[display("A")]
64    RA,
65}
66
67impl Rank {
68    /// All possible ranks
69    pub const ARR_ALL: [Self; N_RANKS as usize] = [
70        Self::R2,
71        Self::R3,
72        Self::R4,
73        Self::R5,
74        Self::R6,
75        Self::R7,
76        Self::R8,
77        Self::R9,
78        Self::RT,
79        Self::RJ,
80        Self::RQ,
81        Self::RK,
82        Self::RA,
83    ];
84
85    /// All ranks used in short deck poker
86    pub const ARR_ALL_SHORT: [Self; 9] = [
87        Self::R6,
88        Self::R7,
89        Self::R8,
90        Self::R9,
91        Self::RT,
92        Self::RJ,
93        Self::RQ,
94        Self::RK,
95        Self::RA,
96    ];
97
98    /// Creates a rank from a u8 value (0-12)
99    pub(crate) fn from_u8(v: u8) -> Self {
100        debug_assert!(v < N_RANKS, "invalid rank: {v}");
101        unsafe { mem::transmute(v) }
102    }
103
104    pub const fn from_char(c: char) -> Option<Self> {
105        match c {
106            '2' => Some(Self::R2),
107            '3' => Some(Self::R3),
108            '4' => Some(Self::R4),
109            '5' => Some(Self::R5),
110            '6' => Some(Self::R6),
111            '7' => Some(Self::R7),
112            '8' => Some(Self::R8),
113            '9' => Some(Self::R9),
114            't' | 'T' => Some(Self::RT),
115            'j' | 'J' => Some(Self::RJ),
116            'q' | 'Q' => Some(Self::RQ),
117            'k' | 'K' => Some(Self::RK),
118            'a' | 'A' => Some(Self::RA),
119            _ => None,
120        }
121    }
122}
123
124impl From<Rank> for char {
125    fn from(value: Rank) -> Self {
126        match value {
127            Rank::R2 => '2',
128            Rank::R3 => '3',
129            Rank::R4 => '4',
130            Rank::R5 => '5',
131            Rank::R6 => '6',
132            Rank::R7 => '7',
133            Rank::R8 => '8',
134            Rank::R9 => '9',
135            Rank::RT => 'T',
136            Rank::RJ => 'J',
137            Rank::RQ => 'Q',
138            Rank::RK => 'K',
139            Rank::RA => 'A',
140        }
141    }
142}
143
144impl TryFrom<char> for Rank {
145    type Error = ParseError;
146
147    fn try_from(c: char) -> Result<Self, Self::Error> {
148        match c {
149            '2' => Ok(Self::R2),
150            '3' => Ok(Self::R3),
151            '4' => Ok(Self::R4),
152            '5' => Ok(Self::R5),
153            '6' => Ok(Self::R6),
154            '7' => Ok(Self::R7),
155            '8' => Ok(Self::R8),
156            '9' => Ok(Self::R9),
157            'T' | 't' => Ok(Self::RT),
158            'J' | 'j' => Ok(Self::RJ),
159            'Q' | 'q' => Ok(Self::RQ),
160            'K' | 'k' => Ok(Self::RK),
161            'A' | 'a' => Ok(Self::RA),
162            _ => Err(ParseError::InvalidRank(c.into())),
163        }
164    }
165}
166
167impl FromStr for Rank {
168    type Err = ParseError;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        let mut cs = s.chars().filter(|c| !c.is_whitespace());
172        if let Some(c) = cs.next()
173            && let Ok(r) = Self::try_from(c)
174            && cs.next().is_none()
175        {
176            return Ok(r);
177        }
178        Err(ParseError::InvalidRank(s.into()))
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use crate::*;
186
187    impl Arbitrary for Rank {
188        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
189            *g.choose(&Self::ARR_ALL).unwrap()
190        }
191    }
192
193    #[test]
194    fn test_consts() {
195        assert_eq!(
196            Rank::ARR_ALL,
197            [
198                Rank::R2,
199                Rank::R3,
200                Rank::R4,
201                Rank::R5,
202                Rank::R6,
203                Rank::R7,
204                Rank::R8,
205                Rank::R9,
206                Rank::RT,
207                Rank::RJ,
208                Rank::RQ,
209                Rank::RK,
210                Rank::RA,
211            ]
212        );
213    }
214
215    #[test]
216    fn test_as_int() {
217        assert_eq!(Rank::R2 as i8, 0);
218        assert_eq!(Rank::R3 as i8, 1);
219        assert_eq!(Rank::R4 as i8, 2);
220        assert_eq!(Rank::R5 as i8, 3);
221        assert_eq!(Rank::R6 as i8, 4);
222        assert_eq!(Rank::R7 as i8, 5);
223        assert_eq!(Rank::R8 as i8, 6);
224        assert_eq!(Rank::R9 as i8, 7);
225        assert_eq!(Rank::RT as i8, 8);
226        assert_eq!(Rank::RJ as i8, 9);
227        assert_eq!(Rank::RQ as i8, 10);
228        assert_eq!(Rank::RK as i8, 11);
229        assert_eq!(Rank::RA as i8, 12);
230    }
231
232    #[test]
233    fn test_from_char() {
234        assert_eq!(Ok(Rank::R2), '2'.try_into());
235        assert_eq!(Ok(Rank::R3), '3'.try_into());
236        assert_eq!(Ok(Rank::R4), '4'.try_into());
237        assert_eq!(Ok(Rank::R5), '5'.try_into());
238        assert_eq!(Ok(Rank::R6), '6'.try_into());
239        assert_eq!(Ok(Rank::R7), '7'.try_into());
240        assert_eq!(Ok(Rank::R8), '8'.try_into());
241        assert_eq!(Ok(Rank::R9), '9'.try_into());
242
243        assert_eq!(Ok(Rank::RT), 'T'.try_into());
244        assert_eq!(Ok(Rank::RJ), 'J'.try_into());
245        assert_eq!(Ok(Rank::RQ), 'Q'.try_into());
246        assert_eq!(Ok(Rank::RK), 'K'.try_into());
247        assert_eq!(Ok(Rank::RA), 'A'.try_into());
248
249        assert_eq!(Ok(Rank::RT), 't'.try_into());
250        assert_eq!(Ok(Rank::RJ), 'j'.try_into());
251        assert_eq!(Ok(Rank::RQ), 'q'.try_into());
252        assert_eq!(Ok(Rank::RK), 'k'.try_into());
253        assert_eq!(Ok(Rank::RA), 'a'.try_into());
254
255        assert_eq!(
256            Err(ParseError::InvalidRank("?".into())),
257            Rank::try_from('?')
258        );
259    }
260
261    #[test]
262    fn test_from_char_option() {
263        assert_eq!(Some(Rank::R2), Rank::from_char('2'));
264        assert_eq!(Some(Rank::R3), Rank::from_char('3'));
265        assert_eq!(Some(Rank::R4), Rank::from_char('4'));
266        assert_eq!(Some(Rank::R5), Rank::from_char('5'));
267        assert_eq!(Some(Rank::R6), Rank::from_char('6'));
268        assert_eq!(Some(Rank::R7), Rank::from_char('7'));
269        assert_eq!(Some(Rank::R8), Rank::from_char('8'));
270        assert_eq!(Some(Rank::R9), Rank::from_char('9'));
271
272        assert_eq!(Some(Rank::RT), Rank::from_char('T'));
273        assert_eq!(Some(Rank::RJ), Rank::from_char('J'));
274        assert_eq!(Some(Rank::RQ), Rank::from_char('Q'));
275        assert_eq!(Some(Rank::RK), Rank::from_char('K'));
276        assert_eq!(Some(Rank::RA), Rank::from_char('A'));
277
278        assert_eq!(Some(Rank::RT), Rank::from_char('t'));
279        assert_eq!(Some(Rank::RJ), Rank::from_char('j'));
280        assert_eq!(Some(Rank::RQ), Rank::from_char('q'));
281        assert_eq!(Some(Rank::RK), Rank::from_char('k'));
282        assert_eq!(Some(Rank::RA), Rank::from_char('a'));
283
284        assert_eq!(None, Rank::from_char('?'));
285        assert_eq!(None, Rank::from_char('1'));
286        assert_eq!(None, Rank::from_char('X'));
287    }
288
289    #[test]
290    fn test_from_str() {
291        assert_eq!(Ok(Rank::R2), " 2 ".parse());
292        assert_eq!(
293            Err(ParseError::InvalidRank("23".into())),
294            "23".parse::<Rank>()
295        );
296        assert!("".parse::<Rank>().is_err());
297        assert!("?".parse::<Rank>().is_err());
298    }
299
300    #[test]
301    fn test_to_string() {
302        assert_eq!("2", &Rank::R2.to_string());
303        assert_eq!("3", &Rank::R3.to_string());
304        assert_eq!("4", &Rank::R4.to_string());
305        assert_eq!("5", &Rank::R5.to_string());
306        assert_eq!("6", &Rank::R6.to_string());
307        assert_eq!("7", &Rank::R7.to_string());
308        assert_eq!("8", &Rank::R8.to_string());
309        assert_eq!("9", &Rank::R9.to_string());
310        assert_eq!("T", &Rank::RT.to_string());
311        assert_eq!("J", &Rank::RJ.to_string());
312        assert_eq!("Q", &Rank::RQ.to_string());
313        assert_eq!("K", &Rank::RK.to_string());
314        assert_eq!("A", &Rank::RA.to_string());
315    }
316
317    #[test]
318    fn test_to_char() {
319        let cs = "23456789TJQKA";
320        for (i, &r) in Rank::ARR_ALL.iter().enumerate() {
321            assert_eq!(cs.chars().nth(i).unwrap(), char::from(r));
322        }
323    }
324}