use crate::Strain;
use core::fmt::{self, Write as _};
use core::num::NonZero;
use thiserror::Error;
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[error("{0} is not a valid level (1..=7)")]
pub struct InvalidLevel(u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Level(NonZero<u8>);
impl Level {
#[must_use]
#[inline]
pub const fn new(level: u8) -> Self {
match Self::try_new(level) {
Ok(l) => l,
Err(_) => panic!("level must be in 1..=7"),
}
}
#[inline]
pub const fn try_new(level: u8) -> Result<Self, InvalidLevel> {
match NonZero::new(level) {
Some(nonzero) if level <= 7 => Ok(Self(nonzero)),
_ => Err(InvalidLevel(level)),
}
}
#[must_use]
#[inline]
pub const fn get(self) -> u8 {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bid {
pub level: Level,
pub strain: Strain,
}
impl fmt::Display for Bid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.level.get(), self.strain)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Penalty {
Undoubled,
Doubled,
Redoubled,
}
impl fmt::Display for Penalty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Undoubled => Ok(()),
Self::Doubled => f.write_char('x'),
Self::Redoubled => f.write_str("xx"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Contract {
pub bid: Bid,
pub penalty: Penalty,
}
impl fmt::Display for Contract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.bid, self.penalty)
}
}
impl From<Bid> for Contract {
fn from(bid: Bid) -> Self {
Self {
bid,
penalty: Penalty::Undoubled,
}
}
}
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 contract_points(self) -> i32 {
let level = self.bid.level.get() 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.get() 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.get() {
6 => (vulnerable as i32 + 2) * 250,
7 => (vulnerable as i32 + 2) * 500,
_ => 0,
};
let per_trick = match self.penalty {
Penalty::Undoubled => 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::Undoubled => overtricks * if vulnerable { 100 } else { 50 },
penalty => penalty as i32 * -compute_doubled_penalty(-overtricks, vulnerable),
}
}
}
}