diff --git a/jong-db/src/db/advance_game_reducer.rs b/jong-db/src/db/advance_game_reducer.rs index d8e3ce9..a0133cf 100644 --- a/jong-db/src/db/advance_game_reducer.rs +++ b/jong-db/src/db/advance_game_reducer.rs @@ -4,19 +4,13 @@ #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; -use super::game_timer_type::GameTimer; - #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] -pub(super) struct AdvanceGameArgs { - pub game_timer: GameTimer, -} +pub(super) struct AdvanceGameArgs {} impl From for super::Reducer { fn from(args: AdvanceGameArgs) -> Self { - Self::AdvanceGame { - game_timer: args.game_timer, - } + Self::AdvanceGame } } @@ -35,8 +29,8 @@ pub trait advance_game { /// The reducer will run asynchronously in the future, /// and this method provides no way to listen for its completion status. /// /// Use [`advance_game:advance_game_then`] to run a callback after the reducer completes. - fn advance_game(&self, game_timer: GameTimer) -> __sdk::Result<()> { - self.advance_game_then(game_timer, |_, _| {}) + fn advance_game(&self) -> __sdk::Result<()> { + self.advance_game_then(|_, _| {}) } /// Request that the remote module invoke the reducer `advance_game` to run as soon as possible, @@ -47,7 +41,6 @@ pub trait advance_game { /// and its status can be observed with the `callback`. fn advance_game_then( &self, - game_timer: GameTimer, callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + Send @@ -58,13 +51,12 @@ pub trait advance_game { impl advance_game for super::RemoteReducers { fn advance_game_then( &self, - game_timer: GameTimer, callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + Send + 'static, ) -> __sdk::Result<()> { self.imp - .invoke_reducer_with_callback(AdvanceGameArgs { game_timer }, callback) + .invoke_reducer_with_callback(AdvanceGameArgs {}, callback) } } diff --git a/jong-db/src/db/mod.rs b/jong-db/src/db/mod.rs index 234f175..83e4053 100644 --- a/jong-db/src/db/mod.rs +++ b/jong-db/src/db/mod.rs @@ -77,7 +77,7 @@ pub use wind_type::Wind; pub enum Reducer { AddBot { lobby_id: u32 }, - AdvanceGame { game_timer: GameTimer }, + AdvanceGame, ClearAll, DiscardTile { tile_id: u32 }, JoinOrCreateLobby { lobby_id: u32 }, @@ -92,7 +92,7 @@ impl __sdk::Reducer for Reducer { fn reducer_name(&self) -> &'static str { match self { Reducer::AddBot { .. } => "add_bot", - Reducer::AdvanceGame { .. } => "advance_game", + Reducer::AdvanceGame => "advance_game", Reducer::ClearAll => "clear_all", Reducer::DiscardTile { .. } => "discard_tile", Reducer::JoinOrCreateLobby { .. } => "join_or_create_lobby", @@ -106,10 +106,8 @@ impl __sdk::Reducer for Reducer { Reducer::AddBot { lobby_id } => __sats::bsatn::to_vec(&add_bot_reducer::AddBotArgs { lobby_id: lobby_id.clone(), }), - Reducer::AdvanceGame { game_timer } => { - __sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs { - game_timer: game_timer.clone(), - }) + Reducer::AdvanceGame => { + __sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs {}) } Reducer::ClearAll => __sats::bsatn::to_vec(&clear_all_reducer::ClearAllArgs {}), Reducer::DiscardTile { tile_id } => { diff --git a/jong-line/src/reducers.rs b/jong-line/src/reducers.rs index 3bdb745..0f3dda9 100644 --- a/jong-line/src/reducers.rs +++ b/jong-line/src/reducers.rs @@ -15,10 +15,77 @@ mod hand; mod lobby; #[reducer] -pub fn advance_game(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> { +pub fn advance_game(ctx: &ReducerContext) -> Result<(), String> { + let game_timer = ctx + .db + .game_timer() + .lobby_id() + .find( + ctx.db + .player() + .identity() + .find(ctx.sender()) + .ok_or("player not in lobby")? + .lobby_id, + ) + .ok_or("no such lobby")?; + advance_game_private(ctx, game_timer) } +fn shuffle_wall(ctx: &ReducerContext, lobby: &mut Lobby) { + let tiles = { + let mut rng = ctx.rng(); + let mut wall: Vec<_> = jong_types::tiles::tiles() + .into_iter() + .map(|tile| ctx.db.tile().insert(DbTile { id: 0, tile })) + .collect(); + wall.shuffle(&mut rng); + wall + }; + ctx.db.wall().insert(DbWall { + // id: 0, + lobby_id: lobby.id, + tiles, + }); + lobby.game_state = GameState::Deal; +} + +fn deal_hands(ctx: &ReducerContext, lobby: &mut Lobby) -> Result<(), String> { + let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap(); + for pob in &lobby.players { + let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13); + wall = ctx.db.wall().lobby_id().update(wall); + tiles.sort_by_key(|t| t.tile); + match pob { + PlayerOrBot::Player { id } if let Some(p) = ctx.db.player().id().find(id) => { + ctx.db.player_hand().insert(PlayerHand { + id: 0, + player_id: p.id, + turn_state: jong_types::TurnState::None, + pond: vec![], + hand: tiles, + working_tile: None, + }); + ctx.db.player_clock().insert(PlayerClock { + id: 0, + player_id: p.id, + renewable: 5, + total: 30, + }); + } + PlayerOrBot::Bot { id } if let Some(mut b) = ctx.db.bot().id().find(id) => { + b.hand = tiles; + ctx.db.bot().id().update(b); + } + _ => Err("couldn't find player or bot".to_string())?, + } + } + lobby.game_state = jong_types::states::GameState::Play; + + Ok(()) +} + #[reducer] pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> { // checks every second (or more? when users make moves) on whether to advance the game's various states @@ -30,60 +97,19 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> GameState::Setup => { // TODO reduce interval beforehand so we don't wait a second? // TODO keep a count to clear stale lobbies - trace!("shuffle wall"); - let tiles = { - let mut rng = ctx.rng(); - let mut wall: Vec<_> = jong_types::tiles::tiles() - .into_iter() - .map(|tile| ctx.db.tile().insert(DbTile { id: 0, tile })) - .collect(); - wall.shuffle(&mut rng); - wall - }; - ctx.db.wall().insert(DbWall { - // id: 0, - lobby_id: lobby.id, - tiles, - }); - lobby.game_state = GameState::Deal; + // trace!("shuffle wall"); + shuffle_wall(ctx, &mut lobby); + ctx.db.lobby().id().update(lobby); + advance_game_private(ctx, game_timer)?; + return Ok(()); } GameState::Deal => { // TODO reduce interval beforehand so this can animate? // TODO change loop to be per interval somehow? - // trace!("deal hands"); - let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap(); - for pob in &lobby.players { - let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13); - wall = ctx.db.wall().lobby_id().update(wall); - tiles.sort_by_key(|t| t.tile); - match pob { - PlayerOrBot::Player { id } - if let Some(p) = ctx.db.player().id().find(id) => - { - ctx.db.player_hand().insert(PlayerHand { - id: 0, - player_id: p.id, - turn_state: jong_types::TurnState::None, - pond: vec![], - hand: tiles, - working_tile: None, - }); - ctx.db.player_clock().insert(PlayerClock { - id: 0, - player_id: p.id, - renewable: 5, - total: 30, - }); - } - PlayerOrBot::Bot { id } if let Some(mut b) = ctx.db.bot().id().find(id) => { - b.hand = tiles; - ctx.db.bot().id().update(b); - } - _ => Err("couldn't find player or bot".to_string())?, - } - } - lobby.game_state = jong_types::states::GameState::Play; - // trace!("dealt hands"); + deal_hands(ctx, &mut lobby)?; + ctx.db.lobby().id().update(lobby); + advance_game_private(ctx, game_timer)?; + return Ok(()); } GameState::Play => { // trace!("in play"); diff --git a/jong-line/src/reducers/lobby.rs b/jong-line/src/reducers/lobby.rs index 46bcf96..17b658a 100644 --- a/jong-line/src/reducers/lobby.rs +++ b/jong-line/src/reducers/lobby.rs @@ -5,7 +5,7 @@ use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; use jong_types::PlayerOrBot; -use crate::tables::*; +use crate::{reducers::advance_game_private, tables::*}; #[reducer] pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<(), String> { @@ -87,11 +87,13 @@ pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> { let lobby = ctx.db.lobby().id().update(lobby); // TODO should we schedule this outside so that we can clear out stale lobbies? - ctx.db.game_timer().insert(GameTimer { + let game_timer = ctx.db.game_timer().insert(GameTimer { id: 0, lobby_id: lobby.id, scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()), }); + + advance_game_private(ctx, game_timer)?; } else { // if lobby doesn't exist, reset player state player.lobby_id = 0; diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index 1b3f4ba..74dbc4f 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -4,7 +4,7 @@ use bevy_spacetimedb::{ ReadStdbDisconnectedMessage, ReadUpdateMessage, StdbPlugin, }; -use jong_db::{self, GameTimerTableAccess, add_bot, set_ready}; +use jong_db::{self, GameTimerTableAccess, add_bot, advance_game, set_ready}; use jong_db::{ BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables, ViewClosedHandsTableAccess, ViewHandTableAccess, @@ -25,7 +25,6 @@ impl Plugin for Riichi { .with_run_fn(DbConnection::run_threaded) .add_table(RemoteTables::player) .add_table(RemoteTables::lobby) - .add_table(RemoteTables::game_timer) // TODO check bevy_spacetimedb PR status .add_view_with_pk(RemoteTables::view_hand, |p| p.id) .add_view_with_pk(RemoteTables::view_closed_hands, |p| { @@ -102,7 +101,6 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { "SELECT b.* FROM bot b JOIN lobby l ON l.id = b.lobby_id".to_string(), "SELECT * FROM view_hand".to_string(), "SELECT * FROM view_closed_hands".to_string(), - "SELECT g.* FROM game_timer g JOIN player p ON g.lobby_id = p.lobby_id".to_string(), ]); while let Ok(event) = recv.recv() { @@ -278,6 +276,7 @@ fn on_lobby_insert_update( stdb.reducers().add_bot(player.lobby_id).unwrap(); } stdb.reducers().set_ready(true).unwrap(); + // stdb.reducers().advance_game().unwrap(); } } jong_db::GameState::Setup => { diff --git a/jong/src/tui.rs b/jong/src/tui.rs index da949c7..88090c2 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -106,14 +106,13 @@ fn discard_tile( drawn: Single<(Entity, &TileId), With>, tiles: Query<&TileId>, ) { - // FIXME why is this not consuming the messages? + // FIXME why is this not consuming the messages? or is it just getting updated too frequently? // TODO disable this when we're not current player? while let Some(message) = selected.read().next() { + trace!("{message:?}"); if let Ok(tile_id) = tiles.get(message.0) { stdb.reducers().discard_tile(tile_id.0).unwrap(); - stdb.reducers() - .advance_game(stdb.db().game_timer().iter().next().unwrap()) - .unwrap(); + stdb.reducers().advance_game().unwrap(); commands.entity(drawn.0).remove::(); } }