diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-23 16:16:35 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-23 16:18:41 -0500 |
commit | 45ce66a29b3d1c49ef8e86b125701e89d58e8a4a (patch) | |
tree | 14d67dfef5c2d5e1aa7e4100526973cbd0ba4c2b | |
parent | 82c447fbfe12ee76dbbdaa36b0de1343d3a91795 (diff) |
Generalize table state to include the result of play
-rw-r--r-- | protocol/src/bridge_engine.rs | 76 | ||||
-rw-r--r-- | protocol/src/play_result.rs | 23 | ||||
-rw-r--r-- | server/src/main.rs | 21 | ||||
-rw-r--r-- | server/src/play.rs | 40 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 80 | ||||
-rw-r--r-- | webapp/src/services.rs | 4 |
6 files changed, 151 insertions, 93 deletions
diff --git a/protocol/src/bridge_engine.rs b/protocol/src/bridge_engine.rs index ee43437..0c583f3 100644 --- a/protocol/src/bridge_engine.rs +++ b/protocol/src/bridge_engine.rs @@ -7,7 +7,6 @@ use log::{error, info}; use rand::{ distributions::Standard, prelude::{Distribution, SliceRandom}, - random, }; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -756,17 +755,17 @@ impl GameState { } = self.bidding()?.clone(); Ok(match bidding.bid(bid)? { BiddingResult::InProgress(bidding) => { - MoveResult::Stay(GameState::Bidding(BiddingState { + MoveResult::Current(GameState::Bidding(BiddingState { dealer, deal, bidding, })) } BiddingResult::Contract(None, _bidding) => { - MoveResult::Go(GameResult) + MoveResult::Next(GameResult) } BiddingResult::Contract(Some(contract), bidding) => { - MoveResult::Stay(GameState::Play(PlayState::new( + MoveResult::Current(GameState::Play(PlayState::new( deal, contract, bidding, ))) } @@ -779,10 +778,10 @@ impl GameState { ) -> Result<MoveResult<GameState, GameResult>, anyhow::Error> { Ok(match self.play_state()?.clone().play(card)? { PlayStateResult::InProgress(play_state) => { - MoveResult::Stay(play_state.into()) + MoveResult::Current(play_state.into()) } PlayStateResult::PlayFinished(result) => { - MoveResult::Go(result.into()) + MoveResult::Next(result.into()) } }) } @@ -870,7 +869,11 @@ impl PlayStatePlayerView { hand: player_position .get_cards(&play_state.playing_deal.deal) .clone(), - previous_trick: play_state.playing_deal.tricks_played.last().cloned(), + previous_trick: play_state + .playing_deal + .tricks_played + .last() + .cloned(), current_trick: play_state.playing_deal.in_progress.clone(), } } @@ -924,6 +927,56 @@ impl GameStatePlayerView { } } +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum TableState { + Unknown, + Game(GameState), + Result(GameResult), +} + +impl Default for TableState { + fn default() -> Self { + TableState::Unknown + } +} + +impl Into<TableState> for MoveResult<GameState, GameResult> { + fn into(self) -> TableState { + match self { + MoveResult::Current(game) => TableState::Game(game), + MoveResult::Next(result) => TableState::Result(result), + } + } +} + +impl Into<TableState> for GameState { + fn into(self) -> TableState { + TableState::Game(self) + } +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum TableStatePlayerView { + Unknown, + Game(GameStatePlayerView), + Result(GameResult), +} + +impl TableStatePlayerView { + pub fn from_table_state( + table: &TableState, + player_position: Player, + ) -> Self { + match table { + TableState::Unknown => TableStatePlayerView::Unknown, + TableState::Game(g) => TableStatePlayerView::Game( + GameStatePlayerView::from_game_state(g, player_position), + ), + TableState::Result(r) => TableStatePlayerView::Result(r.clone()), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -1167,19 +1220,20 @@ mod tests { info!("Start bidding with game state {game_state:#?}"); let raise = |s| Bid::Raise(Raise::from_str(s).unwrap()); - let game_state = game_state.bid(raise("1H")).unwrap().stay().unwrap(); + let game_state = + game_state.bid(raise("1H")).unwrap().current().unwrap(); let game_state = game_state .bid(Bid::Pass) .unwrap() - .stay() + .current() .unwrap() .bid(Bid::Pass) .unwrap() - .stay() + .current() .unwrap() .bid(Bid::Pass) .unwrap() - .stay() + .current() .unwrap(); info!("Start playing with game state {game_state:#?}"); assert_eq!(game_state.is_bidding(), false); diff --git a/protocol/src/play_result.rs b/protocol/src/play_result.rs index 21961ee..2dba276 100644 --- a/protocol/src/play_result.rs +++ b/protocol/src/play_result.rs @@ -1,20 +1,23 @@ -pub enum MoveResult<Stay, Go> { - Stay(Stay), - Go(Go), +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +pub enum MoveResult<Current, Next> { + Current(Current), + Next(Next), } -impl<Stay, Go> MoveResult<Stay, Go> { - pub fn stay(self) -> Result<Stay, anyhow::Error> { +impl<Current, Next> MoveResult<Current, Next> { + pub fn current(self) -> Result<Current, anyhow::Error> { match self { - MoveResult::Stay(o) => Ok(o), - MoveResult::Go(_) => Err(anyhow::anyhow!("Not in stay state")), + MoveResult::Current(o) => Ok(o), + MoveResult::Next(_) => Err(anyhow::anyhow!("Not in current state")), } } - pub fn go(self) -> Result<Go, anyhow::Error> { + pub fn next(self) -> Result<Next, anyhow::Error> { match self { - MoveResult::Go(f) => Ok(f), - MoveResult::Stay(_) => Err(anyhow::anyhow!("Not in go state")), + MoveResult::Next(f) => Ok(f), + MoveResult::Current(_) => Err(anyhow::anyhow!("Not in next state")), } } } diff --git a/server/src/main.rs b/server/src/main.rs index 969bc25..a9e8f69 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -9,7 +9,10 @@ use axum::{ routing::{delete, get, post}, Json, Router, }; -use protocol::{bridge_engine::{Bid, GameStatePlayerView, Player}, card::Card}; +use protocol::{ + bridge_engine::{Bid, GameStatePlayerView, Player, TableStatePlayerView}, + card::Card, +}; use protocol::{Table, UserInfo}; use server::ServerState; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; @@ -147,17 +150,19 @@ async fn get_table_view( _session: AuthenticatedSession, State(state): ServerState, Path(id): Path<Uuid>, -) -> Result<Json<protocol::bridge_engine::GameStatePlayerView>, BridgeError> { +) -> Result<Json<TableStatePlayerView>, BridgeError> { info!("Getting table state for {id:}"); let player_position = Player::South; let jnl = DbJournal::new(state.db.clone(), id); let mut table = play::Table::new_or_replay(jnl).await?; info!("Advancing play"); - while table.game()?.current_player() != player_position { + while table.game_in_progress() + && table.game()?.current_player() != player_position + { advance_play(&mut table).await?; } - let response = Json(GameStatePlayerView::from_game_state( - table.game()?, + let response = Json(TableStatePlayerView::from_table_state( + &table.state, player_position, )); info!("Response: {response:#?}"); @@ -304,6 +309,10 @@ mod tests { pub fn test_setup() { dotenv::dotenv().ok(); - let _ =env_logger::Builder::from_env(Env::default().default_filter_or("info")).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/play.rs b/server/src/play.rs index ffb7407..e0d4733 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -3,10 +3,9 @@ use protocol::{ bot::{BiddingBot, PlayingBot}, bridge_engine::{ Bid, BiddingStatePlayerView, Deal, GameResult, GameState, - PlayStatePlayerView, Player, + PlayStatePlayerView, Player, TableState, }, card::Card, - play_result::MoveResult, simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot}, }; use rand::random; @@ -125,35 +124,7 @@ where { journal: J, settings: TableSettings, - state: TableState, -} - -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] -pub enum TableState { - Unknown, - Game(GameState), - Result(GameResult), -} - -impl Default for TableState { - fn default() -> Self { - TableState::Unknown - } -} - -impl Into<TableState> for MoveResult<GameState, GameResult> { - fn into(self) -> TableState { - match self { - MoveResult::Stay(game) => TableState::Game(game), - MoveResult::Go(result) => TableState::Result(result), - } - } -} - -impl Into<TableState> for GameState { - fn into(self) -> TableState { - TableState::Game(self) - } + pub state: TableState, } impl<J: Journal<TableUpdate>> Table<J> { @@ -167,6 +138,13 @@ impl<J: Journal<TableUpdate>> Table<J> { Ok(table) } + pub fn game_in_progress(&self) -> bool { + match &self.state { + TableState::Game(_) => true, + _ => false, + } + } + pub fn game(&self) -> Result<&GameState, BridgeError> { match &self.state { TableState::Game(g) => Ok(g), diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index e1ec1a1..9702793 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -4,8 +4,8 @@ use crate::{services, use_app_context}; use futures::FutureExt; use log::info; use protocol::bridge_engine::{ - Bid, BiddingStatePlayerView, GameStatePlayerView, - PlayStatePlayerView, Player, + Bid, BiddingStatePlayerView, GameStatePlayerView, PlayStatePlayerView, + Player, TableStatePlayerView, }; use protocol::card::Card; use yew::prelude::*; @@ -30,11 +30,11 @@ pub struct OnlineTableInnerProps { } struct OnlineTableInner { - table_state: Option<GameStatePlayerView>, + table_state: Option<TableStatePlayerView>, } pub enum Msg { - TableStateUpdated(Result<GameStatePlayerView, anyhow::Error>), + TableStateUpdated(Result<TableStatePlayerView, anyhow::Error>), Bid(Bid), Play(Card), } @@ -42,7 +42,9 @@ pub enum Msg { impl OnlineTableInner { fn play(&mut self, ctx: &yew::Context<Self>, card: Card) { let _play_state = match &self.table_state { - Some(GameStatePlayerView::Playing(play_state)) => play_state, + Some(TableStatePlayerView::Game(GameStatePlayerView::Playing( + play_state, + ))) => play_state, _ => { info!( "Cannot play card with table state: {:#?}", @@ -61,6 +63,38 @@ impl OnlineTableInner { .map(Msg::TableStateUpdated), ); } + + fn view_game( + &self, + ctx: &yew::Context<Self>, + game: &GameStatePlayerView, + ) -> Html { + let center = match game { + GameStatePlayerView::Bidding(bidding) => { + bidding_view(bidding, ctx.link().callback(Msg::Bid)) + } + GameStatePlayerView::Playing(playing) => { + playing_view(playing, ctx.link().callback(Msg::Play)) + } + }; + + let leave_table = { + let ctx = ctx.props().app_ctx.clone(); + Callback::from(move |_| ctx.leave_table()) + }; + + html! { + <> + <div class="game-layout"> + {center} + </div> + <p>{format!("This is table {}", ctx.props().table.id)}</p> + <button onclick={leave_table}> + { "Leave table" } + </button> + </> + } + } } impl Component for OnlineTableInner { @@ -106,35 +140,15 @@ impl Component for OnlineTableInner { } fn view(&self, ctx: &yew::Context<Self>) -> Html { - let table_state = match &self.table_state { - Some(x) => x, + match &self.table_state { None => return loading(), - }; - - let center = match table_state { - GameStatePlayerView::Bidding(bidding) => { - bidding_view(bidding, ctx.link().callback(Msg::Bid)) - } - GameStatePlayerView::Playing(playing) => { - playing_view(playing, ctx.link().callback(Msg::Play)) - } - }; - - let leave_table = { - let ctx = ctx.props().app_ctx.clone(); - Callback::from(move |_| ctx.leave_table()) - }; - - html! { - <> - <div class="game-layout"> - {center} - </div> - <p>{format!("This is table {}", ctx.props().table.id)}</p> - <button onclick={leave_table}> - { "Leave table" } - </button> - </> + Some(TableStatePlayerView::Unknown) => html! { + <p>{"An error occurred."}</p> + }, + Some(TableStatePlayerView::Game(game)) => self.view_game(ctx, game), + Some(TableStatePlayerView::Result(result)) => html! { + <p>{"A beautiful result."}</p> + }, } } } diff --git a/webapp/src/services.rs b/webapp/src/services.rs index 212363f..43b1698 100644 --- a/webapp/src/services.rs +++ b/webapp/src/services.rs @@ -1,12 +1,12 @@ use anyhow::Context; use gloo_net::http::Request; -use protocol::{bridge_engine::{GameStatePlayerView, Bid}, Table, card::Card}; +use protocol::{bridge_engine::{TableStatePlayerView, Bid}, Table, card::Card}; use crate::utils::ok_json; pub async fn get_table_player_view( table: Table, -) -> Result<GameStatePlayerView, anyhow::Error> { +) -> Result<TableStatePlayerView, anyhow::Error> { let response = Request::get(&format!("/api/table/{}", table.id)) .send() .await |