summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-12-22 19:09:17 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-12-22 19:09:17 -0500
commit58aa2e4764f90528270ae5b9a61de576260e5483 (patch)
treeb8b9ce071637fa66335108e7430cce849f872933
parenta60500b47c81d15c9b970b58b1c871821dbe934a (diff)
Fixes to the engine enough that the random bot can finish a game
-rw-r--r--Cargo.lock94
-rw-r--r--protocol/src/bridge_engine.rs84
-rw-r--r--protocol/src/simple_bots.rs13
-rw-r--r--server/Cargo.toml1
-rw-r--r--server/src/main.rs10
-rw-r--r--server/src/play.rs48
-rw-r--r--webapp/src/components/table.rs2
7 files changed, 214 insertions, 38 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b6f4c73..81ac06b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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>