summaryrefslogtreecommitdiff
path: root/protocol/src/bridge_engine.rs
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-25 17:10:17 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-27 17:23:11 -0500
commit685ac902e3faf4ed5a76b8c859b01f7d2e2d9ea0 (patch)
tree063eac21566c0dd83f849d7ea9cccb51a618d4de /protocol/src/bridge_engine.rs
parenteeeea174157202cb812fab04844292cbd96bfac0 (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.rs188
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:#?}"),
+ }
}
}