summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-12-23 16:16:35 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-12-23 16:18:41 -0500
commit45ce66a29b3d1c49ef8e86b125701e89d58e8a4a (patch)
tree14d67dfef5c2d5e1aa7e4100526973cbd0ba4c2b
parent82c447fbfe12ee76dbbdaa36b0de1343d3a91795 (diff)
Generalize table state to include the result of play
-rw-r--r--protocol/src/bridge_engine.rs76
-rw-r--r--protocol/src/play_result.rs23
-rw-r--r--server/src/main.rs21
-rw-r--r--server/src/play.rs40
-rw-r--r--webapp/src/components/table.rs80
-rw-r--r--webapp/src/services.rs4
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