1use crate::{Hand, Holding, Rank, Suit};
12use core::cmp::Ord;
13use core::iter::Sum;
14
15pub trait HandEvaluator<T> {
17 #[must_use]
19 fn eval(&self, hand: Hand) -> T;
20
21 #[must_use]
23 fn eval_pair(&self, pair: [Hand; 2]) -> T
24 where
25 T: core::ops::Add<Output = T>,
26 {
27 self.eval(pair[0]) + self.eval(pair[1])
28 }
29}
30
31impl<F: Fn(Hand) -> T, T> HandEvaluator<T> for F {
33 fn eval(&self, hand: Hand) -> T {
34 self(hand)
35 }
36}
37
38#[derive(Debug)]
40pub struct SimpleEvaluator<T: Sum, F: Fn(Holding) -> T>(
41 pub F,
43);
44
45impl<T: Sum, F: Fn(Holding) -> T> HandEvaluator<T> for SimpleEvaluator<T, F> {
46 fn eval(&self, hand: Hand) -> T {
47 Suit::ASC.into_iter().map(|s| (self.0)(hand[s])).sum()
48 }
49}
50
51impl<T: Sum, F: Clone + Fn(Holding) -> T> Clone for SimpleEvaluator<T, F> {
52 fn clone(&self) -> Self {
53 Self(self.0.clone())
54 }
55}
56
57impl<T: Sum, F: Copy + Fn(Holding) -> T> Copy for SimpleEvaluator<T, F> {}
58
59#[must_use]
63pub fn hcp<T: From<u8>>(holding: Holding) -> T {
64 T::from(
65 4 * u8::from(holding.contains(Rank::A))
66 + 3 * u8::from(holding.contains(Rank::K))
67 + 2 * u8::from(holding.contains(Rank::Q))
68 + u8::from(holding.contains(Rank::J)),
69 )
70}
71
72#[must_use]
74#[allow(clippy::cast_possible_truncation)]
76pub fn shortness<T: From<u8>>(holding: Holding) -> T {
77 T::from(3 - holding.len().min(3) as u8)
78}
79
80#[must_use]
86pub fn fifths(holding: Holding) -> f64 {
87 f64::from(
88 40 * i32::from(holding.contains(Rank::A))
89 + 28 * i32::from(holding.contains(Rank::K))
90 + 18 * i32::from(holding.contains(Rank::Q))
91 + 10 * i32::from(holding.contains(Rank::J))
92 + 4 * i32::from(holding.contains(Rank::T)),
93 ) / 10.0
94}
95
96#[must_use]
100pub fn bumrap(holding: Holding) -> f64 {
101 f64::from(
102 18 * i32::from(holding.contains(Rank::A))
103 + 12 * i32::from(holding.contains(Rank::K))
104 + 6 * i32::from(holding.contains(Rank::Q))
105 + 3 * i32::from(holding.contains(Rank::J))
106 + i32::from(holding.contains(Rank::T)),
107 ) * 0.25
108}
109
110#[must_use]
112pub fn ltc<T: From<u8>>(holding: Holding) -> T {
113 let len = holding.len();
114
115 T::from(
116 u8::from(len >= 1 && !holding.contains(Rank::A))
117 + u8::from(len >= 2 && !holding.contains(Rank::K))
118 + u8::from(len >= 3 && !holding.contains(Rank::Q)),
119 )
120}
121
122#[must_use]
126pub fn nltc(holding: Holding) -> f64 {
127 let len = holding.len();
128
129 f64::from(
130 3 * i32::from(len >= 1 && !holding.contains(Rank::A))
131 + 2 * i32::from(len >= 2 && !holding.contains(Rank::K))
132 + i32::from(len >= 3 && !holding.contains(Rank::Q)),
133 ) * 0.5
134}
135
136#[must_use]
142pub fn hcp_plus<T: From<u8>>(holding: Holding) -> T {
143 let count: u8 = hcp(holding);
144 let short: u8 = shortness(holding);
145
146 T::from(if count > 0 && short > 0 {
147 count + short - 1
148 } else {
149 count.max(short)
150 })
151}
152
153pub const FIFTHS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(fifths);
160
161pub const BUMRAP: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(bumrap);
166
167pub const BUMRAP_PLUS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(|x| {
173 let b: f64 = bumrap(x);
174 let s: f64 = shortness(x);
175 b.max(s).max(b + s - 1.0)
176});
177
178pub const NLTC: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(nltc);
186
187pub fn zar<T: From<u8>>(hand: Hand) -> T {
191 let holdings = Suit::ASC.map(|s| hand[s]);
192 let mut lengths = holdings.map(Holding::len);
193 lengths.sort_unstable();
194
195 #[allow(clippy::cast_possible_truncation)]
197 let sum = (lengths[3] + lengths[2]) as u8;
198
199 #[allow(clippy::cast_possible_truncation)]
201 let diff = (lengths[3] - lengths[0]) as u8;
202
203 let honors: u8 = holdings
204 .into_iter()
205 .map(|holding| {
206 let [a, k, q, j] = [Rank::A, Rank::K, Rank::Q, Rank::J].map(|r| holding.contains(r));
207 let count = 6 * u8::from(a) + 4 * u8::from(k) + 2 * u8::from(q) + u8::from(j);
208 let waste = match holding.len() {
209 1 => k || q || j,
210 2 => q || j,
211 _ => false,
212 };
213 count - u8::from(waste)
214 })
215 .sum();
216
217 T::from(honors + sum + diff)
218}