use super::{call, insert_all_seats};
use crate::bidding::constraint::{hcp, support};
use crate::bidding::{Rules, Trie};
use contract_bridge::auction::Call;
use contract_bridge::{Bid, Strain, Suit};
const fn step_below(suit: Suit) -> Option<Suit> {
match suit {
Suit::Clubs => None,
Suit::Diamonds => Some(Suit::Clubs),
Suit::Hearts => Some(Suit::Diamonds),
Suit::Spades => Some(Suit::Hearts),
}
}
fn overcall_advances(over: Suit, level: u8) -> Rules {
let o = Strain::from(over);
let mut rules = Rules::new()
.rule(Bid::new(4, o), 1.6, support(5..) & hcp(..6))
.rule(Bid::new(level + 1, o), 1.0, support(3..) & hcp(6..=9))
.rule(Call::Pass, 0.0, hcp(..6));
if let Some(below) = step_below(over) {
rules = rules.rule(
Bid::new(level + 1, Strain::from(below)),
1.5,
support(3..) & hcp(10..=12),
);
}
rules
}
fn completion(over: Suit, level: u8) -> Rules {
let o = Strain::from(over);
Rules::new()
.rule(Bid::new(4, o), 1.0, hcp(14..))
.rule(Bid::new(level + 1, o), 0.5, hcp(0..))
}
pub(super) fn register(book: &mut Trie) {
for theirs in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {
let opening = Bid::new(1, Strain::from(theirs));
let t = Strain::from(theirs);
for over in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {
let o = Strain::from(over);
if o == t {
continue;
}
let level = if o > t { 1 } else { 2 };
let overcall = call(level, o);
insert_all_seats(
book,
&[Call::Bid(opening), overcall, Call::Pass],
3,
overcall_advances(over, level),
);
if let Some(below) = step_below(over) {
let transfer = call(level + 1, Strain::from(below));
insert_all_seats(
book,
&[
Call::Bid(opening),
overcall,
Call::Pass,
transfer,
Call::Pass,
],
3,
completion(over, level),
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bidding::Rules;
use crate::bidding::context::Context;
use crate::bidding::trie::Classifier;
use contract_bridge::Hand;
use contract_bridge::auction::RelativeVulnerability;
fn best(rules: &Rules, auction: &[Call], hand: &str) -> Call {
let hand: Hand = hand.parse().expect("valid test hand");
let context = Context::new(RelativeVulnerability::NONE, auction);
let logits = rules.classify(hand, &context);
(&logits.0)
.into_iter()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("logits are never NaN"))
.map(|(call, _)| call)
.expect("array is never empty")
}
const ADVANCE: [Call; 3] = [call(1, Strain::Hearts), call(1, Strain::Spades), Call::Pass];
#[test]
fn limit_raise_transfers_below_partners_suit() {
let r = overcall_advances(Suit::Spades, 1);
assert_eq!(
best(&r, &ADVANCE, "KJ4.952.AQ32.J92"),
call(2, Strain::Hearts)
);
}
#[test]
fn simple_raise_stays_natural() {
let r = overcall_advances(Suit::Spades, 1);
assert_eq!(
best(&r, &ADVANCE, "KJ4.952.Q832.Q92"),
call(2, Strain::Spades)
);
}
#[test]
fn weak_hand_with_a_long_fit_jumps_to_game() {
let r = overcall_advances(Suit::Spades, 1);
assert_eq!(
best(&r, &ADVANCE, "KQ542.952.832.92"),
call(4, Strain::Spades)
);
}
#[test]
fn overcaller_completes_or_accepts() {
let r = completion(Suit::Spades, 1);
let auction = [
call(1, Strain::Hearts),
call(1, Strain::Spades),
Call::Pass,
call(2, Strain::Hearts),
Call::Pass,
];
assert_eq!(
best(&r, &auction, "AQJ52.K2.KQ32.92"),
call(4, Strain::Spades)
);
assert_eq!(
best(&r, &auction, "KQ542.K2.Q832.92"),
call(2, Strain::Spades)
);
}
}