use clap::Parser;
use contract_bridge::auction::Auction;
use contract_bridge::deck::full_deal;
use contract_bridge::{AbsoluteVulnerability, FullDeal, Seat, Suit};
use ddss::{NonEmptyStrainFlags, Solver};
use pons::american;
use pons::bidding::Family;
use pons::bidding::american::{set_responsive_overcall, set_responsive_takeout};
use pons::scoring::{final_contract, imps, ns_score_contract};
use rayon::prelude::*;
#[path = "../common/mod.rs"]
#[allow(dead_code)]
mod common;
use common::{bid_out, hand_hcp};
#[derive(Parser)]
struct Args {
#[arg(short, long, default_value = "200000")]
count: usize,
#[arg(short, long, default_value = "none")]
vulnerability: AbsoluteVulnerability,
#[arg(long, default_value = "takeout")]
conv: String,
#[arg(long, default_value = "false")]
filter: bool,
}
fn could_reach_overcalled_raise(deal: &FullDeal) -> bool {
Seat::ALL.iter().any(|&opener| {
if !(11..=21).contains(&hand_hcp(deal[opener])) {
return false;
}
let lho = Seat::ALL[(opener as usize + 1) % 4];
let partner = Seat::ALL[(opener as usize + 2) % 4];
let advancer = Seat::ALL[(opener as usize + 3) % 4];
let advancer_shaped = hand_hcp(deal[advancer]) >= 8
&& Suit::ASC
.iter()
.filter(|&&s| deal[advancer][s].len() >= 4)
.count()
>= 2;
hand_hcp(deal[lho]) >= 8 && hand_hcp(deal[partner]) >= 5 && advancer_shaped
})
}
fn configure(conv: &str, measured: bool) {
set_responsive_takeout(true);
set_responsive_overcall(false);
match conv {
"takeout" => set_responsive_takeout(measured),
"overcall" => set_responsive_overcall(measured),
other => panic!("unknown --conv {other:?} (use takeout or overcall)"),
}
}
#[allow(clippy::cast_precision_loss)]
fn main() {
let args = Args::parse();
let mut rng = rand::rng();
configure(&args.conv, false);
let baseline = american().against(Family::NATURAL);
configure(&args.conv, true);
let conv = american().against(Family::NATURAL);
let mut passing: Vec<FullDeal> = Vec::with_capacity(args.count);
let mut scanned = 0usize;
while passing.len() < args.count {
let deal = full_deal(&mut rng);
scanned += 1;
if args.filter && !could_reach_overcalled_raise(&deal) {
continue;
}
passing.push(deal);
}
eprintln!("scanned {scanned} for {} boards; bidding...", passing.len());
let vul = args.vulnerability;
let results: Vec<_> = passing
.par_iter()
.enumerate()
.map(|(i, &deal)| {
let dealer = Seat::ALL[i % 4];
let table_a = bid_out(&conv, &baseline, true, dealer, vul, &deal);
let table_b = bid_out(&conv, &baseline, false, dealer, vul, &deal);
let contracts = (
final_contract(&table_a, dealer),
final_contract(&table_b, dealer),
);
(deal, contracts, (table_a, table_b))
})
.collect();
let mut deals: Vec<FullDeal> = Vec::with_capacity(results.len());
let mut contracts = Vec::with_capacity(results.len());
let mut auctions: Vec<(Auction, Auction)> = Vec::with_capacity(results.len());
for (deal, c, a) in results {
deals.push(deal);
contracts.push(c);
auctions.push(a);
}
let divergent: Vec<usize> = (0..args.count)
.filter(|&i| contracts[i].0 != contracts[i].1)
.collect();
let solve_deals: Vec<FullDeal> = divergent.iter().map(|&i| deals[i]).collect();
let tables = Solver::lock().solve_deals(&solve_deals, NonEmptyStrainFlags::ALL);
let mut points = 0i64;
let mut total_imps = 0i64;
let mut worst: Vec<(i64, usize)> = Vec::new();
for (&i, table) in divergent.iter().zip(tables.iter()) {
let (contract_a, contract_b) = contracts[i];
let swing = ns_score_contract(contract_a, table, args.vulnerability)
- ns_score_contract(contract_b, table, args.vulnerability);
points += swing;
total_imps += imps(swing);
worst.push((imps(swing), i));
}
worst.sort_by_key(|w| w.0);
eprintln!(
"=== Worst 15 divergent boards for the {} responsive double ===",
args.conv
);
for &(imp, i) in worst.iter().take(15) {
let dealer = Seat::ALL[i % 4];
eprintln!(
"[{imp:+} IMP] dealer {dealer:?} {}\n A (conv NS): {} -> {:?}\n B (conv EW): {} -> {:?}",
deals[i], auctions[i].0, contracts[i].0, auctions[i].1, contracts[i].1,
);
}
println!(
"=== Responsive-double A/B ({} vs floor): {} boards, vulnerability {} ===",
args.conv, args.count, args.vulnerability,
);
if args.filter {
println!(
"(pre-filtered to plausible (1t)-action-(2t): kept {} of {scanned} dealt, {:.1}%)",
args.count,
100.0 * args.count as f64 / scanned.max(1) as f64,
);
}
println!(
"Divergent boards: {} of {} ({:.1}%)",
divergent.len(),
args.count,
100.0 * divergent.len() as f64 / args.count.max(1) as f64,
);
println!(
"{} responsive double vs floor: {points:+} points, {total_imps:+} IMPs ({:+.3} IMPs/filtered-board, {:+.3} IMPs/divergent)",
args.conv,
total_imps as f64 / args.count.max(1) as f64,
total_imps as f64 / divergent.len().max(1) as f64,
);
println!(
"Per raw deal: {:+.4} IMPs/board ({total_imps:+} IMPs over {scanned} dealt)",
total_imps as f64 / scanned.max(1) as f64,
);
}