use crate::card::{Card, Deal, Suit, RankOrder}; use serde::{Deserialize, Serialize}; use anyhow::{anyhow, bail}; use log::{error, info}; 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}; pub const SUIT_DISPLAY_ORDER: [Suit; 4] = [Suit::Diamond, Suit::Club, Suit::Heart, Suit::Spade]; #[derive(PartialEq, Eq, Clone, Copy, Debug, FromRepr, EnumCountMacro, Serialize, Deserialize, EnumIter)] #[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 Deal) -> &'a Vec { match self { Self::West => &deal.west, Self::North => &deal.north, Self::East => &deal.east, Self::South => &deal.south, } } pub fn get_cards_mut<'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, Serialize, Deserialize)] 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, Serialize, Deserialize)] pub struct TurnInPlay { trick: Trick, } #[derive(PartialEq, Eq, Debug)] pub enum TurnInPlayResult { InProgress(TurnInPlay), Trick(Trick), } impl TurnInPlay { pub fn new(p: Player) -> TurnInPlay { TurnInPlay { trick: Trick { leader: p, cards_played: Vec::with_capacity(4), }, } } pub fn suit(&self) -> Option { self.trick .cards_played .iter() .next() .map(|&Card(suit, _)| suit) } pub fn leader(&self) -> Player { self.trick.leader } pub fn cards_played(&self) -> &[Card] { &self.trick.cards_played[..] } pub fn play(mut self: TurnInPlay, card: Card) -> TurnInPlayResult { self.trick.cards_played.push(card); if self.trick.cards_played.len() >= 4 { return TurnInPlayResult::Trick(self.trick); } TurnInPlayResult::InProgress(self) } pub fn next_player(&self) -> Player { self.trick.leader.many_next(self.trick.cards_played.len()) } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct DealInPlay { deal: Deal, tricks_played: Vec, in_progress: TurnInPlay, } #[derive(Debug)] pub enum DealInPlayResult { InProgress(DealInPlay), PlayFinished(Vec), } impl DealInPlay { pub fn new(leader: Player, deal: Deal) -> DealInPlay { DealInPlay { deal, tricks_played: Vec::with_capacity(13), in_progress: TurnInPlay::new(leader), } } pub fn tricks(&self) -> &Vec { &self.tricks_played } pub fn trick_in_play(&self) -> &TurnInPlay { &self.in_progress } 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(&mut self.deal); info!( "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) { TurnInPlayResult::InProgress(turn) => DealInPlayResult::InProgress(Self { in_progress: turn, ..self }), TurnInPlayResult::Trick(trick) => DealInPlayResult::InProgress(Self { in_progress: TurnInPlay::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, Serialize, Deserialize)] 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, Serialize, Deserialize)] 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, Serialize, Deserialize)] 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))?; info!("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, Serialize, Deserialize)] 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, Serialize, Deserialize)] 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, Serialize, Deserialize)] 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, } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum GameState { Bidding { dealer: Player, deal: Deal, }, PassedOut { dealer: Player, deal: Deal, bidding: Bidding, }, Play { dealer: Player, playing_deal: DealInPlay, contract: Contract, bidding: Bidding, }, } impl GameState { pub fn deal(&self) -> &Deal { match self { Self::Bidding { deal, .. } => deal, Self::PassedOut { deal, .. } => deal, Self::Play { playing_deal, .. } => &playing_deal.deal(), } } pub fn dealer(&self) -> Player { match *self { Self::Bidding { dealer, .. } => dealer, Self::PassedOut { dealer, .. } => dealer, Self::Play { dealer, .. } => dealer, } } } pub fn deal() -> Deal { let mut rng = rand::thread_rng(); let mut deal = crate::card::deal(&mut rng); deal.sort(&SUIT_DISPLAY_ORDER, RankOrder::Descending); deal } #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct TableView { pub dealer: Player, pub player_position: Player, pub hand: Vec, } impl TableView { pub fn from_game_state(game_state: &GameState, player_position: Player) -> Self { TableView { dealer: game_state.dealer(), player_position, hand: player_position.get_cards(game_state.deal()).clone(), } } } #[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: TurnInPlayResult) -> TurnInPlay { if let TurnInPlayResult::InProgress(t) = p { t } else { panic!("expected PlayResult::InProgress(): {:?}", p); } } fn as_trick(p: TurnInPlayResult) -> Trick { if let TurnInPlayResult::Trick(t) = p { t } else { panic!("expected PlayResult::Trick(): {:?}", p); } } #[test] fn play_turn() { let turn = TurnInPlay::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() } ); } #[test] fn lead_suit() { let turn = TurnInPlay::new(Player::South); assert_eq!(turn.suit(), None); let turn = as_turn(turn.play("♣4".parse().unwrap())); assert_eq!(turn.suit(), Some("♣".parse().unwrap())); } 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"), } } #[test] fn table_view() { crate::tests::test_setup(); let game_state = GameState::Bidding { dealer: Player::East, deal: mini_deal() }; info!("Game state: {game_state:?}"); for p in Player::iter() { info!("Testing view for {p:?}"); let view = TableView::from_game_state(&game_state, p); assert_eq!(view.player_position, p); assert_eq!(view.dealer, Player::East); assert_eq!(&view.hand, p.get_cards_mut(&mut mini_deal())); } } fn as_playing_hand(result: DealInPlayResult) -> DealInPlay { match result { DealInPlayResult::InProgress(r) => r, DealInPlayResult::PlayFinished(_) => { panic!("expected PlayingDealResult::InProgress(): {:?}", result) } } } #[test] fn play_hand() { let deal = DealInPlay::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"), }) ); } }