summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2023-01-01 20:34:09 -0500
committerKjetil Orbekk <kj@orbekk.com>2023-01-01 20:34:09 -0500
commitd3fbefad9cf25786fb5f28f96eeceb65d0a8b35b (patch)
tree156a23b5c04b93d746ecf592971aefbcc127cfd2
parentbb2ed3a2926384df063e476d10613fa310cd7ffa (diff)
Split bridge_engine into a few separate modules
-rw-r--r--protocol/src/actions.rs66
-rw-r--r--protocol/src/bot.rs4
-rw-r--r--protocol/src/bridge_engine.rs406
-rw-r--r--protocol/src/contract.rs210
-rw-r--r--protocol/src/core.rs116
-rw-r--r--protocol/src/lib.rs3
-rw-r--r--protocol/src/simple_bots.rs16
-rw-r--r--server/src/main.rs3
-rw-r--r--server/src/play.rs13
-rw-r--r--server/src/table.rs4
-rw-r--r--server/tests/table_test.rs2
-rw-r--r--webapp/src/components/bidding.rs2
-rw-r--r--webapp/src/components/bidding_box.rs6
-rw-r--r--webapp/src/components/bidding_table.rs2
-rw-r--r--webapp/src/components/show_bid.rs2
-rw-r--r--webapp/src/components/table.rs6
-rw-r--r--webapp/src/components/trick_in_play.rs2
-rw-r--r--webapp/src/services.rs4
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;