server/domain/types/
card.rs1use serde::{Deserialize, Serialize};
2use strum::IntoEnumIterator;
3
4use crate::domain::CardsError;
5mod face;
6mod rank;
7mod suit;
8mod value;
9
10pub use self::{face::Face, rank::Rank, suit::Suit, value::Value};
11
12#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub struct Card {
17 face: Face,
18 suit: Suit,
19}
20
21impl Card {
22 #[must_use]
24 const fn new(face: Face, suit: Suit) -> Self {
25 Self { face, suit }
26 }
27
28 #[must_use]
32 pub fn all() -> Vec<Self> {
33 let cards_for_suit = |s: Suit| Face::iter().map(move |f| Self::new(f, s));
34 Suit::iter().flat_map(cards_for_suit).collect::<Vec<_>>()
35 }
36
37 #[must_use]
42 pub fn cid(&self) -> String {
43 let Self { face, suit } = self;
44 format!("{face}{suit}")
45 }
46
47 #[must_use]
49 pub fn face(&self) -> Face {
50 self.face
51 }
52
53 #[must_use]
55 pub fn is_jack(&self) -> bool {
56 self.face.is_jack()
57 }
58
59 #[must_use]
61 pub fn suit(&self) -> Suit {
62 self.suit
63 }
64
65 #[must_use]
69 pub fn rank(&self) -> Rank {
70 self.face.rank()
71 }
72
73 #[must_use]
77 pub fn value(&self) -> Value {
78 self.face.value()
79 }
80}
81
82impl std::str::FromStr for Card {
83 type Err = CardsError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 if s.len() == 2 {
87 let mut chars = s.chars();
88 let face = chars.next().ok_or(CardsError::InvalidCard(s.into()))?;
89 let face = Face::try_from(face)?;
90 let suit = chars.next().ok_or(CardsError::InvalidCard(s.into()))?;
91 let suit = Suit::try_from(suit)?;
92 Ok(Card::new(face, suit))
93 } else {
94 Err(CardsError::InvalidCard(s.into()))
95 }
96 }
97}
98
99#[cfg(test)]
100pub(crate) trait CardExt {
101 fn cards_from(self) -> Result<Vec<Card>, CardsError>;
102}
103
104#[cfg(test)]
105impl CardExt for &str {
106 fn cards_from(self) -> Result<Vec<Card>, CardsError> {
107 use std::str::FromStr;
108
109 if !self.len().is_multiple_of(2) {
110 Err(CardsError::InvalidCard(self.into()))
111 } else {
112 (0..self.len())
113 .step_by(2)
114 .map(|i| Card::from_str(&self[i..i + 2]))
115 .collect()
116 }
117 }
118}
119
120impl std::fmt::Debug for Card {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "Card({})", self)
123 }
124}
125
126impl std::fmt::Display for Card {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 self.cid().fmt(f)
129 }
130}
131
132#[cfg(test)]
133#[coverage(off)]
134mod test {
135 use super::*;
136
137 #[test]
138 fn cards_have_definitive_id_rank_and_value() {
139 use std::str::FromStr;
140
141 let suits = "HCDS";
142 let faces = "A23456789TJQK";
143
144 for suit in suits.chars() {
145 for face in faces.chars() {
146 let cid = format!("{face}{suit}");
147 let face = Face::try_from(face).expect("valid face");
148 let card = Card::from_str(cid.as_str()).expect("valid cards str");
149 assert_eq!(card.cid(), cid);
150 assert_eq!(card.rank(), face.rank());
151 assert_eq!(card.value(), face.value());
152 }
153 }
154 }
155}