diff options
Diffstat (limited to 'webapp/src/bridge_engine.rs')
| -rw-r--r-- | webapp/src/bridge_engine.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs index 873a2f0..70fd828 100644 --- a/webapp/src/bridge_engine.rs +++ b/webapp/src/bridge_engine.rs @@ -1,4 +1,12 @@ use crate::card::Card; +use crate::card::Suit; +use anyhow::anyhow; +use log::debug; +use regex::Regex; +use std::fmt; +use std::str::FromStr; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Player { @@ -55,9 +63,174 @@ impl Turn { } } +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, EnumIter)] +pub enum ContractLevel { + One = 1, + Two, + Three, + Four, + Five, + Six, + Seven, +} + +impl fmt::Display for ContractLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", *self as u8) + } +} + +impl fmt::Debug for ContractLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl FromStr for ContractLevel { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> { + match s { + "1" => Ok(ContractLevel::One), + "2" => Ok(ContractLevel::Two), + "3" => Ok(ContractLevel::Three), + "4" => Ok(ContractLevel::Four), + "5" => Ok(ContractLevel::Five), + "6" => Ok(ContractLevel::Six), + "7" => Ok(ContractLevel::Seven), + _ => Err(anyhow!("invalid string: {}", s)), + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Bid { + level: ContractLevel, + suit: Option<Suit>, +} + +impl Bid { + pub fn all_bids() -> Vec<Bid> { + let mut result = Vec::with_capacity(7 * 5); + for level in ContractLevel::iter() { + for suit in Suit::iter() { + result.push(Bid { + level, + suit: Some(suit), + }); + } + result.push(Bid { level, suit: None }); + } + result + } +} + +impl PartialOrd<Self> for Bid { + fn partial_cmp(&self, o: &Self) -> std::option::Option<std::cmp::Ordering> { + if self.level != o.level { + return self.level.partial_cmp(&o.level); + } + if self.suit != o.suit { + if self.suit == None { + return Some(std::cmp::Ordering::Greater); + } + if o.suit == None { + return Some(std::cmp::Ordering::Less); + } + return self.suit.partial_cmp(&o.suit); + } + return Some(std::cmp::Ordering::Equal); + } +} + +impl Ord for Bid { + fn cmp(&self, o: &Self) -> std::cmp::Ordering { + self.partial_cmp(o).unwrap() + } +} + +impl fmt::Display for Bid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!( + f, + "{}{}", + self.level, + self.suit + .map_or("NT".to_string(), |suit| format!("{}", suit)) + ) + } +} + +impl fmt::Debug for Bid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl FromStr for Bid { + type Err = anyhow::Error; + fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> { + lazy_static::lazy_static! { + static ref RE: Regex = Regex::new(r#"\s*(.[0-9]*)\s*(.*)"#).unwrap(); + }; + let caps = RE.captures(s).ok_or(anyhow!("invalid bid: {}", s))?; + debug!("caps: {:?}", caps); + let level = caps[1].parse()?; + let suit = match &caps[2] { + "NT" => None, + x => Some(x.parse()?), + }; + Ok(Bid { level, suit }) + } +} + #[cfg(test)] mod tests { use super::*; + use log::info; + + #[test] + fn bid_conversion() { + crate::tests::test_setup(); + let bid1d = Bid { + level: ContractLevel::One, + suit: Some(Suit::Diamond), + }; + assert_eq!("1♢", format!("{}", bid1d)); + assert_eq!("1♢", format!("{:?}", bid1d)); + assert_eq!(bid1d, Bid::from_str("1D").unwrap()); + + let mut checked_bids = 0; + for bid in Bid::all_bids() { + assert_eq!(bid, Bid::from_str(format!("{}", bid).as_str()).unwrap()); + checked_bids += 1; + } + assert_eq!(checked_bids, 35); + } + + #[test] + fn bid_ord() { + let bid = |s| Bid::from_str(s).unwrap(); + assert!(bid("2♦") < bid("3♦")); + assert!(bid("3♦") < bid("3♥")); + assert!(bid("1♠") < bid("2♣")); + assert!(bid("1♠") < bid("1NT")); + for bid in Bid::all_bids() { + assert_eq!(bid, bid); + } + } + + #[test] + fn contract_level_conversion() { + crate::tests::test_setup(); + assert_eq!("2", format!("{}", ContractLevel::Two)); + assert_eq!("3", format!("{:?}", ContractLevel::Three)); + + let result = ContractLevel::from_str("8"); + info!("{:?}", result); + assert!(result.unwrap_err().to_string().contains("invalid")); + assert_eq!(ContractLevel::Seven, "7".parse().unwrap()); + } #[test] fn next_player() { |
