openpql_prelude/card/
suit.rs

1use super::{CardCount, Display, FromStr, Hash, Idx, ParseError};
2
3/// Card suit representation.
4///
5/// Represents the four card suits (spades, hearts, diamonds, clubs),
6/// with parsing support and conversion utilities.
7#[derive(
8    Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd, Hash, Display, Default,
9)]
10pub enum Suit {
11    #[display("s")]
12    #[default]
13    S = 0,
14    #[display("h")]
15    H,
16    #[display("d")]
17    D,
18    #[display("c")]
19    C,
20}
21
22impl Suit {
23    /// Number of suits in a deck
24    pub const N_SUITS: CardCount = 4;
25
26    /// Array of all 4 suits.
27    pub const ARR_ALL: [Self; Self::N_SUITS as usize] =
28        [Self::S, Self::H, Self::D, Self::C];
29
30    /// Character representations for suits
31    pub const CHARS: [char; Self::N_SUITS as usize] = ['s', 'h', 'd', 'c'];
32    /// Converts a character to a suit, returning `None` if invalid.
33    #[inline]
34    pub const fn from_char(c: char) -> Option<Self> {
35        match c {
36            'S' | 's' => Some(Self::S),
37            'H' | 'h' => Some(Self::H),
38            'D' | 'd' => Some(Self::D),
39            'C' | 'c' => Some(Self::C),
40            _ => None,
41        }
42    }
43
44    #[inline]
45    pub const fn to_char(self) -> char {
46        Self::CHARS[self as usize]
47    }
48
49    #[inline]
50    pub(crate) const fn eq(self, other: Self) -> bool {
51        self as Idx == other as Idx
52    }
53}
54
55impl TryFrom<char> for Suit {
56    type Error = ParseError;
57
58    fn try_from(c: char) -> Result<Self, Self::Error> {
59        Self::from_char(c).ok_or_else(|| ParseError::InvalidSuit(c.into()))
60    }
61}
62
63impl FromStr for Suit {
64    type Err = ParseError;
65
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        let mut cs = s.chars().filter(|c| !c.is_whitespace());
68        if let Some(c) = cs.next()
69            && let Ok(s) = Self::try_from(c)
70            && cs.next().is_none()
71        {
72            return Ok(s);
73        }
74        Err(ParseError::InvalidSuit(s.into()))
75    }
76}
77
78#[cfg(any(test, feature = "quickcheck"))]
79impl quickcheck::Arbitrary for Suit {
80    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
81        *g.choose(&Self::ARR_ALL).unwrap()
82    }
83}
84
85#[cfg(test)]
86#[cfg_attr(coverage_nightly, coverage(off))]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_all() {
92        assert_eq!(Suit::ARR_ALL, [Suit::S, Suit::H, Suit::D, Suit::C]);
93    }
94
95    #[test]
96    fn test_as_int() {
97        assert_eq!(Suit::S as Idx, 0);
98        assert_eq!(Suit::H as Idx, 1);
99        assert_eq!(Suit::D as Idx, 2);
100        assert_eq!(Suit::C as Idx, 3);
101    }
102
103    #[test]
104    fn test_from_char() {
105        assert_eq!('s'.try_into(), Ok(Suit::S));
106        assert_eq!('h'.try_into(), Ok(Suit::H));
107        assert_eq!('d'.try_into(), Ok(Suit::D));
108        assert_eq!('c'.try_into(), Ok(Suit::C));
109
110        assert_eq!('S'.try_into(), Ok(Suit::S));
111        assert_eq!('H'.try_into(), Ok(Suit::H));
112        assert_eq!('D'.try_into(), Ok(Suit::D));
113        assert_eq!('C'.try_into(), Ok(Suit::C));
114
115        assert_eq!(
116            Suit::try_from('?'),
117            Err(ParseError::InvalidSuit("?".into())),
118        );
119    }
120
121    #[test]
122    fn test_from_str() {
123        assert_eq!(" s ".parse(), Ok(Suit::S));
124        assert_eq!(
125            "sS".parse::<Suit>(),
126            Err(ParseError::InvalidSuit("sS".into())),
127        );
128        assert!("".parse::<Suit>().is_err());
129        assert!("?".parse::<Suit>().is_err());
130    }
131
132    #[test]
133    fn test_to_string() {
134        assert_eq!(&Suit::S.to_string(), "s");
135        assert_eq!(&Suit::H.to_string(), "h");
136        assert_eq!(&Suit::D.to_string(), "d");
137        assert_eq!(&Suit::C.to_string(), "c");
138    }
139
140    #[test]
141    fn test_to_char() {
142        assert_eq!(Suit::S.to_char(), 's');
143        assert_eq!(Suit::H.to_char(), 'h');
144        assert_eq!(Suit::D.to_char(), 'd');
145        assert_eq!(Suit::C.to_char(), 'c');
146    }
147}