use crate::card::{Card, Deal, Suit}; use anyhow::{anyhow, bail}; use log::{debug, error}; use regex::Regex; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; use strum::{EnumCount, IntoEnumIterator}; use strum_macros::{EnumCount as EnumCountMacro, EnumIter, FromRepr}; #[derive(PartialEq, Eq, Clone, Copy, Debug, FromRepr, EnumCountMacro)] #[repr(u8)] pub enum Player { West = 0, North, East, South, } impl Player { pub fn next(&self) -> Self { self.many_next(1) } pub fn many_next(self, i: usize) -> Self { Player::from_repr(((self as usize + i) % Player::COUNT) as u8).unwrap() } pub fn short_str(&self) -> &str { match self { Self::West => "W", Self::North => "N", Self::East => "E", Self::South => "W", } } pub fn get_cards<'a>(&self, deal: &'a mut Deal) -> &'a mut Vec { match self { Self::West => &mut deal.west, Self::North => &mut deal.north, Self::East => &mut deal.east, Self::South => &mut deal.south, } } } #[derive(PartialEq, Eq, Debug, Clone)] pub struct Trick { pub leader: Player, pub cards_played: Vec, } impl Trick { pub fn winner(&self) -> Player { error!("XXX: Returning incorrect result for winner"); self.leader } } #[derive(PartialEq, Eq, Debug, Clone)] pub struct PlayTurn { trick: Trick, } #[derive(PartialEq, Eq, Debug)] pub enum PlayResult { InProgress(PlayTurn), Trick(Trick), } impl PlayTurn { pub fn new(p: Player) -> PlayTurn { PlayTurn { trick: Trick { leader: p, cards_played: Vec::with_capacity(4), }, } } pub fn play(mut self: PlayTurn, card: Card) -> PlayResult { self.trick.cards_played.push(card); if self.trick.cards_played.len() >= 4 { return PlayResult::Trick(self.trick); } PlayResult::InProgress(self) } pub fn next_player(&self) -> Player { self.trick.leader.many_next(self.trick.cards_played.len()) } } #[derive(Clone, Debug)] pub struct PlayingDeal { deal: Deal, tricks_played: Vec, in_progress: PlayTurn, } #[derive(Debug)] pub enum PlayingDealResult { InProgress(PlayingDeal), PlayFinished(Vec), } impl PlayingDeal { pub fn new(leader: Player, deal: Deal) -> PlayingDeal { PlayingDeal { deal, tricks_played: Vec::with_capacity(13), in_progress: PlayTurn::new(leader), } } pub fn deal(&self) -> &Deal { &self.deal } pub fn play(mut self: Self, card: Card) -> Result { let player = self.in_progress.next_player(); let player_cards = player.get_cards(&mut self.deal); debug!( "Next player is {:?}, playing card {} from {:?}", player, card, player_cards ); let i = player_cards.iter().position(|&c| c == card).ok_or(anyhow!( "{:?} does not have {}", player, card ))?; player_cards.remove(i); Ok(match self.in_progress.play(card) { PlayResult::InProgress(turn) => PlayingDealResult::InProgress(Self { in_progress: turn, ..self }), PlayResult::Trick(trick) => PlayingDealResult::InProgress(Self { in_progress: PlayTurn::new(trick.winner()), tricks_played: { let mut tricks = self.tricks_played; tricks.push(trick); tricks }, deal: self.deal, }), }) } } #[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::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 enum Bid { Pass, Double, Redouble, Raise(Raise), } impl Bid { pub fn as_raise(&self) -> Option { match self { Bid::Raise(raise) => Some(*raise), _ => None, } } } impl fmt::Display for Bid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match self { Bid::Pass => write!(f, "Pass"), Bid::Double => write!(f, "Double"), Bid::Redouble => write!(f, "Redouble"), Bid::Raise(x) => write!(f, "{}", x), } } } impl fmt::Debug for Bid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match self { Bid::Pass => write!(f, "Pass"), Bid::Double => write!(f, "Double"), Bid::Redouble => write!(f, "Redouble"), Bid::Raise(x) => write!(f, "Raise({})", x), } } } impl FromStr for Bid { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result::Err> { match s.trim().to_ascii_lowercase().as_str() { "pass" => Ok(Bid::Pass), "double" => Ok(Bid::Double), "redouble" => Ok(Bid::Redouble), x => Ok(Bid::Raise(x.parse()?)), } } } #[derive(PartialEq, Eq, Clone, Copy)] pub struct Raise { pub level: ContractLevel, pub suit: Option, } impl Raise { pub fn all_raises() -> Vec { let mut result = Vec::with_capacity(7 * 5); for level in ContractLevel::iter() { for suit in Suit::iter() { result.push(Raise { level, suit: Some(suit), }); } result.push(Raise { level, suit: None }); } result } } impl PartialOrd for Raise { fn partial_cmp(&self, o: &Self) -> Option { if self.level != o.level { return self.level.partial_cmp(&o.level); } if self.suit != o.suit { if self.suit == None { return Some(Ordering::Greater); } if o.suit == None { return Some(Ordering::Less); } return self.suit.partial_cmp(&o.suit); } return Some(Ordering::Equal); } } impl Ord for Raise { fn cmp(&self, o: &Self) -> std::cmp::Ordering { self.partial_cmp(o).unwrap() } } impl fmt::Display for Raise { 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 Raise { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", self) } } impl FromStr for Raise { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result::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 raise: {}", s))?; debug!("caps: {:?}", caps); let level = caps[1].parse()?; let suit = match caps[2].to_ascii_uppercase().as_str() { "NT" => None, x => Some(x.parse()?), }; Ok(Raise { level, suit }) } } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum ContractModifier { None, Doubled, Redoubled, } impl fmt::Display for ContractModifier { fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { match self { ContractModifier::None => Ok(()), ContractModifier::Doubled => write!(f, "x"), ContractModifier::Redoubled => write!(f, "xx"), } } } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct Contract { declarer: Player, highest_bid: Raise, modifier: ContractModifier, } impl fmt::Display for Contract { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { write!( f, "{}{}{}", self.highest_bid, self.declarer.short_str(), self.modifier ) } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct Bidding { pub dealer: Player, pub bids: Vec, } impl Bidding { pub fn new(dealer: Player) -> Bidding { Bidding { dealer, bids: vec![], } } fn declarer(&self) -> Player { // Bids are: [..., winning bid, pass, pass, pass]. self.dealer.many_next(self.bids.len() - 4) } pub fn highest_bid(&self) -> Option { for bid in self.bids.iter().rev() { if let Some(raise) = bid.as_raise() { return Some(raise); } } None } fn passed_out(&self) -> bool { let mut passes = 0; for b in self.bids.iter().rev().take(3) { if b == &Bid::Pass { passes += 1 } } passes == 3 } fn contract(&self) -> Option { match self.highest_bid() { None => None, Some(highest_bid) => Some(Contract { declarer: self.declarer(), highest_bid, modifier: ContractModifier::None, }), } } pub fn bid(mut self, bid: Bid) -> Result { // TODO: Need logic for double and redouble here. if bid.as_raise().is_some() && bid.as_raise() <= self.highest_bid() { bail!( "bid too low: {:?} <= {:?}", bid.as_raise(), self.highest_bid() ); } self.bids.push(bid); if self.passed_out() { Ok(BiddingResult::Contract(self.contract(), self)) } else { Ok(BiddingResult::InProgress(self)) } } } #[derive(Debug, Clone)] pub enum BiddingResult { InProgress(Bidding), Contract(Option, Bidding), } impl BiddingResult { pub fn new(dealer: Player) -> Self { BiddingResult::InProgress(Bidding::new(dealer)) } pub fn bidding(&self) -> &Bidding { match self { BiddingResult::InProgress(bidding) => bidding, BiddingResult::Contract(_, bidding) => bidding, } } } #[cfg(test)] mod tests { use super::*; use log::info; fn as_bidding(r: BiddingResult) -> Bidding { match r { BiddingResult::InProgress(bidding) => bidding, _ => panic!("expected BiddingResult::InProgress(): {:?}", r), } } fn as_contract(r: BiddingResult) -> Option { match r { BiddingResult::Contract(contract, _) => contract, _ => panic!("expected BiddingResult::Contract(): {:?}", r), } } #[test] fn bidding() { crate::tests::test_setup(); let bidding = Bidding::new(Player::South); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); let bidding = as_bidding(bidding.bid(Bid::Raise("1♦".parse().unwrap())).unwrap()); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); let contract = as_contract(bidding.bid(Bid::Pass).unwrap()); assert_eq!( Some(Contract { declarer: Player::West, highest_bid: "1♦".parse().unwrap(), modifier: ContractModifier::None }), contract ); } #[test] fn bid_conversion() { crate::tests::test_setup(); let bid1d = Raise { level: ContractLevel::One, suit: Some(Suit::Diamond), }; assert_eq!("1♢", format!("{}", bid1d)); assert_eq!("1♢", format!("{:?}", bid1d)); assert_eq!(bid1d, Raise::from_str("1D").unwrap()); assert_eq!(Bid::Pass, Bid::from_str("pass").unwrap()); let mut checked_raises = 0; for bid in Raise::all_raises() { assert_eq!(bid, Raise::from_str(format!("{}", bid).as_str()).unwrap()); assert_eq!( Bid::Raise(bid), Bid::from_str(format!("{}", bid).as_str()).unwrap() ); checked_raises += 1; } assert_eq!(checked_raises, 35); } #[test] fn fmt_contract() { assert_eq!( format!( "{}", Contract { declarer: Player::West, highest_bid: "1♥".parse().unwrap(), modifier: ContractModifier::None } ), "1♡W" ); assert_eq!( format!( "{}", Contract { declarer: Player::East, highest_bid: "1♥".parse().unwrap(), modifier: ContractModifier::Doubled } ), "1♡Ex" ); } #[test] fn bid_ord() { let bid = |s| Raise::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 Raise::all_raises() { 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() { let next_players = vec![Player::North, Player::East, Player::South, Player::West] .iter() .map(Player::next) .collect::>(); assert_eq!( next_players, vec![Player::East, Player::South, Player::West, Player::North] ); } #[test] fn many_next_player() { assert_eq!(Player::South, Player::South.many_next(4 * 1234567890)); } fn as_turn(p: PlayResult) -> PlayTurn { if let PlayResult::InProgress(t) = p { t } else { panic!("expected PlayResult::InProgress(): {:?}", p); } } fn as_trick(p: PlayResult) -> Trick { if let PlayResult::Trick(t) = p { t } else { panic!("expected PlayResult::Trick(): {:?}", p); } } #[test] fn play_turn() { let turn = PlayTurn::new(Player::South); assert_eq!(turn.next_player(), Player::South); let turn = as_turn(turn.play("♣4".parse().unwrap())); assert_eq!(turn.next_player(), Player::West); let turn = as_turn(turn.play("♥A".parse().unwrap())); assert_eq!(turn.next_player(), Player::North); let turn = as_turn(turn.play("♣4".parse().unwrap())); assert_eq!(turn.next_player(), Player::East); let trick = as_trick(turn.play("♣A".parse().unwrap())); assert_eq!( trick, Trick { leader: Player::South, cards_played: ["♣4", "♥A", "♣4", "♣A"] .into_iter() .map(|c| c.parse().unwrap()) .collect() } ); } fn mkcard(s: &str) -> Card { Card::from_str(s).unwrap() } fn mkcards(s: &str) -> Vec { s.split(" ").map(mkcard).collect() } fn _example_deal() -> Deal { Deal { west: mkcards("♠5 ♢10 ♡K ♣4 ♡J ♣5 ♢5 ♠9 ♢3 ♠2 ♣2 ♡4 ♠Q"), north: mkcards("♢Q ♡9 ♠7 ♠8 ♠A ♡A ♡5 ♠6 ♢9 ♣3 ♡3 ♣9 ♢J"), east: mkcards("♣10 ♡7 ♢A ♣6 ♡8 ♣Q ♠K ♡10 ♣K ♠3 ♡Q ♣J ♢4"), south: mkcards("♢K ♡6 ♣8 ♢6 ♢7 ♢8 ♣A ♡2 ♣7 ♠10 ♠4 ♠J ♢2"), } } fn mini_deal() -> Deal { Deal { west: mkcards("♢A ♡Q"), north: mkcards("♢Q ♡9"), east: mkcards("♢7 ♡K"), south: mkcards("♢9 ♠9"), } } fn as_playing_hand(result: PlayingDealResult) -> PlayingDeal { match result { PlayingDealResult::InProgress(r) => r, PlayingDealResult::PlayFinished(_) => { panic!("expected PlayingDealResult::InProgress(): {:?}", result) } } } #[test] fn play_hand() { let deal = PlayingDeal::new(Player::West, mini_deal()); assert_eq!(deal.tricks_played, vec!()); { let err = deal.clone().play(mkcard("♥9")).unwrap_err().to_string(); assert_eq!(err, "West does not have ♡9"); } let deal = as_playing_hand(deal.play(mkcard("♢A")).unwrap()); assert_eq!(deal.in_progress.trick.cards_played, vec!(mkcard("♢A"))); let deal = as_playing_hand(deal.play(mkcard("♢Q")).unwrap()); let deal = as_playing_hand(deal.play(mkcard("♥K")).unwrap()); let deal = as_playing_hand(deal.play(mkcard("♢9")).unwrap()); assert_eq!(deal.in_progress.trick.cards_played, []); assert_eq!( deal.tricks_played, vec!(Trick { leader: Player::West, cards_played: mkcards("♢A ♢Q ♡K ♢9"), }) ); } }