diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-21 08:10:38 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-21 08:10:38 -0500 |
commit | 10ecb9e30568bf20287b053a620252d7a80dbd6b (patch) | |
tree | c6a82f7cb4d24071234a1429dbb91815709052be /protocol | |
parent | 27f74d8c366be675e7ab64ca746496a66b3cf024 (diff) |
Add struct for the player view of a hand in play
Diffstat (limited to 'protocol')
-rw-r--r-- | protocol/src/bot.rs | 9 | ||||
-rw-r--r-- | protocol/src/bridge_engine.rs | 289 |
2 files changed, 236 insertions, 62 deletions
diff --git a/protocol/src/bot.rs b/protocol/src/bot.rs index 3bae7db..cc676df 100644 --- a/protocol/src/bot.rs +++ b/protocol/src/bot.rs @@ -1,8 +1,15 @@ use async_trait::async_trait; -use crate::bridge_engine::{BiddingStatePlayerView, Bid}; +use crate::{bridge_engine::{BiddingStatePlayerView, Bid}, card::Card}; #[async_trait] pub trait BiddingBot { async fn bid(&self, bidding: &BiddingStatePlayerView) -> Bid; } + +#[async_trait] +pub trait PlayingBot { + async fn play(&self) -> Card { + todo!() + } +} 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, |