use core::cmp::Ord;
use core::iter::Sum;
use dds_bridge::{Hand, Holding, Rank, Suit};
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)
}