use crate::Strain;
use core::fmt::{self, Write as _};
use core::num::NonZero;
use core::str::FromStr;
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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
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()
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(f)
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid level: expected 1-7")]
pub struct ParseLevelError;
impl FromStr for Level {
type Err = ParseLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.as_bytes();
match (s.len(), s.first()) {
(1, Some(b'1'..=b'7')) => Ok(Self::new(s[0] - b'0')),
_ => Err(ParseLevelError),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bid {
pub level: Level,
pub strain: Strain,
}
impl Bid {
#[must_use]
#[inline]
pub const fn new(level: u8, strain: Strain) -> Self {
Self {
level: Level::new(level),
strain,
}
}
}
impl fmt::Display for Bid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.level, self.strain)
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid bid: expected <level><strain>, e.g. 1N, 3♠, 7NT")]
pub struct ParseBidError;
impl FromStr for Bid {
type Err = ParseBidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 2 {
return Err(ParseBidError);
}
let (level, strain) = s.split_at(1);
let level: Level = level.parse().map_err(|_| ParseBidError)?;
let strain: Strain = strain.parse().map_err(|_| ParseBidError)?;
Ok(Self { level, strain })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[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, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid penalty: expected '', 'x'/'X', or 'xx'/'XX'")]
pub struct ParsePenaltyError;
impl FromStr for Penalty {
type Err = ParsePenaltyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"" => Ok(Self::Undoubled),
"x" => Ok(Self::Doubled),
"xx" => Ok(Self::Redoubled),
_ => Err(ParsePenaltyError),
}
}
}
#[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 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.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),
}
}
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid contract: expected <bid><penalty>, e.g. 1♥, 3♠x, 7NTxx")]
pub struct ParseContractError;
impl FromStr for Contract {
type Err = ParseContractError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let x_count = s
.bytes()
.rev()
.take_while(|c| b'x'.eq_ignore_ascii_case(c))
.take(3)
.count();
let penalty = match x_count {
0 => Penalty::Undoubled,
1 => Penalty::Doubled,
2 => Penalty::Redoubled,
_ => return Err(ParseContractError),
};
s[..s.len() - x_count]
.parse()
.map_or(Err(ParseContractError), |bid| Ok(Self { bid, penalty }))
}
}
#[cfg(feature = "serde")]
mod serde_string {
use super::{Bid, Contract};
use core::fmt::Display;
use core::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
fn serialize<T: Display, S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(value)
}
fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
impl Serialize for Bid {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
serialize(self, s)
}
}
impl<'de> Deserialize<'de> for Bid {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
deserialize(d)
}
}
impl Serialize for Contract {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
serialize(self, s)
}
}
impl<'de> Deserialize<'de> for Contract {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
deserialize(d)
}
}
}