diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-22 11:36:48 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-22 11:36:48 -0500 |
commit | c2145b91775be375779884a2a97365396923aba1 (patch) | |
tree | e8d1c52b98e8d2fcf1fda741bef8bc85ddeaa6f9 /server | |
parent | 0ba28546b94a794d56c56bba35f035200fd0a434 (diff) |
Add typed journals
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main.rs | 30 | ||||
-rw-r--r-- | server/src/play.rs | 174 |
2 files changed, 126 insertions, 78 deletions
diff --git a/server/src/main.rs b/server/src/main.rs index 5b81fc0..35019c5 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -153,11 +153,20 @@ async fn get_table_view( let jnl = DbJournal::new(extension.db.clone(), id); let mut table = play::Table::new_or_replay(jnl).await?; info!("Advancing play"); - while table.game().current_player() != Some(player_position) { + while table + .game() + .ok_or(BridgeError::InvalidRequest( + "game not in progress".to_string(), + ))? + .current_player() + != player_position + { advance_play(&mut table).await?; } let response = Json(GameStatePlayerView::from_game_state( - table.game(), + table.game().ok_or(BridgeError::InvalidRequest( + "game not in progress".to_string(), + ))?, player_position, )); info!("Response: {response:#?}"); @@ -173,14 +182,27 @@ async fn post_bid( info!("Getting table state for {id:}"); let jnl = DbJournal::new(extension.db.clone(), id); let mut table = play::Table::replay(jnl).await?; - if !table.game().is_bidding() { + if !table + .game() + .ok_or(BridgeError::InvalidRequest( + "game not in progress".to_string(), + ))? + .is_bidding() + { return Err(BridgeError::InvalidRequest( "Posting a bid requires that the game is in the bidding phase" .to_string(), )); } let player_position = Player::South; - if table.game().current_player() != Some(player_position) { + if table + .game() + .ok_or(BridgeError::InvalidRequest( + "game not in progress".to_string(), + ))? + .current_player() + != player_position + { return Err(BridgeError::InvalidRequest(format!( "It is not {player_position:?} to play" ))); diff --git a/server/src/play.rs b/server/src/play.rs index 86a4e06..19fa0a0 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -1,20 +1,23 @@ +use std::mem; + use async_trait::async_trait; use protocol::{ bot::BiddingBot, bridge_engine::{Bid, BiddingStatePlayerView, Deal, GameState, Player}, + card::Card, + play_result::MoveResult, simple_bots::AlwaysPassBiddingBot, }; use rand::random; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::json; use sqlx::{query, PgPool}; -use tracing::info; use uuid::Uuid; use crate::error::BridgeError; #[async_trait] -pub trait Journal { +pub trait Journal<Item> { // Next sequence number to use. fn next(&self) -> i64; @@ -22,14 +25,11 @@ pub trait Journal { async fn append( &mut self, seq: i64, - payload: serde_json::Value, + payload: Item, ) -> Result<(), BridgeError>; // Fetch all journal entries with sequence number greater or equal to `seq`. - async fn replay( - &mut self, - seq: i64, - ) -> Result<Vec<serde_json::Value>, BridgeError>; + async fn replay(&mut self, seq: i64) -> Result<Vec<Item>, BridgeError>; } pub struct DbJournal { @@ -45,11 +45,11 @@ impl DbJournal { } #[async_trait] -impl Journal for DbJournal { +impl<Item: Serialize + DeserializeOwned + Send + 'static> Journal<Item> for DbJournal { async fn append( &mut self, seq: i64, - payload: serde_json::Value, + payload: Item, ) -> Result<(), BridgeError> { let result = query!( r#" @@ -58,7 +58,7 @@ impl Journal for DbJournal { "#, self.id, seq, - payload, + json!(payload), ) .execute(&self.db) .await; @@ -74,13 +74,10 @@ impl Journal for DbJournal { Ok(()) } - async fn replay( - &mut self, - seq: i64, - ) -> Result<Vec<serde_json::Value>, BridgeError> { + async fn replay(&mut self, seq: i64) -> Result<Vec<Item>, BridgeError> { let rows = query!( r#" - select seq, payload from object_journal + select seq, payload as "payload!: String" from object_journal where id = $1 and seq >= $2 order by seq "#, @@ -91,7 +88,7 @@ impl Journal for DbJournal { .await?; let mut payloads = vec![]; for v in rows { - payloads.push(v.payload); + payloads.push(serde_json::from_str(&v.payload)?); self.seq = v.seq; } Ok(payloads) @@ -107,6 +104,7 @@ pub enum TableUpdate { NewDeal(Deal, Player), ChangeSettings(TableSettings), Bid(Bid), + SetState(TableState), } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default)] @@ -119,55 +117,97 @@ pub struct TableSettings { pub struct Table<J> where - J: Journal, + J: Journal<TableUpdate>, { journal: J, settings: TableSettings, - game: GameState, + state: TableState, +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum TableState { + Unknown, + Game(GameState), } -impl<J: Journal> Table<J> { - pub fn game(&self) -> &GameState { - &self.game +impl Into<TableState> for GameState { + fn into(self) -> TableState { + TableState::Game(self) } } -impl<J: Journal> Table<J> { +impl<J: Journal<TableUpdate>> Table<J> { + pub fn game(&self) -> Option<&GameState> { + match &self.state { + TableState::Game(g) => Some(g), + _ => None, + } + } +} + +impl<J: Journal<TableUpdate>> Table<J> { pub async fn new(mut journal: J) -> Result<Self, BridgeError> { let game = Self::init(&mut journal).await?; Ok(Table { journal, - game, + state: game.into(), settings: Default::default(), }) } async fn init(journal: &mut J) -> Result<GameState, BridgeError> { let game = GameState::new(random(), random()); - journal.append(0, json!(game)).await?; + journal + .append(0, TableUpdate::SetState(game.clone().into())) + .await?; Ok(game) } pub async fn bid(&mut self, bid: Bid) -> Result<(), BridgeError> { - let game = self.game.clone().bid(bid)?; + let mut state = TableState::Unknown; + mem::swap(&mut state, &mut self.state); + let game = match state { + TableState::Game(game) => game, + _ => { + return Err(BridgeError::InvalidRequest( + "no game in progress".to_string(), + )) + } + }; + let mut state: TableState = match game.bid(bid)? { + MoveResult::Stay(game) => game.into(), + MoveResult::Go(_) => todo!(), + }; self.journal - .append(self.journal.next(), json!(game)) + .append(self.journal.next(), TableUpdate::SetState(state.clone())) .await?; - self.game = game; + mem::swap(&mut state, &mut self.state); + Ok(()) + } + + pub async fn play(&mut self, card: Card) -> Result<(), BridgeError> { Ok(()) } pub async fn replay(mut journal: J) -> Result<Self, BridgeError> { - let games = journal.replay(0).await?; - if games.is_empty() { + let log = journal.replay(0).await?; + if log.is_empty() { return Err(BridgeError::NotFound( "table journal missing".to_string(), )); } - let game = serde_json::from_value(games[games.len() - 1].clone())?; + let mut state = TableState::Unknown; + for update in log { + match update { + TableUpdate::NewDeal(_, _) => todo!(), + TableUpdate::ChangeSettings(_) => todo!(), + TableUpdate::Bid(_) => todo!(), + TableUpdate::SetState(s) => state = s, + } + } Ok(Table { journal, - game, + state: state, settings: Default::default(), }) } @@ -179,29 +219,23 @@ impl<J: Journal> Table<J> { } Ok(Self { journal, - game: game?, + state: game?.into(), settings: Default::default(), }) } } -pub async fn advance_play<J: Journal>( +pub async fn advance_play<J: Journal<TableUpdate>>( table: &mut Table<J>, ) -> Result<(), BridgeError> { - 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" - ))); - } - }; - match table.game() { + let game = table + .game() + .ok_or(BridgeError::InvalidRequest(format!("no game in progress")))?; + match game { GameState::Bidding(bidding) => { let player_view = BiddingStatePlayerView::from_bidding_state( bidding, - current_player, + game.current_player(), ); let bot = AlwaysPassBiddingBot {}; let bid = bot.bid(&player_view).await; @@ -209,11 +243,6 @@ pub async fn advance_play<J: Journal>( Ok(()) } GameState::Play(_) => todo!(), - GameState::PassedOut { - dealer, - deal, - bidding, - } => Err(BridgeError::InvalidRequest(format!("The game is over"))), } } @@ -221,17 +250,22 @@ pub async fn advance_play<J: Journal>( mod test { use super::*; - #[derive(Default)] - pub struct TestJournal { - log: Vec<Option<serde_json::Value>>, + pub struct TestJournal<Item> { + log: Vec<Option<Item>>, + } + + impl<Item> TestJournal<Item> { + pub fn new() -> Self { + Self { log: vec![] } + } } #[async_trait] - impl Journal for TestJournal { + impl<Item: Send + Clone> Journal<Item> for TestJournal<Item> { async fn append( &mut self, seq: i64, - payload: serde_json::Value, + payload: Item, ) -> Result<(), BridgeError> { if seq != self.log.len() as i64 { return Err(BridgeError::UpdateConflict( @@ -243,10 +277,7 @@ mod test { Ok(()) } - async fn replay( - &mut self, - seq: i64, - ) -> Result<Vec<serde_json::Value>, BridgeError> { + async fn replay(&mut self, seq: i64) -> Result<Vec<Item>, BridgeError> { Ok(self.log[seq as usize..] .into_iter() .filter_map(|e| e.clone()) @@ -260,7 +291,7 @@ mod test { #[tokio::test] async fn test_journal() { - let mut jnl: TestJournal = Default::default(); + let mut jnl = TestJournal::new(); let seq = jnl.next(); assert_eq!(jnl.next(), 0); assert_eq!(jnl.append(seq, json!(10)).await.unwrap(), ()); @@ -273,31 +304,26 @@ mod test { #[tokio::test] async fn test_new_table() { - let t1: Table<TestJournal> = - Table::new(Default::default()).await.unwrap(); - match t1.game { - GameState::Bidding { .. } => (), - _ => panic!("should be Bidding"), - }; + let t1 = Table::new(TestJournal::new()).await.unwrap(); + assert!(t1.game().unwrap().is_bidding()); } #[tokio::test] async fn test_replay_table() { - let t1: Table<TestJournal> = - Table::new(Default::default()).await.unwrap(); - let game = t1.game; + let t1 = Table::new(TestJournal::new()).await.unwrap(); + let game = t1.game().unwrap().clone(); let journal = t1.journal; let t2 = Table::replay(journal).await.unwrap(); - assert_eq!(game, t2.game); + assert_eq!(&game, t2.game().unwrap()); } #[tokio::test] async fn test_advance_play() { - let mut t1: Table<TestJournal> = - Table::new(Default::default()).await.unwrap(); - let player = t1.game().current_player(); + let mut t1 = + Table::new(TestJournal::new()).await.unwrap(); + let player = t1.game().unwrap().current_player(); advance_play(&mut t1).await.unwrap(); - assert_ne!(player, t1.game().current_player()); + assert_ne!(player, t1.game().unwrap().current_player()); } } |