1use super::{Display, FromStr, Hash, N_SUITS, ParseError, mem};
2
3#[derive(
22 Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd, Hash, Display, Default,
23)]
24pub enum Suit {
25 #[display("s")]
27 #[default]
28 S = 0,
29 #[display("h")]
31 H,
32 #[display("d")]
34 D,
35 #[display("c")]
37 C,
38}
39
40impl Suit {
41 pub const ARR_ALL: [Self; N_SUITS as usize] =
43 [Self::S, Self::H, Self::D, Self::C];
44
45 pub(crate) fn from_u8(v: u8) -> Self {
47 debug_assert!(v < N_SUITS, "invalid suit: {v}");
48 unsafe { mem::transmute(v) }
49 }
50
51 pub const fn from_char(c: char) -> Option<Self> {
53 match c {
54 'S' | 's' => Some(Self::S),
55 'H' | 'h' => Some(Self::H),
56 'D' | 'd' => Some(Self::D),
57 'C' | 'c' => Some(Self::C),
58 _ => None,
59 }
60 }
61}
62
63impl TryFrom<char> for Suit {
64 type Error = ParseError;
65
66 fn try_from(c: char) -> Result<Self, Self::Error> {
67 match c {
68 'S' | 's' => Ok(Self::S),
69 'H' | 'h' => Ok(Self::H),
70 'D' | 'd' => Ok(Self::D),
71 'C' | 'c' => Ok(Self::C),
72 _ => Err(ParseError::InvalidSuit(c.into())),
73 }
74 }
75}
76
77impl FromStr for Suit {
78 type Err = ParseError;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 let mut cs = s.chars().filter(|c| !c.is_whitespace());
82 if let Some(c) = cs.next()
83 && let Ok(s) = Self::try_from(c)
84 && cs.next().is_none()
85 {
86 return Ok(s);
87 }
88 Err(ParseError::InvalidSuit(s.into()))
89 }
90}
91
92impl From<Suit> for char {
93 fn from(value: Suit) -> Self {
94 match value {
95 Suit::S => 's',
96 Suit::H => 'h',
97 Suit::D => 'd',
98 Suit::C => 'c',
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::*;
107
108 impl Arbitrary for Suit {
109 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
110 *g.choose(&Self::ARR_ALL).unwrap()
111 }
112 }
113
114 #[test]
115 fn test_consts() {
116 assert_eq!(Suit::ARR_ALL, [Suit::S, Suit::H, Suit::D, Suit::C]);
117 }
118
119 #[test]
120 fn test_as_int() {
121 assert_eq!(Suit::S as i8, 0);
122 assert_eq!(Suit::H as i8, 1);
123 assert_eq!(Suit::D as i8, 2);
124 assert_eq!(Suit::C as i8, 3);
125 }
126
127 #[test]
128 fn test_from_char() {
129 assert_eq!(Ok(Suit::S), 's'.try_into());
130 assert_eq!(Ok(Suit::H), 'h'.try_into());
131 assert_eq!(Ok(Suit::D), 'd'.try_into());
132 assert_eq!(Ok(Suit::C), 'c'.try_into());
133
134 assert_eq!(Ok(Suit::S), 'S'.try_into());
135 assert_eq!(Ok(Suit::H), 'H'.try_into());
136 assert_eq!(Ok(Suit::D), 'D'.try_into());
137 assert_eq!(Ok(Suit::C), 'C'.try_into());
138
139 assert_eq!(
140 Err(ParseError::InvalidSuit("?".into())),
141 Suit::try_from('?')
142 );
143 }
144
145 #[test]
146 fn test_from_char_option() {
147 assert_eq!(Some(Suit::S), Suit::from_char('s'));
148 assert_eq!(Some(Suit::H), Suit::from_char('h'));
149 assert_eq!(Some(Suit::D), Suit::from_char('d'));
150 assert_eq!(Some(Suit::C), Suit::from_char('c'));
151
152 assert_eq!(Some(Suit::S), Suit::from_char('S'));
153 assert_eq!(Some(Suit::H), Suit::from_char('H'));
154 assert_eq!(Some(Suit::D), Suit::from_char('D'));
155 assert_eq!(Some(Suit::C), Suit::from_char('C'));
156
157 assert_eq!(None, Suit::from_char('?'));
158 assert_eq!(None, Suit::from_char('1'));
159 assert_eq!(None, Suit::from_char('X'));
160 }
161
162 #[test]
163 fn test_from_str() {
164 assert_eq!(Ok(Suit::S), " s ".parse());
165 assert_eq!(
166 Err(ParseError::InvalidSuit("sS".into())),
167 "sS".parse::<Suit>()
168 );
169 assert!("".parse::<Suit>().is_err());
170 assert!("?".parse::<Suit>().is_err());
171 }
172
173 #[test]
174 fn test_to_string() {
175 assert_eq!("s", &Suit::S.to_string());
176 assert_eq!("h", &Suit::H.to_string());
177 assert_eq!("d", &Suit::D.to_string());
178 assert_eq!("c", &Suit::C.to_string());
179 }
180
181 #[test]
182 fn test_to_char() {
183 assert_eq!('s', char::from(Suit::S));
184 assert_eq!('h', char::from(Suit::H));
185 assert_eq!('d', char::from(Suit::D));
186 assert_eq!('c', char::from(Suit::C));
187 }
188}