#[cfg(test)]
mod test;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Strain {
Clubs,
Diamonds,
Hearts,
Spades,
Notrump,
}
impl Strain {
#[must_use]
#[inline]
pub const fn is_minor(self) -> bool {
matches!(self, Self::Clubs | Self::Diamonds)
}
#[must_use]
#[inline]
pub const fn is_major(self) -> bool {
matches!(self, Self::Hearts | Self::Spades)
}
#[must_use]
#[inline]
pub const fn is_suit(self) -> bool {
!matches!(self, Self::Notrump)
}
#[must_use]
#[inline]
pub const fn is_notrump(self) -> bool {
matches!(self, Self::Notrump)
}
pub const ASC: [Self; 5] = [
Self::Clubs,
Self::Diamonds,
Self::Hearts,
Self::Spades,
Self::Notrump,
];
pub const DESC: [Self; 5] = [
Self::Notrump,
Self::Spades,
Self::Hearts,
Self::Diamonds,
Self::Clubs,
];
pub const SYS: [Self; 5] = [
Self::Spades,
Self::Hearts,
Self::Diamonds,
Self::Clubs,
Self::Notrump,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bid {
pub level: u8,
pub strain: Strain,
}
impl Bid {
#[must_use]
#[inline]
pub const fn new(level: u8, strain: Strain) -> Self {
Self { level, strain }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Call {
Pass,
Double,
Redouble,
Bid(Bid),
}
impl From<Bid> for Call {
#[inline]
fn from(bid: Bid) -> Self {
Self::Bid(bid)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Penalty {
None,
Doubled,
Redoubled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Contract {
pub bid: Bid,
pub penalty: Penalty,
}
impl From<Bid> for Contract {
#[inline]
fn from(bid: Bid) -> Self {
Self {
bid,
penalty: Penalty::None,
}
}
}
#[inline]
const fn compute_doubled_penalty(undertricks: i32, vulnerable: bool) -> i32 {
match undertricks + vulnerable as i32 {
1 => 100,
2 => {
if vulnerable {
200
} else {
300
}
}
many => 300 * many - 400,
}
}
impl Contract {
#[must_use]
#[inline]
pub const fn new(level: u8, strain: Strain, penalty: Penalty) -> Self {
Self {
bid: Bid::new(level, strain),
penalty,
}
}
#[must_use]
#[inline]
pub const fn contract_points(self) -> i32 {
let level = self.bid.level as i32;
let per_trick = self.bid.strain.is_minor() as i32 * -10 + 30;
let notrump = self.bid.strain.is_notrump() as i32 * 10;
(per_trick * level + notrump) << (self.penalty as u8)
}
#[must_use]
#[inline]
pub const fn score(self, tricks: u8, vulnerable: bool) -> i32 {
let overtricks = tricks as i32 - self.bid.level as i32 - 6;
if overtricks >= 0 {
let base = self.contract_points();
let game = if base < 100 {
50
} else if vulnerable {
500
} else {
300
};
let doubled = self.penalty as i32 * 50;
let slam = match self.bid.level {
6 => (vulnerable as i32 + 2) * 250,
7 => (vulnerable as i32 + 2) * 500,
_ => 0,
};
let per_trick = match self.penalty {
Penalty::None => self.bid.strain.is_minor() as i32 * -10 + 30,
penalty => penalty as i32 * if vulnerable { 200 } else { 100 },
};
base + game + slam + doubled + overtricks * per_trick
} else {
match self.penalty {
Penalty::None => overtricks * if vulnerable { 100 } else { 50 },
penalty => penalty as i32 * -compute_doubled_penalty(-overtricks, vulnerable),
}
}
}
}