use super::context::Context;
use super::trie::Classifier;
use contract_bridge::Bid;
use contract_bridge::auction::Call;
use core::fmt;
use std::borrow::Cow;
use std::sync::Arc;
pub trait Guard: Send + Sync {
fn admits(&self, context: &Context<'_>, suffix: &[Call]) -> bool;
fn describe(&self) -> Option<String> {
None
}
}
impl fmt::Debug for dyn Guard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Guard({:p})", &self)
}
}
impl<F> Guard for F
where
F: Fn(&Context<'_>, &[Call]) -> bool + Send + Sync,
{
fn admits(&self, context: &Context<'_>, suffix: &[Call]) -> bool {
self(context, suffix)
}
}
pub const fn guard<F>(f: F) -> F
where
F: Fn(&Context<'_>, &[Call]) -> bool + Send + Sync,
{
f
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Always;
impl Guard for Always {
fn admits(&self, _: &Context<'_>, _: &[Call]) -> bool {
true
}
fn describe(&self) -> Option<String> {
Some("(always)".into())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Undisturbed;
impl Guard for Undisturbed {
fn admits(&self, context: &Context<'_>, _: &[Call]) -> bool {
context.undisturbed()
}
fn describe(&self) -> Option<String> {
Some("(undisturbed)".into())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FirstIs(
pub Call,
);
impl Guard for FirstIs {
fn admits(&self, _: &Context<'_>, suffix: &[Call]) -> bool {
suffix.first() == Some(&self.0)
}
fn describe(&self) -> Option<String> {
Some(format!("{} …", self.0))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OvercallAtMost(
pub Bid,
);
impl Guard for OvercallAtMost {
fn admits(&self, _: &Context<'_>, suffix: &[Call]) -> bool {
matches!(*suffix, [Call::Bid(bid)] if bid <= self.0)
}
fn describe(&self) -> Option<String> {
Some(format!("(overcall ≤{})", self.0))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SuffixIs(
pub Vec<Call>,
);
impl Guard for SuffixIs {
fn admits(&self, _: &Context<'_>, suffix: &[Call]) -> bool {
suffix == self.0.as_slice()
}
fn describe(&self) -> Option<String> {
Some(contract_bridge::auction::display_calls(&self.0).to_string())
}
}
pub trait Rewrite: Send + Sync {
fn rewrite(&self, auction: &[Call], depth: usize) -> Option<Vec<Call>>;
fn describe(&self) -> Option<String> {
None
}
}
impl fmt::Debug for dyn Rewrite {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Rewrite({:p})", &self)
}
}
impl<F> Rewrite for F
where
F: Fn(&[Call], usize) -> Option<Vec<Call>> + Send + Sync,
{
fn rewrite(&self, auction: &[Call], depth: usize) -> Option<Vec<Call>> {
self(auction, depth)
}
}
pub const fn rewriter<F>(f: F) -> F
where
F: Fn(&[Call], usize) -> Option<Vec<Call>> + Send + Sync,
{
f
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReplaceNext(
pub Call,
);
impl Rewrite for ReplaceNext {
fn rewrite(&self, auction: &[Call], depth: usize) -> Option<Vec<Call>> {
(depth < auction.len()).then(|| {
let mut rewritten = auction.to_vec();
rewritten[depth] = self.0;
rewritten
})
}
fn describe(&self) -> Option<String> {
Some(format!("systems on: their call is treated as {}", self.0))
}
}
struct Described<T> {
inner: T,
label: Cow<'static, str>,
}
impl<G: Guard> Guard for Described<G> {
fn admits(&self, context: &Context<'_>, suffix: &[Call]) -> bool {
self.inner.admits(context, suffix)
}
fn describe(&self) -> Option<String> {
Some(self.label.clone().into_owned())
}
}
impl<R: Rewrite> Rewrite for Described<R> {
fn rewrite(&self, auction: &[Call], depth: usize) -> Option<Vec<Call>> {
self.inner.rewrite(auction, depth)
}
fn describe(&self) -> Option<String> {
Some(self.label.clone().into_owned())
}
}
pub fn described_guard(
label: impl Into<Cow<'static, str>>,
inner: impl Guard + 'static,
) -> impl Guard + 'static {
Described {
inner,
label: label.into(),
}
}
pub fn described_rewrite(
label: impl Into<Cow<'static, str>>,
inner: impl Rewrite + 'static,
) -> impl Rewrite + 'static {
Described {
inner,
label: label.into(),
}
}
#[derive(Clone, Debug)]
pub enum Fallback {
Classify(Arc<dyn Classifier>),
Rebase(Arc<dyn Rewrite>),
}
impl Fallback {
pub fn classify(classifier: impl Classifier + 'static) -> Self {
Self::Classify(Arc::new(classifier))
}
pub fn rebase(rewrite: impl Rewrite + 'static) -> Self {
Self::Rebase(Arc::new(rewrite))
}
}
#[cfg(test)]
mod tests {
use super::*;
use contract_bridge::Strain;
use contract_bridge::auction::RelativeVulnerability;
const fn bid(level: u8, strain: Strain) -> Call {
Call::Bid(Bid {
level: contract_bridge::Level::new(level),
strain,
})
}
fn empty_context() -> Context<'static> {
Context::new(RelativeVulnerability::NONE, &[])
}
#[test]
fn test_first_is() {
let guard = FirstIs(Call::Double);
let context = empty_context();
assert!(guard.admits(&context, &[Call::Double]));
assert!(guard.admits(&context, &[Call::Double, Call::Pass]));
assert!(!guard.admits(&context, &[Call::Pass, Call::Double]));
assert!(!guard.admits(&context, &[]));
}
#[test]
fn test_overcall_at_most() {
let guard = OvercallAtMost(Bid::new(2, Strain::Spades));
let context = empty_context();
assert!(guard.admits(&context, &[bid(1, Strain::Spades)]));
assert!(guard.admits(&context, &[bid(2, Strain::Spades)]));
assert!(!guard.admits(&context, &[bid(2, Strain::Notrump)]));
assert!(!guard.admits(&context, &[Call::Double]));
assert!(!guard.admits(&context, &[bid(1, Strain::Spades), Call::Pass]));
}
#[test]
fn test_suffix_is() {
let guard = SuffixIs(vec![bid(2, Strain::Hearts), Call::Double, Call::Pass]);
let context = empty_context();
assert!(guard.admits(
&context,
&[bid(2, Strain::Hearts), Call::Double, Call::Pass]
));
assert!(!guard.admits(&context, &[bid(2, Strain::Hearts), Call::Double]));
assert!(!guard.admits(&context, &[]));
assert!(SuffixIs(vec![]).admits(&context, &[]));
assert_eq!(guard.describe().expect("self-describing"), "2♥ X -");
}
#[test]
fn test_described_wrappers() {
let guard = described_guard("(their overcall) cue -", Always);
let context = empty_context();
assert!(guard.admits(&context, &[Call::Double]));
assert_eq!(guard.describe().as_deref(), Some("(their overcall) cue -"));
let rewrite = described_rewrite("systems on", ReplaceNext(Call::Pass));
assert_eq!(rewrite.describe().as_deref(), Some("systems on"));
assert_eq!(
rewrite.rewrite(&[bid(1, Strain::Notrump), Call::Double], 1),
Some(vec![bid(1, Strain::Notrump), Call::Pass]),
);
}
#[test]
fn test_replace_next() {
let rewrite = ReplaceNext(Call::Pass);
let auction = [bid(1, Strain::Notrump), Call::Double, Call::Pass];
assert_eq!(
rewrite.rewrite(&auction, 1),
Some(vec![bid(1, Strain::Notrump), Call::Pass, Call::Pass]),
);
assert_eq!(rewrite.rewrite(&auction, 3), None);
}
}