openpql_prelude/rating/
hand_type.rs1use super::{Display, FromStr, HandRating, N_HANDTYPE, ParseError, cmp};
2
3#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Display)]
13pub enum HandType {
14 #[default]
16 #[display("HIGH_CARD")]
17 HighCard,
18 #[display("PAIR")]
20 Pair,
21 #[display("TWO_PAIR")]
23 TwoPair,
24 #[display("TRIPS")]
26 Trips,
27 #[display("STRAIGHT")]
29 Straight,
30 #[display("FLUSH")]
32 Flush,
33 #[display("FULL_HOUSE")]
35 FullHouse,
36 #[display("QUADS")]
38 Quads,
39 #[display("STRAIGHT_FLUSH")]
41 StraightFlush,
42}
43
44type Idx = u8;
45
46impl HandType {
47 pub const MAX: Self = Self::StraightFlush;
48 pub const MIN: Self = Self::HighCard;
49
50 pub const ARR_ALL: [Self; N_HANDTYPE] = [
51 Self::HighCard,
52 Self::Pair,
53 Self::TwoPair,
54 Self::Trips,
55 Self::Straight,
56 Self::Flush,
57 Self::FullHouse,
58 Self::Quads,
59 Self::StraightFlush,
60 ];
61
62 const fn to_idx<const SD: bool>(self) -> Idx {
63 match self {
64 Self::HighCard => 0,
65 Self::Pair => 1,
66 Self::TwoPair => 2,
67 Self::Trips => 3,
68 Self::Straight => 4,
69 Self::Flush => 5 + (SD as Idx) * 2, Self::FullHouse => 6,
71 Self::Quads => 8,
72 Self::StraightFlush => 9,
73 }
74 }
75
76 pub fn compare<const SD: bool>(self, other: Self) -> cmp::Ordering {
77 self.to_idx::<SD>().cmp(&other.to_idx::<SD>())
78 }
79}
80
81const MASK_KIND: u16 = 0b1110_0000_0000_0000;
82const MASK_LO: u16 = 0b0000_0000_1111_1111;
83const N_FLUSH_SET_BITS: u32 = 7;
84
85impl From<HandRating> for HandType {
86 fn from(hand_ranking: HandRating) -> Self {
93 match hand_ranking.0 & MASK_KIND {
94 HandRating::MASK_QUADS => {
95 if hand_ranking.0 & MASK_LO == 0 {
96 Self::StraightFlush
97 } else {
98 Self::Quads
99 }
100 }
101 HandRating::MASK_FULLHOUSE | HandRating::MASK_FLUSH => {
102 if hand_ranking.0.count_ones() == N_FLUSH_SET_BITS {
103 Self::Flush
104 } else {
105 Self::FullHouse
106 }
107 }
108 HandRating::MASK_STRAIGHT => Self::Straight,
109 HandRating::MASK_TRIPS => Self::Trips,
110 HandRating::MASK_TWOPAIR => Self::TwoPair,
111 HandRating::MASK_PAIR => Self::Pair,
112 _ => Self::HighCard,
113 }
114 }
115}
116
117impl FromStr for HandType {
118 type Err = ParseError;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 match s.to_ascii_lowercase().trim() {
122 "highcard" => Ok(Self::HighCard),
123 "pair" => Ok(Self::Pair),
124 "twopair" => Ok(Self::TwoPair),
125 "trips" => Ok(Self::Trips),
126 "straight" => Ok(Self::Straight),
127 "flush" => Ok(Self::Flush),
128 "fullhouse" => Ok(Self::FullHouse),
129 "quads" => Ok(Self::Quads),
130 "straightflush" => Ok(Self::StraightFlush),
131 _ => Err(ParseError::InvalidHandType(s.into())),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_ord_holdem() {
142 let mut sorted = HandType::ARR_ALL.to_vec();
143 sorted.sort_unstable_by(|l, r| l.compare::<false>(*r));
144
145 assert_eq!(sorted, HandType::ARR_ALL);
146 }
147
148 #[test]
149 fn test_ord_shortdeck() {
150 let mut sorted = HandType::ARR_ALL.to_vec();
151 sorted.sort_unstable_by(|l, r| l.compare::<true>(*r));
152
153 assert_eq!(
154 sorted,
155 [
156 HandType::HighCard,
157 HandType::Pair,
158 HandType::TwoPair,
159 HandType::Trips,
160 HandType::Straight,
161 HandType::FullHouse,
162 HandType::Flush, HandType::Quads,
164 HandType::StraightFlush,
165 ]
166 );
167 }
168
169 #[test]
170 fn test_from_str() {
171 fn assert_str(s: &str, expected: HandType) {
172 assert_eq!(s.parse(), Ok(expected));
173 }
174
175 assert_str("highcard ", HandType::HighCard);
176 assert_str("pair ", HandType::Pair);
177 assert_str("twopair ", HandType::TwoPair);
178 assert_str("trips ", HandType::Trips);
179 assert_str("straight ", HandType::Straight);
180 assert_str("fullhouse ", HandType::FullHouse);
181 assert_str("flush ", HandType::Flush);
182 assert_str("quads ", HandType::Quads);
183 assert_str("straightflush", HandType::StraightFlush);
184
185 assert_eq!(
186 "invalid".parse::<HandType>(),
187 Err(ParseError::InvalidHandType("invalid".to_string()))
188 );
189 }
190}