1use super::{CardCount, Display, FromStr, Hash, Idx, ParseError};
2#[cfg(feature = "python")]
3use crate::python::*;
4
5#[cfg_attr(feature = "python", pyclass(eq, ord, str, frozen, hash))]
9#[derive(
10 Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd, Hash, Display, Default,
11)]
12pub enum Rank {
13 #[default]
14 #[display("2")]
15 R2 = 0,
16 #[display("3")]
17 R3,
18 #[display("4")]
19 R4,
20 #[display("5")]
21 R5,
22 #[display("6")]
23 R6,
24 #[display("7")]
25 R7,
26 #[display("8")]
27 R8,
28 #[display("9")]
29 R9,
30 #[display("T")]
31 RT,
32 #[display("J")]
33 RJ,
34 #[display("Q")]
35 RQ,
36 #[display("K")]
37 RK,
38 #[display("A")]
39 RA,
40}
41
42impl Rank {
43 pub const N_RANKS: CardCount = 13;
45
46 pub const N_RANKS_SD: CardCount = 9;
48
49 pub const CHARS: [char; Self::N_RANKS as usize] = [
51 '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A',
52 ];
53
54 const ARR_ALL: [Self; Self::N_RANKS as usize] = [
56 Self::R2,
57 Self::R3,
58 Self::R4,
59 Self::R5,
60 Self::R6,
61 Self::R7,
62 Self::R8,
63 Self::R9,
64 Self::RT,
65 Self::RJ,
66 Self::RQ,
67 Self::RK,
68 Self::RA,
69 ];
70
71 const ARR_ALL_SD: [Self; Self::N_RANKS_SD as usize] = [
73 Self::R6,
74 Self::R7,
75 Self::R8,
76 Self::R9,
77 Self::RT,
78 Self::RJ,
79 Self::RQ,
80 Self::RK,
81 Self::RA,
82 ];
83
84 #[inline]
86 pub const fn from_char(c: char) -> Option<Self> {
87 match c {
88 '2' => Some(Self::R2),
89 '3' => Some(Self::R3),
90 '4' => Some(Self::R4),
91 '5' => Some(Self::R5),
92 '6' => Some(Self::R6),
93 '7' => Some(Self::R7),
94 '8' => Some(Self::R8),
95 '9' => Some(Self::R9),
96 't' | 'T' => Some(Self::RT),
97 'j' | 'J' => Some(Self::RJ),
98 'q' | 'Q' => Some(Self::RQ),
99 'k' | 'K' => Some(Self::RK),
100 'a' | 'A' => Some(Self::RA),
101 _ => None,
102 }
103 }
104
105 #[inline]
106 pub const fn to_char(self) -> char {
107 Self::CHARS[self as usize]
108 }
109
110 #[inline]
111 pub const fn all<const SD: bool>() -> &'static [Self] {
112 const {
113 if SD {
114 &Self::ARR_ALL_SD
115 } else {
116 &Self::ARR_ALL
117 }
118 }
119 }
120
121 #[inline]
122 pub(crate) const fn eq(self, other: Self) -> bool {
123 self as Idx == other as Idx
124 }
125}
126
127impl TryFrom<char> for Rank {
128 type Error = ParseError;
129
130 fn try_from(c: char) -> Result<Self, Self::Error> {
131 Self::from_char(c).ok_or_else(|| ParseError::InvalidRank(c.into()))
132 }
133}
134
135impl FromStr for Rank {
136 type Err = ParseError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 let mut cs = s.chars().filter(|c| !c.is_whitespace());
140 if let Some(c) = cs.next()
141 && let Ok(r) = Self::try_from(c)
142 && cs.next().is_none()
143 {
144 return Ok(r);
145 }
146 Err(ParseError::InvalidRank(s.into()))
147 }
148}
149
150#[cfg(feature = "python")]
151#[pymethods]
152impl Rank {
153 #[staticmethod]
154 fn from_str(s: &str) -> PyResult<Self> {
155 Ok(s.parse::<Self>()?)
156 }
157}
158
159#[cfg(any(test, feature = "quickcheck"))]
160impl quickcheck::Arbitrary for Rank {
161 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
162 *g.choose(&Self::ARR_ALL).unwrap()
163 }
164}
165
166#[cfg(test)]
167#[cfg_attr(coverage_nightly, coverage(off))]
168mod tests {
169 use super::*;
170
171 #[quickcheck]
172 fn test_all(rank: Rank) {
173 if rank >= Rank::R6 {
174 assert!(Rank::all::<true>().contains(&rank));
175 }
176
177 assert!(Rank::all::<false>().contains(&rank));
178 }
179
180 #[test]
181 fn test_as_int() {
182 assert_eq!(Rank::R2 as Idx, 0);
183 assert_eq!(Rank::R3 as Idx, 1);
184 assert_eq!(Rank::R4 as Idx, 2);
185 assert_eq!(Rank::R5 as Idx, 3);
186 assert_eq!(Rank::R6 as Idx, 4);
187 assert_eq!(Rank::R7 as Idx, 5);
188 assert_eq!(Rank::R8 as Idx, 6);
189 assert_eq!(Rank::R9 as Idx, 7);
190 assert_eq!(Rank::RT as Idx, 8);
191 assert_eq!(Rank::RJ as Idx, 9);
192 assert_eq!(Rank::RQ as Idx, 10);
193 assert_eq!(Rank::RK as Idx, 11);
194 assert_eq!(Rank::RA as Idx, 12);
195 }
196
197 #[test]
198 fn test_from_char() {
199 assert_eq!('2'.try_into(), Ok(Rank::R2));
200 assert_eq!('3'.try_into(), Ok(Rank::R3));
201 assert_eq!('4'.try_into(), Ok(Rank::R4));
202 assert_eq!('5'.try_into(), Ok(Rank::R5));
203 assert_eq!('6'.try_into(), Ok(Rank::R6));
204 assert_eq!('7'.try_into(), Ok(Rank::R7));
205 assert_eq!('8'.try_into(), Ok(Rank::R8));
206 assert_eq!('9'.try_into(), Ok(Rank::R9));
207
208 assert_eq!('T'.try_into(), Ok(Rank::RT));
209 assert_eq!('J'.try_into(), Ok(Rank::RJ));
210 assert_eq!('Q'.try_into(), Ok(Rank::RQ));
211 assert_eq!('K'.try_into(), Ok(Rank::RK));
212 assert_eq!('A'.try_into(), Ok(Rank::RA));
213
214 assert_eq!('t'.try_into(), Ok(Rank::RT));
215 assert_eq!('j'.try_into(), Ok(Rank::RJ));
216 assert_eq!('q'.try_into(), Ok(Rank::RQ));
217 assert_eq!('k'.try_into(), Ok(Rank::RK));
218 assert_eq!('a'.try_into(), Ok(Rank::RA));
219
220 assert_eq!(
221 Rank::try_from('?'),
222 Err(ParseError::InvalidRank("?".into())),
223 );
224 }
225
226 #[test]
227 fn test_from_str() {
228 assert_eq!(" 2 ".parse(), Ok(Rank::R2));
229 assert_eq!(
230 "23".parse::<Rank>(),
231 Err(ParseError::InvalidRank("23".into())),
232 );
233 assert!("".parse::<Rank>().is_err());
234 assert!("?".parse::<Rank>().is_err());
235 }
236
237 #[test]
238 fn test_to_string() {
239 assert_eq!(
240 Rank::ARR_ALL
241 .iter()
242 .map(Rank::to_string)
243 .collect::<String>(),
244 "23456789TJQKA",
245 );
246 }
247
248 #[test]
249 fn test_to_char() {
250 assert_eq!(
251 Rank::ARR_ALL.map(Rank::to_char).iter().collect::<String>(),
252 "23456789TJQKA",
253 );
254 }
255}