summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-24 10:30:45 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-24 10:30:45 -0500
commit810d2de21f4c47d5f263678c274ae915702d247f (patch)
tree5dd8fad41503d196045a278df607cbbecbbc7d1e
parente732b64fa6881cf25fd353edff4fd76c839e0c8b (diff)
Add `TableView` for representing player hands in the app
-rw-r--r--protocol/src/bridge_engine.rs55
-rw-r--r--protocol/src/lib.rs11
-rw-r--r--server/src/error.rs3
-rw-r--r--server/src/main.rs31
-rw-r--r--webapp/src/components/game.rs4
-rw-r--r--webapp/src/components/table.rs9
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>
</>
}
}