1use std::fmt::{self, Debug, Display, Formatter};
2use std::str::FromStr;
3
4use crate::CribbageCoreError;
5
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum Rank {
8 Ace,
9 Two,
10 Three,
11 Four,
12 Five,
13 Six,
14 Seven,
15 Eight,
16 Nine,
17 Ten,
18 Jack,
19 Queen,
20 King,
21}
22
23impl Rank {
24 pub fn ordinal(self) -> u8 {
25 match self {
26 Rank::Ace => 1,
27 Rank::Two => 2,
28 Rank::Three => 3,
29 Rank::Four => 4,
30 Rank::Five => 5,
31 Rank::Six => 6,
32 Rank::Seven => 7,
33 Rank::Eight => 8,
34 Rank::Nine => 9,
35 Rank::Ten => 10,
36 Rank::Jack => 11,
37 Rank::Queen => 12,
38 Rank::King => 13,
39 }
40 }
41
42 pub fn value(self) -> u8 {
43 match self {
44 Rank::Ace => 1,
45 Rank::Two => 2,
46 Rank::Three => 3,
47 Rank::Four => 4,
48 Rank::Five => 5,
49 Rank::Six => 6,
50 Rank::Seven => 7,
51 Rank::Eight => 8,
52 Rank::Nine => 9,
53 Rank::Ten | Rank::Jack | Rank::Queen | Rank::King => 10,
54 }
55 }
56}
57
58impl Display for Rank {
59 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
60 let s = match self {
61 Rank::Ace => "A",
62 Rank::Two => "2",
63 Rank::Three => "3",
64 Rank::Four => "4",
65 Rank::Five => "5",
66 Rank::Six => "6",
67 Rank::Seven => "7",
68 Rank::Eight => "8",
69 Rank::Nine => "9",
70 Rank::Ten => "T",
71 Rank::Jack => "J",
72 Rank::Queen => "Q",
73 Rank::King => "K",
74 };
75
76 write!(f, "{}", s)
77 }
78}
79
80#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81pub enum Suit {
82 Spades,
83 Diamonds,
84 Clubs,
85 Hearts,
86}
87
88impl Display for Suit {
89 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
90 let s = match self {
91 Suit::Hearts => "H",
92 Suit::Clubs => "C",
93 Suit::Diamonds => "D",
94 Suit::Spades => "S",
95 };
96
97 write!(f, "{}", s)
98 }
99}
100
101#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
102pub struct Card {
103 rank: Rank,
104 suit: Suit,
105}
106
107impl Card {
108 pub fn new(rank: Rank, suit: Suit) -> Card {
109 Card { rank, suit }
110 }
111
112 pub fn rank(self) -> Rank {
113 self.rank
114 }
115
116 pub fn suit(self) -> Suit {
117 self.suit
118 }
119}
120
121impl FromStr for Card {
122 type Err = CribbageCoreError;
123
124 fn from_str(card_string: &str) -> Result<Card, CribbageCoreError> {
125 let chars: Vec<char> = card_string.trim().chars().collect();
126 if chars.len() != 2 {
127 return Err(CribbageCoreError::InvalidCardString);
128 }
129
130 let rank = match chars[0].to_ascii_uppercase() {
131 'A' => Rank::Ace,
132 '2' => Rank::Two,
133 '3' => Rank::Three,
134 '4' => Rank::Four,
135 '5' => Rank::Five,
136 '6' => Rank::Six,
137 '7' => Rank::Seven,
138 '8' => Rank::Eight,
139 '9' => Rank::Nine,
140 'T' => Rank::Ten,
141 'J' => Rank::Jack,
142 'Q' => Rank::Queen,
143 'K' => Rank::King,
144 _ => return Err(CribbageCoreError::InvalidCardString),
145 };
146
147 let suit = match chars[1].to_ascii_uppercase() {
148 'H' => Suit::Hearts,
149 'C' => Suit::Clubs,
150 'D' => Suit::Diamonds,
151 'S' => Suit::Spades,
152 _ => return Err(CribbageCoreError::InvalidCardString),
153 };
154
155 Ok(Card { rank, suit })
156 }
157}
158
159impl Debug for Card {
160 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
161 write!(f, "{}", self.to_string())
162 }
163}
164
165impl Display for Card {
166 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
167 write!(f, "{}{}", self.rank, self.suit)
168 }
169}
170
171#[cfg(test)]
172mod test {
173 use crate::card::{Card, Rank, Suit};
174 use crate::CribbageCoreError;
175 use std::str::FromStr;
176
177 const RANKS: [Rank; 13] = [
178 Rank::Ace,
179 Rank::Two,
180 Rank::Three,
181 Rank::Four,
182 Rank::Five,
183 Rank::Six,
184 Rank::Seven,
185 Rank::Eight,
186 Rank::Nine,
187 Rank::Ten,
188 Rank::Jack,
189 Rank::Queen,
190 Rank::King,
191 ];
192
193 const SUITS: [Suit; 4] = [Suit::Hearts, Suit::Clubs, Suit::Diamonds, Suit::Spades];
194
195 #[test]
196 fn test_rank_to_string() {
197 assert_eq!(Rank::Ace.to_string(), "A".to_string());
198 assert_eq!(Rank::Two.to_string(), "2".to_string());
199 assert_eq!(Rank::Three.to_string(), "3".to_string());
200 assert_eq!(Rank::Four.to_string(), "4".to_string());
201 assert_eq!(Rank::Five.to_string(), "5".to_string());
202 assert_eq!(Rank::Six.to_string(), "6".to_string());
203 assert_eq!(Rank::Seven.to_string(), "7".to_string());
204 assert_eq!(Rank::Eight.to_string(), "8".to_string());
205 assert_eq!(Rank::Nine.to_string(), "9".to_string());
206 assert_eq!(Rank::Ten.to_string(), "T".to_string());
207 assert_eq!(Rank::Jack.to_string(), "J".to_string());
208 assert_eq!(Rank::Queen.to_string(), "Q".to_string());
209 assert_eq!(Rank::King.to_string(), "K".to_string());
210 }
211
212 #[test]
213 fn test_suit_to_string() {
214 assert_eq!(Suit::Hearts.to_string(), "H".to_string());
215 assert_eq!(Suit::Clubs.to_string(), "C".to_string());
216 assert_eq!(Suit::Diamonds.to_string(), "D".to_string());
217 assert_eq!(Suit::Spades.to_string(), "S".to_string());
218 }
219
220 #[test]
221 fn test_card_to_string() {
222 for rank in &RANKS {
223 for suit in &SUITS {
224 let expected = format!("{}{}", rank.to_string(), suit.to_string());
225 let card = Card {
226 rank: *rank,
227 suit: *suit,
228 };
229 assert_eq!(card.to_string(), expected);
230 }
231 }
232 }
233
234 #[test]
235 fn test_card_from_str() {
236 for rank in &RANKS {
237 for suit in &SUITS {
238 let card_string = format!("{}{}", rank.to_string(), suit.to_string());
239 let expected = Card {
240 rank: *rank,
241 suit: *suit,
242 };
243 let card = Card::from_str(&card_string).unwrap();
244 assert_eq!(card, expected);
245 }
246 }
247
248 assert_eq!(
249 Card::from_str(" AS"),
250 Ok(Card {
251 rank: Rank::Ace,
252 suit: Suit::Spades
253 })
254 );
255 assert_eq!(
256 Card::from_str("AS "),
257 Ok(Card {
258 rank: Rank::Ace,
259 suit: Suit::Spades
260 })
261 );
262
263 assert_eq!(
264 Card::from_str(""),
265 Err(CribbageCoreError::InvalidCardString)
266 );
267 assert_eq!(
268 Card::from_str("ASA"),
269 Err(CribbageCoreError::InvalidCardString)
270 );
271 assert_eq!(
272 Card::from_str("AX"),
273 Err(CribbageCoreError::InvalidCardString)
274 );
275 assert_eq!(
276 Card::from_str("XS"),
277 Err(CribbageCoreError::InvalidCardString)
278 );
279 }
280}