diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-22 19:09:17 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-22 19:09:17 -0500 |
commit | 58aa2e4764f90528270ae5b9a61de576260e5483 (patch) | |
tree | b8b9ce071637fa66335108e7430cce849f872933 | |
parent | a60500b47c81d15c9b970b58b1c871821dbe934a (diff) |
Fixes to the engine enough that the random bot can finish a game
-rw-r--r-- | Cargo.lock | 94 | ||||
-rw-r--r-- | protocol/src/bridge_engine.rs | 84 | ||||
-rw-r--r-- | protocol/src/simple_bots.rs | 13 | ||||
-rw-r--r-- | server/Cargo.toml | 1 | ||||
-rw-r--r-- | server/src/main.rs | 10 | ||||
-rw-r--r-- | server/src/play.rs | 48 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 2 |
7 files changed, 214 insertions, 38 deletions
@@ -98,7 +98,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -481,6 +481,40 @@ dependencies = [ ] [[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -829,6 +863,15 @@ dependencies = [ ] [[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1002,6 +1045,16 @@ dependencies = [ ] [[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] name = "ipnet" version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1017,6 +1070,18 @@ dependencies = [ ] [[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1062,6 +1127,12 @@ dependencies = [ ] [[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] name = "lock_api" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1242,7 +1313,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -1500,7 +1571,7 @@ dependencies = [ "anyhow", "async-trait", "dotenv", - "env_logger", + "env_logger 0.8.4", "lazy_static", "log", "rand", @@ -1671,6 +1742,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] +name = "rustix" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[package]] name = "rustls" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1848,6 +1933,7 @@ dependencies = [ "chrono", "cookie", "dotenv", + "env_logger 0.10.0", "lru", "openidconnect", "protocol", @@ -2651,7 +2737,7 @@ dependencies = [ "anyhow", "console_error_panic_hook", "dotenv", - "env_logger", + "env_logger 0.8.4", "getrandom", "gloo-net", "lazy_static", diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index 7699a22..bacee56 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -213,15 +213,19 @@ impl DealInPlay { }) } 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, - }) + let trick_winner = trick.winner(); + let mut tricks = self.tricks_played; + tricks.push(trick); + + if player_cards.is_empty() { + DealInPlayResult::PlayFinished(tricks) + } else { + DealInPlayResult::InProgress(Self { + in_progress: TurnInPlay::new(trick_winner), + tricks_played: tricks, + deal: self.deal, + }) + } } }) } @@ -630,10 +634,7 @@ impl PlayState { self.playing_deal.current_player() } - pub fn play( - self, - card: Card, - ) -> Result<PlayStateResult, anyhow::Error> { + pub fn play(self, card: Card) -> Result<PlayStateResult, anyhow::Error> { Ok(match self.playing_deal.play(card)? { DealInPlayResult::InProgress(playing_deal) => { PlayStateResult::InProgress(Self { @@ -663,6 +664,12 @@ pub enum GameState { Play(PlayState), } +impl Into<GameState> for PlayState { + fn into(self) -> GameState { + GameState::Play(self) + } +} + impl GameState { pub fn new(deal: Deal, dealer: Player) -> Self { Self::Bidding(BiddingState { @@ -709,18 +716,29 @@ impl GameState { } } + pub fn bidding(&self) -> Result<&BiddingState, anyhow::Error> { + match self { + GameState::Bidding(bidding_state) => Ok(bidding_state), + _ => Err(anyhow::anyhow!("not currently bidding")), + } + } + + pub fn play_state(&self) -> Result<&PlayState, anyhow::Error> { + match self { + GameState::Play(play_state) => Ok(play_state), + _ => Err(anyhow::anyhow!("not currently playing")), + } + } + pub fn bid( self, bid: Bid, ) -> Result<MoveResult<GameState, GameResult>, anyhow::Error> { - let (dealer, deal, bidding) = match self { - GameState::Bidding(BiddingState { - dealer, - deal, - bidding, - }) => (dealer, deal, bidding), - _ => bail!("not currently bidding: {self:?}"), - }; + let BiddingState { + dealer, + deal, + bidding, + } = self.bidding()?.clone(); Ok(match bidding.bid(bid)? { BiddingResult::InProgress(bidding) => { MoveResult::Stay(GameState::Bidding(BiddingState { @@ -739,11 +757,31 @@ impl GameState { } }) } + + pub fn play( + self, + card: Card, + ) -> Result<MoveResult<GameState, GameResult>, anyhow::Error> { + Ok(match self.play_state()?.clone().play(card)? { + PlayStateResult::InProgress(play_state) => { + MoveResult::Stay(play_state.into()) + } + PlayStateResult::PlayFinished(result) => { + MoveResult::Go(result.into()) + } + }) + } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct GameResult; +impl Into<GameResult> for PlayResult { + fn into(self) -> GameResult { + GameResult + } +} + #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Deal { pub north: Vec<Card>, @@ -803,7 +841,9 @@ impl PlayStatePlayerView { contract: play_state.contract, dummy: None, declarer_tricks: 0, - hand: player_position.get_cards(&play_state.deal).clone(), + hand: player_position + .get_cards(&play_state.playing_deal.deal) + .clone(), previous_trick: Trick { leader: random(), cards_played: vec![], diff --git a/protocol/src/simple_bots.rs b/protocol/src/simple_bots.rs index 3df7b5d..501f1f4 100644 --- a/protocol/src/simple_bots.rs +++ b/protocol/src/simple_bots.rs @@ -147,4 +147,17 @@ mod tests { (AlwaysPassBiddingBot {}).bid(&player_view).await ); } + + #[tokio::test] + async fn play_until_completion() { + crate::tests::test_setup(); + let bot = RandomPlayingBot {}; + let mut result = PlayStateResult::InProgress(example_play_state()); + while let PlayStateResult::InProgress(play_state) = result { + info!("Play state: {play_state:#?}"); + let player_state = PlayStatePlayerView::from_play_state(&play_state, play_state.current_player()); + let card = bot.play(&player_state).await; + result = play_state.play(card).unwrap(); + } + } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 9d5f1a8..8d8407c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -31,3 +31,4 @@ cookie = "0.16.1" time = "0.1.44" async-trait = "0.1.57" rand = "0.8.4" +env_logger = "0.10.0" diff --git a/server/src/main.rs b/server/src/main.rs index 735258f..03ae3e3 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -296,3 +296,13 @@ async fn login(cookies: Cookies, extension: ContextExtension) -> Redirect { cookies.add(cookie); Redirect::temporary(auth_url.as_str()) } + +#[cfg(test)] +mod tests { + use env_logger::Env; + + pub fn test_setup() { + dotenv::dotenv().ok(); + let _ =env_logger::Builder::from_env(Env::default().default_filter_or("info")).is_test(true).try_init(); + } +} diff --git a/server/src/play.rs b/server/src/play.rs index 974ec58..ffb7407 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -1,5 +1,3 @@ -use std::mem; - use async_trait::async_trait; use protocol::{ bot::{BiddingBot, PlayingBot}, @@ -178,6 +176,15 @@ impl<J: Journal<TableUpdate>> Table<J> { } } + pub fn result(&self) -> Result<&GameResult, BridgeError> { + match &self.state { + TableState::Result(r) => Ok(r), + _ => Err(BridgeError::InvalidRequest( + "no result".to_string(), + )), + } + } + async fn init(&mut self) -> Result<(), BridgeError> { self.insert_and_apply(TableUpdate::NewDeal { deal: random(), @@ -219,7 +226,10 @@ impl<J: Journal<TableUpdate>> Table<J> { self.state = GameState::new(deal, dealer).into(); Ok(()) } - TableUpdate::Play(_) => todo!(), + TableUpdate::Play(card) => { + self.state = self.game()?.clone().play(card)?.into(); + Ok(()) + }, } } @@ -246,7 +256,7 @@ impl<J: Journal<TableUpdate>> Table<J> { Ok(()) } - pub async fn new_or_replay(mut journal: J) -> Result<Self, BridgeError> { + pub async fn new_or_replay(journal: J) -> Result<Self, BridgeError> { let mut table = Self { journal, state: Default::default(), @@ -295,9 +305,11 @@ pub async fn advance_play<J: Journal<TableUpdate>>( #[cfg(test)] mod test { - use std::cell::RefCell; - + use protocol::{bridge_engine::{Raise, ContractLevel}, card::Suit}; use serde_json::json; + use tracing::info; + + use crate::tests::test_setup; use super::*; @@ -313,10 +325,6 @@ mod test { seq: -1, } } - - pub fn reset(&mut self) { - self.seq = -1; - } } #[async_trait] @@ -380,10 +388,28 @@ mod test { // } #[tokio::test] - async fn test_advance_play() { + async fn test_advance_play_once() { let mut t1 = Table::new(TestJournal::new()).await.unwrap(); let player = t1.game().unwrap().current_player(); advance_play(&mut t1).await.unwrap(); assert_ne!(player, t1.game().unwrap().current_player()); } + + #[tokio::test] + async fn test_advance_play_until_result() { + test_setup(); + let mut t1 = Table::new(TestJournal::new()).await.unwrap(); + assert!(t1.game().is_ok()); + let raise1c = Raise { + level: ContractLevel::One, + suit: Some(Suit::Club), + }; + // Make sure the game doesn't get passed out. + t1.bid(Bid::Raise(raise1c)).await.unwrap(); + while t1.game().is_ok() { + info!("Game is: {:#?}", t1.game()); + advance_play(&mut t1).await.unwrap(); + } + assert!(t1.result().is_ok()); + } } diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index fa21102..794785f 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -94,7 +94,7 @@ pub fn online_table(props: &OnlineTableProps) -> Html { html! { <> - p<>{ format!("This is table {}", props.table.id) }</p> + <>{ format!("This is table {}", props.table.id) }</p> <button onclick={leave_table}> { "Leave table" } </button> |