1use super::{
2 HandRatingView, HandType, IdxThreeRanks, IdxTwoRanks, Rank16, RatingInner,
3 fmt,
4};
5
6#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
65pub struct HandRating(pub(crate) RatingInner);
66
67#[allow(clippy::cast_possible_truncation)]
70#[must_use]
71#[inline]
72const fn rank_idx(ranks: Rank16) -> RatingInner {
73 TOTAL_LEADING_ZEROS - ranks.0.leading_zeros() as u16
74}
75
76#[inline]
77const fn rev_rank_idx(idx: RatingInner) -> Rank16 {
78 const MASK_RANK_IDX: RatingInner = 0b1111;
79 Rank16(1 << (idx & MASK_RANK_IDX))
80}
81
82#[must_use]
83#[inline]
84const fn comb2(ranks: Rank16) -> RatingInner {
85 IdxTwoRanks::from_r16(ranks).0 as RatingInner
86}
87
88#[inline]
89fn rev_comb2(i: RatingInner) -> Rank16 {
90 IdxTwoRanks(IdxTwoRanks::MASK_USED & i.to_le_bytes()[0]).to_r16()
91}
92
93#[must_use]
94#[inline]
95const fn comb3(ranks: Rank16) -> RatingInner {
96 IdxThreeRanks::from_r16(ranks).0 as RatingInner
97}
98
99#[inline]
100fn rev_comb3(i: RatingInner) -> Rank16 {
101 IdxThreeRanks(IdxThreeRanks::MASK_USED & i).to_r16()
102}
103
104const TOTAL_LEADING_ZEROS: RatingInner = 15;
105const OFFSET_RANK_IDX: usize = 4;
106const OFFSET_COMB3: usize = 9;
107const OFFSET_HI: usize = 8;
108const MASK_FULLHOUSE_PADDING: RatingInner = 0b0001_1111_0000_0000;
109
110impl HandRating {
111 pub(crate) const MASK_STRAIGHTFLUSH: RatingInner = 0b1110_0000_0000_0000;
112 pub(crate) const MASK_QUADS: RatingInner = 0b1110_0000_0000_0000;
113 pub(crate) const MASK_FULLHOUSE: RatingInner = 0b1100_0000_0000_0000;
114 pub(crate) const MASK_FLUSH: RatingInner = 0b1010_0000_0000_0000;
115 pub(crate) const MASK_STRAIGHT: RatingInner = 0b1000_0000_0000_0000;
116 pub(crate) const MASK_TRIPS: RatingInner = 0b0110_0000_0000_0000;
117 pub(crate) const MASK_TWOPAIR: RatingInner = 0b0100_0000_0000_0000;
118 pub(crate) const MASK_PAIR: RatingInner = 0b0010_0000_0000_0000;
119 pub(crate) const MASK_HIGHCARD: RatingInner = 0b0000_0000_0000_0000;
120
121 pub(crate) const MASK_FULLHOUSE_SD: RatingInner = Self::MASK_FLUSH;
122 pub(crate) const MASK_FLUSH_SD: RatingInner = Self::MASK_FULLHOUSE;
123
124 pub(crate) const fn new_highcard(ranks: Rank16) -> Self {
125 Self(Self::MASK_HIGHCARD | ranks.0)
126 }
127
128 pub(crate) const fn parse_highcard(self) -> Rank16 {
129 Rank16(Rank16::ALL.0 & self.0)
130 }
131
132 pub(crate) const fn new_pair(pair: Rank16, kicker: Rank16) -> Self {
133 Self(Self::MASK_PAIR | rank_idx(pair) << OFFSET_COMB3 | comb3(kicker))
134 }
135
136 pub(crate) fn parse_pair(self) -> (Rank16, Rank16) {
137 (
138 rev_rank_idx((Self::MASK_PAIR ^ self.0) >> OFFSET_COMB3),
139 rev_comb3(self.0),
140 )
141 }
142
143 pub(crate) const fn new_twopair(pairs: Rank16, kicker: Rank16) -> Self {
144 Self(
145 Self::MASK_TWOPAIR
146 | comb2(pairs) << OFFSET_RANK_IDX
147 | rank_idx(kicker),
148 )
149 }
150
151 pub(crate) fn parse_twopair(self) -> (Rank16, Rank16) {
152 (rev_comb2(self.0 >> OFFSET_RANK_IDX), rev_rank_idx(self.0))
153 }
154
155 pub(crate) const fn new_trips(trips: Rank16, kicker: Rank16) -> Self {
156 Self(Self::MASK_TRIPS | rank_idx(trips) << OFFSET_HI | comb2(kicker))
157 }
158
159 pub(crate) fn parse_trips(self) -> (Rank16, Rank16) {
160 (rev_rank_idx(self.0 >> OFFSET_HI), rev_comb2(self.0))
161 }
162
163 pub(crate) const fn new_straight(ranks: Rank16) -> Self {
164 Self(Self::MASK_STRAIGHT | rank_idx(ranks))
165 }
166
167 pub(crate) const fn parse_straight(self) -> Rank16 {
168 rev_rank_idx(self.0)
169 }
170
171 pub(crate) const fn new_flush(ranks: Rank16) -> Self {
172 Self(Self::MASK_FLUSH | ranks.0)
173 }
174
175 pub(crate) const fn new_flush_sd(ranks: Rank16) -> Self {
176 Self(Self::MASK_FLUSH_SD | ranks.0)
177 }
178
179 pub(crate) const fn parse_flush(self) -> Rank16 {
180 self.parse_highcard()
181 }
182
183 pub(crate) const fn new_fullhouse(trips: Rank16, pairs: Rank16) -> Self {
184 Self(
185 Self::MASK_FULLHOUSE
186 | MASK_FULLHOUSE_PADDING
187 | rank_idx(trips) << OFFSET_RANK_IDX
188 | rank_idx(pairs),
189 )
190 }
191
192 pub(crate) const fn new_fullhouse_sd(trips: Rank16, pairs: Rank16) -> Self {
193 Self(
194 Self::MASK_FULLHOUSE_SD
195 | MASK_FULLHOUSE_PADDING
196 | rank_idx(trips) << OFFSET_RANK_IDX
197 | rank_idx(pairs),
198 )
199 }
200
201 pub(crate) const fn parse_fullhouse(self) -> (Rank16, Rank16) {
202 (
203 rev_rank_idx(self.0 >> OFFSET_RANK_IDX),
204 rev_rank_idx(self.0),
205 )
206 }
207
208 pub(crate) const fn new_quad(quad: Rank16, kicker: Rank16) -> Self {
209 Self(
210 Self::MASK_QUADS
211 | rank_idx(quad) << OFFSET_RANK_IDX
212 | rank_idx(kicker),
213 )
214 }
215
216 pub(crate) const fn parse_quad(self) -> (Rank16, Rank16) {
217 self.parse_fullhouse()
218 }
219
220 pub(crate) const fn new_straightflush(ranks: Rank16) -> Self {
221 Self(Self::MASK_STRAIGHTFLUSH | rank_idx(ranks) << OFFSET_HI)
222 }
223
224 pub(crate) const fn parse_straightflush(self) -> Rank16 {
225 rev_rank_idx(self.0 >> OFFSET_HI)
226 }
227}
228
229impl fmt::Display for HandRating {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 let view = HandRatingView::from(*self);
232 let ht = view.hand_type;
233
234 match ht {
235 HandType::HighCard
236 | HandType::Straight
237 | HandType::Flush
238 | HandType::StraightFlush => write!(f, "{ht}({})", view.high),
239
240 HandType::Pair
241 | HandType::TwoPair
242 | HandType::Trips
243 | HandType::FullHouse
244 | HandType::Quads => write!(f, "{ht}({}, {})", view.high, view.low),
245 }
246 }
247}
248
249impl fmt::Debug for HandRating {
250 #![cfg_attr(coverage_nightly, coverage(off))]
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 <Self as fmt::Display>::fmt(self, f)
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use HandType::*;
259
260 use super::*;
261 use crate::*;
262
263 fn assert_str(text: &str, ht: HandType, hi: &str, lo: &str) {
264 assert_eq!(text, mk_rating(ht, hi, lo).to_string());
265 }
266
267 #[test]
268 fn test_display() {
269 assert_str("STRAIGHT_FLUSH(5)", StraightFlush, "5", "");
270 assert_str("QUADS(A, K)", Quads, "A", "K");
271 assert_str("FULL_HOUSE(T, A)", FullHouse, "T", "A");
272 assert_str("FLUSH(6789J)", Flush, "J6789", "");
273 assert_str("STRAIGHT(9)", Straight, "9", "");
274 assert_str("TRIPS(T, KA)", Trips, "T", "AK");
275 assert_str("TWO_PAIR(6T, K)", TwoPair, "T6", "K");
276 assert_str("PAIR(J, 89K)", Pair, "J", "K98");
277 assert_str("HIGH_CARD(89JQK)", HighCard, "KQJ98", "");
278 }
279
280 #[test]
281 fn test_default() {
282 assert_eq!(HandRating::default().0, RatingInner::MIN);
283 }
284
285 #[quickcheck]
286 fn test_bijection(ranks: Distinct<5, Rank>) {
287 let r5 = Rank16::from(&ranks[..]);
288
289 let hi = Rank16::from(ranks[0]);
290 let lo = Rank16::from(ranks[1]);
291 let r2 = Rank16::from(&ranks[..2]);
292 let r3 = Rank16::from(&ranks[2..]);
293 let tl = Rank16::from(ranks[4]);
294
295 assert_eq!(r5, HandRating::new_highcard(r5).parse_highcard());
296 assert_eq!((hi, r3), HandRating::new_pair(hi, r3).parse_pair());
297 assert_eq!((r2, tl), HandRating::new_twopair(r2, tl).parse_twopair());
298 assert_eq!((tl, r2), HandRating::new_trips(tl, r2).parse_trips());
299 assert_eq!(lo, HandRating::new_straight(lo).parse_straight());
300 assert_eq!(r5, HandRating::new_flush(r5).parse_flush());
301 assert_eq!(r5, HandRating::new_flush_sd(r5).parse_flush());
302 assert_eq!(
303 (hi, lo),
304 HandRating::new_fullhouse(hi, lo).parse_fullhouse()
305 );
306 assert_eq!(
307 (hi, lo),
308 HandRating::new_fullhouse_sd(hi, lo).parse_fullhouse()
309 );
310 assert_eq!((hi, lo), HandRating::new_quad(hi, lo).parse_quad());
311 assert_eq!(hi, HandRating::new_straightflush(hi).parse_straightflush());
312 }
313}