diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-11-24 10:30:45 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-11-24 10:30:45 -0500 |
commit | 810d2de21f4c47d5f263678c274ae915702d247f (patch) | |
tree | 5dd8fad41503d196045a278df607cbbecbbc7d1e | |
parent | e732b64fa6881cf25fd353edff4fd76c839e0c8b (diff) |
Add `TableView` for representing player hands in the app
-rw-r--r-- | protocol/src/bridge_engine.rs | 55 | ||||
-rw-r--r-- | protocol/src/lib.rs | 11 | ||||
-rw-r--r-- | server/src/error.rs | 3 | ||||
-rw-r--r-- | server/src/main.rs | 31 | ||||
-rw-r--r-- | webapp/src/components/game.rs | 4 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 9 |
6 files changed, 88 insertions, 25 deletions
diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index e9c09f6..261f650 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -11,7 +11,7 @@ use strum_macros::{EnumCount as EnumCountMacro, EnumIter, FromRepr}; 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)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, FromRepr, EnumCountMacro, Serialize, Deserialize, EnumIter)] #[repr(u8)] pub enum Player { West = 0, @@ -38,7 +38,16 @@ impl Player { } } - pub fn get_cards<'a>(&self, deal: &'a mut Deal) -> &'a mut Vec<Card> { + 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, @@ -147,7 +156,7 @@ impl DealInPlay { pub fn play(mut self: Self, card: Card) -> Result<DealInPlayResult, anyhow::Error> { let player = self.in_progress.next_player(); - let player_cards = player.get_cards(&mut self.deal); + let player_cards = player.get_cards_mut(&mut self.deal); info!( "Next player is {:?}, playing card {} from {:?}", @@ -484,6 +493,7 @@ pub enum GameState { bidding: Bidding, }, Play { + dealer: Player, playing_deal: DealInPlay, contract: Contract, bidding: Bidding, @@ -498,6 +508,14 @@ impl GameState { Self::Play { playing_deal, .. } => &playing_deal.deal(), } } + + pub fn dealer(&self) -> Player { + match *self { + Self::Bidding { dealer, .. } => dealer, + Self::PassedOut { dealer, .. } => dealer, + Self::Play { dealer, .. } => dealer, + } + } } pub fn deal() -> Deal { @@ -507,6 +525,23 @@ pub fn deal() -> Deal { deal } +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +pub struct TableView { + dealer: Player, + player_position: Player, + hand: Vec<Card>, +} + +impl TableView { + pub fn from_game_state(game_state: &GameState, player_position: Player) -> Self { + TableView { + dealer: game_state.dealer(), + player_position, + hand: player_position.get_cards(game_state.deal()).clone(), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -711,6 +746,20 @@ mod tests { } } + #[test] + fn table_view() { + crate::tests::test_setup(); + let game_state = GameState::Bidding { dealer: Player::East, deal: mini_deal() }; + info!("Game state: {game_state:?}"); + for p in Player::iter() { + info!("Testing view for {p:?}"); + let view = TableView::from_game_state(&game_state, p); + assert_eq!(view.player_position, p); + assert_eq!(view.dealer, Player::East); + assert_eq!(&view.hand, p.get_cards_mut(&mut mini_deal())); + } + } + fn as_playing_hand(result: DealInPlayResult) -> DealInPlay { match result { DealInPlayResult::InProgress(r) => r, diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 41268a9..fbf8047 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,3 +1,5 @@ +use bridge_engine::{Player, GameState}; +use card::Card; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub mod card; @@ -14,15 +16,12 @@ pub struct Table { pub id: Uuid, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] -pub struct TableView { - pub m: String, -} - #[cfg(test)] mod tests { + use env_logger::Env; + pub fn test_setup() { dotenv::dotenv().ok(); - let _ = env_logger::builder().is_test(true).try_init(); + let _ =env_logger::Builder::from_env(Env::default().default_filter_or("info")).is_test(true).try_init(); } } diff --git a/server/src/error.rs b/server/src/error.rs index 6c2dc41..611d8e4 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -17,6 +17,9 @@ pub enum BridgeError { #[error("Invalid request: {0}")] InvalidRequest(String), + #[error("Version conflict when updating object {0} to version {1}")] + JournalConflict(String, i64), + #[error("Requesting token failed")] OpenidRequestTokenError(#[from] RequestTokenError), diff --git a/server/src/main.rs b/server/src/main.rs index 900d261..130506c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,15 +1,15 @@ -use std::{collections::HashMap, env, str::FromStr, sync::Arc}; use serde_json::json; +use std::{collections::HashMap, env, str::FromStr, sync::Arc}; use uuid::Uuid; use auth::AuthenticatedSession; use axum::{ - extract::{Extension, Query, Path}, + extract::{Extension, Path, Query}, response::Redirect, routing::{delete, get, post}, Json, Router, }; -use protocol::{Table, UserInfo, bridge_engine}; +use protocol::{bridge_engine::{self, TableView, Player}, Table, UserInfo}; use server::ContextExtension; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; use tower_http::trace::TraceLayer; @@ -17,13 +17,16 @@ use tracing::info; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod auth; mod error; -mod server; mod play; -use crate::{error::BridgeError, play::{DbJournal, Journal}}; +mod server; use crate::{ auth::{Authenticator, SessionId}, server::ServerContext, }; +use crate::{ + error::BridgeError, + play::{DbJournal, Journal}, +}; use sqlx::{postgres::PgPoolOptions, PgPool}; #[tokio::main] @@ -46,7 +49,9 @@ async fn main() { .expect("db connection"); let mut jnl = DbJournal::new(db_pool.clone(), Uuid::new_v4()); - jnl.append(0, json!("starting server")).await.expect("new object"); + jnl.append(0, json!("starting server")) + .await + .expect("new object"); info!("Running db migrations"); sqlx::migrate!().run(&db_pool).await.expect("db migration"); @@ -83,12 +88,13 @@ async fn main() { async fn get_table_view( _session: AuthenticatedSession, extension: ContextExtension, - Path(id): Path<Uuid> -) -> Result<Json<protocol::bridge_engine::GameState>, BridgeError> { - info!("Getting table state for table {id:}"); + Path(id): Path<Uuid>, +) -> Result<Json<protocol::bridge_engine::TableView>, BridgeError> { + info!("Getting table state for {id:}"); let jnl = DbJournal::new(extension.db.clone(), id); let table = play::Table::new_or_replay(jnl).await?; - let response: Json<bridge_engine::GameState> = Json(table.game().clone()); + let response = + Json(TableView::from_game_state(table.game(), Player::South)); info!("Response: {response:#?}"); Ok(response) } @@ -100,10 +106,11 @@ async fn leave_table( sqlx::query!( r#" delete from table_players where player_id = $1 - "#, session.player_id + "#, + session.player_id ) .execute(&extension.db) - .await?; + .await?; Ok(()) } diff --git a/webapp/src/components/game.rs b/webapp/src/components/game.rs index 02774e7..9511eb2 100644 --- a/webapp/src/components/game.rs +++ b/webapp/src/components/game.rs @@ -24,6 +24,7 @@ pub fn game() -> Html { let state = state.clone(); Callback::from(move |card| { if let GameState::Play { + dealer, playing_deal, contract, bidding, @@ -33,6 +34,7 @@ pub fn game() -> Html { match playing_deal.play(card) { Err(err) => error!("Could not play card: {:?}", err), Ok(DealInPlayResult::InProgress(playing_deal)) => state.set(GameState::Play { + dealer, playing_deal, contract, bidding, @@ -52,6 +54,7 @@ pub fn game() -> Html { Callback::from(move |(contract, bidding)| { state.set(match contract { Some(contract) => GameState::Play { + dealer, playing_deal: DealInPlay::new(dealer, deal.clone()), contract, bidding, @@ -69,6 +72,7 @@ pub fn game() -> Html { } } GameState::Play { + dealer, playing_deal, contract: _, bidding: _, diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index 3561131..6d861d0 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -1,5 +1,6 @@ use gloo_net::http::Request; use log::info; +use protocol::bridge_engine::TableView; use yew::prelude::*; use crate::use_app_context; @@ -8,7 +9,7 @@ use crate::use_app_context; pub fn table(props: &TableProps) -> Html { let ctx = use_app_context(); - let table_state: UseStateHandle<Option<String>> = use_state(|| None); + let table_state: UseStateHandle<Option<TableView>> = use_state(|| None); { // TODO update this from server state let table_state = table_state.clone(); @@ -20,8 +21,8 @@ pub fn table(props: &TableProps) -> Html { let response = Request::get(&format!("/api/table/{}", props.table.id)) .send() .await?; - let table: protocol::bridge_engine::GameState = response.json().await?; - table_state.set(Some(format!("{:#?}", table))); + let table = response.json().await?; + table_state.set(Some(table)); Ok(()) }); || () @@ -43,7 +44,7 @@ pub fn table(props: &TableProps) -> Html { <button onclick={leave_table}> { "Leave table" } </button> - <pre>{ table_state.as_ref().map_or("".to_string(), |t| format!("{}", t)) }</pre> + <pre>{ format!("Table view: {:?}", *table_state) }</pre> </> } } |