From 55f6d120d492755a1f2a5bd66d1819c8b2ad9f31 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Wed, 21 Sep 2022 16:20:59 -0400 Subject: Add state machine for playing a deal --- webapp/src/bridge_engine.rs | 166 +++++++++++++++++++++++++++++++++++++++--- webapp/src/components/game.rs | 26 +++---- 2 files changed, 165 insertions(+), 27 deletions(-) (limited to 'webapp/src') diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs index 2a10063..c559576 100644 --- a/webapp/src/bridge_engine.rs +++ b/webapp/src/bridge_engine.rs @@ -1,7 +1,6 @@ -use crate::card::Card; -use crate::card::Suit; +use crate::card::{Card, Deal, Suit}; use anyhow::{anyhow, bail}; -use log::debug; +use log::{debug, error}; use regex::Regex; use std::cmp::Ordering; use std::fmt; @@ -35,17 +34,33 @@ impl Player { 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)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct Trick { pub leader: Player, pub cards_played: Vec, } -#[derive(PartialEq, Eq, Debug)] +impl Trick { + pub fn winner(&self) -> Player { + error!("XXX: Returning incorrect result for winner"); + self.leader + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] pub struct PlayTurn { - in_progress: Trick, + trick: Trick, } #[derive(PartialEq, Eq, Debug)] @@ -57,7 +72,7 @@ pub enum PlayResult { impl PlayTurn { pub fn new(p: Player) -> PlayTurn { PlayTurn { - in_progress: Trick { + trick: Trick { leader: p, cards_played: Vec::with_capacity(4), }, @@ -65,12 +80,71 @@ impl PlayTurn { } pub fn play(mut self: PlayTurn, card: Card) -> PlayResult { - self.in_progress.cards_played.push(card); - if self.in_progress.cards_played.len() >= 4 { - return PlayResult::Trick(self.in_progress); + 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 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)] @@ -271,7 +345,13 @@ pub struct Contract { 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) + write!( + f, + "{}{}{}", + self.highest_bid, + self.declarer.short_str(), + self.modifier + ) } } @@ -511,9 +591,13 @@ mod tests { #[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, @@ -526,4 +610,64 @@ mod tests { } ); } + + 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"), + }) + ); + } } diff --git a/webapp/src/components/game.rs b/webapp/src/components/game.rs index 21b0966..5258041 100644 --- a/webapp/src/components/game.rs +++ b/webapp/src/components/game.rs @@ -8,12 +8,6 @@ use yew::prelude::*; pub const SUIT_DISPLAY_ORDER: [Suit; 4] = [Suit::Diamond, Suit::Club, Suit::Heart, Suit::Spade]; -#[derive(Debug)] -enum Phase { - Bidding, - Cardplay, -} - #[derive(Debug)] enum GameState { Bidding { @@ -21,12 +15,12 @@ enum GameState { deal: Deal, }, PassedOut { - dealer: Player, + _dealer: Player, deal: Deal, - bidding: bridge_engine::Bidding, + _bidding: bridge_engine::Bidding, }, Play { - dealer: Player, + _dealer: Player, deal: Deal, contract: Contract, bidding: bridge_engine::Bidding, @@ -78,15 +72,15 @@ pub fn game() -> Html { Callback::from(move |(contract, bidding)| { state.set(match contract { Some(contract) => GameState::Play { - dealer: dealer, + _dealer: dealer, deal: deal.clone(), contract, bidding, }, None => GameState::PassedOut { - dealer: dealer, + _dealer: dealer, deal: deal.clone(), - bidding, + _bidding: bidding, }, }); }) @@ -96,10 +90,10 @@ pub fn game() -> Html { } } GameState::Play { - dealer, - deal, - contract, - bidding, + _dealer: _, + deal: _, + contract: _, + bidding: _, } => html! {

{ "Time to play" }

}, GameState::PassedOut { .. } => html! {

{ "Everyone passed" }

}, }; -- cgit v1.2.3