use core::cmp::Ord;
use core::iter::Sum;
use dds_bridge::deal::{Hand, Holding, SmallSet};
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 {
hand.0.into_iter().map(&self.0).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(14))
+ 3 * u8::from(holding.contains(13))
+ 2 * u8::from(holding.contains(12))
+ u8::from(holding.contains(11)),
)
}
#[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(14))
+ 28 * i32::from(holding.contains(13))
+ 18 * i32::from(holding.contains(12))
+ 10 * i32::from(holding.contains(11))
+ 4 * i32::from(holding.contains(10)),
) / 10.0
}
#[must_use]
pub fn bumrap(holding: Holding) -> f64 {
f64::from(
18 * i32::from(holding.contains(14))
+ 12 * i32::from(holding.contains(13))
+ 6 * i32::from(holding.contains(12))
+ 3 * i32::from(holding.contains(11))
+ i32::from(holding.contains(10)),
) * 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(14))
+ u8::from(len >= 2 && !holding.contains(13))
+ u8::from(len >= 3 && !holding.contains(12)),
)
}
#[must_use]
pub fn nltc(holding: Holding) -> f64 {
let len = holding.len();
f64::from(
3 * i32::from(len >= 1 && !holding.contains(14))
+ 2 * i32::from(len >= 2 && !holding.contains(13))
+ i32::from(len >= 3 && !holding.contains(12)),
) * 0.5
}
#[must_use]
pub fn hcp_plus<T: From<u8> + PartialOrd>(holding: Holding) -> T {
let count = hcp(holding);
let short = shortness(holding);
if count < short { short } else { count }
}
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| bumrap(x).max(shortness(x)));
pub const NLTC: SimpleEvaluator<f64, fn(Holding) -> f64> = SimpleEvaluator(nltc);
pub fn zar<T: From<u8>>(hand: Hand) -> T {
let holdings = hand.0;
let mut lengths = holdings.map(SmallSet::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] = [14, 13, 12, 11].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)
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_four_kings() {
use approx::assert_ulps_eq;
const KXXX: Holding = Holding::from_bits_truncate(0b01000_0000_0111_00);
const KXX: Holding = Holding::from_bits_truncate(0b01000_0000_0011_00);
const HAND: Hand = Hand([KXXX, KXX, KXX, KXX]);
assert_eq!(SimpleEvaluator(hcp::<u8>).eval(HAND), 12);
assert_ulps_eq!(FIFTHS.eval(HAND), 2.8 * 4.0);
assert_ulps_eq!(BUMRAP.eval(HAND), 12.0);
assert_eq!(SimpleEvaluator(ltc::<u8>).eval(HAND), 8);
assert_ulps_eq!(NLTC.eval(HAND), 8.0);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_random_from_cuebids() {
use approx::assert_ulps_eq;
const KJ53: Holding = Holding::from_bits_truncate(0b01010_0000_1010_00);
const K84: Holding = Holding::from_bits_truncate(0b01000_0100_0100_00);
const XX: Holding = Holding::from_bits_truncate(0b00000_0000_0110_00);
const KT85: Holding = Holding::from_bits_truncate(0b01001_0100_1000_00);
const HAND: Hand = Hand([KT85, XX, K84, KJ53]);
assert_eq!(SimpleEvaluator(hcp::<u8>).eval(HAND), 10);
assert_eq!(SimpleEvaluator(hcp_plus::<u8>).eval(HAND), 11);
assert_ulps_eq!(FIFTHS.eval(HAND), 9.8);
assert_ulps_eq!(BUMRAP.eval(HAND), 10.0);
assert_ulps_eq!(BUMRAP_PLUS.eval(HAND), 11.0);
assert_eq!(SimpleEvaluator(ltc::<u8>).eval(HAND), 8);
assert_ulps_eq!(NLTC.eval(HAND), 8.5);
}