diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-11-27 16:45:07 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-11-27 17:23:11 -0500 |
commit | d8de16a7187d2a05fd043946cf4cb32449a5aa3b (patch) | |
tree | 6c1a212232203dbb6a9da48c192e10bef5d3b932 | |
parent | 854f247b6b7bf1106f31d7f23a326c0904d4f87e (diff) |
Add basic bot trait for bot bidding
-rw-r--r-- | Cargo.lock | 6 | ||||
-rw-r--r-- | protocol/Cargo.toml | 2 | ||||
-rw-r--r-- | protocol/src/bot.rs | 8 | ||||
-rw-r--r-- | protocol/src/bridge_engine.rs | 151 | ||||
-rw-r--r-- | protocol/src/lib.rs | 4 | ||||
-rw-r--r-- | protocol/src/simple_bots.rs | 35 |
6 files changed, 146 insertions, 60 deletions
@@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -1436,6 +1436,7 @@ name = "protocol" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "dotenv", "env_logger", "lazy_static", @@ -1446,6 +1447,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", + "tokio", "uuid", ] diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 473e0e3..c7dda82 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -10,12 +10,14 @@ serde = { version = "1.0.145", features = ["derive"] } serde_json = "1.0.85" uuid = { version = "1.2.0", features = ["serde", "wasm-bindgen", "v4", "fast-rng"] } rand = "0.8.4" +tokio = { version = "1.21.2", features = ["full"] } anyhow = "1.0" strum = "0.24" strum_macros = "0.24" log = "0.4" regex = "1.0" lazy_static = "1.4" +async-trait = "0.1.58" [dev-dependencies] env_logger = "0.8.4" diff --git a/protocol/src/bot.rs b/protocol/src/bot.rs new file mode 100644 index 0000000..3bae7db --- /dev/null +++ b/protocol/src/bot.rs @@ -0,0 +1,8 @@ +use async_trait::async_trait; + +use crate::bridge_engine::{BiddingStatePlayerView, Bid}; + +#[async_trait] +pub trait BiddingBot { + async fn bid(&self, bidding: &BiddingStatePlayerView) -> Bid; +} diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index 5c7ce4d..6b3cf7d 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -1,6 +1,7 @@ use crate::card::{Card, Deal, RankOrder, Suit}; use anyhow::{anyhow, bail}; use log::{error, info}; +use rand::{prelude::Distribution, distributions::Standard}; use regex::Regex; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -59,6 +60,15 @@ impl Player { } } +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, @@ -484,47 +494,78 @@ impl BiddingResult { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BiddingState { + pub dealer: Player, + pub deal: Deal, + pub bidding: Bidding, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BiddingStatePlayerView { + pub dealer: Player, + pub player_position: Player, + pub hand: Vec<Card>, + pub bidding: Bidding, +} + +impl BiddingStatePlayerView { + pub fn from_bidding_state(bidding_state: &BiddingState, player_position: Player) -> Self { + let BiddingState { + dealer, + deal, + bidding, + } = bidding_state; + Self { + dealer: *dealer, + player_position, + hand: player_position.get_cards(deal).clone(), + bidding: bidding.clone(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PlayState { + dealer: Player, + deal: Deal, + contract: Contract, + bidding: Bidding, + playing_deal: DealInPlay, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum GameState { - Bidding { - dealer: Player, - deal: Deal, - bidding: Bidding, - }, + Bidding(BiddingState), + Play(PlayState), PassedOut { dealer: Player, deal: Deal, bidding: Bidding, }, - Play { - dealer: Player, - playing_deal: DealInPlay, - contract: Contract, - bidding: Bidding, - }, } impl GameState { pub fn new(deal: Deal, dealer: Player) -> Self { - Self::Bidding { + Self::Bidding(BiddingState { dealer, deal, bidding: Bidding::new(dealer), - } + }) } pub fn deal(&self) -> &Deal { match self { - Self::Bidding { deal, .. } => deal, + Self::Bidding(BiddingState { deal, .. }) => deal, Self::PassedOut { deal, .. } => deal, - Self::Play { playing_deal, .. } => &playing_deal.deal(), + Self::Play(PlayState { playing_deal, .. }) => &playing_deal.deal(), } } pub fn dealer(&self) -> Player { match *self { - Self::Bidding { dealer, .. } => dealer, + Self::Bidding(BiddingState { dealer, .. }) => dealer, Self::PassedOut { dealer, .. } => dealer, - Self::Play { dealer, .. } => dealer, + Self::Play(PlayState { dealer, .. }) => dealer, } } @@ -546,30 +587,31 @@ impl GameState { pub fn bid(self, bid: Bid) -> Result<Self, anyhow::Error> { let (dealer, deal, bidding) = match self { - GameState::Bidding { + GameState::Bidding(BiddingState { dealer, deal, bidding, - } => (dealer, deal, bidding), + }) => (dealer, deal, bidding), _ => bail!("not currently bidding: {self:?}"), }; Ok(match bidding.bid(bid)? { - BiddingResult::InProgress(bidding) => GameState::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 { + BiddingResult::Contract(Some(contract), bidding) => GameState::Play(PlayState { + deal: deal.clone(), dealer, playing_deal: DealInPlay::new(contract.declarer, deal), contract, bidding, - }, + }), }) } } @@ -583,12 +625,7 @@ pub fn deal() -> Deal { #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub enum GameStatePlayerView { - Bidding { - dealer: Player, - player_position: Player, - hand: Vec<Card>, - bidding: Bidding, - }, + Bidding(BiddingStatePlayerView), PassedOut { dealer: Player, player_position: Player, @@ -614,33 +651,17 @@ pub enum GameStatePlayerView { impl GameStatePlayerView { pub fn from_game_state(game_state: &GameState, player_position: Player) -> Self { match game_state { - GameState::Bidding { - dealer, - deal, - bidding, - } => GameStatePlayerView::Bidding { - dealer: *dealer, - player_position, - bidding: bidding.clone(), - hand: player_position.get_cards(deal).clone(), - }, - GameState::PassedOut { - dealer, - deal, - bidding, - } => todo!(), - GameState::Play { - dealer, - playing_deal, - contract, - bidding, - } => todo!(), + GameState::Bidding(bidding_state) => GameStatePlayerView::Bidding( + BiddingStatePlayerView::from_bidding_state(bidding_state, player_position), + ), + GameState::PassedOut { .. } => todo!(), + GameState::Play { .. } => todo!(), } } pub fn hand(&self) -> &Vec<Card> { match self { - GameStatePlayerView::Bidding { hand, .. } => hand, + GameStatePlayerView::Bidding(BiddingStatePlayerView { hand, .. }) => hand, GameStatePlayerView::PassedOut { deal, player_position, @@ -840,10 +861,10 @@ mod tests { 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"), + west: mkcards("♠Q ♠9 ♠5 ♠2 ♥K ♥J ♥4 ♣5 ♣4 ♣2 ♦10 ♦5 ♦3"), + north: mkcards("♠A ♠8 ♠7 ♠6 ♥A ♥9 ♥5 ♥3 ♣9 ♣3 ♦Q ♦J ♦9"), + east: mkcards("♠K ♠3 ♥Q ♥10 ♥8 ♥7 ♣K ♣Q ♣J ♣10 ♣6 ♦A ♦4"), + south: mkcards("♠J ♠10 ♠4 ♥6 ♥2 ♣A ♣8 ♣7 ♦K ♦8 ♦7 ♦6 ♦2"), } } @@ -857,6 +878,24 @@ mod tests { } #[test] + fn example_deal_is_sorted() { + crate::tests::test_setup(); + let mut sorted = example_deal(); + sorted.sort(&SUIT_DISPLAY_ORDER, RankOrder::Descending); + let pp = |hand: &[Card]| { + hand.iter() + .map(|c| format!("{}", c)) + .collect::<Vec<_>>() + .join(" ") + }; + info!("{}", pp(&sorted.west)); + info!("{}", pp(&sorted.north)); + info!("{}", pp(&sorted.east)); + info!("{}", pp(&sorted.south)); + assert_eq!(example_deal(), sorted); + } + + #[test] fn game_state() { crate::tests::test_setup(); let game_state = GameState::new(mini_deal(), Player::North); @@ -889,7 +928,7 @@ mod tests { info!("Testing view for {p:?}"); let view = GameStatePlayerView::from_game_state(&game_state, p); match view { - GameStatePlayerView::Bidding { dealer, hand, .. } => { + GameStatePlayerView::Bidding(BiddingStatePlayerView { dealer, hand, .. }) => { assert_eq!(dealer, Player::East); assert_eq!(&hand, p.get_cards(&mini_deal())); } diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index fbf8047..8b48fec 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,9 +1,9 @@ -use bridge_engine::{Player, GameState}; -use card::Card; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub mod card; pub mod bridge_engine; +pub mod bot; +pub mod simple_bots; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct UserInfo { diff --git a/protocol/src/simple_bots.rs b/protocol/src/simple_bots.rs new file mode 100644 index 0000000..ffa1be2 --- /dev/null +++ b/protocol/src/simple_bots.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; + +use crate::{bot::BiddingBot, bridge_engine::{BiddingStatePlayerView, Bid}}; + +struct AlwaysPassBiddingBot {} + +#[async_trait] +impl BiddingBot for AlwaysPassBiddingBot { + async fn bid(&self, bidding: &BiddingStatePlayerView) -> Bid { + Bid::Pass + } +} + +#[cfg(test)] +mod tests { + use super::*; + use log::info; + use rand::random; + use crate::bridge_engine::{deal, GameState, BiddingState, Bidding, BiddingStatePlayerView}; + + #[tokio::test] + async fn always_passing_bot_passes() { + crate::tests::test_setup(); + let dealer = random(); + let player_position = random(); + let bidding_state = BiddingState { + dealer, + deal: deal(), + bidding: Bidding::new(dealer), + }; + let player_view = BiddingStatePlayerView::from_bidding_state(&bidding_state, player_position); + info!("Bidding state: {bidding_state:#?}"); + assert_eq!(Bid::Pass, (AlwaysPassBiddingBot {}).bid(&player_view).await); + } +} |