use crate::{ card::{make_deck, sort_cards, Card, RankOrder, Suit}, play_result::MoveResult, }; use anyhow::{anyhow, bail}; use log::info; use rand::{ distributions::Standard, prelude::{Distribution, SliceRandom}, }; use regex::Regex; use serde::{Deserialize, Serialize}; 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, } } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Player { let min = Player::West as u8; let max = Player::South as u8; let v = rng.gen_range(min..=max); Player::from_repr(v).unwrap() } } #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct Trick { pub leader: Player, pub cards_played: Vec, } impl Trick { pub fn suit(&self) -> Option { self.cards_played.iter().next().map(|&Card(suit, _)| suit) } // TODO: This should be moved somewhere we can guarantee that cards are non-empty. pub fn winner(&self, trump_suit: Option) -> Player { let suit = self.suit(); let value = |c: &Card| { if Some(c.suit()) == trump_suit { 14 + c.rank() as i8 } else if Some(c.suit()) == suit { c.rank() as i8 } else { 0 } }; let (i, _) = self .cards_played .iter() .enumerate() .max_by(|&(_, c1), &(_, c2)| value(c1).cmp(&value(c2))) .unwrap(); self.leader.many_next(i) } } #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct TurnInPlay { pub 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, ) -> MoveResult { self.trick.cards_played.push(card); if self.trick.cards_played.len() >= 4 { return MoveResult::Next(self.trick); } MoveResult::Current(self) } pub fn current_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, trump_suit: Option, tricks_played: Vec, in_progress: TurnInPlay, } impl DealInPlay { pub fn new( leader: Player, trump_suit: Option, deal: Deal, ) -> DealInPlay { DealInPlay { deal, trump_suit, 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 current_player(&self) -> Player { self.in_progress.current_player() } pub fn is_dummy_visible(&self) -> bool { !self.tricks_played.is_empty() || !self.in_progress.trick.cards_played.is_empty() } pub fn play( mut self: Self, card: Card, ) -> Result>, anyhow::Error> { let player = self.current_player(); let player_cards = player.get_cards_mut(&mut self.deal); if let Some(suit) = self.in_progress.suit() { if card.suit() != suit && player_cards.iter().find(|c| c.suit() == suit).is_some() { return Err(anyhow!("Must follow {suit} suit")); } } 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) { MoveResult::Current(turn) => MoveResult::Current(Self { in_progress: turn, ..self }), MoveResult::Next(trick) => { let trick_winner = trick.winner(self.trump_suit); let mut tricks = self.tricks_played; tricks.push(trick); if player_cards.is_empty() { MoveResult::Next(tricks) } else { MoveResult::Current(Self { trump_suit: self.trump_suit, in_progress: TurnInPlay::new(trick_winner), tricks_played: 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 { pub declarer: Player, pub highest_bid: Raise, pub modifier: ContractModifier, } impl Contract { pub fn dummy(&self) -> Player { self.declarer.many_next(2) } pub fn leader(&self) -> Player { self.declarer.many_next(3) } } 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 current_bidder(&self) -> Player { self.dealer.many_next(self.bids.len()) } 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 { if self.bids.len() < 4 { return false; } 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 struct BiddingState { pub dealer: Player, pub deal: Deal, pub bidding: Bidding, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct BiddingStatePlayerView { pub dealer: Player, pub player_position: Player, pub hand: Vec, pub bidding: Bidding, } impl BiddingStatePlayerView { pub fn from_bidding_state( bidding_state: &BiddingState, player_position: Player, ) -> Self { let BiddingState { dealer, deal, bidding, } = bidding_state; Self { dealer: *dealer, player_position, hand: player_position.get_cards(deal).clone(), bidding: bidding.clone(), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct PlayState { pub deal: Deal, pub contract: Contract, pub bidding: Bidding, pub playing_deal: DealInPlay, } impl PlayState { pub fn new(deal: Deal, contract: Contract, bidding: Bidding) -> Self { let playing_deal = DealInPlay::new( contract.declarer.many_next(3), contract.highest_bid.suit, deal.clone(), ); Self { deal, contract, bidding, playing_deal, } } pub fn dealer(&self) -> Player { self.bidding.dealer } pub fn current_player(&self) -> Player { self.playing_deal.current_player() } pub fn play(self, card: Card) -> Result { Ok(match self.playing_deal.play(card)? { MoveResult::Current(playing_deal) => { PlayStateResult::InProgress(Self { playing_deal, ..self }) } MoveResult::Next(_) => PlayStateResult::PlayFinished(PlayResult), }) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct PlayResult; #[derive(Debug)] pub enum PlayStateResult { InProgress(PlayState), PlayFinished(PlayResult), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum GameState { Bidding(BiddingState), Play(PlayState), } impl Into for PlayState { fn into(self) -> GameState { GameState::Play(self) } } impl GameState { pub fn new(deal: Deal, dealer: Player) -> Self { Self::Bidding(BiddingState { dealer, deal, bidding: Bidding::new(dealer), }) } pub fn deal(&self) -> &Deal { match self { Self::Bidding(BiddingState { deal, .. }) => deal, Self::Play(PlayState { playing_deal, .. }) => &playing_deal.deal(), } } pub fn dealer(&self) -> Player { match self { Self::Bidding(BiddingState { dealer, .. }) => *dealer, Self::Play(play_state) => play_state.dealer(), } } pub fn current_player(&self) -> Player { match self { GameState::Bidding(bidding) => bidding.bidding.current_bidder(), GameState::Play(play) => play.current_player(), } } pub fn is_bidding(&self) -> bool { if let GameState::Bidding { .. } = self { true } else { false } } pub fn is_playing(&self) -> bool { if let GameState::Play { .. } = self { true } else { false } } pub fn bidding(&self) -> Result<&BiddingState, anyhow::Error> { match self { GameState::Bidding(bidding_state) => Ok(bidding_state), _ => Err(anyhow::anyhow!("not currently bidding")), } } pub fn play_state(&self) -> Result<&PlayState, anyhow::Error> { match self { GameState::Play(play_state) => Ok(play_state), _ => Err(anyhow::anyhow!("not currently playing")), } } pub fn bid( self, bid: Bid, ) -> Result, anyhow::Error> { let BiddingState { dealer, deal, bidding, } = self.bidding()?.clone(); Ok(match bidding.bid(bid)? { BiddingResult::InProgress(bidding) => { MoveResult::Current(GameState::Bidding(BiddingState { dealer, deal, bidding, })) } BiddingResult::Contract(None, _bidding) => { MoveResult::Next(GameResult) } BiddingResult::Contract(Some(contract), bidding) => { MoveResult::Current(GameState::Play(PlayState::new( deal, contract, bidding, ))) } }) } pub fn play( self, card: Card, ) -> Result, anyhow::Error> { Ok(match self.play_state()?.clone().play(card)? { PlayStateResult::InProgress(play_state) => { MoveResult::Current(play_state.into()) } PlayStateResult::PlayFinished(result) => { MoveResult::Next(result.into()) } }) } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct GameResult; impl Into for PlayResult { fn into(self) -> GameResult { GameResult } } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Deal { pub north: Vec, pub west: Vec, pub south: Vec, pub east: Vec, } impl Deal { pub fn sort(&mut self, suits: &[Suit; 4], ord: RankOrder) { sort_cards(suits, ord, self.north.as_mut_slice()); sort_cards(suits, ord, self.west.as_mut_slice()); sort_cards(suits, ord, self.south.as_mut_slice()); sort_cards(suits, ord, self.east.as_mut_slice()); } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Deal { let mut deck = make_deck(); deck.shuffle(rng); let mut deck = deck.iter(); let north = deck.by_ref().take(13).cloned().collect(); let west = deck.by_ref().take(13).cloned().collect(); let south = deck.by_ref().take(13).cloned().collect(); let east = deck.by_ref().take(13).cloned().collect(); Deal { north, west, south, east, } } } #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct PlayStatePlayerView { pub player_position: Player, pub bidding: Bidding, pub contract: Contract, // If None, the lead has not been played. pub dummy: Option>, pub declarer_tricks: u8, pub hand: Vec, pub previous_trick: Option, pub current_trick: TurnInPlay, } impl PlayStatePlayerView { pub fn from_play_state( play_state: &PlayState, player_position: Player, ) -> Self { let dummy = if play_state.playing_deal.is_dummy_visible() { Some( play_state .contract .dummy() .get_cards(&play_state.playing_deal.deal) .clone(), ) } else { None }; Self { player_position, bidding: play_state.bidding.clone(), contract: play_state.contract, dummy, declarer_tricks: 0, hand: player_position .get_cards(&play_state.playing_deal.deal) .clone(), previous_trick: play_state .playing_deal .tricks_played .last() .cloned(), current_trick: play_state.playing_deal.in_progress.clone(), } } pub fn dealer(&self) -> Player { self.bidding.dealer } pub fn current_player(&self) -> Player { self.current_trick.current_player() } } #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub enum GameStatePlayerView { Bidding(BiddingStatePlayerView), Playing(PlayStatePlayerView), } impl GameStatePlayerView { pub fn from_game_state( game_state: &GameState, player_position: Player, ) -> Self { match game_state { GameState::Bidding(bidding_state) => GameStatePlayerView::Bidding( BiddingStatePlayerView::from_bidding_state( bidding_state, player_position, ), ), GameState::Play(play_state) => GameStatePlayerView::Playing( PlayStatePlayerView::from_play_state( play_state, player_position, ), ), } } pub fn hand(&self) -> &Vec { match self { GameStatePlayerView::Bidding(BiddingStatePlayerView { hand, .. }) => hand, GameStatePlayerView::Playing(PlayStatePlayerView { hand, .. }) => hand, } } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum TableState { Unknown, Game(GameState), Result(GameResult), } impl Default for TableState { fn default() -> Self { TableState::Unknown } } impl Into for MoveResult { fn into(self) -> TableState { match self { MoveResult::Current(game) => TableState::Game(game), MoveResult::Next(result) => TableState::Result(result), } } } impl Into for GameState { fn into(self) -> TableState { TableState::Game(self) } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum TableStatePlayerView { Unknown, Game(GameStatePlayerView), Result(GameResult), } impl TableStatePlayerView { pub fn from_table_state( table: &TableState, player_position: Player, ) -> Self { match table { TableState::Unknown => TableStatePlayerView::Unknown, TableState::Game(g) => TableStatePlayerView::Game( GameStatePlayerView::from_game_state(g, player_position), ), TableState::Result(r) => TableStatePlayerView::Result(r.clone()), } } } #[cfg(test)] mod tests { use super::*; use log::info; use rand::random; 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); assert_eq!(bidding.current_bidder(), Player::South); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); assert_eq!(bidding.current_bidder(), Player::West); let bidding = as_bidding(bidding.bid(Bid::Raise("1♦".parse().unwrap())).unwrap()); assert_eq!(bidding.current_bidder(), Player::North); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); assert_eq!(bidding.current_bidder(), Player::East); let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); assert_eq!(bidding.current_bidder(), Player::South); 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)); } #[test] fn play_turn() { let turn = TurnInPlay::new(Player::South); assert_eq!(turn.current_player(), Player::South); let turn = turn.play("♣4".parse().unwrap()).current().unwrap(); assert_eq!(turn.current_player(), Player::West); let turn = turn.play("♥A".parse().unwrap()).current().unwrap(); assert_eq!(turn.current_player(), Player::North); let turn = turn.play("♣4".parse().unwrap()).current().unwrap(); assert_eq!(turn.current_player(), Player::East); let trick = turn.play("♣A".parse().unwrap()).next().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 = turn.play("♣4".parse().unwrap()).current().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("♠Q ♠9 ♠5 ♠2 ♥K ♥J ♥4 ♣5 ♣4 ♣2 ♦10 ♦5 ♦3"), north: mkcards("♠A ♠8 ♠7 ♠6 ♥A ♥9 ♥5 ♥3 ♣9 ♣3 ♦Q ♦J ♦9"), east: mkcards("♠K ♠3 ♥Q ♥10 ♥8 ♥7 ♣K ♣Q ♣J ♣10 ♣6 ♦A ♦4"), south: mkcards("♠J ♠10 ♠4 ♥6 ♥2 ♣A ♣8 ♣7 ♦K ♦8 ♦7 ♦6 ♦2"), } } fn mini_deal() -> Deal { Deal { west: mkcards("♢A ♡Q"), north: mkcards("♢Q ♡9"), east: mkcards("♢7 ♡K"), south: mkcards("♥10 ♠9"), } } #[test] fn example_deal_is_sorted() { crate::tests::test_setup(); let mut sorted = example_deal(); sorted.sort(&SUIT_DISPLAY_ORDER, RankOrder::Descending); let pp = |hand: &[Card]| { hand.iter() .map(|c| format!("{}", c)) .collect::>() .join(" ") }; info!("{}", pp(&sorted.west)); info!("{}", pp(&sorted.north)); info!("{}", pp(&sorted.east)); info!("{}", pp(&sorted.south)); assert_eq!(example_deal(), sorted); } #[test] fn game_state() { crate::tests::test_setup(); let game_state = GameState::new(mini_deal(), Player::North); assert_eq!(game_state.deal(), &mini_deal()); assert_eq!(game_state.dealer(), Player::North); assert_eq!(game_state.is_bidding(), true); info!("Start bidding with game state {game_state:#?}"); let raise = |s| Bid::Raise(Raise::from_str(s).unwrap()); let game_state = game_state.bid(raise("1H")).unwrap().current().unwrap(); let game_state = game_state .bid(Bid::Pass) .unwrap() .current() .unwrap() .bid(Bid::Pass) .unwrap() .current() .unwrap() .bid(Bid::Pass) .unwrap() .current() .unwrap(); info!("Start playing with game state {game_state:#?}"); assert_eq!(game_state.is_bidding(), false); assert_eq!(game_state.is_playing(), true); } #[test] fn table_view() { crate::tests::test_setup(); let game_state = GameState::new(mini_deal(), Player::East); info!("Game state: {game_state:?}"); for p in Player::iter() { info!("Testing view for {p:?}"); let view = GameStatePlayerView::from_game_state(&game_state, p); match view { GameStatePlayerView::Bidding(BiddingStatePlayerView { dealer, hand, .. }) => { assert_eq!(dealer, Player::East); assert_eq!(&hand, p.get_cards(&mini_deal())); } _ => panic!("expected bidding: {view:#?}"), } } } fn some_play_state() -> PlayState { crate::tests::test_setup(); let deal = random(); let raise1c = Raise { level: ContractLevel::One, suit: Some(Suit::Club), }; let contract = Contract { declarer: random(), highest_bid: raise1c, modifier: ContractModifier::Doubled, }; let bidding = Bidding { dealer: random(), bids: vec![Bid::Raise(raise1c), Bid::Pass, Bid::Pass, Bid::Pass], }; PlayState::new(deal, contract, bidding) } #[test] fn play_state() { some_play_state(); } #[test] fn play_state_player_view() { crate::tests::test_setup(); let play_state = some_play_state(); let player = random(); let player_state = PlayStatePlayerView::from_play_state(&play_state, player); assert_eq!(play_state.dealer(), player_state.dealer()); assert_eq!(player_state.player_position, player); assert_eq!(player_state.current_player(), play_state.current_player()); } fn as_playing_hand( result: MoveResult>, ) -> DealInPlay { match result { MoveResult::Current(r) => r, MoveResult::Next(_) => { panic!("expected PlayingDealResult::InProgress(): {:?}", result) } } } #[test] fn pass_out_bid() { let mut bidding = Bidding::new(random()); for _i in 0..3 { bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); assert!(!bidding.passed_out()); } bidding = match bidding.bid(Bid::Pass).unwrap() { BiddingResult::Contract(None, bidding) => bidding, _ => panic!("should be passed out"), }; assert!(bidding.passed_out()); } #[test] fn play_hand() { let deal = DealInPlay::new(Player::West, None, 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 err = deal.clone().play(mkcard("♡9")).unwrap_err().to_string(); assert_eq!(err, "Must follow ♢ suit"); } let deal = as_playing_hand(deal.play(mkcard("♢Q")).unwrap()); let deal = as_playing_hand(deal.play(mkcard("♢7")).unwrap()); let deal = as_playing_hand(deal.play(mkcard("♡10")).unwrap()); assert_eq!(deal.in_progress.trick.cards_played, []); assert_eq!( deal.tricks_played, vec!(Trick { leader: Player::West, cards_played: mkcards("♢A ♢Q ♢7 ♡10"), }) ); let trick = &deal.tricks_played[0]; assert_eq!(trick.winner(None), Player::West); assert_eq!(trick.winner(Some(Suit::Heart)), Player::South); } }