use crate::{Hand, Holding, Rank, Suit};
use core::cmp::Ord;
use core::iter::Sum;
pub trait HandEvaluator<T> {
#[must_use]
fn eval(&self, hand: Hand) -> T;
#[must_use]
fn eval_pair(&self, pair: [Hand; 2]) -> T
where
T: core::ops::Add<Output = T>,
{
self.eval(pair[0]) + self.eval(pair[1])
}
}
impl<F: Fn(Hand) -> T, T> HandEvaluator<T> for F {
fn eval(&self, hand: Hand) -> T {
self(hand)
}
}
#[derive(Debug)]
pub struct SimpleEvaluator<T: Sum, F: Fn(Holding) -> T>(
pub F,
);
impl<T: Sum, F: Fn(Holding) -> T> HandEvaluator<T> for SimpleEvaluator<T, F> {
fn eval(&self, hand: Hand) -> T {
Suit::ASC.into_iter().map(|s| (self.0)(hand[s])).sum()
}
}
impl<T: Sum, F: Clone + Fn(Holding) -> T> Clone for SimpleEvaluator<T, F> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Sum, F: Copy + Fn(Holding) -> T> Copy for SimpleEvaluator<T, F> {}
#[must_use]
pub fn hcp<T: From<u8>>(holding: Holding) -> T {
T::from(
4 * u8::from(holding.contains(Rank::A))
+ 3 * u8::from(holding.contains(Rank::K))
+ 2 * u8::from(holding.contains(Rank::Q))
+ u8::from(holding.contains(Rank::J)),
)
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn shortness<T: From<u8>>(holding: Holding) -> T {
T::from(3 - holding.len().min(3) as u8)
}
#[must_use]
pub fn fifths(holding: Holding) -> f64 {
f64::from(
40 * i32::from(holding.contains(Rank::A))
+ 28 * i32::from(holding.contains(Rank::K))
+ 18 * i32::from(holding.contains(Rank::Q))
+ 10 * i32::from(holding.contains(Rank::J))
+ 4 * i32::from(holding.contains(Rank::T)),
) / 10.0
}
#[must_use]
pub fn bumrap(holding: Holding) -> f64 {
f64::from(
18 * i32::from(holding.contains(Rank::A))
+ 12 * i32::from(holding.contains(Rank::K))
+ 6 * i32::from(holding.contains(Rank::Q))
+ 3 * i32::from(holding.contains(Rank::J))
+ i32::from(holding.contains(Rank::T)),
) * 0.25
}
#[must_use]
pub fn ltc<T: From<u8>>(holding: Holding) -> T {
let len = holding.len();
T::from(
u8::from(len >= 1 && !holding.contains(Rank::A))
+ u8::from(len >= 2 && !holding.contains(Rank::K))
+ u8::from(len >= 3 && !holding.contains(Rank::Q)),
)
}
#[must_use]
pub fn nltc(holding: Holding) -> f64 {
let len = holding.len();
f64::from(
3 * i32::from(len >= 1 && !holding.contains(Rank::A))
+ 2 * i32::from(len >= 2 && !holding.contains(Rank::K))
+ i32::from(len >= 3 && !holding.contains(Rank::Q)),
) * 0.5
}
#[must_use]
pub fn hcp_plus<T: From<u8>>(holding: Holding) -> T {
let count: u8 = hcp(holding);
let short: u8 = shortness(holding);
T::from(if count > 0 && short > 0 {
count + short - 1
} else {
count.max(short)
})
}
pub const FIFTHS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(fifths);
pub const BUMRAP: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(bumrap);
pub const BUMRAP_PLUS: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(|x| {
let b: f64 = bumrap(x);
let s: f64 = shortness(x);
b.max(s).max(b + s - 1.0)
});
pub const NLTC: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(nltc);
pub fn zar<T: From<u8>>(hand: Hand) -> T {
let holdings = Suit::ASC.map(|s| hand[s]);
let mut lengths = holdings.map(Holding::len);
lengths.sort_unstable();
#[allow(clippy::cast_possible_truncation)]
let sum = (lengths[3] + lengths[2]) as u8;
#[allow(clippy::cast_possible_truncation)]
let diff = (lengths[3] - lengths[0]) as u8;
let honors: u8 = holdings
.into_iter()
.map(|holding| {
let [a, k, q, j] = [Rank::A, Rank::K, Rank::Q, Rank::J].map(|r| holding.contains(r));
let count = 6 * u8::from(a) + 4 * u8::from(k) + 2 * u8::from(q) + u8::from(j);
let waste = match holding.len() {
1 => k || q || j,
2 => q || j,
_ => false,
};
count - u8::from(waste)
})
.sum();
T::from(honors + sum + diff)
}
fn cccc_suit(holding: Holding) -> i32 {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let len = holding.len() as i32;
let [a, k, q, j, t] =
[Rank::A, Rank::K, Rank::Q, Rank::J, Rank::T].map(|r| holding.contains(r));
let nine = holding.contains(Rank::new(9));
let eight = holding.contains(Rank::new(8));
let akq = i32::from(a) + i32::from(k) + i32::from(q);
let akqj = akq + i32::from(j);
let mut quality = 400 * i32::from(a)
+ 300 * i32::from(k)
+ 200 * i32::from(q)
+ 100 * i32::from(j)
+ 50 * i32::from(t);
if (2..=6).contains(&len) {
quality += 50 * i32::from(t && (j || akq >= 2));
quality += 50 * i32::from(nine && (eight || t || akqj == 2));
}
quality += 50 * i32::from((4..=6).contains(&len) && nine && !eight && !t && akqj == 3);
quality += 100 * i32::from(len >= 7 && !(q && j));
quality += 100 * i32::from(len >= 8 && !q);
quality += 100 * i32::from(len >= 9 && !q && !j);
let body = 300 * i32::from(a)
+ if k {
if len >= 2 { 200 } else { 50 }
} else {
0
}
+ if q {
match (len, a || k) {
(..=1, _) => 0,
(2, true) => 50,
(2, false) => 25,
(_, true) => 100,
(_, false) => 75,
}
} else {
0
}
+ if j {
match akq {
2 => 50,
1 => 25,
_ => 0,
}
} else {
0
}
+ 25 * i32::from(t && (akqj == 2 || (akqj == 1 && nine)));
let shortness = match len {
0 => 300,
1 => 200,
2 => 100,
_ => 0,
};
quality * len / 10 + body + shortness
}
#[must_use]
pub fn cccc(hand: Hand) -> f64 {
let suits: i32 = Suit::ASC.into_iter().map(|s| cccc_suit(hand[s])).sum();
let flat = Suit::ASC.into_iter().all(|s| hand[s].len() >= 3);
let total = suits - 100 + 50 * i32::from(flat);
debug_assert_eq!(total.rem_euclid(5), 0);
f64::from(total) / 100.0
}