diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-11-25 17:10:17 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-11-27 17:23:11 -0500 |
commit | 685ac902e3faf4ed5a76b8c859b01f7d2e2d9ea0 (patch) | |
tree | 063eac21566c0dd83f849d7ea9cccb51a618d4de /protocol/src/bridge_engine.rs | |
parent | eeeea174157202cb812fab04844292cbd96bfac0 (diff) |
Add state machine for GameState and corresponding player view of the state
Diffstat (limited to 'protocol/src/bridge_engine.rs')
-rw-r--r-- | protocol/src/bridge_engine.rs | 188 |
1 files changed, 163 insertions, 25 deletions
diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index a11eda4..5c7ce4d 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -1,8 +1,8 @@ -use crate::card::{Card, Deal, Suit, RankOrder}; -use serde::{Deserialize, Serialize}; +use crate::card::{Card, Deal, RankOrder, Suit}; use anyhow::{anyhow, bail}; use log::{error, info}; use regex::Regex; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; @@ -11,7 +11,9 @@ 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)] +#[derive( + PartialEq, Eq, Clone, Copy, Debug, FromRepr, EnumCountMacro, Serialize, Deserialize, EnumIter, +)] #[repr(u8)] pub enum Player { West = 0, @@ -378,9 +380,9 @@ impl fmt::Display for ContractModifier { #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] pub struct Contract { - declarer: Player, - highest_bid: Raise, - modifier: ContractModifier, + pub declarer: Player, + pub highest_bid: Raise, + pub modifier: ContractModifier, } impl fmt::Display for Contract { @@ -486,6 +488,7 @@ pub enum GameState { Bidding { dealer: Player, deal: Deal, + bidding: Bidding, }, PassedOut { dealer: Player, @@ -501,6 +504,14 @@ pub enum GameState { } impl GameState { + pub fn new(deal: Deal, dealer: Player) -> Self { + Self::Bidding { + dealer, + deal, + bidding: Bidding::new(dealer), + } + } + pub fn deal(&self) -> &Deal { match self { Self::Bidding { deal, .. } => deal, @@ -516,6 +527,51 @@ impl GameState { Self::Play { dealer, .. } => dealer, } } + + 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 bid(self, bid: Bid) -> Result<Self, anyhow::Error> { + let (dealer, deal, bidding) = match self { + GameState::Bidding { + dealer, + deal, + bidding, + } => (dealer, deal, bidding), + _ => bail!("not currently bidding: {self:?}"), + }; + Ok(match bidding.bid(bid)? { + BiddingResult::InProgress(bidding) => GameState::Bidding { + dealer, + deal, + bidding, + }, + BiddingResult::Contract(None, bidding) => GameState::PassedOut { + dealer, + deal, + bidding, + }, + BiddingResult::Contract(Some(contract), bidding) => GameState::Play { + dealer, + playing_deal: DealInPlay::new(contract.declarer, deal), + contract, + bidding, + }, + }) + } } pub fn deal() -> Deal { @@ -526,18 +582,72 @@ pub fn deal() -> Deal { } #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] -pub struct TableView { - pub dealer: Player, - pub player_position: Player, - pub hand: Vec<Card>, +pub enum GameStatePlayerView { + Bidding { + dealer: Player, + player_position: Player, + hand: Vec<Card>, + bidding: Bidding, + }, + PassedOut { + dealer: Player, + player_position: Player, + deal: Deal, + bidding: Bidding, + }, + Lead { + dealer: Player, + player_position: Player, + contract: Contract, + hand: Vec<Card>, + }, + Play { + dealer: Player, + player_position: Player, + contract: Contract, + trick: Trick, + dummy: Vec<Card>, + hand: Vec<Card>, + }, } -impl TableView { +impl GameStatePlayerView { 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(), + match game_state { + GameState::Bidding { + dealer, + deal, + bidding, + } => GameStatePlayerView::Bidding { + dealer: *dealer, + player_position, + bidding: bidding.clone(), + hand: player_position.get_cards(deal).clone(), + }, + GameState::PassedOut { + dealer, + deal, + bidding, + } => todo!(), + GameState::Play { + dealer, + playing_deal, + contract, + bidding, + } => todo!(), + } + } + + pub fn hand(&self) -> &Vec<Card> { + match self { + GameStatePlayerView::Bidding { hand, .. } => hand, + GameStatePlayerView::PassedOut { + deal, + player_position, + .. + } => player_position.get_cards(deal), + GameStatePlayerView::Lead { hand, .. } => hand, + GameStatePlayerView::Play { hand, .. } => hand, } } } @@ -728,12 +838,12 @@ mod tests { s.split(" ").map(mkcard).collect() } - fn _example_deal() -> Deal { + 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"), + 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"), } } @@ -747,16 +857,44 @@ mod tests { } #[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(); + assert_eq!(game_state.is_bidding(), true); + let game_state = game_state + .bid(Bid::Pass) + .unwrap() + .bid(Bid::Pass) + .unwrap() + .bid(Bid::Pass) + .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::Bidding { dealer: Player::East, deal: mini_deal() }; + 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 = 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())); + let view = GameStatePlayerView::from_game_state(&game_state, p); + match view { + GameStatePlayerView::Bidding { dealer, hand, .. } => { + assert_eq!(dealer, Player::East); + assert_eq!(&hand, p.get_cards(&mini_deal())); + } + _ => panic!("expected bidding: {view:#?}"), + } } } |