summaryrefslogtreecommitdiff
path: root/webapp/src
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-09-21 16:20:59 -0400
committerKjetil Orbekk <kj@orbekk.com>2022-09-21 16:20:59 -0400
commit55f6d120d492755a1f2a5bd66d1819c8b2ad9f31 (patch)
tree42425ab456cf8f51f9cfb8a1ac6e8a3bdeaac7f7 /webapp/src
parentea1ce2fc2a5fa05ea8774ec325c5afe8ace358de (diff)
Add state machine for playing a deal
Diffstat (limited to 'webapp/src')
-rw-r--r--webapp/src/bridge_engine.rs166
-rw-r--r--webapp/src/components/game.rs26
2 files changed, 165 insertions, 27 deletions
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<Card> {
+ 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<Card>,
}
-#[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<Trick>,
+ in_progress: PlayTurn,
+}
+
+#[derive(Debug)]
+pub enum PlayingDealResult {
+ InProgress(PlayingDeal),
+ PlayFinished(Vec<Trick>),
+}
+
+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<PlayingDealResult, anyhow::Error> {
+ 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<Card> {
+ 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
@@ -9,24 +9,18 @@ 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 {
dealer: Player,
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! { <p>{ "Time to play" }</p> },
GameState::PassedOut { .. } => html! { <p>{ "Everyone passed" }</p> },
};