summaryrefslogtreecommitdiff
path: root/protocol/src/bridge_engine.rs
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-12-21 08:10:38 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-12-21 08:10:38 -0500
commit10ecb9e30568bf20287b053a620252d7a80dbd6b (patch)
treec6a82f7cb4d24071234a1429dbb91815709052be /protocol/src/bridge_engine.rs
parent27f74d8c366be675e7ab64ca746496a66b3cf024 (diff)
Add struct for the player view of a hand in play
Diffstat (limited to 'protocol/src/bridge_engine.rs')
-rw-r--r--protocol/src/bridge_engine.rs289
1 files changed, 228 insertions, 61 deletions
diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs
index 925676e..50f619b 100644
--- a/protocol/src/bridge_engine.rs
+++ b/protocol/src/bridge_engine.rs
@@ -1,7 +1,10 @@
-use crate::card::{Card, RankOrder, Suit, sort_cards, make_deck};
+use crate::card::{make_deck, sort_cards, Card, RankOrder, Suit};
use anyhow::{anyhow, bail};
use log::{error, info};
-use rand::{prelude::{Distribution, SliceRandom}, distributions::Standard};
+use rand::{
+ distributions::Standard,
+ prelude::{Distribution, SliceRandom}, random,
+};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
@@ -10,10 +13,20 @@ 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];
+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,
+ PartialEq,
+ Eq,
+ Clone,
+ Copy,
+ Debug,
+ FromRepr,
+ EnumCountMacro,
+ Serialize,
+ Deserialize,
+ EnumIter,
)]
#[repr(u8)]
pub enum Player {
@@ -166,7 +179,10 @@ impl DealInPlay {
&self.deal
}
- pub fn play(mut self: Self, card: Card) -> Result<DealInPlayResult, anyhow::Error> {
+ pub fn play(
+ mut self: Self,
+ card: Card,
+ ) -> Result<DealInPlayResult, anyhow::Error> {
let player = self.in_progress.next_player();
let player_cards = player.get_cards_mut(&mut self.deal);
@@ -182,24 +198,38 @@ impl DealInPlay {
player_cards.remove(i);
Ok(match self.in_progress.play(card) {
- TurnInPlayResult::InProgress(turn) => DealInPlayResult::InProgress(Self {
- in_progress: turn,
- ..self
- }),
- TurnInPlayResult::Trick(trick) => DealInPlayResult::InProgress(Self {
- in_progress: TurnInPlay::new(trick.winner()),
- tricks_played: {
- let mut tricks = self.tricks_played;
- tricks.push(trick);
- tricks
- },
- deal: self.deal,
- }),
+ TurnInPlayResult::InProgress(turn) => {
+ DealInPlayResult::InProgress(Self {
+ in_progress: turn,
+ ..self
+ })
+ }
+ TurnInPlayResult::Trick(trick) => {
+ DealInPlayResult::InProgress(Self {
+ in_progress: TurnInPlay::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, Serialize, Deserialize)]
+#[derive(
+ PartialEq,
+ Eq,
+ PartialOrd,
+ Ord,
+ Clone,
+ Copy,
+ EnumIter,
+ Serialize,
+ Deserialize,
+)]
pub enum ContractLevel {
One = 1,
Two,
@@ -211,13 +241,19 @@ pub enum ContractLevel {
}
impl fmt::Display for ContractLevel {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ 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> {
+ fn fmt(
+ &self,
+ f: &mut std::fmt::Formatter<'_>,
+ ) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", self)
}
}
@@ -225,7 +261,9 @@ impl fmt::Debug for ContractLevel {
impl FromStr for ContractLevel {
type Err = anyhow::Error;
- fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
+ fn from_str(
+ s: &str,
+ ) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
match s {
"1" => Ok(ContractLevel::One),
"2" => Ok(ContractLevel::Two),
@@ -257,7 +295,10 @@ impl Bid {
}
impl fmt::Display for Bid {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ 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"),
@@ -268,7 +309,10 @@ impl fmt::Display for Bid {
}
impl fmt::Debug for Bid {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ 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"),
@@ -280,7 +324,9 @@ impl fmt::Debug for Bid {
impl FromStr for Bid {
type Err = anyhow::Error;
- fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
+ fn from_str(
+ s: &str,
+ ) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"pass" => Ok(Bid::Pass),
"double" => Ok(Bid::Double),
@@ -337,7 +383,10 @@ impl Ord for Raise {
}
impl fmt::Display for Raise {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ fn fmt(
+ &self,
+ f: &mut std::fmt::Formatter<'_>,
+ ) -> std::result::Result<(), std::fmt::Error> {
write!(
f,
"{}{}",
@@ -349,14 +398,19 @@ impl fmt::Display for Raise {
}
impl fmt::Debug for Raise {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ 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<Self, <Self as std::str::FromStr>::Err> {
+ fn from_str(
+ s: &str,
+ ) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
lazy_static::lazy_static! {
static ref RE: Regex = Regex::new(r#"\s*(.[0-9]*)\s*(.*)"#).unwrap();
};
@@ -379,7 +433,10 @@ pub enum ContractModifier {
}
impl fmt::Display for ContractModifier {
- fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
+ fn fmt(
+ &self,
+ f: &mut fmt::Formatter,
+ ) -> std::result::Result<(), std::fmt::Error> {
match self {
ContractModifier::None => Ok(()),
ContractModifier::Doubled => write!(f, "x"),
@@ -396,7 +453,10 @@ pub struct Contract {
}
impl fmt::Display for Contract {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
+ fn fmt(
+ &self,
+ f: &mut fmt::Formatter<'_>,
+ ) -> std::result::Result<(), fmt::Error> {
write!(
f,
"{}{}{}",
@@ -440,7 +500,9 @@ impl Bidding {
}
fn passed_out(&self) -> bool {
- if self.bids.len() < 4 { return false; }
+ if self.bids.len() < 4 {
+ return false;
+ }
let mut passes = 0;
for b in self.bids.iter().rev().take(3) {
if b == &Bid::Pass {
@@ -514,7 +576,10 @@ pub struct BiddingStatePlayerView {
}
impl BiddingStatePlayerView {
- pub fn from_bidding_state(bidding_state: &BiddingState, player_position: Player) -> Self {
+ pub fn from_bidding_state(
+ bidding_state: &BiddingState,
+ player_position: Player,
+ ) -> Self {
let BiddingState {
dealer,
deal,
@@ -531,13 +596,29 @@ impl BiddingStatePlayerView {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlayState {
- pub dealer: Player,
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),
+ deal.clone());
+ Self {
+ deal,
+ contract,
+ bidding,
+ playing_deal,
+ }
+ }
+
+ pub fn dealer(&self) -> Player {
+ self.bidding.dealer
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum GameState {
Bidding(BiddingState),
@@ -567,18 +648,24 @@ impl GameState {
}
pub fn dealer(&self) -> Player {
- match *self {
- Self::Bidding(BiddingState { dealer, .. }) => dealer,
- Self::PassedOut { dealer, .. } => dealer,
- Self::Play(PlayState { dealer, .. }) => dealer,
+ match self {
+ Self::Bidding(BiddingState { dealer, .. }) => *dealer,
+ Self::PassedOut { dealer, .. } => *dealer,
+ Self::Play(play_state) => play_state.dealer(),
}
}
pub fn current_player(&self) -> Option<Player> {
match self {
- GameState::Bidding(bidding) => Some(bidding.bidding.current_bidder()),
+ GameState::Bidding(bidding) => {
+ Some(bidding.bidding.current_bidder())
+ }
GameState::Play(_) => todo!(),
- GameState::PassedOut { dealer, deal, bidding } => None,
+ GameState::PassedOut {
+ dealer,
+ deal,
+ bidding,
+ } => None,
}
}
@@ -608,23 +695,22 @@ impl GameState {
_ => bail!("not currently bidding: {self:?}"),
};
Ok(match bidding.bid(bid)? {
- BiddingResult::InProgress(bidding) => GameState::Bidding(BiddingState {
- dealer,
- deal,
- bidding,
- }),
+ BiddingResult::InProgress(bidding) => {
+ GameState::Bidding(BiddingState {
+ dealer,
+ deal,
+ bidding,
+ })
+ }
BiddingResult::Contract(None, bidding) => GameState::PassedOut {
dealer,
deal,
bidding,
},
- BiddingResult::Contract(Some(contract), bidding) => GameState::Play(PlayState {
- deal: deal.clone(),
- dealer,
- playing_deal: DealInPlay::new(contract.declarer, deal),
- contract,
- bidding,
- }),
+ BiddingResult::Contract(Some(contract), bidding) => {
+ GameState::Play(
+ PlayState::new(deal, contract, bidding))
+ }
})
}
}
@@ -665,6 +751,37 @@ impl Distribution<Deal> for Standard {
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
+pub struct PlayStatePlayerView {
+ player_position: Player,
+ bidding: Bidding,
+ contract: Contract,
+ // If None, the lead has not been played.
+ dummy: Option<Vec<Card>>,
+ declarer_tricks: u8,
+ hand: Vec<Card>,
+ previous_trick: Trick,
+ current_trick: TurnInPlay,
+}
+
+impl PlayStatePlayerView {
+ pub fn from_play_state(
+ play_state: &PlayState,
+ player_position: Player,
+ ) -> Self {
+ Self {
+ player_position,
+ bidding: play_state.bidding.clone(),
+ contract: play_state.contract,
+ dummy: None,
+ declarer_tricks: 0,
+ hand: vec!(),
+ previous_trick: Trick { leader: random(), cards_played: vec!() },
+ current_trick: TurnInPlay::new(random()),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub enum GameStatePlayerView {
Bidding(BiddingStatePlayerView),
PassedOut {
@@ -690,10 +807,16 @@ pub enum GameStatePlayerView {
}
impl GameStatePlayerView {
- pub fn from_game_state(game_state: &GameState, player_position: Player) -> Self {
+ 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),
+ BiddingStatePlayerView::from_bidding_state(
+ bidding_state,
+ player_position,
+ ),
),
GameState::PassedOut { .. } => todo!(),
GameState::Play { .. } => todo!(),
@@ -702,7 +825,10 @@ impl GameStatePlayerView {
pub fn hand(&self) -> &Vec<Card> {
match self {
- GameStatePlayerView::Bidding(BiddingStatePlayerView { hand, .. }) => hand,
+ GameStatePlayerView::Bidding(BiddingStatePlayerView {
+ hand,
+ ..
+ }) => hand,
GameStatePlayerView::PassedOut {
deal,
player_position,
@@ -741,7 +867,8 @@ mod tests {
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());
+ 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);
@@ -773,7 +900,10 @@ mod tests {
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::from_str(format!("{}", bid).as_str()).unwrap()
+ );
assert_eq!(
Bid::Raise(bid),
Bid::from_str(format!("{}", bid).as_str()).unwrap()
@@ -836,10 +966,11 @@ mod tests {
#[test]
fn next_player() {
- let next_players = vec![Player::North, Player::East, Player::South, Player::West]
- .iter()
- .map(Player::next)
- .collect::<Vec<_>>();
+ let next_players =
+ vec![Player::North, Player::East, Player::South, Player::West]
+ .iter()
+ .map(Player::next)
+ .collect::<Vec<_>>();
assert_eq!(
next_players,
vec![Player::East, Player::South, Player::West, Player::North]
@@ -975,7 +1106,11 @@ mod tests {
info!("Testing view for {p:?}");
let view = GameStatePlayerView::from_game_state(&game_state, p);
match view {
- GameStatePlayerView::Bidding(BiddingStatePlayerView { dealer, hand, .. }) => {
+ GameStatePlayerView::Bidding(BiddingStatePlayerView {
+ dealer,
+ hand,
+ ..
+ }) => {
assert_eq!(dealer, Player::East);
assert_eq!(&hand, p.get_cards(&mini_deal()));
}
@@ -984,6 +1119,38 @@ mod tests {
}
}
+ 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_deal =
+ PlayStatePlayerView::from_play_state(&play_state, random());
+ }
+
fn as_playing_hand(result: DealInPlayResult) -> DealInPlay {
match result {
DealInPlayResult::InProgress(r) => r,