diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2023-01-01 20:34:09 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2023-01-01 20:34:09 -0500 |
commit | d3fbefad9cf25786fb5f28f96eeceb65d0a8b35b (patch) | |
tree | 156a23b5c04b93d746ecf592971aefbcc127cfd2 | |
parent | bb2ed3a2926384df063e476d10613fa310cd7ffa (diff) |
Split bridge_engine into a few separate modules
-rw-r--r-- | protocol/src/actions.rs | 66 | ||||
-rw-r--r-- | protocol/src/bot.rs | 4 | ||||
-rw-r--r-- | protocol/src/bridge_engine.rs | 406 | ||||
-rw-r--r-- | protocol/src/contract.rs | 210 | ||||
-rw-r--r-- | protocol/src/core.rs | 116 | ||||
-rw-r--r-- | protocol/src/lib.rs | 3 | ||||
-rw-r--r-- | protocol/src/simple_bots.rs | 16 | ||||
-rw-r--r-- | server/src/main.rs | 3 | ||||
-rw-r--r-- | server/src/play.rs | 13 | ||||
-rw-r--r-- | server/src/table.rs | 4 | ||||
-rw-r--r-- | server/tests/table_test.rs | 2 | ||||
-rw-r--r-- | webapp/src/components/bidding.rs | 2 | ||||
-rw-r--r-- | webapp/src/components/bidding_box.rs | 6 | ||||
-rw-r--r-- | webapp/src/components/bidding_table.rs | 2 | ||||
-rw-r--r-- | webapp/src/components/show_bid.rs | 2 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 6 | ||||
-rw-r--r-- | webapp/src/components/trick_in_play.rs | 2 | ||||
-rw-r--r-- | webapp/src/services.rs | 4 |
18 files changed, 449 insertions, 418 deletions
diff --git a/protocol/src/actions.rs b/protocol/src/actions.rs new file mode 100644 index 0000000..defe1f9 --- /dev/null +++ b/protocol/src/actions.rs @@ -0,0 +1,66 @@ +use core::fmt; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use crate::contract::LevelAndSuit; + +#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum Bid { + Pass, + Double, + Redouble, + Raise(LevelAndSuit), +} + +impl Bid { + pub fn as_raise(&self) -> Option<LevelAndSuit> { + match self { + Bid::Raise(raise) => Some(*raise), + _ => None, + } + } +} + +impl fmt::Display for Bid { + 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"), + Bid::Redouble => write!(f, "Redouble"), + Bid::Raise(x) => write!(f, "{}", x), + } + } +} + +impl fmt::Debug for Bid { + 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"), + Bid::Redouble => write!(f, "Redouble"), + Bid::Raise(x) => write!(f, "Raise({})", x), + } + } +} + +impl FromStr for Bid { + type Err = anyhow::Error; + 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), + "redouble" => Ok(Bid::Redouble), + x => Ok(Bid::Raise(x.parse()?)), + } + } +} + diff --git a/protocol/src/bot.rs b/protocol/src/bot.rs index a85fda0..24495c8 100644 --- a/protocol/src/bot.rs +++ b/protocol/src/bot.rs @@ -1,8 +1,8 @@ use async_trait::async_trait; use crate::{ - bridge_engine::{Bid, BiddingStatePlayerView, PlayStatePlayerView}, - card::Card, + bridge_engine::{BiddingStatePlayerView, PlayStatePlayerView}, + card::Card, actions::Bid, }; #[async_trait] diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index d04bdf0..74a8262 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -1,90 +1,16 @@ use crate::{ - card::{make_deck, sort_cards, Card, RankOrder, Suit}, + card::{Card, Suit}, move_result::MoveResult, + core::{Player, Deal}, actions::Bid, contract::{LevelAndSuit, Contract, ContractModifier} }; use anyhow::{anyhow, bail}; use log::info; -use rand::{ - distributions::Standard, - prelude::{Distribution, SliceRandom}, -}; -use regex::Regex; use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, borrow::Cow}; -use std::fmt; -use std::str::FromStr; -use strum::{EnumCount, IntoEnumIterator}; -use strum_macros::{EnumCount as EnumCountMacro, EnumIter, FromRepr}; +use std::borrow::Cow; 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, -)] -#[repr(u8)] -pub enum Player { - West = 0, - North, - East, - South, -} - -impl Player { - pub fn next(&self) -> Self { - self.many_next(1) - } - - pub fn many_next(self, i: usize) -> Self { - Player::from_repr(((self as usize + i) % Player::COUNT) as u8).unwrap() - } - - pub fn short_str(&self) -> &str { - match self { - Self::West => "W", - Self::North => "N", - Self::East => "E", - Self::South => "W", - } - } - - pub fn get_cards<'a>(&self, deal: &'a Deal) -> &'a Vec<Card> { - match self { - Self::West => &deal.west, - Self::North => &deal.north, - Self::East => &deal.east, - Self::South => &deal.south, - } - } - - pub fn get_cards_mut<'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, - } - } -} - -impl Distribution<Player> for Standard { - fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Player { - let min = Player::West as u8; - let max = Player::South as u8; - let v = rng.gen_range(min..=max); - Player::from_repr(v).unwrap() - } -} - #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct Trick { pub leader: Player, @@ -255,266 +181,6 @@ impl DealInPlay { } } -#[derive( - PartialEq, - Eq, - PartialOrd, - Ord, - Clone, - Copy, - EnumIter, - Serialize, - Deserialize, -)] -pub enum ContractLevel { - One = 1, - Two, - Three, - Four, - Five, - Six, - Seven, -} - -impl fmt::Display for ContractLevel { - 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> { - write!(f, "{}", self) - } -} - -impl FromStr for ContractLevel { - type Err = anyhow::Error; - - 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), - "3" => Ok(ContractLevel::Three), - "4" => Ok(ContractLevel::Four), - "5" => Ok(ContractLevel::Five), - "6" => Ok(ContractLevel::Six), - "7" => Ok(ContractLevel::Seven), - _ => Err(anyhow!("invalid string: {}", s)), - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub enum Bid { - Pass, - Double, - Redouble, - Raise(Raise), -} - -impl Bid { - pub fn as_raise(&self) -> Option<Raise> { - match self { - Bid::Raise(raise) => Some(*raise), - _ => None, - } - } -} - -impl fmt::Display for Bid { - 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"), - Bid::Redouble => write!(f, "Redouble"), - Bid::Raise(x) => write!(f, "{}", x), - } - } -} - -impl fmt::Debug for Bid { - 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"), - Bid::Redouble => write!(f, "Redouble"), - Bid::Raise(x) => write!(f, "Raise({})", x), - } - } -} - -impl FromStr for Bid { - type Err = anyhow::Error; - 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), - "redouble" => Ok(Bid::Redouble), - x => Ok(Bid::Raise(x.parse()?)), - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct Raise { - pub level: ContractLevel, - pub suit: Option<Suit>, -} - -impl Raise { - pub fn all_raises() -> Vec<Raise> { - let mut result = Vec::with_capacity(7 * 5); - for level in ContractLevel::iter() { - for suit in Suit::iter() { - result.push(Raise { - level, - suit: Some(suit), - }); - } - result.push(Raise { level, suit: None }); - } - result - } -} - -impl PartialOrd<Self> for Raise { - fn partial_cmp(&self, o: &Self) -> Option<Ordering> { - if self.level != o.level { - return self.level.partial_cmp(&o.level); - } - if self.suit != o.suit { - if self.suit.is_none() { - return Some(Ordering::Greater); - } - if o.suit.is_none() { - return Some(Ordering::Less); - } - return self.suit.partial_cmp(&o.suit); - } - Some(Ordering::Equal) - } -} - -impl Ord for Raise { - fn cmp(&self, o: &Self) -> std::cmp::Ordering { - self.partial_cmp(o).unwrap() - } -} - -impl fmt::Display for Raise { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::result::Result<(), std::fmt::Error> { - write!( - f, - "{}{}", - self.level, - self.suit - .map_or("NT".to_string(), |suit| format!("{}", suit)) - ) - } -} - -impl fmt::Debug for Raise { - 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> { - lazy_static::lazy_static! { - static ref RE: Regex = Regex::new(r#"\s*(.[0-9]*)\s*(.*)"#).unwrap(); - }; - let caps = RE - .captures(s) - .ok_or_else(|| anyhow!("invalid raise: {}", s))?; - info!("caps: {:?}", caps); - let level = caps[1].parse()?; - let suit = match caps[2].to_ascii_uppercase().as_str() { - "NT" => None, - x => Some(x.parse()?), - }; - Ok(Raise { level, suit }) - } -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub enum ContractModifier { - None, - Doubled, - Redoubled, -} - -impl fmt::Display for ContractModifier { - fn fmt( - &self, - f: &mut fmt::Formatter, - ) -> std::result::Result<(), std::fmt::Error> { - match self { - ContractModifier::None => Ok(()), - ContractModifier::Doubled => write!(f, "x"), - ContractModifier::Redoubled => write!(f, "xx"), - } - } -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub struct Contract { - pub declarer: Player, - pub highest_bid: Raise, - pub modifier: ContractModifier, -} - -impl Contract { - pub fn dummy(&self) -> Player { - self.declarer.many_next(2) - } - - pub fn leader(&self) -> Player { - self.declarer.many_next(3) - } -} - -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 - ) - } -} - #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Bidding { pub dealer: Player, @@ -538,7 +204,7 @@ impl Bidding { self.dealer.many_next(self.bids.len()) } - pub fn highest_bid(&self) -> Option<Raise> { + pub fn highest_bid(&self) -> Option<LevelAndSuit> { for bid in self.bids.iter().rev() { if let Some(raise) = bid.as_raise() { return Some(raise); @@ -856,49 +522,6 @@ impl GameState { } } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Deal { - pub north: Vec<Card>, - pub west: Vec<Card>, - pub south: Vec<Card>, - pub east: Vec<Card>, -} - -impl Deal { - pub fn empty() -> Self { - Self { - north: Vec::with_capacity(13), - west: Vec::with_capacity(13), - south: Vec::with_capacity(13), - east: Vec::with_capacity(13), - } - } - - pub fn sort(&mut self, suits: &[Suit; 4], ord: RankOrder) { - sort_cards(suits, ord, self.north.as_mut_slice()); - sort_cards(suits, ord, self.west.as_mut_slice()); - sort_cards(suits, ord, self.south.as_mut_slice()); - sort_cards(suits, ord, self.east.as_mut_slice()); - } -} - -impl Distribution<Deal> for Standard { - fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Deal { - let mut deck = make_deck(); - deck.shuffle(rng); - let mut deck = deck.iter(); - let north = deck.by_ref().take(13).cloned().collect(); - let west = deck.by_ref().take(13).cloned().collect(); - let south = deck.by_ref().take(13).cloned().collect(); - let east = deck.by_ref().take(13).cloned().collect(); - Deal { - north, - west, - south, - east, - } - } -} #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct PlayStatePlayerView { @@ -1059,9 +682,14 @@ impl TableStatePlayerView { #[cfg(test)] mod tests { + use std::str::FromStr; + + use crate::{contract::ContractLevel, card::RankOrder}; + use super::*; use log::info; use rand::random; + use strum::IntoEnumIterator; fn as_bidding(r: BiddingResult) -> Bidding { match r { @@ -1105,21 +733,21 @@ mod tests { #[test] fn bid_conversion() { crate::tests::test_setup(); - let bid1d = Raise { + let bid1d = LevelAndSuit { level: ContractLevel::One, suit: Some(Suit::Diamond), }; assert_eq!("1♢", format!("{}", bid1d)); assert_eq!("1♢", format!("{:?}", bid1d)); - assert_eq!(bid1d, Raise::from_str("1D").unwrap()); + assert_eq!(bid1d, LevelAndSuit::from_str("1D").unwrap()); assert_eq!(Bid::Pass, Bid::from_str("pass").unwrap()); let mut checked_raises = 0; - for bid in Raise::all_raises() { + for bid in LevelAndSuit::all_raises() { assert_eq!( bid, - Raise::from_str(format!("{}", bid).as_str()).unwrap() + LevelAndSuit::from_str(format!("{}", bid).as_str()).unwrap() ); assert_eq!( Bid::Raise(bid), @@ -1159,12 +787,12 @@ mod tests { #[test] fn bid_ord() { - let bid = |s| Raise::from_str(s).unwrap(); + let bid = |s| LevelAndSuit::from_str(s).unwrap(); assert!(bid("2♦") < bid("3♦")); assert!(bid("3♦") < bid("3♥")); assert!(bid("1♠") < bid("2♣")); assert!(bid("1♠") < bid("1NT")); - for bid in Raise::all_raises() { + for bid in LevelAndSuit::all_raises() { assert_eq!(bid, bid); } } @@ -1283,7 +911,7 @@ mod tests { assert!(game_state.is_bidding()); info!("Start bidding with game state {game_state:#?}"); - let raise = |s| Bid::Raise(Raise::from_str(s).unwrap()); + let raise = |s| Bid::Raise(LevelAndSuit::from_str(s).unwrap()); let game_state = game_state.bid(raise("1H")).unwrap().current().unwrap(); let game_state = game_state @@ -1329,7 +957,7 @@ mod tests { fn some_play_state() -> PlayState { crate::tests::test_setup(); let deal = random(); - let raise1c = Raise { + let raise1c = LevelAndSuit { level: ContractLevel::One, suit: Some(Suit::Club), }; diff --git a/protocol/src/contract.rs b/protocol/src/contract.rs new file mode 100644 index 0000000..4c1d2f3 --- /dev/null +++ b/protocol/src/contract.rs @@ -0,0 +1,210 @@ +use core::fmt; +use std::{str::FromStr, cmp::Ordering}; + +use anyhow::anyhow; +use regex::Regex; +use serde::{Serialize, Deserialize}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +use crate::{core::Player, card::Suit}; + +#[derive( + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + EnumIter, + Serialize, + Deserialize, +)] +pub enum ContractLevel { + One = 1, + Two, + Three, + Four, + Five, + Six, + Seven, +} + +impl fmt::Display for ContractLevel { + 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> { + write!(f, "{}", self) + } +} + +impl FromStr for ContractLevel { + type Err = anyhow::Error; + + 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), + "3" => Ok(ContractLevel::Three), + "4" => Ok(ContractLevel::Four), + "5" => Ok(ContractLevel::Five), + "6" => Ok(ContractLevel::Six), + "7" => Ok(ContractLevel::Seven), + _ => Err(anyhow!("invalid string: {}", s)), + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +pub enum ContractModifier { + None, + Doubled, + Redoubled, +} + +impl fmt::Display for ContractModifier { + fn fmt( + &self, + f: &mut fmt::Formatter, + ) -> std::result::Result<(), std::fmt::Error> { + match self { + ContractModifier::None => Ok(()), + ContractModifier::Doubled => write!(f, "x"), + ContractModifier::Redoubled => write!(f, "xx"), + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +pub struct Contract { + pub declarer: Player, + pub highest_bid: LevelAndSuit, + pub modifier: ContractModifier, +} + +impl Contract { + pub fn dummy(&self) -> Player { + self.declarer.many_next(2) + } + + pub fn leader(&self) -> Player { + self.declarer.many_next(3) + } +} + +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 + ) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct LevelAndSuit { + pub level: ContractLevel, + pub suit: Option<Suit>, +} + +impl LevelAndSuit { + pub fn all_raises() -> Vec<LevelAndSuit> { + let mut result = Vec::with_capacity(7 * 5); + for level in ContractLevel::iter() { + for suit in Suit::iter() { + result.push(LevelAndSuit { + level, + suit: Some(suit), + }); + } + result.push(LevelAndSuit { level, suit: None }); + } + result + } +} + +impl PartialOrd<Self> for LevelAndSuit { + fn partial_cmp(&self, o: &Self) -> Option<Ordering> { + if self.level != o.level { + return self.level.partial_cmp(&o.level); + } + if self.suit != o.suit { + if self.suit.is_none() { + return Some(Ordering::Greater); + } + if o.suit.is_none() { + return Some(Ordering::Less); + } + return self.suit.partial_cmp(&o.suit); + } + Some(Ordering::Equal) + } +} + +impl Ord for LevelAndSuit { + fn cmp(&self, o: &Self) -> std::cmp::Ordering { + self.partial_cmp(o).unwrap() + } +} + +impl fmt::Display for LevelAndSuit { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + write!( + f, + "{}{}", + self.level, + self.suit + .map_or("NT".to_string(), |suit| format!("{}", suit)) + ) + } +} + +impl fmt::Debug for LevelAndSuit { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl FromStr for LevelAndSuit { + type Err = anyhow::Error; + 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(); + }; + let caps = RE + .captures(s) + .ok_or_else(|| anyhow!("invalid raise: {}", s))?; + let level = caps[1].parse()?; + let suit = match caps[2].to_ascii_uppercase().as_str() { + "NT" => None, + x => Some(x.parse()?), + }; + Ok(LevelAndSuit { level, suit }) + } +} diff --git a/protocol/src/core.rs b/protocol/src/core.rs new file mode 100644 index 0000000..7939fb2 --- /dev/null +++ b/protocol/src/core.rs @@ -0,0 +1,116 @@ +use rand::{prelude::Distribution, distributions::Standard, seq::SliceRandom}; +use strum::EnumCount; +use strum_macros::{EnumCount, FromRepr, EnumIter}; +use serde::{Serialize, Deserialize}; + +use crate::card::{Card, Suit, RankOrder, sort_cards, make_deck}; + +#[derive( + PartialEq, + Eq, + Clone, + Copy, + Debug, + FromRepr, + EnumCount, + Serialize, + Deserialize, + EnumIter, +)] +#[repr(u8)] +pub enum Player { + West = 0, + North, + East, + South, +} + +impl Player { + pub fn next(&self) -> Self { + self.many_next(1) + } + + pub fn many_next(self, i: usize) -> Self { + Player::from_repr(((self as usize + i) % Player::COUNT) as u8).unwrap() + } + + pub fn short_str(&self) -> &str { + match self { + Self::West => "W", + Self::North => "N", + Self::East => "E", + Self::South => "W", + } + } + + pub fn get_cards<'a>(&self, deal: &'a Deal) -> &'a Vec<Card> { + match self { + Self::West => &deal.west, + Self::North => &deal.north, + Self::East => &deal.east, + Self::South => &deal.south, + } + } + + pub fn get_cards_mut<'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, + } + } +} + +impl Distribution<Player> for Standard { + fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Player { + let min = Player::West as u8; + let max = Player::South as u8; + let v = rng.gen_range(min..=max); + Player::from_repr(v).unwrap() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Deal { + pub north: Vec<Card>, + pub west: Vec<Card>, + pub south: Vec<Card>, + pub east: Vec<Card>, +} + +impl Deal { + pub fn empty() -> Self { + Self { + north: Vec::with_capacity(13), + west: Vec::with_capacity(13), + south: Vec::with_capacity(13), + east: Vec::with_capacity(13), + } + } + + pub fn sort(&mut self, suits: &[Suit; 4], ord: RankOrder) { + sort_cards(suits, ord, self.north.as_mut_slice()); + sort_cards(suits, ord, self.west.as_mut_slice()); + sort_cards(suits, ord, self.south.as_mut_slice()); + sort_cards(suits, ord, self.east.as_mut_slice()); + } +} + +impl Distribution<Deal> for Standard { + fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Deal { + let mut deck = make_deck(); + deck.shuffle(rng); + let mut deck = deck.iter(); + let north = deck.by_ref().take(13).cloned().collect(); + let west = deck.by_ref().take(13).cloned().collect(); + let south = deck.by_ref().take(13).cloned().collect(); + let east = deck.by_ref().take(13).cloned().collect(); + Deal { + north, + west, + south, + east, + } + } +} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index f828be5..088802f 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -5,6 +5,9 @@ pub mod bridge_engine; pub mod card; pub mod move_result; pub mod simple_bots; +pub mod contract; +pub mod actions; +pub mod core; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct UserInfo { diff --git a/protocol/src/simple_bots.rs b/protocol/src/simple_bots.rs index 182229f..3df50fb 100644 --- a/protocol/src/simple_bots.rs +++ b/protocol/src/simple_bots.rs @@ -5,8 +5,9 @@ use rand::{ }; use crate::{ + actions::Bid, bot::{BiddingBot, PlayingBot}, - bridge_engine::{Bid, BiddingStatePlayerView, PlayStatePlayerView}, + bridge_engine::{BiddingStatePlayerView, PlayStatePlayerView}, card::Card, }; @@ -45,14 +46,19 @@ impl PlayingBot for RandomPlayingBot { #[cfg(test)] mod tests { + use crate::{ + bridge_engine::SUIT_DISPLAY_ORDER, + card::RankOrder, + contract::{Contract, ContractLevel, ContractModifier, LevelAndSuit}, + core::{Deal, Player}, + move_result::MoveResult, + }; use std::str::FromStr; - use crate::{move_result::MoveResult, bridge_engine::SUIT_DISPLAY_ORDER, card::RankOrder}; use super::*; use crate::{ bridge_engine::{ - Bidding, BiddingState, BiddingStatePlayerView, Contract, - ContractLevel, ContractModifier, Deal, PlayState, Player, Raise, + Bidding, BiddingState, BiddingStatePlayerView, PlayState, }, card::Suit, }; @@ -118,7 +124,7 @@ mod tests { fn example_play_state() -> PlayState { let deal = example_deal(); - let raise1c = Raise { + let raise1c = LevelAndSuit { level: ContractLevel::One, suit: Some(Suit::Club), }; diff --git a/server/src/main.rs b/server/src/main.rs index 3e3985f..d6fc222 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,8 +10,9 @@ use axum::{ Json, Router, }; use protocol::{ - bridge_engine::{Bid, Player, TableStatePlayerView}, + bridge_engine::TableStatePlayerView, card::Card, + actions::Bid, core::Player }; use protocol::{Table, UserInfo}; use server::server::ServerState; diff --git a/server/src/play.rs b/server/src/play.rs index a825ece..893cb95 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -1,11 +1,13 @@ use async_trait::async_trait; use protocol::{ + actions::Bid, bot::{BiddingBot, PlayingBot}, bridge_engine::{ - Bid, BiddingStatePlayerView, Deal, PlayResult, GameState, - PlayStatePlayerView, Player, TableState, + BiddingStatePlayerView, GameState, PlayResult, PlayStatePlayerView, + TableState, }, card::Card, + core::{Deal, Player}, simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot}, }; use rand::random; @@ -294,10 +296,7 @@ pub async fn advance_play<J: Journal<TableUpdate>>( #[cfg(test)] mod test { - use protocol::{ - bridge_engine::{ContractLevel, Raise}, - card::Suit, - }; + use protocol::{contract::{LevelAndSuit, ContractLevel}, card::Suit}; use serde_json::json; use tracing::info; @@ -392,7 +391,7 @@ mod test { test_setup(); let mut t1 = Table::new(TestJournal::new()).await.unwrap(); assert!(t1.game().is_ok()); - let raise1c = Raise { + let raise1c = LevelAndSuit { level: ContractLevel::One, suit: Some(Suit::Club), }; diff --git a/server/src/table.rs b/server/src/table.rs index 068fb24..cc00327 100644 --- a/server/src/table.rs +++ b/server/src/table.rs @@ -2,10 +2,10 @@ use async_trait::async_trait; use protocol::{ bot::{BiddingBot, PlayingBot}, bridge_engine::{ - Bid, BiddingStatePlayerView, GameState, PlayStatePlayerView, TableState, + BiddingStatePlayerView, GameState, PlayStatePlayerView, TableState, }, card::Card, - simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot}, + simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot}, actions::Bid, }; use rand::random; diff --git a/server/tests/table_test.rs b/server/tests/table_test.rs index 4d80f32..bece8ae 100644 --- a/server/tests/table_test.rs +++ b/server/tests/table_test.rs @@ -1,4 +1,4 @@ -use protocol::{card::{Rank, Suit}, bridge_engine::TableState}; +use protocol::bridge_engine::TableState; use server::table::{Table, InMemoryTable}; mod common; diff --git a/webapp/src/components/bidding.rs b/webapp/src/components/bidding.rs index 9b92930..cce854b 100644 --- a/webapp/src/components/bidding.rs +++ b/webapp/src/components/bidding.rs @@ -1,6 +1,6 @@ use crate::components::{BiddingBox, BiddingTable}; use log::error; -use protocol::bridge_engine::{self, BiddingResult, Contract, Player}; +use protocol::{bridge_engine::{self, BiddingResult}, core::Player, contract::Contract}; use yew::prelude::*; #[derive(PartialEq, Properties, Clone)] diff --git a/webapp/src/components/bidding_box.rs b/webapp/src/components/bidding_box.rs index 0b660ea..5d013e4 100644 --- a/webapp/src/components/bidding_box.rs +++ b/webapp/src/components/bidding_box.rs @@ -1,10 +1,10 @@ use crate::components::bid_css_class; -use protocol::bridge_engine::{Bid, Raise}; +use protocol::{contract::LevelAndSuit, actions::Bid}; use yew::prelude::*; #[function_component(BiddingBox)] pub fn bidding_box(props: &BiddingBoxProps) -> Html { - let bids = Raise::all_raises().into_iter().map(|raise| { + let bids = LevelAndSuit::all_raises().into_iter().map(|raise| { let mut class = if Some(raise) <= props.current_bid { classes!("disabled") } else { @@ -38,6 +38,6 @@ pub fn bidding_box(props: &BiddingBoxProps) -> Html { #[derive(PartialEq, Properties, Clone)] pub struct BiddingBoxProps { - pub current_bid: Option<Raise>, + pub current_bid: Option<LevelAndSuit>, pub on_bid: Callback<Bid>, } diff --git a/webapp/src/components/bidding_table.rs b/webapp/src/components/bidding_table.rs index 161f85a..d03779c 100644 --- a/webapp/src/components/bidding_table.rs +++ b/webapp/src/components/bidding_table.rs @@ -1,5 +1,5 @@ use crate::components::bid_css_class; -use protocol::bridge_engine::{Bid, Bidding, Player}; +use protocol::{actions::Bid, core::Player, bridge_engine::Bidding}; use yew::prelude::*; #[function_component(BiddingTable)] diff --git a/webapp/src/components/show_bid.rs b/webapp/src/components/show_bid.rs index 81cc7aa..a292b5c 100644 --- a/webapp/src/components/show_bid.rs +++ b/webapp/src/components/show_bid.rs @@ -1,4 +1,4 @@ -use protocol::bridge_engine::{Bidding, Contract}; +use protocol::{bridge_engine::Bidding, contract::Contract}; use yew::prelude::*; #[derive(PartialEq, Properties, Clone)] diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index c8bdf6b..c1b4b47 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -3,11 +3,13 @@ use crate::components::{BiddingBox, BiddingTable, Hand, TrickInPlay, HandDiagram use crate::{services, use_app_context}; use futures::FutureExt; use log::info; +use protocol::actions::Bid; use protocol::bridge_engine::{ - Bid, BiddingStatePlayerView, GameStatePlayerView, PlayResult, - PlayStatePlayerView, Player, TableStatePlayerView, + BiddingStatePlayerView, GameStatePlayerView, PlayResult, + PlayStatePlayerView, TableStatePlayerView, }; use protocol::card::Card; +use protocol::core::Player; use yew::prelude::*; #[derive(PartialEq, Properties, Clone)] diff --git a/webapp/src/components/trick_in_play.rs b/webapp/src/components/trick_in_play.rs index 509b06c..883a3c1 100644 --- a/webapp/src/components/trick_in_play.rs +++ b/webapp/src/components/trick_in_play.rs @@ -1,5 +1,5 @@ use crate::components::Card; -use protocol::bridge_engine::{Player, Trick}; +use protocol::{bridge_engine::Trick, core::Player}; use yew::prelude::*; #[function_component(TrickInPlay)] diff --git a/webapp/src/services.rs b/webapp/src/services.rs index cd0c649..61f9c69 100644 --- a/webapp/src/services.rs +++ b/webapp/src/services.rs @@ -1,9 +1,9 @@ use anyhow::Context; use gloo_net::http::Request; use protocol::{ - bridge_engine::{Bid, TableStatePlayerView}, + bridge_engine::{TableStatePlayerView}, card::Card, - Table, + Table, actions::Bid, }; use crate::utils::ok_json; |