use approx::assert_ulps_eq;
use contract_bridge::eval::{
BUMRAP, BUMRAP_PLUS, FIFTHS, HandEvaluator, NLTC, SimpleEvaluator, cccc, hcp, hcp_plus, ltc,
shortness, zar,
};
use contract_bridge::{Hand, Holding};
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_four_kings() {
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::new(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);
assert_eq!(zar::<u8>(HAND), 24);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_random_from_cuebids() {
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::new(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);
assert_eq!(zar::<u8>(HAND), 23);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_zar_waste() {
const A9876: Holding = Holding::from_bits_truncate(0b10000_1100_1100_00);
const K: Holding = Holding::from_bits_truncate(0b01000_0000_0000_00);
const QJ: Holding = Holding::from_bits_truncate(0b00110_0000_0000_00);
const T5432: Holding = Holding::from_bits_truncate(0b00001_0000_1111_00);
const HAND: Hand = Hand::new(T5432, QJ, K, A9876);
assert_eq!(zar::<u8>(HAND), 25);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_zar_aces() {
const AXXX: Holding = Holding::from_bits_truncate(0b10000_0000_0111_00);
const AXX: Holding = Holding::from_bits_truncate(0b10000_0000_0011_00);
const HAND: Hand = Hand::new(AXXX, AXX, AXX, AXX);
assert_eq!(zar::<u8>(HAND), 32);
}
#[test]
fn test_empty_hand() {
let hand = Hand::default();
assert_eq!(SimpleEvaluator(hcp::<u8>).eval(hand), 0);
assert_eq!(SimpleEvaluator(shortness::<u8>).eval(hand), 12);
assert_eq!(SimpleEvaluator(hcp_plus::<u8>).eval(hand), 12);
assert_ulps_eq!(FIFTHS.eval(hand), 0.0);
assert_ulps_eq!(BUMRAP.eval(hand), 0.0);
assert_ulps_eq!(BUMRAP_PLUS.eval(hand), 12.0);
assert_eq!(SimpleEvaluator(ltc::<u8>).eval(hand), 0);
assert_ulps_eq!(NLTC.eval(hand), 0.0);
assert_eq!(zar::<u8>(hand), 0);
assert_ulps_eq!(cccc(hand), 11.0);
}
#[test]
fn test_cccc_vectors() {
const VECTORS: [(&str, f64); 34] = [
("AKQJ.AKQ.AKQ.AKQ", 35.60),
("5432.432.432.432", -0.50),
("Q2.5432.5432.432", 0.65),
("AQ.5432.5432.432", 4.70),
("KQ.5432.5432.432", 3.50),
("QJ.5432.5432.432", 1.10),
("Q.5432.5432.5432", 1.20),
("K.5432.5432.5432", 1.80),
("A.5432.5432.5432", 4.40),
("J.5432.5432.5432", 1.10),
("AJ.5432.5432.432", 4.25),
("QJ2.5432.5432.32", 1.90),
("5432.5432.5432.2", 1.00),
("65432.5432.32.32", 1.00),
("65432.5432.5432.", 2.00),
("AT98.432.432.432", 4.75),
("AK92.432.432.432", 7.50),
("AKT2.432.432.432", 7.95),
("AKJ2.432.432.432", 8.20),
("AKQ92.432.432.32", 10.75),
("AKQT2.432.432.32", 11.00),
("JT98.65432.432.2", 2.25),
("T98765.5432.32.2", 2.60),
("AQ32.K53.QJ4.A92", 14.90),
("AQJ52.K942.32.T9", 12.40),
("AKQJ52.AK2.A2.32", 23.90),
("AK98765.432.32.2", 12.60),
("AKQJ876.432.32.2", 15.00),
("AK987654.32.32.2", 15.20),
("AKJ87654.32.32.2", 16.50),
("AKQ87654.32.32.2", 17.00),
("AK9876543.32.2.2", 18.00),
("AKQ876543.32.2.2", 19.00),
("AKT98765.432.2.2", 15.85),
];
for (text, expected) in VECTORS {
let hand: Hand = text.parse().expect(text);
let value = cccc(hand);
assert!(
approx::ulps_eq!(value, expected),
"cccc({text}) = {value}, expected {expected}",
);
}
}
#[test]
fn test_cccc_seven_card_suits() {
const VECTORS: [(&str, f64); 3] = [
("AKJ8765.432.32.2", 13.80), ("AKQ8765.432.32.2", 15.00), ("KQT9832.A54.32.2", 14.00), ];
for (text, expected) in VECTORS {
let hand: Hand = text.parse().expect(text);
let value = cccc(hand);
assert!(
approx::ulps_eq!(value, expected),
"cccc({text}) = {value}, expected {expected}",
);
}
}
#[test]
fn test_cccc_distribution() {
#[allow(clippy::cast_possible_truncation)]
let contribution = |holding: Holding| {
let hand = Hand::new(holding, Holding::EMPTY, Holding::EMPTY, Holding::EMPTY);
(cccc(hand) * 100.0).round() as i32 - 800
};
let mut buckets = std::collections::HashMap::<(usize, i32), u64>::new();
for bits in 0..0x2000u16 {
let holding = Holding::from_bits_truncate(bits << 2);
*buckets
.entry((holding.len(), contribution(holding)))
.or_insert(0) += 1;
}
let empty = std::collections::HashMap::<i32, u128>::new();
let mut dp = vec![[empty.clone(), empty.clone()]; 14];
dp[0][1].insert(0, 1);
for _ in 0..4 {
let mut next = vec![[empty.clone(), empty.clone()]; 14];
for (cards, flats) in dp.iter().enumerate() {
for (flat, values) in flats.iter().enumerate() {
for (&value, &count) in values {
for (&(len, delta), &ways) in &buckets {
if cards + len <= 13 {
let flat = flat & usize::from(len >= 3);
*next[cards + len][flat].entry(value + delta).or_insert(0) +=
count * u128::from(ways);
}
}
}
}
}
dp = next;
}
let mut histogram = std::collections::BTreeMap::<i32, u128>::new();
for (flat, values) in dp[13].iter().enumerate() {
for (&value, &count) in values {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let total = value - 100 + 50 * flat as i32;
*histogram.entry(total).or_insert(0) += count;
}
}
let deals: u128 = histogram.values().sum();
assert_eq!(deals, 635_013_559_600);
assert_eq!(histogram.len(), 710);
let (&min, &min_count) = histogram.first_key_value().unwrap();
assert_eq!((min, min_count), (-50, 27_500_000));
assert_eq!(histogram.get(&-35), Some(&44_550_000));
assert_eq!(histogram.last_key_value(), Some((&3560, &8)));
assert_eq!(histogram.get(&1120), Some(&2_817_112_924));
let moment = |power: u32| {
histogram
.iter()
.map(|(&value, &count)| i128::from(value).pow(power) * i128::try_from(count).unwrap())
.sum::<i128>()
};
assert_eq!(moment(1), 690_414_769_219_640);
assert_eq!(moment(2), 892_257_036_126_722_600);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_eval_pair() {
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::new(KXXX, KXX, KXX, KXX);
assert_eq!(SimpleEvaluator(hcp::<u8>).eval_pair([HAND, HAND]), 24);
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn test_bumrap_plus_shortness() {
const A: Holding = Holding::from_bits_truncate(0b10000_0000_0000_00);
assert_ulps_eq!(BUMRAP_PLUS.0(A), 5.5);
assert_ulps_eq!(BUMRAP_PLUS.0(Holding::EMPTY), 3.0);
}