use contract_bridge::auction::{Auction, Call};
use contract_bridge::eval::hcp as holding_hcp;
use contract_bridge::{AbsoluteVulnerability, Contract, FullDeal, Hand, Seat, Suit};
use ddss::{NonEmptyStrainFlags, Solver, TrickCountTable};
use pons::bidding::context::relative;
use pons::bidding::{Stance, System};
use pons::scoring::imps;
pub fn hand_hcp(hand: Hand) -> u8 {
Suit::ASC.iter().map(|&s| holding_hcp::<u8>(hand[s])).sum()
}
pub const fn seat_to_act(dealer: Seat, len: usize) -> Seat {
Seat::ALL[(dealer as usize + len) % 4]
}
pub fn next_call(
stance: &Stance,
hand: Hand,
dealer: Seat,
vul: AbsoluteVulnerability,
auction: &Auction,
) -> Call {
let seat = seat_to_act(dealer, auction.len());
let Some(logits) = stance.classify(hand, relative(vul, seat), auction) else {
return Call::Pass;
};
let mut scored: Vec<(Call, f32)> = logits
.iter()
.map(|(call, &logit)| (call, logit))
.filter(|&(_, logit)| logit.is_finite())
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("logits are never NaN"));
scored
.into_iter()
.map(|(call, _)| call)
.find(|&call| auction.can_push(call).is_ok())
.unwrap_or(Call::Pass)
}
pub fn bid_out(
conv: &Stance,
baseline: &Stance,
conv_is_ns: bool,
dealer: Seat,
vul: AbsoluteVulnerability,
deal: &FullDeal,
) -> Auction {
let mut auction = Auction::new();
while !auction.has_ended() {
let seat = seat_to_act(dealer, auction.len());
let seat_is_ns = matches!(seat, Seat::North | Seat::South);
let stance = if seat_is_ns == conv_is_ns {
conv
} else {
baseline
};
auction.push(next_call(stance, deal[seat], dealer, vul, &auction));
}
auction
}
pub fn bid_uncontested(
stance: &Stance,
dealer: Seat,
vul: AbsoluteVulnerability,
deal: &FullDeal,
) -> Auction {
let mut auction = Auction::new();
while !auction.has_ended() {
let seat = seat_to_act(dealer, auction.len());
let call = if matches!(seat, Seat::East | Seat::West) {
Call::Pass
} else {
next_call(stance, deal[seat], dealer, vul, &auction)
};
auction.push(call);
}
auction
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Board {
pub deal: FullDeal,
pub dealer: Seat,
pub table_a: Auction,
pub table_b: Auction,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dump {
pub our_label: String,
pub their_label: String,
pub vulnerability: AbsoluteVulnerability,
#[cfg_attr(feature = "serde", serde(default))]
pub seed: Option<u64>,
#[cfg_attr(feature = "serde", serde(default))]
pub gen_args: Vec<String>,
pub boards: Vec<Board>,
}
#[allow(clippy::cast_precision_loss)]
pub fn mean_with_ci(values: &[i64]) -> (f64, f64) {
let n = values.len();
if n < 2 {
return (0.0, 0.0);
}
let mean = values.iter().sum::<i64>() as f64 / n as f64;
let variance = values
.iter()
.map(|&v| {
let d = v as f64 - mean;
d * d
})
.sum::<f64>()
/ (n - 1) as f64;
(mean, 1.96 * (variance / n as f64).sqrt())
}
pub struct Scored {
pub divergent: Vec<usize>,
pub board_imps: Vec<i64>,
pub swings: Vec<(usize, i64, i64)>,
pub total_points: i64,
pub total_imps: i64,
pub tables: Vec<TrickCountTable>,
}
pub type Reached = Option<(Contract, Seat)>;
pub fn score_boards(
contracts: &[(Reached, Reached)],
deals: &[FullDeal],
vul: AbsoluteVulnerability,
scorer: impl Fn(Reached, &TrickCountTable, AbsoluteVulnerability) -> i64,
) -> Scored {
let divergent: Vec<usize> = (0..contracts.len())
.filter(|&index| contracts[index].0 != contracts[index].1)
.collect();
let solve: Vec<FullDeal> = divergent.iter().map(|&index| deals[index]).collect();
let tables = Solver::lock().solve_deals(&solve, NonEmptyStrainFlags::ALL);
let mut total_points = 0i64;
let mut board_imps = vec![0i64; contracts.len()];
let mut swings: Vec<(usize, i64, i64)> = Vec::with_capacity(divergent.len());
for (&index, table) in divergent.iter().zip(tables.iter()) {
let (contract_a, contract_b) = contracts[index];
let swing = scorer(contract_a, table, vul) - scorer(contract_b, table, vul);
total_points += swing;
board_imps[index] = imps(swing);
swings.push((index, swing, imps(swing)));
}
let total_imps = board_imps.iter().sum();
Scored {
divergent,
board_imps,
swings,
total_points,
total_imps,
tables,
}
}