use super::{call, insert_uncontested, slam};
use crate::bidding::constraint::{
Cons, Constraint, balanced, described, hcp, len, point_count, points, stopper_in, top_honors,
};
use crate::bidding::{Alert, Context, Rules, Trie};
use contract_bridge::auction::Call;
use contract_bridge::{Bid, Hand, Holding, Rank, Strain, Suit};
use std::cell::Cell;
pub const PUPPET: Alert = Alert("puppet");
pub const EUROPEAN: Alert = Alert("european");
const STAYMAN: Alert = Alert("stayman");
const JACOBY: Alert = Alert("jacoby-transfer");
const BOTH_MAJORS: Alert = Alert("both-majors");
const TEXAS: Alert = Alert("texas");
const SMOLEN: Alert = Alert("smolen");
const SPLINTER: Alert = Alert("splinter");
const SLAM_TRY: Alert = Alert("slam-try");
const INV_5CARD: Alert = Alert("inv-5card-major");
thread_local! {
static NOTRUMP_MINORS: Cell<Alert> = const { Cell::new(PUPPET) };
}
pub fn set_notrump_minors(variant: Alert) {
NOTRUMP_MINORS.with(|cell| cell.set(variant));
}
pub(crate) fn notrump_minors() -> Alert {
NOTRUMP_MINORS.with(Cell::get)
}
#[must_use]
pub fn notrump_responses() -> Rules {
let direct_4m_max: u8 = if texas_slam_drive() { 15 } else { 18 };
let prefer_longer = transfer_longer_major();
let head = if prefer_longer {
Rules::new().rule(
Bid::new(2, Strain::Diamonds),
2.0,
len(Suit::Hearts, 5..)
& (len(Suit::Spades, ..4)
| (len(Suit::Spades, 4..=4) & hcp(..9))
| (len(Suit::Spades, 5..) & longer_major(Suit::Hearts, Suit::Spades))
| (equal_majors() & points(..8))
| major_splinter_reroute(Suit::Hearts)),
)
} else {
Rules::new().rule(
Bid::new(2, Strain::Diamonds),
2.0,
len(Suit::Hearts, 5..)
& (len(Suit::Spades, ..4) | hcp(..9) | major_splinter_reroute(Suit::Hearts)),
)
}
.alert(JACOBY);
let head = match (prefer_longer, invitational_5card_majors()) {
(true, true) => head.rule(
Bid::new(2, Strain::Hearts),
2.0,
len(Suit::Spades, 5..)
& (len(Suit::Hearts, ..4)
| (len(Suit::Hearts, 4..=4) & (hcp(..8) | len(Suit::Spades, 6..)))
| (len(Suit::Hearts, 5..) & longer_major(Suit::Spades, Suit::Hearts))
| (equal_majors() & slam_55_reroute())),
),
(true, false) => head.rule(
Bid::new(2, Strain::Hearts),
2.0,
len(Suit::Spades, 5..)
& (len(Suit::Hearts, ..4)
| (len(Suit::Hearts, 4..=4) & hcp(..9))
| (len(Suit::Hearts, 5..) & longer_major(Suit::Spades, Suit::Hearts))
| (equal_majors() & slam_55_reroute())
| major_splinter_reroute(Suit::Spades)),
),
(false, true) => head.rule(
Bid::new(2, Strain::Hearts),
2.0,
len(Suit::Spades, 5..)
& (len(Suit::Hearts, ..4) | hcp(..8) | len(Suit::Spades, 6..) | slam_55_reroute()),
),
(false, false) => head.rule(
Bid::new(2, Strain::Hearts),
2.0,
len(Suit::Spades, 5..)
& (len(Suit::Hearts, ..4)
| hcp(..9)
| slam_55_reroute()
| major_splinter_reroute(Suit::Spades)),
),
}
.alert(JACOBY);
head
.rule(
Bid::new(3, Strain::Diamonds),
2.1,
len(Suit::Hearts, 5..)
& len(Suit::Spades, 5..)
& points(8..)
& described(
"equal lengths only under the longer-major discipline",
move |hand: Hand, _: &Context<'_>| {
!prefer_longer || hand[Suit::Hearts].len() == hand[Suit::Spades].len()
},
)
& described(
"both-majors 3♦ capped at minimum game force when the slam reroute is on",
|hand: Hand, _: &Context<'_>| {
!transfer_gf_majors() || usize::from(point_count(hand)) <= 16
},
),
)
.alert(BOTH_MAJORS)
.rule(
Bid::new(4, Strain::Clubs),
2.5,
len(Suit::Hearts, 6..)
& len(Suit::Spades, ..5)
& texas_strength_gate(Suit::Hearts)
& not_major_splinter_slam(Suit::Hearts),
)
.alert(TEXAS)
.rule(
Bid::new(4, Strain::Diamonds),
2.5,
len(Suit::Spades, 6..)
& len(Suit::Hearts, ..5)
& texas_strength_gate(Suit::Spades)
& not_major_splinter_slam(Suit::Spades),
)
.alert(TEXAS)
.rule(
Bid::new(4, Strain::Hearts),
2.6,
len(Suit::Hearts, 6..)
& len(Suit::Spades, ..5)
& hcp(15..=direct_4m_max)
& not_major_splinter_slam(Suit::Hearts),
)
.alert(TEXAS)
.rule(
Bid::new(4, Strain::Spades),
2.6,
len(Suit::Spades, 6..)
& len(Suit::Hearts, ..5)
& hcp(15..=direct_4m_max)
& not_major_splinter_slam(Suit::Spades),
)
.alert(TEXAS)
.rule(
Bid::new(2, Strain::Clubs),
1.5,
(len(Suit::Hearts, 4..=4) | len(Suit::Spades, 4..=4)) & hcp(8..) & !flat_4333(),
)
.alert(STAYMAN)
.rule(
Bid::new(4, Strain::Notrump),
1.2,
hcp(16..=17) & len(Suit::Hearts, ..5) & len(Suit::Spades, ..5),
)
.rule(
Bid::new(3, Strain::Notrump),
1.0,
hcp(9..) & len(Suit::Hearts, ..5) & len(Suit::Spades, ..5),
)
.chain(long_minor_force_rule())
.rule(
Call::Pass,
0.0,
(hcp(..8) & len(Suit::Hearts, ..5) & len(Suit::Spades, ..5))
| (hcp(8..=8) & flat_4333()),
)
.chain(puppet_minors())
.chain(european_minors())
.chain(garbage_stayman_rule())
.chain(crawling_stayman_rule())
.gated(move |alert| alert != dormant_minors())
}
fn garbage_stayman_rule() -> Rules {
if !garbage_stayman() {
return Rules::new();
}
Rules::new()
.rule(
Bid::new(2, Strain::Clubs),
1.5,
(len(Suit::Hearts, 4..) | len(Suit::Spades, 4..))
& len(Suit::Hearts, 3..)
& len(Suit::Spades, 3..)
& len(Suit::Clubs, ..3)
& len(Suit::Diamonds, 3..)
& hcp(..5)
& !flat_4333(),
)
.alert(STAYMAN)
.rule(
Bid::new(2, Strain::Clubs),
1.5,
(len(Suit::Hearts, 4..) | len(Suit::Spades, 4..))
& len(Suit::Hearts, 3..)
& len(Suit::Spades, 3..)
& len(Suit::Clubs, ..3)
& len(Suit::Diamonds, 4..)
& hcp(5..8)
& !flat_4333(),
)
.alert(STAYMAN)
}
fn crawling_stayman_rule() -> Rules {
if !crawling_stayman() {
return Rules::new();
}
Rules::new()
.rule(
Bid::new(2, Strain::Clubs),
1.5,
len(Suit::Hearts, 4..=4)
& len(Suit::Spades, 4..=4)
& len(Suit::Diamonds, ..=1)
& hcp(..8),
)
.alert(STAYMAN)
}
fn long_minor_force_rule() -> Rules {
if !long_minor_force() {
return Rules::new();
}
Rules::new().rule(
Bid::new(3, Strain::Notrump),
1.4,
hcp(8..=8)
& len(Suit::Hearts, ..4)
& len(Suit::Spades, ..4)
& ((len(Suit::Clubs, 6..) & (len(Suit::Clubs, 7..) | top_honors(Suit::Clubs, 2..)))
| (len(Suit::Diamonds, 6..)
& (len(Suit::Diamonds, 7..) | top_honors(Suit::Diamonds, 2..)))),
)
}
fn dormant_minors() -> Alert {
if notrump_minors() == PUPPET {
EUROPEAN
} else {
PUPPET
}
}
fn puppet_minors() -> Rules {
Rules::new()
.rule(
Bid::new(2, Strain::Spades),
1.3,
len(Suit::Clubs, 6..)
| (hcp(8..=8)
& balanced()
& len(Suit::Hearts, ..4)
& len(Suit::Spades, ..4)
& !flat_4333()),
)
.alert(PUPPET)
.rule(
Bid::new(2, Strain::Notrump),
1.3,
len(Suit::Diamonds, 6..) | (len(Suit::Diamonds, 5..) & len(Suit::Clubs, 4..)),
)
.alert(PUPPET)
.rule(
Bid::new(3, Strain::Clubs),
1.6,
balanced()
& hcp(9..=15)
& (len(Suit::Hearts, 3..=3) | len(Suit::Spades, 3..=3))
& len(Suit::Hearts, ..5)
& len(Suit::Spades, ..5)
& !flat_4333(),
)
.alert(PUPPET)
}
fn european_minors() -> Rules {
Rules::new()
.rule(Bid::new(2, Strain::Spades), 1.3, len(Suit::Clubs, 6..))
.alert(EUROPEAN)
.rule(
Bid::new(2, Strain::Notrump),
1.3,
hcp(8..=8)
& balanced()
& len(Suit::Hearts, ..4)
& len(Suit::Spades, ..4)
& !flat_4333(),
)
.alert(EUROPEAN)
.rule(
Bid::new(3, Strain::Clubs),
1.3,
len(Suit::Diamonds, 6..) | (len(Suit::Diamonds, 5..) & len(Suit::Clubs, 4..)),
)
.alert(EUROPEAN)
}
pub(super) fn stayman_answers() -> Rules {
Rules::new()
.rule(Bid::new(2, Strain::Hearts), 1.0, len(Suit::Hearts, 4..))
.rule(
Bid::new(2, Strain::Spades),
1.0,
len(Suit::Spades, 4..) & len(Suit::Hearts, ..4),
)
.rule(
Bid::new(2, Strain::Diamonds),
0.5,
len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
}
fn stayman_answers_uncontested() -> Rules {
let mut rules = Rules::new();
if stayman_both_majors() {
let both = len(Suit::Hearts, 4..) & len(Suit::Spades, 4..);
rules = rules
.rule(Bid::new(2, Strain::Notrump), 1.1, both & hcp(16..))
.alert(BOTH_MAJORS);
}
if stayman_5card_max() {
rules = rules
.rule(
Bid::new(3, Strain::Hearts),
1.1,
len(Suit::Hearts, 5..) & hcp(16..),
)
.rule(
Bid::new(3, Strain::Spades),
1.1,
len(Suit::Spades, 5..) & hcp(16..),
);
}
rules.chain(stayman_answers())
}
fn both_majors_max_responder() -> Rules {
Rules::new()
.rule(
Bid::new(3, Strain::Diamonds),
1.0,
described("spades > hearts", |hand: Hand, _: &Context<'_>| {
hand[Suit::Spades].len() > hand[Suit::Hearts].len()
}),
)
.alert(BOTH_MAJORS)
.rule(
Bid::new(3, Strain::Clubs),
1.0,
described("hearts ≥ spades", |hand: Hand, _: &Context<'_>| {
hand[Suit::Hearts].len() >= hand[Suit::Spades].len()
}),
)
.alert(BOTH_MAJORS)
}
fn both_majors_relay_complete(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::from(major)), 1.0, hcp(0..))
.alert(BOTH_MAJORS)
}
fn both_majors_relay_placement(major: Suit) -> Rules {
let other = match major {
Suit::Spades => Suit::Hearts,
_ => Suit::Spades,
};
Rules::new().rule(
Bid::new(4, Strain::from(major)),
1.3,
described("game values for the agreed major", move |hand: Hand, _| {
let double_fit = usize::from(hand[other].len() >= 4);
fit_value(hand, major) + double_fit >= 8
}),
)
}
fn fit_value(hand: Hand, major: Suit) -> usize {
usize::from(point_count(hand)) + hand[major].len().saturating_sub(4)
}
fn five_card_max_rebid(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::from(major)), 1.3, len(major, 3..))
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(0..))
}
thread_local! {
static TRANSFER_SUPER_ACCEPT: Cell<bool> = const { Cell::new(false) };
}
pub fn set_transfer_super_accept(on: bool) {
TRANSFER_SUPER_ACCEPT.with(|cell| cell.set(on));
}
pub(super) fn transfer_super_accept() -> bool {
TRANSFER_SUPER_ACCEPT.with(Cell::get)
}
thread_local! {
static TRANSFER_SLAM_TRY: Cell<bool> = const { Cell::new(true) };
}
pub fn set_transfer_slam_try(on: bool) {
TRANSFER_SLAM_TRY.with(|cell| cell.set(on));
}
fn transfer_slam_try() -> bool {
TRANSFER_SLAM_TRY.with(Cell::get)
}
thread_local! {
static TEXAS_SLAM_DRIVE: Cell<bool> = const { Cell::new(true) };
}
pub fn set_texas_slam_drive(on: bool) {
TEXAS_SLAM_DRIVE.with(|cell| cell.set(on));
}
fn texas_slam_drive() -> bool {
TEXAS_SLAM_DRIVE.with(Cell::get)
}
thread_local! {
static TRANSFER_GF_MAJORS: Cell<bool> = const { Cell::new(true) };
static MINOR_MIN_TO_3NT: Cell<bool> = const { Cell::new(false) };
static TRANSFER_GF_HEARTS: Cell<bool> = const { Cell::new(true) };
}
pub fn set_transfer_gf_majors(on: bool) {
TRANSFER_GF_MAJORS.with(|cell| cell.set(on));
}
fn transfer_gf_majors() -> bool {
TRANSFER_GF_MAJORS.with(Cell::get)
}
pub fn set_minor_min_to_3nt(on: bool) {
MINOR_MIN_TO_3NT.with(|cell| cell.set(on));
}
fn minor_min_to_3nt() -> bool {
MINOR_MIN_TO_3NT.with(Cell::get)
}
pub fn set_transfer_gf_hearts(on: bool) {
TRANSFER_GF_HEARTS.with(|cell| cell.set(on));
}
fn transfer_gf_hearts() -> bool {
transfer_gf_majors() && TRANSFER_GF_HEARTS.with(Cell::get)
}
fn texas_slam_drive_rebid() -> Rules {
if !texas_slam_drive() {
return Rules::new();
}
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.4, hcp(16..))
.alert(slam::RKCB)
}
thread_local! {
static GARBAGE_STAYMAN: Cell<bool> = const { Cell::new(true) };
static STAYMAN_BOTH_MAJORS: Cell<bool> = const { Cell::new(true) };
static STAYMAN_5CARD_MAX: Cell<bool> = const { Cell::new(true) };
static INVITATIONAL_5CARD_MAJORS: Cell<bool> = const { Cell::new(true) };
static CRAWLING_STAYMAN: Cell<bool> = const { Cell::new(true) };
static TRANSFER_LONGER_MAJOR: Cell<bool> = const { Cell::new(true) };
static STAYMAN_CUE_CONTINUATION: Cell<bool> = const { Cell::new(true) };
static STAYMAN_MINOR_SLAM_TRY: Cell<bool> = const { Cell::new(true) };
static TEXAS_GAME_FLOOR: Cell<u8> = const { Cell::new(14) };
static SIXCARD_INVITE_FLOOR: Cell<u8> = const { Cell::new(13) };
static SIXCARD_ACCEPT_FLOOR: Cell<u8> = const { Cell::new(18) };
static LONG_MINOR_FORCE: Cell<bool> = const { Cell::new(false) };
}
pub fn set_garbage_stayman(on: bool) {
GARBAGE_STAYMAN.with(|cell| cell.set(on));
}
pub fn set_stayman_both_majors(on: bool) {
STAYMAN_BOTH_MAJORS.with(|cell| cell.set(on));
}
pub fn set_stayman_5card_max(on: bool) {
STAYMAN_5CARD_MAX.with(|cell| cell.set(on));
}
pub(crate) fn garbage_stayman() -> bool {
GARBAGE_STAYMAN.with(Cell::get)
}
pub fn set_long_minor_force(on: bool) {
LONG_MINOR_FORCE.with(|cell| cell.set(on));
}
fn long_minor_force() -> bool {
LONG_MINOR_FORCE.with(Cell::get)
}
fn stayman_both_majors() -> bool {
STAYMAN_BOTH_MAJORS.with(Cell::get)
}
fn stayman_5card_max() -> bool {
STAYMAN_5CARD_MAX.with(Cell::get)
}
pub fn set_invitational_5card_majors(on: bool) {
INVITATIONAL_5CARD_MAJORS.with(|cell| cell.set(on));
}
fn invitational_5card_majors() -> bool {
INVITATIONAL_5CARD_MAJORS.with(Cell::get)
}
pub fn set_transfer_longer_major(on: bool) {
TRANSFER_LONGER_MAJOR.with(|cell| cell.set(on));
}
fn transfer_longer_major() -> bool {
TRANSFER_LONGER_MAJOR.with(Cell::get)
}
pub fn set_crawling_stayman(on: bool) {
CRAWLING_STAYMAN.with(|cell| cell.set(on));
}
pub(crate) fn crawling_stayman() -> bool {
CRAWLING_STAYMAN.with(Cell::get)
}
pub fn set_stayman_cue_continuation(on: bool) {
STAYMAN_CUE_CONTINUATION.with(|cell| cell.set(on));
}
fn stayman_cue_continuation() -> bool {
STAYMAN_CUE_CONTINUATION.with(Cell::get)
}
pub fn set_stayman_minor_slam_try(on: bool) {
STAYMAN_MINOR_SLAM_TRY.with(|cell| cell.set(on));
}
fn stayman_minor_slam_try() -> bool {
STAYMAN_MINOR_SLAM_TRY.with(Cell::get)
}
pub fn set_texas_game_floor(floor: u8) {
TEXAS_GAME_FLOOR.with(|cell| cell.set(floor));
}
fn texas_game_floor() -> usize {
usize::from(TEXAS_GAME_FLOOR.with(Cell::get))
}
fn texas_strength_gate(major: Suit) -> Cons<impl Constraint + Clone> {
let floor = texas_game_floor();
described("six-card-major game blast", move |hand: Hand, _| {
usize::from(point_count(hand)) + hand[major].len() >= floor
})
}
pub fn set_sixcard_invite_floor(floor: u8) {
SIXCARD_INVITE_FLOOR.with(|cell| cell.set(floor));
}
pub fn set_sixcard_accept_floor(floor: u8) {
SIXCARD_ACCEPT_FLOOR.with(|cell| cell.set(floor));
}
fn sixcard_invite_floor() -> usize {
usize::from(SIXCARD_INVITE_FLOOR.with(Cell::get))
}
fn sixcard_accept_floor() -> usize {
usize::from(SIXCARD_ACCEPT_FLOOR.with(Cell::get))
}
fn sixcard_invite_active() -> bool {
sixcard_invite_floor() < texas_game_floor()
}
pub(super) fn complete_transfer(into: Suit) -> Rules {
let mut rules = Rules::new();
if transfer_super_accept() {
rules = rules.rule(
Bid::new(3, Strain::from(into)),
1.5,
len(into, 4..) & hcp(17..),
);
}
rules.rule(Bid::new(2, Strain::from(into)), 1.0, hcp(0..))
}
fn complete_texas(into: Suit) -> Rules {
Rules::new().rule(Bid::new(4, Strain::from(into)), 1.0, hcp(0..))
}
fn sixcard_invite_rebid(major: Suit) -> Rules {
if !sixcard_invite_active() {
return Rules::new();
}
let floor = sixcard_invite_floor();
Rules::new().rule(
Bid::new(3, Strain::from(major)),
1.3,
len(major, 6..)
& len(other_major(major), ..5)
& described("six-card invitational value", move |hand: Hand, _| {
usize::from(point_count(hand)) + hand[major].len() >= floor
}),
)
}
fn accept_sixcard_invitation(major: Suit) -> Rules {
let floor = sixcard_accept_floor();
Rules::new()
.rule(
Bid::new(4, Strain::from(major)),
1.0,
described("accept six-card invite", move |hand: Hand, _| {
usize::from(point_count(hand)) + hand[major].len() >= floor
}),
)
.rule(Call::Pass, 0.0, hcp(0..))
}
fn slam_try_answer() -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, hcp(17..))
.alert(slam::RKCB)
.rule(Call::Pass, 0.0, hcp(..17))
}
const fn other_major(major: Suit) -> Suit {
match major {
Suit::Hearts => Suit::Spades,
_ => Suit::Hearts,
}
}
fn control_in(suit: Suit) -> Cons<impl Constraint + Clone> {
described(
format!("control in {suit}"),
move |hand: Hand, _: &Context<'_>| {
let holding = hand[suit];
holding.contains(Rank::A) || holding.contains(Rank::K)
},
)
}
fn stayman_major_rebid(major: Suit) -> Rules {
let other = Strain::from(other_major(major));
let strain = Strain::from(major);
let reroute = invitational_5card_majors() && major == Suit::Hearts;
let spade_cap = if reroute {
len(Suit::Spades, ..5)
} else {
len(Suit::Spades, ..14)
};
let mut rules = Rules::new();
if reroute {
rules = rules.rule(
Bid::new(2, Strain::Spades),
1.3,
len(Suit::Spades, 5..) & hcp(8..),
);
}
let rules = rules
.rule(
Bid::new(3, other),
1.4,
len(major, 4..) & hcp(9..) & (balanced() | hcp(16..)) & spade_cap.clone(),
)
.alert(SLAM_TRY)
.rule(
Bid::new(4, strain),
1.3,
len(major, 4..)
& described("game value for the fit", move |hand: Hand, _| {
fit_value(hand, major) >= 9
})
& spade_cap.clone(),
)
.rule(
Bid::new(3, strain),
1.2,
len(major, 4..)
& described("invitational value for the fit", move |hand: Hand, _| {
fit_value(hand, major) == 8
})
& spade_cap.clone(),
)
.rule(
Bid::new(4, Strain::Notrump),
1.2,
len(major, ..4) & hcp(16..=17),
)
.rule(
Bid::new(3, Strain::Notrump),
1.0,
len(major, ..4) & hcp(9..),
)
.rule(
Bid::new(2, Strain::Notrump),
1.0,
len(major, ..4) & hcp(8..=8),
);
if stayman_minor_slam_try() {
rules
.rule(
Bid::new(3, Strain::Clubs),
1.25,
len(Suit::Clubs, 5..) & hcp(14..) & len(major, ..4),
)
.rule(
Bid::new(3, Strain::Diamonds),
1.25,
len(Suit::Diamonds, 5..) & hcp(14..) & len(major, ..4),
)
} else {
rules
}
}
fn flat_4333() -> Cons<impl Constraint + Clone> {
balanced()
& len(Suit::Clubs, 3..)
& len(Suit::Diamonds, 3..)
& len(Suit::Hearts, 3..)
& len(Suit::Spades, 3..)
}
fn stayman_slam_try_answer(major: Suit) -> Rules {
let mut rules = Rules::new().rule(Bid::new(3, Strain::Notrump), 1.4, flat_4333());
let mut weight = 1.3;
for cue in [Suit::Clubs, Suit::Diamonds, Suit::Hearts] {
if Strain::from(cue) < Strain::from(major) {
rules = rules.rule(
Bid::new(4, Strain::from(cue)),
weight,
hcp(17..) & control_in(cue),
);
weight -= 0.05;
}
}
rules.rule(Bid::new(4, Strain::from(major)), 1.0, hcp(0..))
}
fn stayman_cue_rebid(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.2, hcp(14..))
.alert(slam::RKCB)
.rule(Bid::new(4, Strain::from(major)), 1.0, hcp(0..))
}
fn stayman_minor_answer(minor: Suit) -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::from(minor)),
1.3,
len(minor, 4..) & hcp(16..),
)
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(0..))
}
fn stayman_minor_slam_rkcb() -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, hcp(0..))
.alert(slam::RKCB)
}
fn transfer_slam_try_rebid(major: Suit) -> Rules {
if !transfer_slam_try() {
return Rules::new();
}
if (major == Suit::Spades && transfer_gf_majors())
|| (major == Suit::Hearts && transfer_gf_hearts())
{
return Rules::new();
}
Rules::new()
.rule(
Bid::new(3, Strain::from(other_major(major))),
1.4,
len(major, 5..) & len(other_major(major), ..4) & hcp(16..),
)
.alert(SLAM_TRY)
}
fn transfer_spade_gf_rebid() -> Rules {
if !transfer_gf_majors() {
return Rules::new();
}
let minor_floor: u8 = if minor_min_to_3nt() { 17 } else { 10 };
Rules::new()
.rule(
Bid::new(3, Strain::Hearts),
1.5,
len(Suit::Hearts, 5..) & points(17..),
)
.rule(
Bid::new(3, Strain::Clubs),
1.45,
len(Suit::Clubs, 4..) & len(Suit::Hearts, ..4) & points(minor_floor..),
)
.rule(
Bid::new(3, Strain::Diamonds),
1.45,
len(Suit::Diamonds, 4..) & len(Suit::Hearts, ..4) & points(minor_floor..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.4,
len(Suit::Spades, ..6)
& len(Suit::Hearts, ..4)
& len(Suit::Clubs, ..4)
& len(Suit::Diamonds, ..4)
& points(10..)
& hcp(..16),
)
.rule(
Bid::new(4, Strain::Notrump),
1.4,
len(Suit::Spades, 5..)
& len(Suit::Hearts, ..4)
& len(Suit::Clubs, ..4)
& len(Suit::Diamonds, ..4)
& hcp(16..),
)
.alert(SLAM_TRY)
.rule(
Bid::new(4, Strain::Clubs),
1.55,
len(Suit::Spades, 6..) & splinter_short(Suit::Clubs) & points(16..),
)
.alert(SPLINTER)
.rule(
Bid::new(4, Strain::Diamonds),
1.55,
len(Suit::Spades, 6..) & splinter_short(Suit::Diamonds) & points(16..),
)
.alert(SPLINTER)
.rule(
Bid::new(4, Strain::Hearts),
1.55,
len(Suit::Spades, 6..) & splinter_short(Suit::Hearts) & points(16..),
)
.alert(SPLINTER)
}
fn transfer_heart_gf_rebid() -> Rules {
if !transfer_gf_hearts() {
return Rules::new();
}
let minor_floor: u8 = if minor_min_to_3nt() { 17 } else { 10 };
Rules::new()
.rule(
Bid::new(3, Strain::Clubs),
1.45,
len(Suit::Clubs, 4..) & len(Suit::Spades, ..4) & points(minor_floor..),
)
.rule(
Bid::new(3, Strain::Diamonds),
1.45,
len(Suit::Diamonds, 4..) & len(Suit::Spades, ..4) & points(minor_floor..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.4,
len(Suit::Hearts, ..6)
& len(Suit::Spades, ..4)
& len(Suit::Clubs, ..4)
& len(Suit::Diamonds, ..4)
& points(10..)
& hcp(..16),
)
.rule(
Bid::new(3, Strain::Spades),
1.55,
len(Suit::Hearts, 6..) & splinter_short(Suit::Spades) & points(16..),
)
.alert(SPLINTER)
.rule(
Bid::new(4, Strain::Clubs),
1.55,
len(Suit::Hearts, 6..) & splinter_short(Suit::Clubs) & points(16..),
)
.alert(SPLINTER)
.rule(
Bid::new(4, Strain::Diamonds),
1.55,
len(Suit::Hearts, 6..) & splinter_short(Suit::Diamonds) & points(16..),
)
.alert(SPLINTER)
.rule(
Bid::new(4, Strain::Notrump),
1.4,
len(Suit::Hearts, 5..)
& len(Suit::Spades, ..4)
& len(Suit::Clubs, ..4)
& len(Suit::Diamonds, ..4)
& hcp(16..),
)
.alert(SLAM_TRY)
}
fn gf_quant_answer(major: Suit) -> Rules {
Rules::new()
.rule(
Bid::new(6, Strain::from(major)),
1.3,
hcp(17..) & len(major, 3..),
)
.rule(Bid::new(6, Strain::Notrump), 1.2, hcp(17..))
.rule(Call::Pass, 0.0, hcp(..17))
}
fn gf_minor_answer(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::from(major)), 1.1, len(major, 3..))
.rule(Bid::new(3, Strain::Notrump), 1.0, len(major, ..3))
}
fn gf_splinter_answer(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, hcp(17..))
.alert(slam::RKCB)
.rule(Bid::new(4, Strain::from(major)), 0.0, hcp(..17))
}
fn longer_major(longer: Suit, shorter: Suit) -> Cons<impl Constraint + Clone> {
described(
format!("{longer} longer than {shorter}"),
move |hand: Hand, _: &Context<'_>| hand[longer].len() > hand[shorter].len(),
)
}
fn equal_majors() -> Cons<impl Constraint + Clone> {
described("equal-length majors", |hand: Hand, _: &Context<'_>| {
hand[Suit::Hearts].len() == hand[Suit::Spades].len()
})
}
fn slam_55_reroute() -> Cons<impl Constraint + Clone> {
described(
"5-5 slam reroute to the spade transfer",
|hand: Hand, _: &Context<'_>| {
transfer_gf_majors()
&& hand[Suit::Hearts].len() >= 5
&& usize::from(point_count(hand)) >= 17
},
)
}
fn is_splinter_holding(holding: Holding) -> bool {
holding.is_empty()
|| (holding.len() == 1 && !holding.contains(Rank::A) && !holding.contains(Rank::K))
}
fn splinter_short(suit: Suit) -> Cons<impl Constraint + Clone> {
described(
"void or a low singleton",
move |hand: Hand, _: &Context<'_>| is_splinter_holding(hand[suit]),
)
}
fn is_major_splinter_slam(hand: Hand, major: Suit) -> bool {
let active = if major == Suit::Hearts {
transfer_gf_hearts()
} else {
transfer_gf_majors()
};
active
&& hand[major].len() >= 6
&& usize::from(point_count(hand)) >= 16
&& Suit::ASC
.into_iter()
.filter(|&suit| suit != major)
.any(|suit| is_splinter_holding(hand[suit]))
}
fn major_splinter_reroute(major: Suit) -> Cons<impl Constraint + Clone> {
described(
"6-card-major splinter reroute to the transfer",
move |hand: Hand, _: &Context<'_>| is_major_splinter_slam(hand, major),
)
}
fn not_major_splinter_slam(major: Suit) -> Cons<impl Constraint + Clone> {
described(
"not a describable 6-card-major splinter slam",
move |hand: Hand, _: &Context<'_>| !is_major_splinter_slam(hand, major),
)
}
fn transfer_slam_try_answer(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, hcp(17..))
.alert(slam::RKCB)
.rule(Bid::new(4, Strain::from(major)), 0.0, hcp(..17))
}
fn stayman_no_major_rebid() -> Rules {
let rules = Rules::new()
.rule(
Bid::new(3, Strain::Hearts),
1.4,
len(Suit::Hearts, 4..=4) & len(Suit::Spades, 5..) & hcp(9..),
)
.alert(SMOLEN)
.rule(
Bid::new(3, Strain::Spades),
1.4,
len(Suit::Spades, 4..=4) & len(Suit::Hearts, 5..) & hcp(9..),
)
.alert(SMOLEN)
.rule(Bid::new(4, Strain::Notrump), 1.2, hcp(16..=17))
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(9..))
.rule(Bid::new(2, Strain::Notrump), 1.0, hcp(8..=8));
let rules = if crawling_stayman() {
rules.rule(
Bid::new(2, Strain::Hearts),
1.4,
len(Suit::Hearts, 4..) & len(Suit::Diamonds, ..=1) & hcp(..8),
)
} else {
rules
};
let rules = if invitational_5card_majors() {
rules.rule(
Bid::new(2, Strain::Spades),
1.1,
len(Suit::Spades, 5..) & hcp(8..=8),
)
} else {
rules
};
if stayman_minor_slam_try() {
rules
.rule(
Bid::new(3, Strain::Clubs),
1.25,
len(Suit::Clubs, 5..) & hcp(14..),
)
.rule(
Bid::new(3, Strain::Diamonds),
1.25,
len(Suit::Diamonds, 5..) & hcp(14..),
)
} else {
rules
}
}
pub(super) fn smolen_completion(five_card: Suit) -> Rules {
let strain = Strain::from(five_card);
Rules::new()
.rule(Bid::new(4, strain), 1.0, len(five_card, 3..))
.rule(Bid::new(3, Strain::Notrump), 0.5, len(five_card, ..3))
}
pub(super) fn smolen_at_three() -> Rules {
Rules::new()
.rule(
Bid::new(3, Strain::Hearts),
1.4,
len(Suit::Hearts, 4..=4) & len(Suit::Spades, 5..),
)
.alert(SMOLEN)
.rule(
Bid::new(3, Strain::Spades),
1.4,
len(Suit::Spades, 4..=4) & len(Suit::Hearts, 5..),
)
.alert(SMOLEN)
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(0..))
}
fn accept_invitation(game: Bid) -> Rules {
Rules::new()
.rule(game, 1.0, hcp(17..))
.rule(Call::Pass, 0.0, hcp(0..))
}
fn accept_major_invitation(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.1, hcp(17..) & flat_4333())
.rule(Bid::new(4, Strain::from(major)), 1.0, hcp(17..))
.rule(Call::Pass, 0.0, hcp(0..))
}
fn answer_crawling_stayman() -> Rules {
Rules::new()
.rule(
Bid::new(3, Strain::Clubs),
1.0,
len(Suit::Hearts, ..3) & len(Suit::Spades, ..3),
)
.rule(
Bid::new(2, Strain::Spades),
1.0,
len(Suit::Hearts, ..3) & len(Suit::Spades, 3..),
)
.rule(Call::Pass, 0.0, len(Suit::Hearts, 3..))
}
fn answer_inv_5card_spades() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Spades),
1.2,
hcp(17..) & len(Suit::Spades, 3..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.1,
hcp(17..) & len(Suit::Spades, ..3),
)
.rule(Call::Pass, 0.0, hcp(0..))
}
fn answer_inv_5card_both() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Spades),
1.3,
hcp(17..) & len(Suit::Spades, 3..),
)
.rule(
Bid::new(4, Strain::Hearts),
1.2,
hcp(17..) & len(Suit::Spades, ..3),
)
.rule(
Bid::new(3, Strain::Spades),
1.1,
hcp(..17) & len(Suit::Spades, 3..),
)
.rule(
Bid::new(3, Strain::Hearts),
1.0,
hcp(..17) & len(Suit::Spades, ..3),
)
}
fn inv_5card_raise(strain: Strain) -> Rules {
Rules::new()
.rule(Bid::new(4, strain), 1.0, hcp(9..))
.rule(Call::Pass, 0.0, hcp(0..))
}
fn transfer_heart_invite_rebid() -> Rules {
Rules::new()
.rule(
Bid::new(2, Strain::Notrump),
1.2,
len(Suit::Hearts, 5..) & len(Suit::Spades, 4..=4) & hcp(8..=8),
)
.alert(INV_5CARD)
.rule(
Bid::new(2, Strain::Spades),
1.2,
len(Suit::Hearts, 5..) & len(Suit::Spades, ..4) & hcp(8..=8),
)
.alert(INV_5CARD)
}
fn answer_transfer_heart_single() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Hearts),
1.4,
hcp(17..) & len(Suit::Hearts, 3..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.3,
hcp(17..) & len(Suit::Hearts, ..3),
)
.rule(
Bid::new(3, Strain::Hearts),
1.1,
hcp(..17) & len(Suit::Hearts, 3..),
)
.rule(Bid::new(2, Strain::Notrump), 0.0, hcp(0..))
}
fn answer_transfer_heart_spade() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Hearts),
1.6,
hcp(17..) & len(Suit::Hearts, 3..),
)
.rule(
Bid::new(4, Strain::Spades),
1.5,
hcp(17..) & len(Suit::Hearts, ..3) & len(Suit::Spades, 4..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.4,
hcp(17..) & len(Suit::Hearts, ..3) & len(Suit::Spades, ..4),
)
.rule(
Bid::new(3, Strain::Hearts),
1.2,
hcp(..17) & len(Suit::Hearts, 3..),
)
.rule(
Bid::new(3, Strain::Spades),
1.1,
hcp(..17) & len(Suit::Hearts, ..3) & len(Suit::Spades, 4..),
)
.rule(Call::Pass, 0.0, hcp(0..))
}
fn transfer_spade_invite_rebid() -> Rules {
Rules::new()
.rule(
Bid::new(2, Strain::Notrump),
1.2,
len(Suit::Spades, 5..) & len(Suit::Hearts, ..4) & hcp(8..=8),
)
.alert(INV_5CARD)
}
fn answer_transfer_spade_single() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Spades),
1.4,
hcp(17..) & len(Suit::Spades, 3..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.3,
hcp(17..) & len(Suit::Spades, ..3),
)
.rule(
Bid::new(3, Strain::Spades),
1.1,
hcp(..17) & len(Suit::Spades, 3..),
)
.rule(Call::Pass, 0.0, hcp(0..))
}
fn puppet_answers() -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Hearts), 1.0, len(Suit::Hearts, 5..))
.rule(
Bid::new(3, Strain::Spades),
1.0,
len(Suit::Spades, 5..) & len(Suit::Hearts, ..5),
)
.rule(
Bid::new(3, Strain::Diamonds),
0.5,
len(Suit::Hearts, ..5) & len(Suit::Spades, ..5),
)
}
fn puppet_major_rebid(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::from(major)), 1.0, len(major, 3..))
.rule(Bid::new(3, Strain::Notrump), 0.5, len(major, ..3))
}
fn puppet_deny_rebid() -> Rules {
Rules::new()
.rule(
Bid::new(3, Strain::Hearts),
1.0,
len(Suit::Spades, 4..=4) & len(Suit::Hearts, 3..=3),
)
.alert(SMOLEN)
.rule(
Bid::new(3, Strain::Spades),
1.0,
len(Suit::Hearts, 4..=4) & len(Suit::Spades, 3..=3),
)
.alert(SMOLEN)
.rule(Bid::new(3, Strain::Notrump), 0.5, hcp(0..))
}
fn puppet_smolen_completion(shown_major: Suit) -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::from(shown_major)),
1.0,
len(shown_major, 4..),
)
.rule(Bid::new(3, Strain::Notrump), 0.5, len(shown_major, ..4))
}
fn five_five_major_answer() -> Rules {
Rules::new()
.rule(
Bid::new(4, Strain::Spades),
1.2,
hcp(17..) & len(Suit::Spades, 3..),
)
.rule(
Bid::new(4, Strain::Hearts),
1.2,
hcp(17..) & len(Suit::Spades, ..3) & len(Suit::Hearts, 3..),
)
.rule(
Bid::new(3, Strain::Notrump),
1.2,
hcp(17..) & len(Suit::Spades, ..3) & len(Suit::Hearts, ..3),
)
.rule(Bid::new(3, Strain::Spades), 1.0, len(Suit::Spades, 3..))
.rule(Bid::new(3, Strain::Hearts), 1.0, len(Suit::Spades, ..3))
}
fn five_five_min_rebid(major: Suit) -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::from(major)), 1.0, points(10..))
.rule(Call::Pass, 0.9, points(..10))
}
fn pass_out() -> Rules {
Rules::new().rule(Call::Pass, 0.0, hcp(0..))
}
fn diamond_transfer_answer() -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Diamonds), 1.0, len(Suit::Diamonds, 3..))
.rule(Bid::new(3, Strain::Clubs), 0.5, len(Suit::Diamonds, ..3))
}
fn diamond_transfer_game(threshold: u8) -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(threshold..))
.rule(Call::Pass, 0.0, hcp(..threshold))
}
fn diamond_transfer_correct(threshold: u8) -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(threshold..))
.rule(
Bid::new(3, Strain::Diamonds),
0.5,
len(Suit::Diamonds, 6..) & hcp(..threshold),
)
.rule(Call::Pass, 0.0, len(Suit::Diamonds, ..6) & hcp(..threshold))
}
fn club_splinter(short: Suit, threshold: u8) -> Cons<impl Constraint + Clone> {
len(Suit::Clubs, 6..) & hcp(threshold..) & len(short, ..2)
}
fn club_no_shortness(threshold: u8) -> Cons<impl Constraint + Clone> {
len(Suit::Clubs, 6..)
& hcp(threshold..)
& len(Suit::Diamonds, 2..)
& len(Suit::Hearts, 2..)
& len(Suit::Spades, 2..)
}
fn two_spade_answer() -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Clubs), 1.0, hcp(17..))
.rule(Bid::new(2, Strain::Notrump), 0.9, hcp(0..))
}
fn two_spade_over_min() -> Rules {
Rules::new()
.rule(Call::Pass, 0.0, hcp(8..=8) & balanced())
.rule(
Bid::new(3, Strain::Clubs),
0.8,
len(Suit::Clubs, 6..) & hcp(..8),
)
.rule(
Bid::new(3, Strain::Diamonds),
1.0,
club_splinter(Suit::Diamonds, 8),
)
.alert(SPLINTER)
.rule(
Bid::new(3, Strain::Hearts),
1.0,
club_splinter(Suit::Hearts, 8),
)
.alert(SPLINTER)
.rule(
Bid::new(3, Strain::Spades),
1.0,
club_splinter(Suit::Spades, 8),
)
.alert(SPLINTER)
.rule(Bid::new(3, Strain::Notrump), 0.9, club_no_shortness(8))
.alert(PUPPET)
}
fn two_spade_over_max() -> Rules {
Rules::new()
.rule(Call::Pass, 0.0, len(Suit::Clubs, 6..) & hcp(..8))
.rule(
Bid::new(3, Strain::Diamonds),
1.0,
club_splinter(Suit::Diamonds, 8),
)
.alert(SPLINTER)
.rule(
Bid::new(3, Strain::Hearts),
1.0,
club_splinter(Suit::Hearts, 8),
)
.alert(SPLINTER)
.rule(
Bid::new(3, Strain::Spades),
1.0,
club_splinter(Suit::Spades, 8),
)
.alert(SPLINTER)
.rule(
Bid::new(3, Strain::Notrump),
0.9,
(hcp(8..=8) & balanced()) | club_no_shortness(8),
)
}
fn pick_game_over_club_splinter(short: Suit) -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, stopper_in(short))
.rule(Bid::new(5, Strain::Clubs), 0.9, hcp(0..))
}
fn european_two_spade_answer() -> Rules {
Rules::new().rule(Bid::new(3, Strain::Clubs), 0.0, hcp(0..))
}
fn european_two_spade_rebid() -> Rules {
Rules::new()
.rule(Call::Pass, 0.0, len(Suit::Clubs, 6..) & hcp(..8))
.rule(
Bid::new(3, Strain::Diamonds),
1.0,
club_splinter(Suit::Diamonds, 8),
)
.rule(
Bid::new(3, Strain::Hearts),
1.0,
club_splinter(Suit::Hearts, 8),
)
.rule(
Bid::new(3, Strain::Spades),
1.0,
club_splinter(Suit::Spades, 8),
)
.rule(Bid::new(3, Strain::Notrump), 0.9, club_no_shortness(8))
}
fn european_two_nt_answer() -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(17..))
.rule(Call::Pass, 0.0, hcp(..17))
}
fn european_three_club_answer() -> Rules {
Rules::new().rule(Bid::new(3, Strain::Diamonds), 0.0, hcp(0..))
}
fn two_notrump_responses() -> Rules {
let prefer_longer = transfer_longer_major();
Rules::new()
.rule(
Bid::new(3, Strain::Diamonds),
2.0,
len(Suit::Hearts, 5..)
& described(
"hearts not outnumbered (longer-major discipline)",
move |hand: Hand, _: &Context<'_>| {
!prefer_longer || hand[Suit::Hearts].len() >= hand[Suit::Spades].len()
},
),
)
.alert(JACOBY)
.rule(
Bid::new(3, Strain::Hearts),
2.0,
len(Suit::Spades, 5..)
& described(
"spades longer (longer-major discipline)",
move |hand: Hand, _: &Context<'_>| {
!prefer_longer || hand[Suit::Spades].len() > hand[Suit::Hearts].len()
},
),
)
.alert(JACOBY)
.rule(
Bid::new(3, Strain::Clubs),
1.5,
(len(Suit::Hearts, 4..=4) | len(Suit::Spades, 4..=4)) & hcp(5..) & !flat_4333(),
)
.rule(
Bid::new(4, Strain::Notrump),
1.2,
hcp(11..=12) & len(Suit::Hearts, ..5) & len(Suit::Spades, ..5),
)
.rule(
Bid::new(3, Strain::Notrump),
1.0,
hcp(5..=10) & len(Suit::Hearts, ..5) & len(Suit::Spades, ..5),
)
.rule(Call::Pass, 0.0, hcp(..5))
}
fn stayman_answers_at_three() -> Rules {
Rules::new()
.rule(Bid::new(3, Strain::Hearts), 1.0, len(Suit::Hearts, 4..))
.rule(
Bid::new(3, Strain::Spades),
1.0,
len(Suit::Spades, 4..) & len(Suit::Hearts, ..4),
)
.rule(
Bid::new(3, Strain::Diamonds),
0.5,
len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
}
fn complete_transfer_at_three(into: Suit) -> Rules {
Rules::new().rule(Bid::new(3, Strain::from(into)), 1.0, hcp(0..))
}
fn quantitative_answer(accept_hcp: u8) -> Rules {
Rules::new()
.rule(Bid::new(6, Strain::Notrump), 1.0, hcp(accept_hcp..))
.rule(Call::Pass, 0.0, hcp(0..))
}
fn after_rebid_two_notrump() -> Rules {
Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.2, hcp(12..=13))
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(6..))
.rule(Call::Pass, 0.0, hcp(..6))
}
fn accept_quantitative_nineteen() -> Rules {
Rules::new()
.rule(Bid::new(6, Strain::Notrump), 1.0, hcp(19..))
.rule(Call::Pass, 0.0, hcp(0..))
}
pub(super) fn register(book: &mut Trie) {
register_one_nt(book);
register_two_nt_and_rebids(book);
}
pub(super) fn register_one_nt(book: &mut Trie) {
let one_nt = call(1, Strain::Notrump);
let four_nt = call(4, Strain::Notrump);
let two_c = call(2, Strain::Clubs);
let two_d = call(2, Strain::Diamonds);
let two_h = call(2, Strain::Hearts);
let two_s = call(2, Strain::Spades);
let three_c = call(3, Strain::Clubs);
let three_d = call(3, Strain::Diamonds);
let three_h = call(3, Strain::Hearts);
let three_s = call(3, Strain::Spades);
let puppet = notrump_minors() == PUPPET;
insert_uncontested(book, &[one_nt], notrump_responses());
insert_uncontested(book, &[one_nt, two_c], stayman_answers_uncontested());
insert_uncontested(book, &[one_nt, two_d], complete_transfer(Suit::Hearts));
insert_uncontested(book, &[one_nt, two_h], complete_transfer(Suit::Spades));
insert_uncontested(book, &[one_nt, four_nt], quantitative_answer(17));
insert_uncontested(
book,
&[one_nt, two_c, two_h],
stayman_major_rebid(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_s],
stayman_major_rebid(Suit::Spades),
);
insert_uncontested(
book,
&[one_nt, two_c, two_h, three_s],
stayman_slam_try_answer(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_s, three_h],
stayman_slam_try_answer(Suit::Spades),
);
if stayman_cue_continuation() {
for (answer, three_om, major) in [
(two_h, three_s, Suit::Hearts),
(two_s, three_h, Suit::Spades),
] {
for cue_suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts] {
if Strain::from(cue_suit) >= Strain::from(major) {
continue; }
let path = [
one_nt,
two_c,
answer,
three_om,
call(4, Strain::from(cue_suit)),
];
insert_uncontested(book, &path, stayman_cue_rebid(major));
slam::install_rkcb(book, &path, major);
}
}
}
if stayman_minor_slam_try() {
for answer in [two_h, two_s, two_d] {
for (three_m, minor) in [(three_c, Suit::Clubs), (three_d, Suit::Diamonds)] {
insert_uncontested(
book,
&[one_nt, two_c, answer, three_m],
stayman_minor_answer(minor),
);
let path = [one_nt, two_c, answer, three_m, call(4, Strain::from(minor))];
insert_uncontested(book, &path, stayman_minor_slam_rkcb());
slam::install_rkcb(book, &path, minor);
}
}
}
insert_uncontested(book, &[one_nt, two_c, two_d], stayman_no_major_rebid());
insert_uncontested(
book,
&[one_nt, two_c, two_d, three_h],
smolen_completion(Suit::Spades),
);
insert_uncontested(
book,
&[one_nt, two_c, two_d, three_s],
smolen_completion(Suit::Hearts),
);
let two_nt = call(2, Strain::Notrump);
insert_uncontested(
book,
&[one_nt, two_c, two_h, three_h],
accept_major_invitation(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_s, three_s],
accept_major_invitation(Suit::Spades),
);
for major_answer in [two_h, two_s, two_d] {
insert_uncontested(
book,
&[one_nt, two_c, major_answer, two_nt],
accept_invitation(Bid::new(3, Strain::Notrump)),
);
}
insert_uncontested(
book,
&[one_nt, two_c, two_h, four_nt],
quantitative_answer(17),
);
insert_uncontested(
book,
&[one_nt, two_c, two_s, four_nt],
quantitative_answer(17),
);
insert_uncontested(
book,
&[one_nt, two_c, two_d, four_nt],
quantitative_answer(17),
);
if crawling_stayman() {
insert_uncontested(
book,
&[one_nt, two_c, two_d, two_h],
answer_crawling_stayman(),
);
}
if invitational_5card_majors() {
insert_uncontested(
book,
&[one_nt, two_c, two_d, two_s],
answer_inv_5card_spades(),
);
insert_uncontested(
book,
&[one_nt, two_c, two_h, two_s],
answer_inv_5card_both(),
);
insert_uncontested(
book,
&[one_nt, two_c, two_h, two_s, three_h],
inv_5card_raise(Strain::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_h, two_s, three_s],
inv_5card_raise(Strain::Spades),
);
insert_uncontested(
book,
&[one_nt, two_d, two_h, two_s],
answer_transfer_heart_single(),
);
insert_uncontested(
book,
&[one_nt, two_d, two_h, two_nt],
answer_transfer_heart_spade(),
);
insert_uncontested(
book,
&[one_nt, two_h, two_s, two_nt],
answer_transfer_spade_single(),
);
}
if invitational_5card_majors()
|| sixcard_invite_active()
|| transfer_slam_try()
|| transfer_gf_hearts()
{
let mut heart_rebid = Rules::new();
if invitational_5card_majors() {
heart_rebid = heart_rebid.chain(transfer_heart_invite_rebid());
}
heart_rebid = heart_rebid.chain(sixcard_invite_rebid(Suit::Hearts));
heart_rebid = heart_rebid.chain(transfer_slam_try_rebid(Suit::Hearts));
heart_rebid = heart_rebid.chain(transfer_heart_gf_rebid());
insert_uncontested(book, &[one_nt, two_d, two_h], heart_rebid);
}
if invitational_5card_majors()
|| sixcard_invite_active()
|| transfer_slam_try()
|| transfer_gf_majors()
{
let mut spade_rebid = Rules::new();
if invitational_5card_majors() {
spade_rebid = spade_rebid.chain(transfer_spade_invite_rebid());
}
spade_rebid = spade_rebid.chain(sixcard_invite_rebid(Suit::Spades));
spade_rebid = spade_rebid.chain(transfer_slam_try_rebid(Suit::Spades));
spade_rebid = spade_rebid.chain(transfer_spade_gf_rebid());
insert_uncontested(book, &[one_nt, two_h, two_s], spade_rebid);
}
if transfer_slam_try() && !transfer_gf_hearts() {
insert_uncontested(
book,
&[one_nt, two_d, two_h, three_s],
transfer_slam_try_answer(Suit::Hearts),
);
slam::install_rkcb(book, &[one_nt, two_d, two_h, three_s], Suit::Hearts);
}
if transfer_slam_try() || transfer_gf_majors() {
insert_uncontested(
book,
&[one_nt, two_h, two_s, three_h],
transfer_slam_try_answer(Suit::Spades),
);
slam::install_rkcb(book, &[one_nt, two_h, two_s, three_h], Suit::Spades);
}
if transfer_gf_majors() {
insert_uncontested(
book,
&[one_nt, two_h, two_s, call(4, Strain::Notrump)],
gf_quant_answer(Suit::Spades),
);
for minor in [Strain::Clubs, Strain::Diamonds] {
let three_m = call(3, minor);
insert_uncontested(
book,
&[one_nt, two_h, two_s, three_m],
gf_minor_answer(Suit::Spades),
);
}
for short in [Strain::Clubs, Strain::Diamonds, Strain::Hearts] {
let four_short = call(4, short);
insert_uncontested(
book,
&[one_nt, two_h, two_s, four_short],
gf_splinter_answer(Suit::Spades),
);
slam::install_rkcb(book, &[one_nt, two_h, two_s, four_short], Suit::Spades);
}
}
if transfer_gf_hearts() {
insert_uncontested(
book,
&[one_nt, two_d, two_h, call(4, Strain::Notrump)],
gf_quant_answer(Suit::Hearts),
);
for minor in [Strain::Clubs, Strain::Diamonds] {
let three_m = call(3, minor);
insert_uncontested(
book,
&[one_nt, two_d, two_h, three_m],
gf_minor_answer(Suit::Hearts),
);
}
for splinter in [
call(3, Strain::Spades),
call(4, Strain::Clubs),
call(4, Strain::Diamonds),
] {
insert_uncontested(
book,
&[one_nt, two_d, two_h, splinter],
gf_splinter_answer(Suit::Hearts),
);
slam::install_rkcb(book, &[one_nt, two_d, two_h, splinter], Suit::Hearts);
}
}
if sixcard_invite_active() {
insert_uncontested(
book,
&[one_nt, two_d, two_h, three_h],
accept_sixcard_invitation(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_h, two_s, three_s],
accept_sixcard_invitation(Suit::Spades),
);
}
if stayman_both_majors() {
insert_uncontested(book, &[one_nt, two_c, two_nt], both_majors_max_responder());
insert_uncontested(
book,
&[one_nt, two_c, two_nt, call(3, Strain::Clubs)],
both_majors_relay_complete(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_nt, call(3, Strain::Diamonds)],
both_majors_relay_complete(Suit::Spades),
);
insert_uncontested(
book,
&[one_nt, two_c, two_nt, call(3, Strain::Clubs), three_h],
both_majors_relay_placement(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, two_nt, call(3, Strain::Diamonds), three_s],
both_majors_relay_placement(Suit::Spades),
);
}
if stayman_5card_max() {
insert_uncontested(
book,
&[one_nt, two_c, three_h],
five_card_max_rebid(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, two_c, three_s],
five_card_max_rebid(Suit::Spades),
);
}
let three_c = call(3, Strain::Clubs);
let three_d = call(3, Strain::Diamonds);
if puppet {
insert_uncontested(book, &[one_nt, three_c], puppet_answers());
insert_uncontested(
book,
&[one_nt, three_c, three_h],
puppet_major_rebid(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, three_c, three_s],
puppet_major_rebid(Suit::Spades),
);
insert_uncontested(book, &[one_nt, three_c, three_d], puppet_deny_rebid());
insert_uncontested(
book,
&[one_nt, three_c, three_d, three_h],
puppet_smolen_completion(Suit::Spades),
);
insert_uncontested(
book,
&[one_nt, three_c, three_d, three_s],
puppet_smolen_completion(Suit::Hearts),
);
} else {
insert_uncontested(book, &[one_nt, three_c], european_three_club_answer());
insert_uncontested(book, &[one_nt, three_c, three_d], diamond_transfer_game(8));
}
insert_uncontested(book, &[one_nt, three_d], five_five_major_answer());
insert_uncontested(
book,
&[one_nt, three_d, three_h],
five_five_min_rebid(Suit::Hearts),
);
insert_uncontested(
book,
&[one_nt, three_d, three_s],
five_five_min_rebid(Suit::Spades),
);
let four_c = call(4, Strain::Clubs);
let four_d = call(4, Strain::Diamonds);
let four_h = call(4, Strain::Hearts);
let four_s = call(4, Strain::Spades);
insert_uncontested(book, &[one_nt, four_c], complete_texas(Suit::Hearts));
insert_uncontested(book, &[one_nt, four_d], complete_texas(Suit::Spades));
insert_uncontested(book, &[one_nt, four_h], slam_try_answer());
insert_uncontested(book, &[one_nt, four_s], slam_try_answer());
slam::install_rkcb(book, &[one_nt, four_h], Suit::Hearts);
slam::install_rkcb(book, &[one_nt, four_s], Suit::Spades);
if texas_slam_drive() {
insert_uncontested(book, &[one_nt, four_c, four_h], texas_slam_drive_rebid());
insert_uncontested(book, &[one_nt, four_d, four_s], texas_slam_drive_rebid());
slam::install_rkcb(book, &[one_nt, four_c, four_h], Suit::Hearts);
slam::install_rkcb(book, &[one_nt, four_d, four_s], Suit::Spades);
}
if puppet {
insert_uncontested(book, &[one_nt, two_nt], diamond_transfer_answer());
insert_uncontested(book, &[one_nt, two_nt, three_d], diamond_transfer_game(8));
insert_uncontested(
book,
&[one_nt, two_nt, three_c],
diamond_transfer_correct(8),
);
insert_uncontested(book, &[one_nt, two_nt, three_c, three_d], pass_out());
} else {
insert_uncontested(book, &[one_nt, two_nt], european_two_nt_answer());
}
if puppet {
insert_uncontested(book, &[one_nt, two_s], two_spade_answer());
insert_uncontested(book, &[one_nt, two_s, two_nt], two_spade_over_min());
insert_uncontested(book, &[one_nt, two_s, three_c], two_spade_over_max());
insert_uncontested(book, &[one_nt, two_s, two_nt, three_c], pass_out());
for opener_reply in [two_nt, three_c] {
for short in [Suit::Diamonds, Suit::Hearts, Suit::Spades] {
insert_uncontested(
book,
&[one_nt, two_s, opener_reply, call(3, Strain::from(short))],
pick_game_over_club_splinter(short),
);
}
}
} else {
insert_uncontested(book, &[one_nt, two_s], european_two_spade_answer());
insert_uncontested(book, &[one_nt, two_s, three_c], european_two_spade_rebid());
for short in [Suit::Diamonds, Suit::Hearts, Suit::Spades] {
insert_uncontested(
book,
&[one_nt, two_s, three_c, call(3, Strain::from(short))],
pick_game_over_club_splinter(short),
);
}
}
}
pub(super) fn register_two_nt_and_rebids(book: &mut Trie) {
let one_nt = call(1, Strain::Notrump);
let two_nt = call(2, Strain::Notrump);
let four_nt = call(4, Strain::Notrump);
let bases: &[(&[Call], u8)] = &[
(&[two_nt], 21),
(
&[call(2, Strain::Clubs), call(2, Strain::Diamonds), two_nt],
24,
),
(
&[call(2, Strain::Clubs), call(2, Strain::Hearts), two_nt],
24,
),
];
for (base, accept_hcp) in bases {
insert_uncontested(book, base, two_notrump_responses());
let extend = |tail: Call| -> Vec<Call> {
base.iter().copied().chain(core::iter::once(tail)).collect()
};
insert_uncontested(
book,
&extend(call(3, Strain::Clubs)),
stayman_answers_at_three(),
);
insert_uncontested(
book,
&extend(call(3, Strain::Diamonds)),
complete_transfer_at_three(Suit::Hearts),
);
insert_uncontested(
book,
&extend(call(3, Strain::Hearts)),
complete_transfer_at_three(Suit::Spades),
);
insert_uncontested(book, &extend(four_nt), quantitative_answer(*accept_hcp));
let extend2 =
|a: Call, b: Call| -> Vec<Call> { base.iter().copied().chain([a, b]).collect() };
let extend3 = |a: Call, b: Call, c: Call| -> Vec<Call> {
base.iter().copied().chain([a, b, c]).collect()
};
let (three_c, three_d) = (call(3, Strain::Clubs), call(3, Strain::Diamonds));
let (three_h, three_s) = (call(3, Strain::Hearts), call(3, Strain::Spades));
insert_uncontested(book, &extend2(three_c, three_d), smolen_at_three());
insert_uncontested(
book,
&extend3(three_c, three_d, three_h),
smolen_completion(Suit::Spades),
);
insert_uncontested(
book,
&extend3(three_c, three_d, three_s),
smolen_completion(Suit::Hearts),
);
}
let rebid_prefixes: &[&[Call]] = &[
&[call(1, Strain::Hearts), call(1, Strain::Spades)],
&[call(1, Strain::Clubs), call(1, Strain::Diamonds)],
&[call(1, Strain::Clubs), call(1, Strain::Hearts)],
&[call(1, Strain::Clubs), call(1, Strain::Spades)],
&[call(1, Strain::Diamonds), call(1, Strain::Hearts)],
&[call(1, Strain::Diamonds), call(1, Strain::Spades)],
&[call(1, Strain::Hearts), one_nt],
&[call(1, Strain::Spades), one_nt],
];
for prefix in rebid_prefixes {
let mut our = prefix.to_vec();
our.push(two_nt);
insert_uncontested(book, &our, after_rebid_two_notrump());
our.push(four_nt);
insert_uncontested(book, &our, accept_quantitative_nineteen());
}
}
#[cfg(test)]
mod tests {
use crate::american;
use crate::bidding::{Family, System};
use contract_bridge::auction::{Call, RelativeVulnerability};
use contract_bridge::{Bid, Strain};
const P: Call = Call::Pass;
fn bid(level: u8, strain: Strain) -> Call {
Call::Bid(Bid::new(level, strain))
}
fn best(auction: &[Call], hand: &str) -> Call {
let hand = hand.parse().expect("valid test hand");
let logits = american()
.against(Family::NATURAL)
.classify(hand, RelativeVulnerability::NONE, auction)
.expect("a decision");
(&logits.0)
.into_iter()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("logits are never NaN"))
.map(|(call, _)| call)
.expect("the logits array is never empty")
}
#[test]
fn transfers_prefer_the_longer_major() {
let one_nt = [bid(1, Strain::Notrump), P];
assert_eq!(best(&one_nt, "QJ9642.98763.4.3"), bid(2, Strain::Hearts));
assert_eq!(best(&one_nt, "KJ9642.AKJ63.J.3"), bid(2, Strain::Hearts));
assert_eq!(best(&one_nt, "98763.QJ9642.4.3"), bid(2, Strain::Diamonds));
assert_eq!(best(&one_nt, "J9863.J9642.4.3"), bid(2, Strain::Diamonds));
assert_eq!(best(&one_nt, "KJ863.KJ642.4.3"), bid(3, Strain::Diamonds));
assert_eq!(best(&one_nt, "AKJ63.AKJ42.4.3"), bid(2, Strain::Hearts));
let over_completion = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
assert_eq!(
best(&over_completion, "AKJ63.AKJ42.4.3"),
bid(3, Strain::Hearts)
);
let two_nt = [bid(2, Strain::Notrump), P];
assert_eq!(best(&two_nt, "QJ9642.98763.4.3"), bid(3, Strain::Hearts));
assert_eq!(best(&two_nt, "J9863.J9642.4.3"), bid(3, Strain::Diamonds));
}
#[test]
fn south_african_texas_slam_try() {
let one_nt = [bid(1, Strain::Notrump), P];
assert_eq!(best(&one_nt, "42.AKJ872.KQ4.K2"), bid(4, Strain::Clubs));
assert_eq!(best(&one_nt, "42.AKJ872.Q43.32"), bid(4, Strain::Clubs));
assert_eq!(best(&one_nt, "42.AKJ872.KQ4.Q2"), bid(4, Strain::Hearts));
let over_try = [bid(1, Strain::Notrump), P, bid(4, Strain::Hearts), P];
assert_eq!(best(&over_try, "KQ3.K53.AQ54.K92"), bid(4, Strain::Notrump));
assert_eq!(best(&over_try, "KQ3.K53.KQ54.Q92"), P);
let over_transfer = [bid(1, Strain::Notrump), P, bid(4, Strain::Clubs), P];
assert_eq!(
best(&over_transfer, "KQ3.K53.KQ54.Q92"),
bid(4, Strain::Hearts)
);
let over_completion = [
bid(1, Strain::Notrump),
P,
bid(4, Strain::Clubs),
P,
bid(4, Strain::Hearts),
P,
];
assert_eq!(
best(&over_completion, "42.AKJ872.KQ4.K2"),
bid(4, Strain::Notrump)
);
assert_eq!(best(&over_completion, "42.AKJ872.Q43.32"), P);
let over_ask = [
bid(1, Strain::Notrump),
P,
bid(4, Strain::Clubs),
P,
bid(4, Strain::Hearts),
P,
bid(4, Strain::Notrump),
P,
];
assert_eq!(best(&over_ask, "KQ3.K53.AQ54.K92"), bid(5, Strain::Hearts));
}
#[test]
fn transfer_gf_majors_five_five_and_quantitative() {
use crate::bidding::american::set_transfer_gf_majors;
let one_nt = [bid(1, Strain::Notrump), P];
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
let slam_55 = "AKQ52.AK432.32.4";
let min_55 = "KQ542.KQ432.32.4";
let single = "AKQ52.A32.K32.Q2";
set_transfer_gf_majors(false);
assert_eq!(best(&one_nt, slam_55), bid(3, Strain::Diamonds));
assert_eq!(best(&after, single), bid(3, Strain::Hearts));
set_transfer_gf_majors(true);
assert_eq!(best(&one_nt, slam_55), bid(2, Strain::Hearts));
assert_eq!(best(&after, slam_55), bid(3, Strain::Hearts));
assert_eq!(best(&one_nt, min_55), bid(3, Strain::Diamonds));
assert_eq!(best(&after, single), bid(4, Strain::Notrump));
let over_quant = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
bid(4, Strain::Notrump),
P,
];
assert_eq!(
best(&over_quant, "AQ4.KQ3.KQJ2.Q32"),
bid(6, Strain::Spades)
);
assert_eq!(best(&over_quant, "AQ4.K83.KJ72.Q83"), P);
let over_55 = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
bid(3, Strain::Hearts),
P,
];
assert_eq!(best(&over_55, "AQ4.KQ3.KQJ2.Q32"), bid(4, Strain::Notrump));
assert_eq!(best(&over_55, "AQ43.K83.KJ7.Q83"), bid(4, Strain::Spades));
set_transfer_gf_majors(true); }
#[test]
fn transfer_gf_majors_minor_side_suits() {
use crate::bidding::american::{set_minor_min_to_3nt, set_transfer_gf_majors};
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
let min_club = "KJ542.32.32.AK32";
let min_diamond = "KJ542.32.AK32.32";
let slam_club = "AKQ52.32.32.AKQ2";
set_transfer_gf_majors(true);
set_minor_min_to_3nt(false);
assert_eq!(best(&after, min_club), bid(3, Strain::Clubs));
assert_eq!(best(&after, min_diamond), bid(3, Strain::Diamonds));
set_minor_min_to_3nt(true);
assert_eq!(best(&after, min_club), bid(3, Strain::Notrump));
assert_eq!(best(&after, slam_club), bid(3, Strain::Clubs));
set_minor_min_to_3nt(false);
let over_minor = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
bid(3, Strain::Clubs),
P,
];
assert_eq!(
best(&over_minor, "AQ4.KQ3.KQJ2.Q32"),
bid(4, Strain::Spades)
);
assert_eq!(
best(&over_minor, "A4.KQ32.KQJ2.Q32"),
bid(3, Strain::Notrump)
);
set_transfer_gf_majors(true); }
#[test]
fn transfer_gf_majors_choice_of_games_3nt() {
use crate::bidding::american::set_transfer_gf_majors;
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
set_transfer_gf_majors(true);
assert_eq!(best(&after, "AQ654.K72.Q83.J4"), bid(3, Strain::Notrump));
assert_ne!(best(&after, "AQ6543.K72.Q8.J4"), bid(3, Strain::Notrump));
assert_eq!(best(&after, "AQ654.K7.Q8.J432"), bid(3, Strain::Clubs));
}
#[test]
fn transfer_gf_majors_spade_splinters() {
use crate::bidding::american::set_transfer_gf_majors;
let one_nt = [bid(1, Strain::Notrump), P];
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
let splinter = "AKQ432.AK2.Q43.2";
let stiff_ace = "AKQ432.AK2.Q43.A";
set_transfer_gf_majors(false);
assert_eq!(best(&one_nt, splinter), bid(4, Strain::Diamonds));
set_transfer_gf_majors(true);
assert_eq!(best(&one_nt, splinter), bid(2, Strain::Hearts));
assert_eq!(best(&after, splinter), bid(4, Strain::Clubs));
assert_eq!(best(&one_nt, stiff_ace), bid(4, Strain::Diamonds));
let over_splinter = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
bid(4, Strain::Clubs),
P,
];
assert_eq!(
best(&over_splinter, "AQ3.KJ32.KQ32.Q3"),
bid(4, Strain::Notrump)
);
assert_eq!(
best(&over_splinter, "KQ3.KJ32.KJ32.Q3"),
bid(4, Strain::Spades)
);
set_transfer_gf_majors(true); }
#[test]
fn transfer_gf_hearts_minors_and_quant() {
use crate::bidding::american::{set_transfer_gf_hearts, set_transfer_gf_majors};
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
];
let min_club = "32.KJ542.32.AK32";
let min_diamond = "32.KJ542.AK32.32";
let quant = "Q32.AKJ42.KJ2.Q2";
set_transfer_gf_majors(true);
set_transfer_gf_hearts(true);
assert_eq!(best(&after, min_club), bid(3, Strain::Clubs));
assert_eq!(best(&after, min_diamond), bid(3, Strain::Diamonds));
assert_eq!(best(&after, quant), bid(4, Strain::Notrump));
let over_minor = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
bid(3, Strain::Clubs),
P,
];
assert_eq!(
best(&over_minor, "AQ4.KQ3.KQJ2.Q32"),
bid(4, Strain::Hearts)
);
assert_eq!(
best(&over_minor, "AQ32.K3.KQJ2.Q32"),
bid(3, Strain::Notrump)
);
let over_quant = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
bid(4, Strain::Notrump),
P,
];
assert_eq!(
best(&over_quant, "AQ42.KQ3.KJ5.Q32"),
bid(6, Strain::Hearts)
);
assert_eq!(best(&over_quant, "KJ42.KQ3.KJ5.Q32"), P);
set_transfer_gf_hearts(true); }
#[test]
fn transfer_gf_hearts_spade_splinter() {
use crate::bidding::american::{set_transfer_gf_hearts, set_transfer_gf_majors};
let one_nt = [bid(1, Strain::Notrump), P];
let after = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
];
let spade_short = "2.AKQ432.Q43.AK2";
let diamond_short = "AK2.AKQ432.2.Q43";
let stiff_ace = "A.AKQ432.Q43.Q42";
set_transfer_gf_majors(true);
set_transfer_gf_hearts(true);
assert_eq!(best(&one_nt, spade_short), bid(2, Strain::Diamonds));
assert_eq!(best(&after, spade_short), bid(3, Strain::Spades));
assert_eq!(best(&after, diamond_short), bid(4, Strain::Diamonds));
assert_eq!(best(&one_nt, stiff_ace), bid(4, Strain::Clubs));
let over_splinter = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
bid(3, Strain::Spades),
P,
];
assert_eq!(
best(&over_splinter, "KJ32.AQ3.KQ32.Q3"),
bid(4, Strain::Notrump)
);
assert_eq!(
best(&over_splinter, "KJ32.KQ3.KJ32.Q3"),
bid(4, Strain::Hearts)
);
set_transfer_gf_hearts(true); }
#[test]
fn sixcard_major_invite() {
use crate::bidding::american::set_sixcard_invite_floor;
let one_nt = [bid(1, Strain::Notrump), P];
let inv = "J43.KQ8765.32.32";
let weak = "543.KQ8765.32.32";
set_sixcard_invite_floor(14);
let after_transfer = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
];
assert_ne!(best(&after_transfer, inv), bid(3, Strain::Hearts));
set_sixcard_invite_floor(13);
assert_eq!(best(&one_nt, inv), bid(2, Strain::Diamonds));
assert_eq!(best(&after_transfer, inv), bid(3, Strain::Hearts));
assert_ne!(best(&after_transfer, weak), bid(3, Strain::Hearts));
let over_invite = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
bid(3, Strain::Hearts),
P,
];
assert_eq!(
best(&over_invite, "AK5.32.AQ74.K963"),
bid(4, Strain::Hearts)
); assert_eq!(best(&over_invite, "AK5.32.AQ74.Q963"), P); assert_eq!(
best(&over_invite, "AK5.432.AQ7.Q963"),
bid(4, Strain::Hearts)
);
let spade_inv = "KQ8765.J43.32.32";
assert_eq!(best(&one_nt, spade_inv), bid(2, Strain::Hearts));
let after_spade = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
assert_eq!(best(&after_spade, spade_inv), bid(3, Strain::Spades));
set_sixcard_invite_floor(13); }
#[test]
fn systems_on_over_two_clubs() {
use contract_bridge::auction::Auction;
let best_legal = |auction: &[Call], hand: &str| -> Call {
let hand = hand.parse().expect("valid test hand");
let logits = american()
.against(Family::NATURAL)
.classify(hand, RelativeVulnerability::NONE, auction)
.expect("a decision");
let mut played = Auction::new();
for &c in auction {
played.push(c);
}
let mut scored: Vec<_> = (&logits.0)
.into_iter()
.filter(|(_, l)| l.is_finite())
.collect();
scored.sort_by(|x, y| y.1.partial_cmp(x.1).expect("no NaN"));
scored
.into_iter()
.map(|(c, _)| c)
.find(|&c| played.can_push(c).is_ok())
.unwrap_or(Call::Pass)
};
let over_2c = [bid(1, Strain::Notrump), bid(2, Strain::Clubs)];
assert_eq!(
best_legal(&over_2c, "2.KJ876.5432.432"),
bid(2, Strain::Diamonds)
);
assert_eq!(
best_legal(&over_2c, "KJ876.2.5432.432"),
bid(2, Strain::Hearts)
);
assert_eq!(best_legal(&over_2c, "KJ32.KQ43.432.43"), Call::Double);
let over_xfer = [
bid(1, Strain::Notrump),
bid(2, Strain::Clubs),
bid(2, Strain::Diamonds),
P,
];
assert_eq!(
best_legal(&over_xfer, "KQ3.A53.KQ54.K92"),
bid(2, Strain::Hearts)
);
let over_dbl = [
bid(1, Strain::Notrump),
bid(2, Strain::Clubs),
Call::Double,
P,
];
assert_eq!(
best_legal(&over_dbl, "AQ3.KJ54.KQ4.92"),
bid(2, Strain::Hearts)
);
}
#[test]
fn penalty_pass_over_two_clubs() {
use crate::bidding::american::set_penalty_pass;
let opener = "A2.K3.Q42.AK432";
let over_dbl = [
bid(1, Strain::Notrump),
bid(2, Strain::Clubs),
Call::Double,
P,
];
let uncontested_stayman = [bid(1, Strain::Notrump), P, bid(2, Strain::Clubs), P];
set_penalty_pass(Some((4, 4, true)));
assert_eq!(best(&over_dbl, opener), Call::Pass);
assert_eq!(best(&uncontested_stayman, opener), bid(2, Strain::Diamonds));
set_penalty_pass(None);
assert_eq!(best(&over_dbl, opener), bid(2, Strain::Diamonds));
}
#[test]
fn invitational_five_four_majors() {
use crate::bidding::american::set_invitational_5card_majors;
let one_nt = [bid(1, Strain::Notrump), P];
let s5h4 = "KQ864.Q1043.J2.32";
let s6h4 = "KQ8642.QJ43.32.2";
let h5s4 = "Q1043.KQ864.J2.32";
let h5 = "Q3.KQ864.J32.432";
set_invitational_5card_majors(true);
assert_eq!(best(&one_nt, s5h4), bid(2, Strain::Clubs));
assert_eq!(best(&one_nt, s6h4), bid(4, Strain::Diamonds));
assert_eq!(best(&one_nt, h5s4), bid(2, Strain::Diamonds));
let stayman_no_major = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
P,
];
assert_eq!(best(&stayman_no_major, s5h4), bid(2, Strain::Spades));
let stayman_hearts = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
];
assert_eq!(best(&stayman_hearts, s5h4), bid(2, Strain::Spades));
let over_two_s = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
assert_eq!(
best(&over_two_s, "AK4.KQ32.A65.J32"),
bid(4, Strain::Spades)
);
let after_transfer = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
];
assert_eq!(best(&after_transfer, h5s4), bid(2, Strain::Notrump));
assert_eq!(best(&after_transfer, h5), bid(2, Strain::Spades));
let over_two_nt = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Notrump),
P,
];
assert_eq!(
best(&over_two_nt, "AK2.A104.KQ32.J2"),
bid(4, Strain::Hearts)
);
let two_d_doubled = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
Call::Double,
];
assert_eq!(best(&two_d_doubled, s5h4), bid(2, Strain::Spades));
set_invitational_5card_majors(false);
assert_eq!(best(&one_nt, s5h4), bid(2, Strain::Hearts));
assert_eq!(
best(&two_d_doubled, "KQ32.Q943.J32.43"),
bid(2, Strain::Notrump)
);
set_invitational_5card_majors(true); }
#[test]
fn single_suited_spade_invite() {
let s5 = "KQ864.Q3.J32.432";
let one_nt = [bid(1, Strain::Notrump), P];
assert_eq!(best(&one_nt, s5), bid(2, Strain::Hearts));
let after_transfer = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
];
assert_eq!(best(&after_transfer, s5), bid(2, Strain::Notrump));
assert_ne!(
best(&after_transfer, "Q9864.32.J32.432"),
bid(2, Strain::Notrump)
);
let over_invite = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Hearts),
P,
bid(2, Strain::Spades),
P,
bid(2, Strain::Notrump),
P,
];
assert_eq!(
best(&over_invite, "AK3.K32.KQ32.Q32"),
bid(4, Strain::Spades)
);
assert_eq!(
best(&over_invite, "KQ.AK42.KQ32.432"),
bid(3, Strain::Notrump)
);
assert_eq!(
best(&over_invite, "AK3.Q32.KQ32.Q32"),
bid(3, Strain::Spades)
);
assert_eq!(best(&over_invite, "KQ.Q432.KQ32.A32"), P);
}
#[test]
fn crawling_stayman_escape() {
use crate::bidding::american::set_crawling_stayman;
let one_nt = [bid(1, Strain::Notrump), P];
let h4414 = "QJ32.Q1043.4.T543";
let h4405 = "QJ32.Q1043..T9432";
set_crawling_stayman(true);
assert_eq!(best(&one_nt, h4414), bid(2, Strain::Clubs));
assert_eq!(best(&one_nt, h4405), bid(2, Strain::Clubs));
let two_d = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
P,
];
assert_eq!(best(&two_d, h4414), bid(2, Strain::Hearts));
assert_eq!(best(&two_d, h4405), bid(2, Strain::Hearts));
let crawl = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
P,
bid(2, Strain::Hearts),
P,
];
assert_eq!(best(&crawl, "A32.K43.KQ32.A52"), P); assert_eq!(best(&crawl, "K43.A2.KQ32.A432"), bid(2, Strain::Spades)); assert_eq!(best(&crawl, "K2.A2.KJ43.AJ432"), bid(3, Strain::Clubs));
let two_d_doubled = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
Call::Double,
];
assert_eq!(best(&two_d_doubled, h4414), bid(2, Strain::Hearts));
let crawl_doubled = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Diamonds),
Call::Double,
bid(2, Strain::Hearts),
P,
];
assert_eq!(
best(&crawl_doubled, "K43.A2.KQ32.A432"),
bid(2, Strain::Spades)
);
set_crawling_stayman(false);
assert_eq!(best(&one_nt, h4414), P);
set_crawling_stayman(true); }
#[test]
fn stayman_minor_slam_try() {
use crate::bidding::american::set_stayman_minor_slam_try;
set_stayman_minor_slam_try(true);
let after_2h = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
];
assert_eq!(best(&after_2h, "AJ54.32.32.AKQ32"), bid(3, Strain::Clubs));
let after_3c = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
bid(3, Strain::Clubs),
P,
];
assert_eq!(best(&after_3c, "A2.AQJ2.K32.Q543"), bid(4, Strain::Clubs));
assert_eq!(best(&after_3c, "A2.AQJ2.K432.Q54"), bid(3, Strain::Notrump));
let after_4c = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
bid(3, Strain::Clubs),
P,
bid(4, Strain::Clubs),
P,
];
assert_eq!(best(&after_4c, "AJ54.32.32.AKQ32"), bid(4, Strain::Notrump));
set_stayman_minor_slam_try(false);
assert_ne!(best(&after_2h, "AJ54.32.32.AKQ32"), bid(3, Strain::Clubs));
}
#[test]
fn both_majors_relay_game_placement() {
let relay = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Notrump),
P,
bid(3, Strain::Clubs),
P,
bid(3, Strain::Hearts),
P,
];
assert_eq!(best(&relay, "KQ54.J932.654.J2"), bid(4, Strain::Hearts));
assert_eq!(best(&relay, "K32.A654.J432.32"), bid(4, Strain::Hearts));
assert_eq!(best(&relay, "Q32.KJ954.762.32"), P);
assert_eq!(best(&relay, "K32.QJ54.J432.32"), P);
}
#[test]
fn stayman_fit_raise_by_value() {
let stayman = [
bid(1, Strain::Notrump),
P,
bid(2, Strain::Clubs),
P,
bid(2, Strain::Hearts),
P,
];
assert_eq!(best(&stayman, "K32.Q654.K32.432"), bid(3, Strain::Hearts));
assert_eq!(best(&stayman, "Q543.K654.K432.2"), bid(4, Strain::Hearts));
assert_eq!(best(&stayman, "K32.Q654.Q32.432"), P);
}
}