1use crate::{Hand, Holding, Rank, Suit};
13use core::cmp::Ord;
14use core::iter::Sum;
15
16pub trait HandEvaluator<T> {
18 #[must_use]
20 fn eval(&self, hand: Hand) -> T;
21
22 #[must_use]
24 fn eval_pair(&self, pair: [Hand; 2]) -> T
25 where
26 T: core::ops::Add<Output = T>,
27 {
28 self.eval(pair[0]) + self.eval(pair[1])
29 }
30}
31
32impl<F: Fn(Hand) -> T, T> HandEvaluator<T> for F {
34 fn eval(&self, hand: Hand) -> T {
35 self(hand)
36 }
37}
38
39#[derive(Debug)]
41pub struct SimpleEvaluator<T: Sum, F: Fn(Holding) -> T>(
42 pub F,
44);
45
46impl<T: Sum, F: Fn(Holding) -> T> HandEvaluator<T> for SimpleEvaluator<T, F> {
47 fn eval(&self, hand: Hand) -> T {
48 Suit::ASC.into_iter().map(|s| (self.0)(hand[s])).sum()
49 }
50}
51
52impl<T: Sum, F: Clone + Fn(Holding) -> T> Clone for SimpleEvaluator<T, F> {
53 fn clone(&self) -> Self {
54 Self(self.0.clone())
55 }
56}
57
58impl<T: Sum, F: Copy + Fn(Holding) -> T> Copy for SimpleEvaluator<T, F> {}
59
60#[must_use]
64pub fn hcp<T: From<u8>>(holding: Holding) -> T {
65 T::from(
66 4 * u8::from(holding.contains(Rank::A))
67 + 3 * u8::from(holding.contains(Rank::K))
68 + 2 * u8::from(holding.contains(Rank::Q))
69 + u8::from(holding.contains(Rank::J)),
70 )
71}
72
73#[must_use]
75#[allow(clippy::cast_possible_truncation)]
77pub fn shortness<T: From<u8>>(holding: Holding) -> T {
78 T::from(3 - holding.len().min(3) as u8)
79}
80
81#[must_use]
87pub fn fifths(holding: Holding) -> f64 {
88 f64::from(
89 40 * i32::from(holding.contains(Rank::A))
90 + 28 * i32::from(holding.contains(Rank::K))
91 + 18 * i32::from(holding.contains(Rank::Q))
92 + 10 * i32::from(holding.contains(Rank::J))
93 + 4 * i32::from(holding.contains(Rank::T)),
94 ) / 10.0
95}
96
97#[must_use]
101pub fn bumrap(holding: Holding) -> f64 {
102 f64::from(
103 18 * i32::from(holding.contains(Rank::A))
104 + 12 * i32::from(holding.contains(Rank::K))
105 + 6 * i32::from(holding.contains(Rank::Q))
106 + 3 * i32::from(holding.contains(Rank::J))
107 + i32::from(holding.contains(Rank::T)),
108 ) * 0.25
109}
110
111#[must_use]
113pub fn ltc<T: From<u8>>(holding: Holding) -> T {
114 let len = holding.len();
115
116 T::from(
117 u8::from(len >= 1 && !holding.contains(Rank::A))
118 + u8::from(len >= 2 && !holding.contains(Rank::K))
119 + u8::from(len >= 3 && !holding.contains(Rank::Q)),
120 )
121}
122
123#[must_use]
127pub fn nltc(holding: Holding) -> f64 {
128 let len = holding.len();
129
130 f64::from(
131 3 * i32::from(len >= 1 && !holding.contains(Rank::A))
132 + 2 * i32::from(len >= 2 && !holding.contains(Rank::K))
133 + i32::from(len >= 3 && !holding.contains(Rank::Q)),
134 ) * 0.5
135}
136
137#[must_use]
143pub fn hcp_plus<T: From<u8>>(holding: Holding) -> T {
144 let count: u8 = hcp(holding);
145 let short: u8 = shortness(holding);
146
147 T::from(if count > 0 && short > 0 {
148 count + short - 1
149 } else {
150 count.max(short)
151 })
152}
153
154pub const FIFTHS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(fifths);
161
162pub const BUMRAP: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(bumrap);
167
168pub const BUMRAP_PLUS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(|x| {
174 let b: f64 = bumrap(x);
175 let s: f64 = shortness(x);
176 b.max(s).max(b + s - 1.0)
177});
178
179pub const NLTC: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(nltc);
187
188pub fn zar<T: From<u8>>(hand: Hand) -> T {
192 let holdings = Suit::ASC.map(|s| hand[s]);
193 let mut lengths = holdings.map(Holding::len);
194 lengths.sort_unstable();
195
196 #[allow(clippy::cast_possible_truncation)]
198 let sum = (lengths[3] + lengths[2]) as u8;
199
200 #[allow(clippy::cast_possible_truncation)]
202 let diff = (lengths[3] - lengths[0]) as u8;
203
204 let honors: u8 = holdings
205 .into_iter()
206 .map(|holding| {
207 let [a, k, q, j] = [Rank::A, Rank::K, Rank::Q, Rank::J].map(|r| holding.contains(r));
208 let count = 6 * u8::from(a) + 4 * u8::from(k) + 2 * u8::from(q) + u8::from(j);
209 let waste = match holding.len() {
210 1 => k || q || j,
211 2 => q || j,
212 _ => false,
213 };
214 count - u8::from(waste)
215 })
216 .sum();
217
218 T::from(honors + sum + diff)
219}
220
221fn cccc_suit(holding: Holding) -> i32 {
223 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
225 let len = holding.len() as i32;
226 let [a, k, q, j, t] =
227 [Rank::A, Rank::K, Rank::Q, Rank::J, Rank::T].map(|r| holding.contains(r));
228 let nine = holding.contains(Rank::new(9));
229 let eight = holding.contains(Rank::new(8));
230 let akq = i32::from(a) + i32::from(k) + i32::from(q);
231 let akqj = akq + i32::from(j);
232
233 let mut quality = 400 * i32::from(a)
235 + 300 * i32::from(k)
236 + 200 * i32::from(q)
237 + 100 * i32::from(j)
238 + 50 * i32::from(t);
239
240 if (2..=6).contains(&len) {
241 quality += 50 * i32::from(t && (j || akq >= 2));
242 quality += 50 * i32::from(nine && (eight || t || akqj == 2));
243 }
244 quality += 50 * i32::from((4..=6).contains(&len) && nine && !eight && !t && akqj == 3);
245 quality += 100 * i32::from(len >= 7 && !(q && j));
246 quality += 100 * i32::from(len >= 8 && !q);
247 quality += 100 * i32::from(len >= 9 && !q && !j);
248
249 let body = 300 * i32::from(a)
251 + if k {
252 if len >= 2 { 200 } else { 50 }
253 } else {
254 0
255 }
256 + if q {
257 match (len, a || k) {
258 (..=1, _) => 0,
259 (2, true) => 50,
260 (2, false) => 25,
261 (_, true) => 100,
262 (_, false) => 75,
263 }
264 } else {
265 0
266 }
267 + if j {
268 match akq {
269 2 => 50,
270 1 => 25,
271 _ => 0,
272 }
273 } else {
274 0
275 }
276 + 25 * i32::from(t && (akqj == 2 || (akqj == 1 && nine)));
277
278 let shortness = match len {
280 0 => 300,
281 1 => 200,
282 2 => 100,
283 _ => 0,
284 };
285
286 quality * len / 10 + body + shortness
287}
288
289#[must_use]
303pub fn cccc(hand: Hand) -> f64 {
304 let suits: i32 = Suit::ASC.into_iter().map(|s| cccc_suit(hand[s])).sum();
305 let flat = Suit::ASC.into_iter().all(|s| hand[s].len() >= 3);
306 let total = suits - 100 + 50 * i32::from(flat);
307 debug_assert_eq!(total.rem_euclid(5), 0);
308 f64::from(total) / 100.0
309}