diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-18 15:33:02 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-18 15:33:02 -0500 |
commit | 27f74d8c366be675e7ab64ca746496a66b3cf024 (patch) | |
tree | 7bee8fee0b8753d1c3974e33bcb155b509bae100 | |
parent | 4512c5ead9406206de32e37490b7a4ac792d93bf (diff) |
Add bidding from webapp
-rw-r--r-- | protocol/src/bridge_engine.rs | 16 | ||||
-rw-r--r-- | server/Cargo.toml | 1 | ||||
-rw-r--r-- | server/src/main.rs | 2 | ||||
-rw-r--r-- | server/src/play.rs | 15 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 79 |
5 files changed, 90 insertions, 23 deletions
diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index ec88183..925676e 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -440,6 +440,7 @@ impl Bidding { } fn passed_out(&self) -> bool { + if self.bids.len() < 4 { return false; } let mut passes = 0; for b in self.bids.iter().rev().take(3) { if b == &Bid::Pass { @@ -717,6 +718,7 @@ impl GameStatePlayerView { mod tests { use super::*; use log::info; + use rand::random; fn as_bidding(r: BiddingResult) -> Bidding { match r { @@ -992,6 +994,20 @@ mod tests { } #[test] + fn pass_out_bid() { + let mut bidding = Bidding::new(random()); + for i in 0..3 { + bidding = as_bidding(bidding.bid(Bid::Pass).unwrap()); + assert!(!bidding.passed_out()); + } + bidding = match bidding.bid(Bid::Pass).unwrap() { + BiddingResult::Contract(None, bidding) => bidding, + _ => panic!("should be passed out"), + }; + assert!(bidding.passed_out()); + } + + #[test] fn play_hand() { let deal = DealInPlay::new(Player::West, mini_deal()); assert_eq!(deal.tricks_played, vec!()); diff --git a/server/Cargo.toml b/server/Cargo.toml index ca3475d..9d5f1a8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +# TODO Upgrade to 0.6: https://github.com/tokio-rs/axum/blob/main/axum/CHANGELOG.md axum = "0.5" dotenv = "0.15.0" serde = { version = "1.0.145", features = ["derive"] } diff --git a/server/src/main.rs b/server/src/main.rs index a8429ec..5b81fc0 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -172,7 +172,7 @@ async fn post_bid( ) -> Result<Json<()>, BridgeError> { info!("Getting table state for {id:}"); let jnl = DbJournal::new(extension.db.clone(), id); - let mut table = play::Table::new_or_replay(jnl).await?; + let mut table = play::Table::replay(jnl).await?; if !table.game().is_bidding() { return Err(BridgeError::InvalidRequest( "Posting a bid requires that the game is in the bidding phase" diff --git a/server/src/play.rs b/server/src/play.rs index e78a19e..79d241c 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -4,6 +4,7 @@ use rand::random; use serde::{Deserialize, Serialize}; use serde_json::json; use sqlx::{query, PgPool}; +use tracing::info; use uuid::Uuid; use crate::error::BridgeError; @@ -56,9 +57,9 @@ impl Journal for DbJournal { } async fn replay(&mut self, seq: i64) -> Result<Vec<serde_json::Value>, BridgeError> { - let results = query!( + let rows =query!( r#" - select payload from object_journal + select seq, payload from object_journal where id = $1 and seq >= $2 order by seq "#, @@ -66,8 +67,13 @@ impl Journal for DbJournal { seq ) .fetch_all(&self.db) - .await?; - Ok(results.into_iter().map(|v| v.payload).collect()) + .await?; + let mut payloads = vec!(); + for v in rows { + payloads.push(v.payload); + self.seq = v.seq; + } + Ok(payloads) } fn next(&self) -> i64 { @@ -145,6 +151,7 @@ pub async fn advance_play<J: Journal>(table: &mut Table<J>) -> Result<(), Bridge let current_player = match table.game().current_player() { Some(player) => player, None => { + info!("Could not make play. Game: {:#?}", table.game()); return Err(BridgeError::InvalidRequest(format!("No play to make for game"))); }, }; diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index 7c0b3a3..17c37b1 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -1,31 +1,51 @@ +use std::future::Future; +use std::pin::Pin; + use crate::components::{BiddingBox, BiddingTable, Hand}; use crate::use_app_context; use crate::utils::ok_json; use anyhow::Context; use gloo_net::http::Request; use log::info; -use protocol::bridge_engine::{GameStatePlayerView, BiddingState, BiddingStatePlayerView}; +use protocol::bridge_engine::{ + Bid, BiddingState, BiddingStatePlayerView, GameStatePlayerView, +}; use yew::prelude::*; #[function_component(OnlineTable)] pub fn online_table(props: &OnlineTableProps) -> Html { let ctx = use_app_context(); - let table_state: UseStateHandle<Option<GameStatePlayerView>> = use_state(|| None); - { - // TODO update this from server state + let table_state: UseStateHandle<Option<GameStatePlayerView>> = + use_state(|| None); + + let update_table_state = { let table_state = table_state.clone(); let props = props.clone(); + || { + Box::pin(async move { + // let table_state = table_state.clone(); + let props = props.clone(); + let response = + Request::get(&format!("/api/table/{}", props.table.id)) + .send() + .await + .context("fetching table data")?; + let table = ok_json(response).await?; + table_state.set(Some(table)); + Ok(()) + }) + as Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>> + } + }; + + { let ctx = ctx.clone(); + let update_table_state = update_table_state.clone(); use_effect_with_deps( move |_| { ctx.spawn_async(async move { - let response = Request::get(&format!("/api/table/{}", props.table.id)) - .send() - .await - .context("fetching table data")?; - let table = ok_json(response).await?; - table_state.set(Some(table)); + update_table_state().await?; Ok(()) }); || () @@ -34,6 +54,29 @@ pub fn online_table(props: &OnlineTableProps) -> Html { ); } + let on_bid = { + let ctx = ctx.clone(); + let props = props.clone(); + let update_table_state = update_table_state.clone(); + Callback::from(move |bid| { + let update_table_state = update_table_state.clone(); + info!("Bid clicked: {:?}", bid); + ctx.spawn_async(async move { + let bid_response = Request::post(&format!( + "/api/table/{}/bid", + props.table.id + )) + .json(&bid)? + .send() + .await + .context("submitting bid")?; + let () = ok_json(bid_response).await?; + update_table_state().await?; + Ok(()) + }); + }) + }; + let leave_table = { let ctx = ctx.clone(); Callback::from(move |_| { @@ -48,7 +91,7 @@ pub fn online_table(props: &OnlineTableProps) -> Html { { "Leave table" } </button> if let Some(table_state) = &*table_state { - <Table table={ table_state.clone() }/> + <Table table={ table_state.clone() } { on_bid } /> } </> } @@ -66,16 +109,15 @@ pub fn table(props: &TableProps) -> Html { info!("Card clicked: {}", card); }) }; - let on_bid = { - Callback::from(move |bid| { - info!("Bid clicked: {:?}", bid); - }) - }; + let center = match &props.table { - GameStatePlayerView::Bidding(BiddingStatePlayerView { bidding, .. }) => html! { + GameStatePlayerView::Bidding(BiddingStatePlayerView { + bidding, + .. + }) => html! { <> <BiddingTable bidding={bidding.clone()} /> - <BiddingBox current_bid={bidding.highest_bid().clone()} { on_bid } /> + <BiddingBox current_bid={bidding.highest_bid().clone()} on_bid={ props.on_bid.clone() } /> { format!("It is {:?} to bid", bidding.current_bidder()) } </> }, @@ -100,4 +142,5 @@ pub fn table(props: &TableProps) -> Html { #[derive(PartialEq, Properties, Clone)] pub struct TableProps { pub table: GameStatePlayerView, + pub on_bid: Callback<Bid>, } |