use super::super::Alert;
use super::super::Rules;
use super::super::Trie;
use super::super::constraint::{
Cons, Constraint, balanced, described, hcp, len, points, stopper_in, support,
};
use crate::bidding::context::Context;
use contract_bridge::auction::Call;
use contract_bridge::{Bid, Hand, Strain, Suit};
use std::cell::Cell;
std::thread_local! {
static LONGER_MAJOR_RESPONSE: Cell<bool> = const { Cell::new(false) };
static UP_THE_LINE: Cell<bool> = const { Cell::new(true) };
}
pub fn set_longer_major_response(on: bool) {
LONGER_MAJOR_RESPONSE.with(|cell| cell.set(on));
}
pub(crate) fn longer_major_response() -> bool {
LONGER_MAJOR_RESPONSE.with(Cell::get)
}
pub fn set_up_the_line(on: bool) {
UP_THE_LINE.with(|cell| cell.set(on));
}
pub(crate) fn up_the_line() -> bool {
UP_THE_LINE.with(Cell::get)
}
fn spades_first() -> Cons<impl Constraint + Clone> {
described(
"spades longer than hearts, or equal five-plus",
|hand: Hand, _: &Context<'_>| {
let spades = hand[Suit::Spades].len();
let hearts = hand[Suit::Hearts].len();
spades > hearts || (spades == hearts && spades >= 5)
},
)
}
const JACOBY_2NT: Alert = Alert("jacoby-2nt");
const SPLINTER: Alert = Alert("splinter");
const WEAK_JUMP_SHIFT: Alert = Alert("weak-jump-shift");
const INVERTED_MINOR: Alert = Alert("inverted-minor");
const GAME_FORCE: Alert = Alert("game-force");
#[must_use]
pub fn major_responses(major: Suit) -> Rules {
let trump = Strain::from(major);
let mut rules = Rules::new()
.rule(
Bid::new(2, Strain::Notrump),
3.0,
support(4..) & points(13..),
)
.alert(JACOBY_2NT)
.rule(Bid::new(3, trump), 2.0, support(4..) & points(10..=12))
.rule(Bid::new(4, trump), 1.6, support(5..) & points(..6))
.rule(Bid::new(2, trump), 1.5, support(3..) & points(6..=9))
.rule(Bid::new(1, Strain::Notrump), 0.5, hcp(6..=12))
.rule(Call::Pass, 0.0, hcp(..6));
if major == Suit::Hearts {
rules = rules.rule(
Bid::new(1, Strain::Spades),
1.7,
len(Suit::Spades, 4..) & points(6..) & !support(4..),
);
}
let splinter_suits: &[Suit] = if major == Suit::Hearts {
&[Suit::Spades, Suit::Clubs, Suit::Diamonds]
} else {
&[Suit::Clubs, Suit::Diamonds, Suit::Hearts]
};
for &x in splinter_suits {
let (level, strain) = splinter_bid(major, x);
rules = rules
.rule(
Bid::new(level, strain),
2.8,
support(4..) & points(10..=13) & len(x, ..=1),
)
.alert(SPLINTER);
}
let wjs_suits: &[Suit] = if major == Suit::Hearts {
&[Suit::Spades, Suit::Clubs, Suit::Diamonds]
} else {
&[Suit::Clubs, Suit::Diamonds, Suit::Hearts]
};
for &x in wjs_suits {
let (level, strain) = wjs_bid(major, x);
rules = rules
.rule(Bid::new(level, strain), 1.0, len(x, 6..) & points(2..=5))
.alert(WEAK_JUMP_SHIFT);
}
let mut weight = 1.1;
for suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts] {
if Strain::from(suit) < trump {
rules = rules
.rule(
Bid::new(2, Strain::from(suit)),
weight,
len(suit, 4..) & points(13..) & !support(4..),
)
.alert(GAME_FORCE);
weight -= 0.05;
}
}
rules
}
pub(super) fn splinter_bid(major: Suit, x: Suit) -> (u8, Strain) {
let major_strain = Strain::from(major);
let x_strain = Strain::from(x);
if x_strain > major_strain {
(3, x_strain)
} else {
(4, x_strain)
}
}
fn wjs_bid(major: Suit, x: Suit) -> (u8, Strain) {
let major_strain = Strain::from(major);
let x_strain = Strain::from(x);
if x_strain > major_strain {
(2, x_strain)
} else {
(3, x_strain)
}
}
#[must_use]
pub fn minor_responses(minor: Suit) -> Rules {
let trump = Strain::from(minor);
let mut rules = Rules::new();
rules = if longer_major_response() {
rules
.rule(
Bid::new(1, Strain::Spades),
1.5,
len(Suit::Spades, 4..) & points(6..) & spades_first(),
)
.rule(
Bid::new(1, Strain::Hearts),
1.4,
len(Suit::Hearts, 4..) & points(6..) & !spades_first(),
)
} else {
rules
.rule(
Bid::new(1, Strain::Hearts),
1.5,
len(Suit::Hearts, 4..) & points(6..),
)
.rule(
Bid::new(1, Strain::Spades),
1.4,
len(Suit::Spades, 4..) & points(6..) & len(Suit::Hearts, ..4),
)
};
if minor == Suit::Clubs && up_the_line() {
rules = rules.rule(
Bid::new(1, Strain::Diamonds),
1.2,
len(Suit::Diamonds, 4..)
& points(6..)
& len(Suit::Hearts, ..4)
& len(Suit::Spades, ..4),
);
}
rules = rules
.rule(
Bid::new(3, Strain::Notrump),
1.0,
hcp(13..) & balanced() & len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
.rule(
Bid::new(2, Strain::Notrump),
1.0,
hcp(11..=12) & balanced() & len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
.rule(
Bid::new(1, Strain::Notrump),
0.5,
hcp(6..=10) & len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
.rule(
Bid::new(2, trump),
1.25,
support(5..) & points(10..) & len(Suit::Hearts, ..4) & len(Suit::Spades, ..4),
)
.alert(INVERTED_MINOR)
.rule(Bid::new(3, trump), 1.1, support(5..) & points(..=9))
.alert(INVERTED_MINOR)
.rule(Call::Pass, 0.0, hcp(..6));
for x in [Suit::Hearts, Suit::Spades] {
rules = rules
.rule(
Bid::new(2, Strain::from(x)),
1.0,
len(x, 6..) & points(2..=5),
)
.alert(WEAK_JUMP_SHIFT);
}
if minor == Suit::Diamonds {
rules = rules
.rule(
Bid::new(2, Strain::Clubs),
1.3,
len(Suit::Clubs, 4..)
& points(13..)
& len(Suit::Hearts, ..4)
& len(Suit::Spades, ..4),
)
.alert(GAME_FORCE);
}
rules
}
pub(super) fn register(book: &mut Trie) {
for major in [Suit::Hearts, Suit::Spades] {
super::insert_uncontested(
book,
&[super::call(1, Strain::from(major))],
major_responses(major),
);
}
for minor in [Suit::Clubs, Suit::Diamonds] {
super::insert_uncontested(
book,
&[super::call(1, Strain::from(minor))],
minor_responses(minor),
);
}
for major in [Suit::Hearts, Suit::Spades] {
let m_strain = Strain::from(major);
let splinter_suits: &[Suit] = if major == Suit::Hearts {
&[Suit::Spades, Suit::Clubs, Suit::Diamonds]
} else {
&[Suit::Clubs, Suit::Diamonds, Suit::Hearts]
};
for &x in splinter_suits {
let (level, strain) = splinter_bid(major, x);
let splinter = super::call(level, strain);
let our_calls = &[super::call(1, m_strain), splinter];
let after_splinter = Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, points(16..))
.alert(super::slam::RKCB)
.rule(Bid::new(4, m_strain), 0.5, hcp(0..));
super::insert_uncontested(book, our_calls, after_splinter);
super::slam::install_rkcb(book, our_calls, major);
}
}
for minor in [Suit::Clubs, Suit::Diamonds] {
let m_strain = Strain::from(minor);
let our_calls = &[super::call(1, m_strain), super::call(2, m_strain)];
let after_inv_raise = Rules::new()
.rule(Bid::new(2, Strain::Notrump), 1.0, hcp(12..=14) & balanced())
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(18..=19))
.rule(
Bid::new(2, Strain::Hearts),
0.8,
stopper_in(Suit::Hearts) & hcp(15..),
)
.rule(
Bid::new(2, Strain::Spades),
0.8,
stopper_in(Suit::Spades) & hcp(15..),
)
.rule(Bid::new(3, m_strain), 0.5, hcp(0..));
super::insert_uncontested(book, our_calls, after_inv_raise);
let after_2nt = Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(13..))
.rule(Bid::new(3, m_strain), 0.5, hcp(0..));
let our_calls_2nt = &[
super::call(1, m_strain),
super::call(2, m_strain),
super::call(2, Strain::Notrump),
];
super::insert_uncontested(book, our_calls_2nt, after_2nt);
let after_3nt = Rules::new()
.rule(Bid::new(4, Strain::Notrump), 1.0, points(14..))
.alert(super::slam::RKCB)
.rule(Call::Pass, 0.5, hcp(0..));
let our_calls_3nt = &[
super::call(1, m_strain),
super::call(2, m_strain),
super::call(3, Strain::Notrump),
];
super::insert_uncontested(book, our_calls_3nt, after_3nt);
super::slam::install_rkcb(book, our_calls_3nt, minor);
for major in [Suit::Hearts, Suit::Spades] {
let major_strain = Strain::from(major);
let after_major = Rules::new()
.rule(Bid::new(3, Strain::Notrump), 1.0, hcp(13..))
.rule(Bid::new(2, Strain::Notrump), 0.8, hcp(10..=12))
.rule(Bid::new(3, m_strain), 0.5, hcp(0..));
let our_calls_major = &[
super::call(1, m_strain),
super::call(2, m_strain),
super::call(2, major_strain),
];
super::insert_uncontested(book, our_calls_major, after_major);
let after_2nt_4th = Rules::new().rule(Bid::new(3, Strain::Notrump), 0.5, hcp(0..));
let our_calls_2nt_4th = &[
super::call(1, m_strain),
super::call(2, m_strain),
super::call(2, major_strain),
super::call(2, Strain::Notrump),
];
super::insert_uncontested(book, our_calls_2nt_4th, after_2nt_4th);
}
}
}