cribbage_core/
card.rs

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}