use super::System;
use super::array::Logits;
use super::context::Context;
use super::trie::{Provenance, Trie};
use contract_bridge::Hand;
use contract_bridge::auction::{Call, RelativeVulnerability};
use core::ops::{Deref, DerefMut};
fn resolve(
trie: &Trie,
hand: Hand,
vul: RelativeVulnerability,
auction: &[Call],
) -> Option<Logits> {
let context = Context::new(vul, auction).with_prefixes(trie.common_prefixes(auction));
let (classifier, _) = trie.resolve(&context, auction)?;
Some(classifier.classify(hand, &context))
}
#[derive(Clone, Debug, Default)]
pub struct Constructive(pub Trie);
impl Constructive {
#[must_use]
pub const fn new() -> Self {
Self(Trie::new())
}
}
impl Deref for Constructive {
type Target = Trie;
fn deref(&self) -> &Trie {
&self.0
}
}
impl DerefMut for Constructive {
fn deref_mut(&mut self) -> &mut Trie {
&mut self.0
}
}
impl System for Constructive {
fn classify(&self, hand: Hand, vul: RelativeVulnerability, auction: &[Call]) -> Option<Logits> {
(Phase::of(auction) == Phase::Constructive)
.then(|| resolve(&self.0, hand, vul, auction))
.flatten()
}
}
#[derive(Clone, Debug, Default)]
pub struct Defensive(pub Trie);
impl Defensive {
#[must_use]
pub const fn new() -> Self {
Self(Trie::new())
}
}
impl Deref for Defensive {
type Target = Trie;
fn deref(&self) -> &Trie {
&self.0
}
}
impl DerefMut for Defensive {
fn deref_mut(&mut self) -> &mut Trie {
&mut self.0
}
}
impl System for Defensive {
fn classify(&self, hand: Hand, vul: RelativeVulnerability, auction: &[Call]) -> Option<Logits> {
(Phase::of(auction) == Phase::Defensive)
.then(|| resolve(&self.0, hand, vul, auction))
.flatten()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Phase {
Constructive,
Competitive,
Defensive,
}
impl Phase {
#[must_use]
pub fn of(auction: &[Call]) -> Self {
let Some(opening) = auction.iter().position(|&call| call != Call::Pass) else {
return Self::Constructive;
};
if opening % 2 != auction.len() % 2 {
return Self::Defensive;
}
let mut their_calls = auction[opening + 1..].iter().step_by(2);
if their_calls.any(|&call| call != Call::Pass) {
Self::Competitive
} else {
Self::Constructive
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Family(pub &'static str);
impl Family {
pub const NATURAL: Self = Self("natural");
pub const POLISH_CLUB: Self = Self("polish-club");
pub const STRONG_CLUB: Self = Self("strong-club");
pub const WEAK_NOTRUMP: Self = Self("weak-notrump");
}
impl Default for Family {
fn default() -> Self {
Self::NATURAL
}
}
#[derive(Clone, Debug, Default)]
pub struct Competitive(pub Trie);
impl Competitive {
#[must_use]
pub const fn new() -> Self {
Self(Trie::new())
}
}
impl Deref for Competitive {
type Target = Trie;
fn deref(&self) -> &Trie {
&self.0
}
}
impl DerefMut for Competitive {
fn deref_mut(&mut self) -> &mut Trie {
&mut self.0
}
}
impl System for Competitive {
fn classify(&self, hand: Hand, vul: RelativeVulnerability, auction: &[Call]) -> Option<Logits> {
(Phase::of(auction) == Phase::Competitive)
.then(|| resolve(&self.0, hand, vul, auction))
.flatten()
}
}
#[derive(Clone, Debug, Default)]
pub struct Pair {
pub family: Family,
pub constructive: Constructive,
pub competitive: Competitive,
pub defensive: Defensive,
competitive_vs: Vec<(Family, Competitive)>,
defensive_vs: Vec<(Family, Defensive)>,
}
impl Pair {
#[must_use]
pub const fn new(
family: Family,
constructive: Constructive,
competitive: Competitive,
defensive: Defensive,
) -> Self {
Self {
family,
constructive,
competitive,
defensive,
competitive_vs: Vec::new(),
defensive_vs: Vec::new(),
}
}
#[must_use]
pub fn competitive_vs(mut self, them: Family, book: Competitive) -> Self {
self.competitive_vs.push((them, book));
self
}
#[must_use]
pub fn defensive_vs(mut self, them: Family, book: Defensive) -> Self {
self.defensive_vs.push((them, book));
self
}
#[must_use]
pub fn against(&self, them: Family) -> Stance {
let competitive = self
.competitive_vs
.iter()
.find(|entry| entry.0 == them)
.map_or(&self.competitive, |entry| &entry.1);
let defensive = self
.defensive_vs
.iter()
.find(|entry| entry.0 == them)
.map_or(&self.defensive, |entry| &entry.1);
let mut bound = competitive.0.clone();
let collisions = bound.merge(self.constructive.0.clone());
debug_assert!(
collisions.is_empty(),
"competitive and constructive books collide at {collisions:?}"
);
Stance {
constructive: self.constructive.0.clone(),
competitive: bound,
defensive: defensive.0.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Stance {
constructive: Trie,
competitive: Trie,
defensive: Trie,
}
impl Stance {
fn trie_for(&self, auction: &[Call]) -> &Trie {
match Phase::of(auction) {
Phase::Constructive => &self.constructive,
Phase::Competitive => &self.competitive,
Phase::Defensive => &self.defensive,
}
}
#[must_use]
pub fn classify_with_provenance(
&self,
hand: Hand,
vul: RelativeVulnerability,
auction: &[Call],
) -> Option<(Logits, Provenance)> {
let trie = self.trie_for(auction);
let context = Context::new(vul, auction).with_prefixes(trie.common_prefixes(auction));
let (classifier, provenance) = trie.resolve(&context, auction)?;
Some((classifier.classify(hand, &context), provenance))
}
}
impl System for Stance {
fn classify(&self, hand: Hand, vul: RelativeVulnerability, auction: &[Call]) -> Option<Logits> {
resolve(self.trie_for(auction), hand, vul, auction)
}
}
#[cfg(test)]
mod tests {
use super::Phase;
use contract_bridge::auction::Call;
use contract_bridge::{Bid, Strain};
const fn bid(level: u8, strain: Strain) -> Call {
Call::Bid(Bid::new(level, strain))
}
const P: Call = Call::Pass;
const ONE_HEART: Call = bid(1, Strain::Hearts);
const ONE_SPADE: Call = bid(1, Strain::Spades);
const TWO_CLUBS: Call = bid(2, Strain::Clubs);
const TWO_HEARTS: Call = bid(2, Strain::Hearts);
const TWO_SPADES: Call = bid(2, Strain::Spades);
#[test]
fn test_phase_before_any_opening() {
assert_eq!(Phase::of(&[]), Phase::Constructive);
assert_eq!(Phase::of(&[P]), Phase::Constructive);
assert_eq!(Phase::of(&[P, P, P]), Phase::Constructive);
assert_eq!(Phase::of(&[P, P, P, P]), Phase::Constructive);
}
#[test]
fn test_phase_when_we_opened_undisturbed() {
assert_eq!(Phase::of(&[ONE_HEART, P]), Phase::Constructive);
assert_eq!(
Phase::of(&[ONE_HEART, P, TWO_HEARTS, P]),
Phase::Constructive
);
assert_eq!(Phase::of(&[P, P, ONE_SPADE, P]), Phase::Constructive);
}
#[test]
fn test_phase_when_they_intervened() {
assert_eq!(Phase::of(&[ONE_HEART, TWO_CLUBS]), Phase::Competitive);
assert_eq!(Phase::of(&[ONE_HEART, Call::Double]), Phase::Competitive);
assert_eq!(Phase::of(&[P, ONE_HEART, Call::Double]), Phase::Competitive);
assert_eq!(
Phase::of(&[ONE_HEART, P, TWO_HEARTS, TWO_SPADES]),
Phase::Competitive
);
assert_eq!(
Phase::of(&[ONE_SPADE, Call::Double, Call::Redouble, P]),
Phase::Competitive
);
}
#[test]
fn test_phase_when_they_opened() {
assert_eq!(Phase::of(&[ONE_HEART]), Phase::Defensive);
assert_eq!(Phase::of(&[P, P, ONE_SPADE]), Phase::Defensive);
assert_eq!(Phase::of(&[ONE_HEART, TWO_CLUBS, P]), Phase::Defensive);
assert_eq!(
Phase::of(&[ONE_HEART, P, TWO_HEARTS, TWO_SPADES, P]),
Phase::Defensive
);
}
}