1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
#[cfg(test)]
mod test;
/// Denomination, a suit or notrump
///
/// We choose this representation over `Option<Suit>` because we are not sure if
/// the latter can be optimized to a single byte.
///
/// The order of the suits deviates from [`dds`][dds], but this order provides
/// natural ordering by deriving [`PartialOrd`] and [`Ord`].
///
/// [dds]: https://github.com/dds-bridge/dds
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum Strain {
/// ♣
Clubs,
/// ♦
Diamonds,
/// ♥
Hearts,
/// ♠
Spades,
/// NT, the strain not proposing a trump suit
Notrump,
}
impl Strain {
/// Whether this strain is a minor suit (clubs or diamonds)
#[must_use]
pub const fn is_minor(self) -> bool {
matches!(self, Self::Clubs | Self::Diamonds)
}
/// Whether this strain is a major suit (hearts or spades)
#[must_use]
pub const fn is_major(self) -> bool {
matches!(self, Self::Hearts | Self::Spades)
}
/// Whether this strain is a suit
#[must_use]
pub const fn is_suit(self) -> bool {
!matches!(self, Self::Notrump)
}
/// Whether this strain is notrump
#[must_use]
pub const fn is_notrump(self) -> bool {
matches!(self, Self::Notrump)
}
/// Helper constant for iteration over all strains
pub const ALL: [Self; 5] = [
Self::Clubs,
Self::Diamonds,
Self::Hearts,
Self::Spades,
Self::Notrump,
];
/// Helper constant for iteration over all suits
pub const SUITS: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
}
/// A call that proposes a contract
///
/// The order of the fields ensures natural ordering by deriving [`PartialOrd`]
/// and [`Ord`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Bid {
/// The number of tricks (adding the book of 6 tricks) to take to fulfill
/// the contract
pub level: u8,
/// The strain of the contract
pub strain: Strain,
}
impl Bid {
/// Create a bid from level and strain
#[must_use]
pub const fn new(level: u8, strain: Strain) -> Self {
Self { level, strain }
}
}
/// Any legal announcement in the bidding stage
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Call {
/// A call indicating no wish to change the contract
Pass,
/// A call increasing penalties and bonuses for the contract
Double,
/// A call doubling the score to the previous double
Redouble,
/// A call proposing a contract
Bid(Bid),
}
impl From<Bid> for Call {
fn from(bid: Bid) -> Self {
Self::Bid(bid)
}
}
/// Penalty inflicted on a contract
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Penalty {
/// No penalty
None,
/// Penalty by [`Call::Double`]
Doubled,
/// Penalty by [`Call::Redouble`]
Redoubled,
}
/// The statement of the pair winning the bidding that they will take at least
/// the number of tricks (in addition to the book of 6 tricks), and the strain
/// denotes the trump suit.
#[derive(Debug, Clone, Copy)]
pub struct Contract {
/// The basic part of a contract
pub bid: Bid,
/// The penalty inflicted on the contract
pub penalty: Penalty,
}
impl From<Bid> for Contract {
fn from(bid: Bid) -> Self {
Self {
bid,
penalty: Penalty::None,
}
}
}
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 {
/// Create a contract from level, strain, and penalty
#[must_use]
pub const fn new(level: u8, strain: Strain, penalty: Penalty) -> Self {
Self {
bid: Bid::new(level, strain),
penalty,
}
}
/// Base score for making this contract
///
/// <https://en.wikipedia.org/wiki/Bridge_scoring#Contract_points>
#[must_use]
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)
}
/// Score for this contract given the number of taken tricks and
/// vulnerability
///
/// The score is positive if the declarer makes the contract, and negative
/// if the declarer fails.
#[must_use]
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),
}
}
}
}