diff --git a/jong-db/src/db/game_timer_table.rs b/jong-db/src/db/game_timer_table.rs deleted file mode 100644 index 55cea89..0000000 --- a/jong-db/src/db/game_timer_table.rs +++ /dev/null @@ -1,190 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#![allow(unused, clippy::all)] -use super::lobby_timer_type::LobbyTimer; -use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; - -/// Table handle for the table `game_timer`. -/// -/// Obtain a handle from the [`GameTimerTableAccess::game_timer`] method on [`super::RemoteTables`], -/// like `ctx.db.game_timer()`. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.game_timer().on_insert(...)`. -pub struct GameTimerTableHandle<'ctx> { - imp: __sdk::TableHandle, - ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -#[allow(non_camel_case_types)] -/// Extension trait for access to the table `game_timer`. -/// -/// Implemented for [`super::RemoteTables`]. -pub trait GameTimerTableAccess { - #[allow(non_snake_case)] - /// Obtain a [`GameTimerTableHandle`], which mediates access to the table `game_timer`. - fn game_timer(&self) -> GameTimerTableHandle<'_>; -} - -impl GameTimerTableAccess for super::RemoteTables { - fn game_timer(&self) -> GameTimerTableHandle<'_> { - GameTimerTableHandle { - imp: self.imp.get_table::("game_timer"), - ctx: std::marker::PhantomData, - } - } -} - -pub struct GameTimerInsertCallbackId(__sdk::CallbackId); -pub struct GameTimerDeleteCallbackId(__sdk::CallbackId); - -impl<'ctx> __sdk::Table for GameTimerTableHandle<'ctx> { - type Row = LobbyTimer; - type EventContext = super::EventContext; - - fn count(&self) -> u64 { - self.imp.count() - } - fn iter(&self) -> impl Iterator + '_ { - self.imp.iter() - } - - type InsertCallbackId = GameTimerInsertCallbackId; - - fn on_insert( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> GameTimerInsertCallbackId { - GameTimerInsertCallbackId(self.imp.on_insert(Box::new(callback))) - } - - fn remove_on_insert(&self, callback: GameTimerInsertCallbackId) { - self.imp.remove_on_insert(callback.0) - } - - type DeleteCallbackId = GameTimerDeleteCallbackId; - - fn on_delete( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> GameTimerDeleteCallbackId { - GameTimerDeleteCallbackId(self.imp.on_delete(Box::new(callback))) - } - - fn remove_on_delete(&self, callback: GameTimerDeleteCallbackId) { - self.imp.remove_on_delete(callback.0) - } -} - -pub struct GameTimerUpdateCallbackId(__sdk::CallbackId); - -impl<'ctx> __sdk::TableWithPrimaryKey for GameTimerTableHandle<'ctx> { - type UpdateCallbackId = GameTimerUpdateCallbackId; - - fn on_update( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, - ) -> GameTimerUpdateCallbackId { - GameTimerUpdateCallbackId(self.imp.on_update(Box::new(callback))) - } - - fn remove_on_update(&self, callback: GameTimerUpdateCallbackId) { - self.imp.remove_on_update(callback.0) - } -} - -/// Access to the `lobby_id` unique index on the table `game_timer`, -/// which allows point queries on the field of the same name -/// via the [`GameTimerLobbyIdUnique::find`] method. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.game_timer().lobby_id().find(...)`. -pub struct GameTimerLobbyIdUnique<'ctx> { - imp: __sdk::UniqueConstraintHandle, - phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -impl<'ctx> GameTimerTableHandle<'ctx> { - /// Get a handle on the `lobby_id` unique index on the table `game_timer`. - pub fn lobby_id(&self) -> GameTimerLobbyIdUnique<'ctx> { - GameTimerLobbyIdUnique { - imp: self.imp.get_unique_constraint::("lobby_id"), - phantom: std::marker::PhantomData, - } - } -} - -impl<'ctx> GameTimerLobbyIdUnique<'ctx> { - /// Find the subscribed row whose `lobby_id` column value is equal to `col_val`, - /// if such a row is present in the client cache. - pub fn find(&self, col_val: &u32) -> Option { - self.imp.find(col_val) - } -} - -/// Access to the `scheduled_id` unique index on the table `game_timer`, -/// which allows point queries on the field of the same name -/// via the [`GameTimerScheduledIdUnique::find`] method. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.game_timer().scheduled_id().find(...)`. -pub struct GameTimerScheduledIdUnique<'ctx> { - imp: __sdk::UniqueConstraintHandle, - phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -impl<'ctx> GameTimerTableHandle<'ctx> { - /// Get a handle on the `scheduled_id` unique index on the table `game_timer`. - pub fn scheduled_id(&self) -> GameTimerScheduledIdUnique<'ctx> { - GameTimerScheduledIdUnique { - imp: self.imp.get_unique_constraint::("scheduled_id"), - phantom: std::marker::PhantomData, - } - } -} - -impl<'ctx> GameTimerScheduledIdUnique<'ctx> { - /// Find the subscribed row whose `scheduled_id` column value is equal to `col_val`, - /// if such a row is present in the client cache. - pub fn find(&self, col_val: &u64) -> Option { - self.imp.find(col_val) - } -} - -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - let _table = client_cache.get_or_make_table::("game_timer"); - _table.add_unique_constraint::("lobby_id", |row| &row.lobby_id); - _table.add_unique_constraint::("scheduled_id", |row| &row.scheduled_id); -} - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") - .with_cause(e) - .into() - }) -} - -#[allow(non_camel_case_types)] -/// Extension trait for query builder access to the table `LobbyTimer`. -/// -/// Implemented for [`__sdk::QueryTableAccessor`]. -pub trait game_timerQueryTableAccess { - #[allow(non_snake_case)] - /// Get a query builder for the table `LobbyTimer`. - fn game_timer(&self) -> __sdk::__query_builder::Table; -} - -impl game_timerQueryTableAccess for __sdk::QueryTableAccessor { - fn game_timer(&self) -> __sdk::__query_builder::Table { - __sdk::__query_builder::Table::new("game_timer") - } -} diff --git a/jong-db/src/db/mod.rs b/jong-db/src/db/mod.rs index c8a61cd..e5c0c6e 100644 --- a/jong-db/src/db/mod.rs +++ b/jong-db/src/db/mod.rs @@ -14,7 +14,6 @@ pub mod db_tile_type; pub mod discard_tile_reducer; pub mod dragon_type; pub mod game_state_type; -pub mod game_timer_table; pub mod hand_view_type; pub mod join_or_create_lobby_reducer; pub mod lobby_table; @@ -30,6 +29,7 @@ pub mod set_ready_reducer; pub mod suit_type; pub mod tile_type; pub mod turn_state_type; +pub mod user_table; pub mod user_type; pub mod view_closed_hands_table; pub mod view_hand_table; @@ -44,7 +44,6 @@ pub use db_tile_type::DbTile; pub use discard_tile_reducer::discard_tile; pub use dragon_type::Dragon; pub use game_state_type::GameState; -pub use game_timer_table::*; pub use hand_view_type::HandView; pub use join_or_create_lobby_reducer::join_or_create_lobby; pub use lobby_table::*; @@ -60,6 +59,7 @@ pub use set_ready_reducer::set_ready; pub use suit_type::Suit; pub use tile_type::Tile; pub use turn_state_type::TurnState; +pub use user_table::*; pub use user_type::User; pub use view_closed_hands_table::*; pub use view_hand_table::*; @@ -130,10 +130,10 @@ impl __sdk::Reducer for Reducer { #[doc(hidden)] pub struct DbUpdate { bot: __sdk::TableUpdate, - game_timer: __sdk::TableUpdate, lobby: __sdk::TableUpdate, player_clock: __sdk::TableUpdate, player_config: __sdk::TableUpdate, + user: __sdk::TableUpdate, view_closed_hands: __sdk::TableUpdate, view_hand: __sdk::TableUpdate, } @@ -147,9 +147,6 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { "bot" => db_update .bot .append(bot_table::parse_table_update(table_update)?), - "game_timer" => db_update - .game_timer - .append(game_timer_table::parse_table_update(table_update)?), "lobby" => db_update .lobby .append(lobby_table::parse_table_update(table_update)?), @@ -159,6 +156,9 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { "player_config" => db_update .player_config .append(player_config_table::parse_table_update(table_update)?), + "user" => db_update + .user + .append(user_table::parse_table_update(table_update)?), "view_closed_hands" => db_update .view_closed_hands .append(view_closed_hands_table::parse_table_update(table_update)?), @@ -194,9 +194,6 @@ impl __sdk::DbUpdate for DbUpdate { diff.bot = cache .apply_diff_to_table::("bot", &self.bot) .with_updates_by_pk(|row| &row.id); - diff.game_timer = cache - .apply_diff_to_table::("game_timer", &self.game_timer) - .with_updates_by_pk(|row| &row.scheduled_id); diff.lobby = cache .apply_diff_to_table::("lobby", &self.lobby) .with_updates_by_pk(|row| &row.id); @@ -206,6 +203,9 @@ impl __sdk::DbUpdate for DbUpdate { diff.player_config = cache .apply_diff_to_table::("player_config", &self.player_config) .with_updates_by_pk(|row| &row.id); + diff.user = cache + .apply_diff_to_table::("user", &self.user) + .with_updates_by_pk(|row| &row.identity); diff.view_closed_hands = cache.apply_diff_to_table::("view_closed_hands", &self.view_closed_hands); diff.view_hand = cache.apply_diff_to_table::("view_hand", &self.view_hand); @@ -219,9 +219,6 @@ impl __sdk::DbUpdate for DbUpdate { "bot" => db_update .bot .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), - "game_timer" => db_update - .game_timer - .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "lobby" => db_update .lobby .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -231,6 +228,9 @@ impl __sdk::DbUpdate for DbUpdate { "player_config" => db_update .player_config .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "user" => db_update + .user + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "view_closed_hands" => db_update .view_closed_hands .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -253,9 +253,6 @@ impl __sdk::DbUpdate for DbUpdate { "bot" => db_update .bot .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), - "game_timer" => db_update - .game_timer - .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "lobby" => db_update .lobby .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -265,6 +262,9 @@ impl __sdk::DbUpdate for DbUpdate { "player_config" => db_update .player_config .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "user" => db_update + .user + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "view_closed_hands" => db_update .view_closed_hands .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -287,10 +287,10 @@ impl __sdk::DbUpdate for DbUpdate { #[doc(hidden)] pub struct AppliedDiff<'r> { bot: __sdk::TableAppliedDiff<'r, Bot>, - game_timer: __sdk::TableAppliedDiff<'r, LobbyTimer>, lobby: __sdk::TableAppliedDiff<'r, Lobby>, player_clock: __sdk::TableAppliedDiff<'r, PlayerClock>, player_config: __sdk::TableAppliedDiff<'r, PlayerConfig>, + user: __sdk::TableAppliedDiff<'r, User>, view_closed_hands: __sdk::TableAppliedDiff<'r, HandView>, view_hand: __sdk::TableAppliedDiff<'r, PlayerHand>, __unused: std::marker::PhantomData<&'r ()>, @@ -307,7 +307,6 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks: &mut __sdk::DbCallbacks, ) { callbacks.invoke_table_row_callbacks::("bot", &self.bot, event); - callbacks.invoke_table_row_callbacks::("game_timer", &self.game_timer, event); callbacks.invoke_table_row_callbacks::("lobby", &self.lobby, event); callbacks.invoke_table_row_callbacks::( "player_clock", @@ -319,6 +318,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { &self.player_config, event, ); + callbacks.invoke_table_row_callbacks::("user", &self.user, event); callbacks.invoke_table_row_callbacks::( "view_closed_hands", &self.view_closed_hands, @@ -970,19 +970,19 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { bot_table::register_table(client_cache); - game_timer_table::register_table(client_cache); lobby_table::register_table(client_cache); player_clock_table::register_table(client_cache); player_config_table::register_table(client_cache); + user_table::register_table(client_cache); view_closed_hands_table::register_table(client_cache); view_hand_table::register_table(client_cache); } const ALL_TABLE_NAMES: &'static [&'static str] = &[ "bot", - "game_timer", "lobby", "player_clock", "player_config", + "user", "view_closed_hands", "view_hand", ]; diff --git a/jong-db/src/db/user_table.rs b/jong-db/src/db/user_table.rs new file mode 100644 index 0000000..fe60dce --- /dev/null +++ b/jong-db/src/db/user_table.rs @@ -0,0 +1,192 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::user_type::User; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `user`. +/// +/// Obtain a handle from the [`UserTableAccess::user`] method on [`super::RemoteTables`], +/// like `ctx.db.user()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.user().on_insert(...)`. +pub struct UserTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `user`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait UserTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`UserTableHandle`], which mediates access to the table `user`. + fn user(&self) -> UserTableHandle<'_>; +} + +impl UserTableAccess for super::RemoteTables { + fn user(&self) -> UserTableHandle<'_> { + UserTableHandle { + imp: self.imp.get_table::("user"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct UserInsertCallbackId(__sdk::CallbackId); +pub struct UserDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for UserTableHandle<'ctx> { + type Row = User; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = UserInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> UserInsertCallbackId { + UserInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: UserInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = UserDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> UserDeleteCallbackId { + UserDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: UserDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct UserUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for UserTableHandle<'ctx> { + type UpdateCallbackId = UserUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> UserUpdateCallbackId { + UserUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: UserUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `identity` unique index on the table `user`, +/// which allows point queries on the field of the same name +/// via the [`UserIdentityUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.user().identity().find(...)`. +pub struct UserIdentityUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> UserTableHandle<'ctx> { + /// Get a handle on the `identity` unique index on the table `user`. + pub fn identity(&self) -> UserIdentityUnique<'ctx> { + UserIdentityUnique { + imp: self + .imp + .get_unique_constraint::<__sdk::Identity>("identity"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> UserIdentityUnique<'ctx> { + /// Find the subscribed row whose `identity` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &__sdk::Identity) -> Option { + self.imp.find(col_val) + } +} + +/// Access to the `config_id` unique index on the table `user`, +/// which allows point queries on the field of the same name +/// via the [`UserConfigIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.user().config_id().find(...)`. +pub struct UserConfigIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> UserTableHandle<'ctx> { + /// Get a handle on the `config_id` unique index on the table `user`. + pub fn config_id(&self) -> UserConfigIdUnique<'ctx> { + UserConfigIdUnique { + imp: self.imp.get_unique_constraint::("config_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> UserConfigIdUnique<'ctx> { + /// Find the subscribed row whose `config_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &u32) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("user"); + _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); + _table.add_unique_constraint::("config_id", |row| &row.config_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `User`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait userQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `User`. + fn user(&self) -> __sdk::__query_builder::Table; +} + +impl userQueryTableAccess for __sdk::QueryTableAccessor { + fn user(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("user") + } +} diff --git a/jong-line/src/reducers.rs b/jong-line/src/reducers.rs index 3b30ab1..500c4a1 100644 --- a/jong-line/src/reducers.rs +++ b/jong-line/src/reducers.rs @@ -5,9 +5,12 @@ use spacetimedb::{ ReducerContext, ScheduleAt::Interval, Table as _, rand::seq::SliceRandom, reducer, }; -use crate::tables::{ - DbTile, Lobby, LobbyTimer, PlayerClock, PlayerHand, Wall, bot, game_timer, lobby as _, - player_clock, player_config, player_hand, tile, user, wall, +use crate::{ + reducers::hand::{discard_tile, discard_tile_private}, + tables::{ + DbTile, Lobby, LobbyTimer, PlayerClock, PlayerHand, Wall, bot, game_timer, lobby as _, + player_clock, player_config, player_hand, tile, user, wall, + }, }; use jong_types::{GameState, TurnState}; @@ -139,6 +142,14 @@ pub fn advance_game_private( ctx.db.player_clock().player_id().update(clock); } else { // TODO bot / auto discard + discard_tile_private( + ctx, + lobby.id, + ctx.db.player_config().id().find(curr_player).unwrap(), + hand.working_tile.unwrap().id, + ) + .unwrap(); + return Ok(()); } } TurnState::Menzen => {} diff --git a/jong-line/src/reducers/hand.rs b/jong-line/src/reducers/hand.rs index 936808d..afba4ac 100644 --- a/jong-line/src/reducers/hand.rs +++ b/jong-line/src/reducers/hand.rs @@ -9,14 +9,26 @@ use crate::tables::*; #[reducer] pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { let player = ctx.db.user().identity().find(ctx.sender()).unwrap(); + let config = ctx.db.player_config().id().find(player.config_id).unwrap(); + + discard_tile_private(ctx, player.lobby_id, config, tile_id)?; + + Ok(()) +} + +pub(crate) fn discard_tile_private( + ctx: &ReducerContext, + lobby_id: u32, + player_config: PlayerConfig, + tile_id: u32, +) -> Result<(), String> { let mut hand = ctx .db .player_hand() .player_id() - .find(player.config_id) + .find(player_config.id) .unwrap(); - // TODO we can probably remove a buncha these errors let dealt = ctx.db.tile().id().find(tile_id).unwrap(); let drawn = hand.working_tile.unwrap(); @@ -27,13 +39,7 @@ pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { // dealt from hand let dealt = hand.hand.remove(i); hand.hand.push(drawn); - if ctx - .db - .player_config() - .id() - .find(player.config_id) - .is_some_and(|c| c.sort) - { + if player_config.sort { hand.hand.sort_by_key(|t| t.tile); } @@ -48,24 +54,15 @@ pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { hand.turn_state = TurnState::None; ctx.db.player_hand().player_id().update(hand); - let mut clock = ctx - .db - .player_clock() - .player_id() - .find(player.config_id) - .unwrap(); - clock.renew(); - ctx.db.player_clock().player_id().update(clock); + if let Some(mut clock) = ctx.db.player_clock().player_id().find(player_config.id) { + clock.renew(); + ctx.db.player_clock().player_id().update(clock); + } - let mut lobby = ctx.db.lobby().id().find(player.lobby_id).unwrap(); + let mut lobby = ctx.db.lobby().id().find(lobby_id).unwrap(); lobby.next_player(); ctx.db.lobby().id().update(lobby); - debug!( - "player {} discarded tile {:?}", - player.identity, dealt_tile.tile - ); - Ok(()) } diff --git a/jong-line/src/reducers/lobby.rs b/jong-line/src/reducers/lobby.rs index 7bb9a7c..13bbfc7 100644 --- a/jong-line/src/reducers/lobby.rs +++ b/jong-line/src/reducers/lobby.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use log::{info, warn}; +use log::{debug, info, warn}; use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; use crate::{reducers::advance_game_private, tables::*}; @@ -50,6 +50,50 @@ pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<( Ok(()) } +#[reducer] +pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> { + let mut user = ctx.db.user().identity().find(ctx.sender()).unwrap(); + let mut player = ctx.db.player_config().id().find(user.config_id).unwrap(); + + player.ready = ready; + let player = ctx.db.player_config().id().update(player); + + if player.ready { + debug!("player readied"); + } else { + debug!("player unreadied"); + } + + if let Some(mut lobby) = ctx.db.lobby().id().find(user.lobby_id) + && lobby.players.len() == 4 + && lobby.players.iter().all(|id| { + ctx.db + .player_config() + .id() + .find(id) + .is_some_and(|p| p.ready) + }) + && ctx.db.game_timer().lobby_id().find(user.lobby_id).is_none() + { + lobby.game_state = jong_types::states::GameState::Setup; + let lobby = ctx.db.lobby().id().update(lobby); + + // TODO should we schedule this outside so that we can clear out stale lobbies? + let game_timer = ctx.db.game_timer().insert(LobbyTimer { + lobby_id: lobby.id, + scheduled_id: 0, + scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()), + }); + + advance_game_private(ctx, game_timer)?; + } else { + // TODO if lobby doesn't exist, reset player state? + // FIXME return error here + } + + Ok(()) +} + #[reducer] pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { if lobby_id == 0 { @@ -63,7 +107,7 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { id: 0, name: String::new(), ready: true, - sort: true, + sort: false, }); let bot = ctx.db.bot().insert(Bot { id: 0, @@ -78,44 +122,3 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { Err(format!("lobby {lobby_id} doesn't exist or is full")) } } - -#[reducer] -pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> { - let mut user = ctx.db.user().identity().find(ctx.sender()).unwrap(); - let mut player = ctx.db.player_config().id().find(user.config_id).unwrap(); - - player.ready = ready; - let player = ctx.db.player_config().id().update(player); - - if let Some(mut lobby) = ctx.db.lobby().id().find(user.lobby_id) - && lobby.players.len() == 4 - && lobby.players.iter().all(|id| { - ctx.db - .player_config() - .id() - .find(id) - .is_some_and(|p| p.ready) - }) - { - lobby.game_state = jong_types::states::GameState::Setup; - let lobby = ctx.db.lobby().id().update(lobby); - - // TODO should we schedule this outside so that we can clear out stale lobbies? - let game_timer = ctx.db.game_timer().insert(LobbyTimer { - lobby_id: lobby.id, - scheduled_id: 0, - scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()), - }); - - advance_game_private(ctx, game_timer)?; - } else { - // TODO if lobby doesn't exist, reset player state - - user.lobby_id = 0; - user = ctx.db.user().identity().update(user); - - return Err(format!("couldn't find lobby with id: {}", user.lobby_id)); - } - - Ok(()) -} diff --git a/jong-line/src/tables.rs b/jong-line/src/tables.rs index 11a0611..0b09f6c 100644 --- a/jong-line/src/tables.rs +++ b/jong-line/src/tables.rs @@ -7,7 +7,7 @@ use jong_types::{ use crate::reducers::advance_game_private; -#[table(accessor = user)] +#[table(accessor = user, public)] #[table(accessor = logged_out_user)] #[derive(Debug)] pub struct User { @@ -18,7 +18,6 @@ pub struct User { #[index(btree)] pub lobby_id: u32, - #[unique] pub config_id: u32, } @@ -112,7 +111,7 @@ pub struct PlayerHand { pub working_tile: Option, } -#[table(accessor = game_timer, scheduled(advance_game_private), public)] +#[table(accessor = game_timer, scheduled(advance_game_private))] pub struct LobbyTimer { #[unique] pub lobby_id: u32, diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index 60b5cbc..7da69e4 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -5,10 +5,12 @@ use bevy::render::render_resource::AsBindGroupShaderType; use bevy_spacetimedb::{ReadInsertMessage, ReadInsertUpdateMessage, ReadUpdateMessage, StdbPlugin}; use jong_db::{ - self, GameTimerTableAccess, PlayerClockTableAccess, add_bot, advance_game, set_ready, + self, PlayerClockTableAccess, PlayerConfigTableAccess, UserTableAccess, add_bot, advance_game, + join_or_create_lobby, lobbyQueryTableAccess, player_configQueryTableAccess, set_ready, + userQueryTableAccess, }; use jong_db::{ - BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables, + BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, RemoteTables, ViewClosedHandsTableAccess, ViewHandTableAccess, }; use jong_types::*; @@ -26,15 +28,14 @@ impl Plugin for Riichi { .with_uri("http://localhost:3000") .with_module_name("jong-line") .with_run_fn(DbConnection::run_threaded) + .add_table(RemoteTables::user) .add_table(RemoteTables::lobby) - .add_table(RemoteTables::player) .add_table(RemoteTables::bot) + .add_table(RemoteTables::player_config) .add_table(RemoteTables::player_clock) // 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| { - PlayerOrBot::from(&p.player) - }); + .add_view_with_pk(RemoteTables::view_hand, |p| p.player_id) + .add_view_with_pk(RemoteTables::view_closed_hands, |p| p.player_id); let plugins = if let Some(token) = creds_store().load().expect("i/o error loading credentials") { // FIXME patch plugin so this takes Option? @@ -52,9 +53,10 @@ impl Plugin for Riichi { .add_systems( Update, ( + on_user_insert_update, on_lobby_insert_update, - on_player_insert_update, on_bot_insert_update, + on_player_config_insert_update, ), ) .add_systems( @@ -65,17 +67,6 @@ impl Plugin for Riichi { } } -/// on subscribe we need to check: -/// if we're in a game already -/// spawn (restore) all current game state -/// spawn all players and hands -/// else -/// spawn self player -/// then -/// wait for lobbies -/// spawn other players -/// spawn all hands and ponds - fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { while stdb.try_identity().is_none() { bevy::platform::thread::sleep(Duration::from_secs(1)); @@ -90,116 +81,20 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { .on_error(|_, err| { error!("subs failed: {err}"); }) - .subscribe([ - // TODO change these to sub/unsub based on being in lobby and some such - format!( - "SELECT p.* FROM player p WHERE p.identity = '{}'", - stdb.identity() - ), - // TODO add filter for lobby id for all of these later - "SELECT l.* FROM lobby l JOIN player p ON l.id = p.lobby_id".to_string(), - "SELECT p.* FROM player p JOIN lobby l ON p.lobby_id = l.id".to_string(), - "SELECT c.* FROM player_clock c JOIN player p ON c.player_id = p.id".to_string(), - "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(), - ]); + .add_query(|q| q.from.user().r#where(|u| u.identity.eq(stdb.identity()))) + .subscribe(); + // TODO more sub/unsub based on being in lobby and some such, current done in tui::keybaord while let Ok(()) = rx.recv() { // todo!() } } -fn sync_closed_hand( - stdb: SpacetimeDB, - - mut messages: MessageReader, - mut commands: Commands, - - tiles: Query<(Entity, &TileId)>, - hands: Query<(Entity, &mut Closed, &Hand)>, - ponds: Query<(Entity, &Pond)>, -) { - for SyncClosedHand(id) in messages.read() { - let Some(hand_view) = stdb - .db() - .view_closed_hands() - .iter() - .find(|hand| PlayerOrBot::from(&hand.player) == *id) - else { - todo!() - }; - - let hand_ent = hands - .iter() - .find_map(|(e, c, h)| (h.owner == *id).then_some(e)) - .unwrap_or_else(|| commands.spawn(Hand { owner: *id }).id()); - let pond_ent = ponds - .iter() - .find_map(|(e, p)| (p.owner == *id).then_some(e)) - .unwrap_or_else(|| commands.spawn(Pond { owner: *id }).id()); - - let pond: Vec = hand_view - .pond - .iter() - .map(|dbt| { - tiles - .iter() - .find_map(|(e, i)| (i.0 == dbt.id).then_some(e)) - .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) - }) - .collect(); - - commands.entity(hand_ent).insert(Closed { - length: hand_view.hand_length, - }); - commands.entity(pond_ent).replace_children(&pond); - - if hand_view.drawn { - commands.spawn(Drawn); - } - } -} - -fn sync_player_clock() {} - -fn on_player_insert_update( - stdb: SpacetimeDB, - mut messages: ReadInsertUpdateMessage, - - mut commands: Commands, - - players: Query<(Entity, &Player)>, -) { - for msg in messages.read() { - trace!("on_player_insert_update"); - let player = &msg.new; - - let player_ent = players - .iter() - .find_map(|(e, p)| (p.id == PlayerOrBot::Player { id: player.id }).then_some(e)) - .unwrap_or_else(|| { - commands - .spawn(Player { - id: PlayerOrBot::Player { id: msg.new.id }, - }) - .id() - }); - - if player.identity == stdb.identity() { - commands.entity(player_ent).insert(MainPlayer); - } else { - } - } -} - -fn on_bot_insert_update(stdb: SpacetimeDB, mut messages: ReadInsertUpdateMessage) { - for msg in messages.read() {} -} +fn on_user_insert_update() {} #[derive(Resource, Default)] pub struct Lobby { - pub players: Vec, + pub players: Vec, } fn on_lobby_insert_update( @@ -216,11 +111,12 @@ fn on_lobby_insert_update( for msg in messages.read() { // trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); - let player = stdb + let player = stdb.db().user().identity().find(&stdb.identity()).unwrap(); + let player_config = stdb .db() - .player() - .identity() - .find(&stdb.identity()) + .player_config() + .id() + .find(&player.config_id) .unwrap(); match msg.new.game_state { @@ -229,7 +125,7 @@ fn on_lobby_insert_update( } jong_db::GameState::Lobby => { // trace!("game entered lobby"); - if !player.ready { + if !player_config.ready { for _ in 0..3 { stdb.reducers().add_bot(player.lobby_id).unwrap(); } @@ -251,54 +147,106 @@ fn on_lobby_insert_update( } } - lobby.players = msg.new.players.iter().map(|p| p.into()).collect(); - + lobby.players = msg.new.players.clone(); next_gamestate.set(msg.new.game_state.into()); } } -fn on_view_hand_insert(mut messages: ReadInsertMessage) { - for msg in messages.read() { - trace!("on_view_hand_insert"); +fn on_bot_insert_update(stdb: SpacetimeDB, mut messages: ReadInsertUpdateMessage) { + for msg in messages.read() {} +} - +fn on_player_config_insert_update( + stdb: SpacetimeDB, + mut messages: ReadInsertUpdateMessage, + + mut commands: Commands, + + players: Query<(Entity, &Player)>, +) { + for msg in messages.read() { + trace!("on_player_insert_update"); + let player = &msg.new; + + let player_ent = players + .iter() + .find_map(|(e, p)| (p.id == player.id).then_some(e)) + .unwrap_or_else(|| commands.spawn(Player { id: player.id }).id()); + + if player.id + == stdb + .db() + .user() + .identity() + .find(&stdb.identity()) + .unwrap() + .config_id + { + commands.entity(player_ent).insert(MainPlayer); + } else { + } } } -fn on_view_hand_update(mut messages: ReadUpdateMessage) { +fn on_view_hand_insert( + mut messages: ReadInsertMessage, + mut commands: Commands, + tiles: Query<(Entity, &TileId)>, + hands: Query<(Entity, &Hand)>, + ponds: Query<(Entity, &Pond)>, + mut next_turnstate: ResMut>, +) { + for msg in messages.read() { + trace!("on_view_hand_insert"); + sync_open_hand( + &mut commands, + tiles, + hands, + ponds, + &mut next_turnstate, + &msg.row, + ); + } +} + +fn on_view_hand_update( + mut messages: ReadUpdateMessage, + mut commands: Commands, + tiles: Query<(Entity, &TileId)>, + hands: Query<(Entity, &Hand)>, + ponds: Query<(Entity, &Pond)>, + mut next_turnstate: ResMut>, +) { for msg in messages.read() { trace!("on_view_hand_update"); + sync_open_hand( + &mut commands, + tiles, + hands, + ponds, + &mut next_turnstate, + &msg.new, + ); } } fn sync_open_hand( - commands: Commands, + commands: &mut Commands, tiles: Query<(Entity, &TileId)>, hands: Query<(Entity, &Hand)>, ponds: Query<(Entity, &Pond)>, next_turnstate: &mut ResMut>, - player_hand: PlayerHand, + player_hand: &PlayerHand, ) { + let player_id = player_hand.player_id; let hand_ent = hands .iter() - .find_map(|(e, h)| (h.owner == player_hand.player_id).then_some(e)) - .unwrap_or_else(|| { - commands - .spawn(Hand { - owner: PlayerOrBot::Player { id: *id }, - }) - .id() - }); + .find_map(|(e, h)| (h.player_id == player_id).then_some(e)) + .unwrap_or_else(|| commands.spawn(Hand { player_id }).id()); let pond_ent = ponds .iter() - .find_map(|(e, p)| (p.owner == PlayerOrBot::Player { id: *id }).then_some(e)) - .unwrap_or_else(|| { - commands - .spawn(Pond { - owner: PlayerOrBot::Player { id: *id }, - }) - .id() - }); + .find_map(|(e, p)| (p.player_id == player_id).then_some(e)) + .unwrap_or_else(|| commands.spawn(Pond { player_id }).id()); // hand and pond both still need ability to spawn for the reconnect case let hand: Vec = player_hand @@ -325,7 +273,7 @@ fn sync_open_hand( commands.entity(hand_ent).replace_children(&hand); commands.entity(pond_ent).replace_children(&pond); - if let Some(dbt) = player_hand.working_tile + if let Some(dbt) = &player_hand.working_tile && player_hand.turn_state == jong_db::TurnState::Tsumo { commands.spawn((Drawn, Tile::from(&dbt.tile), TileId(dbt.id))); @@ -333,3 +281,55 @@ fn sync_open_hand( next_turnstate.set(player_hand.turn_state.into()); } + +// fn sync_closed_hand( +// stdb: SpacetimeDB, + +// mut messages: MessageReader, +// mut commands: Commands, + +// tiles: Query<(Entity, &TileId)>, +// hands: Query<(Entity, &mut Closed, &Hand)>, +// ponds: Query<(Entity, &Pond)>, +// ) { +// for SyncClosedHand(id) in messages.read() { +// let Some(hand_view) = stdb +// .db() +// .view_closed_hands() +// .iter() +// .find(|hand| PlayerOrBot::from(&hand.player) == *id) +// else { +// todo!() +// }; + +// let hand_ent = hands +// .iter() +// .find_map(|(e, c, h)| (h.owner == *id).then_some(e)) +// .unwrap_or_else(|| commands.spawn(Hand { owner: *id }).id()); +// let pond_ent = ponds +// .iter() +// .find_map(|(e, p)| (p.owner == *id).then_some(e)) +// .unwrap_or_else(|| commands.spawn(Pond { owner: *id }).id()); + +// let pond: Vec = hand_view +// .pond +// .iter() +// .map(|dbt| { +// tiles +// .iter() +// .find_map(|(e, i)| (i.0 == dbt.id).then_some(e)) +// .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) +// }) +// .collect(); + +// commands.entity(hand_ent).insert(Closed { +// length: hand_view.hand_length, +// }); +// commands.entity(pond_ent).replace_children(&pond); + +// if hand_view.drawn { +// commands.spawn(Drawn); +// } +// } +// } + diff --git a/jong/src/riichi/player.rs b/jong/src/riichi/player.rs index 3ae2681..bb90d87 100644 --- a/jong/src/riichi/player.rs +++ b/jong/src/riichi/player.rs @@ -1,10 +1,8 @@ use bevy::prelude::*; -use jong_types::PlayerOrBot; - #[derive(Component)] pub struct Player { - pub id: PlayerOrBot, + pub id: u32, } #[derive(Component)] @@ -25,17 +23,18 @@ pub struct TileId(pub u32); #[derive(Component)] pub struct Hand { - pub owner: PlayerOrBot, + pub player_id: u32, } #[derive(Component)] pub struct Closed { + pub player_id: u32, pub(crate) length: u8, } #[derive(Component)] pub struct Pond { - pub owner: PlayerOrBot, + pub player_id: u32, } #[derive(Component)] diff --git a/jong/src/tui.rs b/jong/src/tui.rs index 18a47d2..fa38da2 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -8,7 +8,7 @@ use spacetimedb_sdk::Table; use tui_logger::TuiWidgetState; use jong::{SpacetimeDB, riichi::player::*}; -use jong_db::{self, GameTimerTableAccess, advance_game, discard_tile as _}; +use jong_db::{self, advance_game, discard_tile as _}; use jong_types::states::{GameState, TurnState}; mod input; diff --git a/jong/src/tui/input/keyboard.rs b/jong/src/tui/input/keyboard.rs index da9ce40..64685d6 100644 --- a/jong/src/tui/input/keyboard.rs +++ b/jong/src/tui/input/keyboard.rs @@ -1,11 +1,16 @@ use bevy::prelude::*; use bevy_ratatui::crossterm::event::KeyCode; use bevy_ratatui::event::KeyMessage; +use spacetimedb_sdk::DbContext; +use spacetimedb_sdk::Table; use tui_logger::TuiWidgetEvent; use jong::SpacetimeDB; -use jong_db::PlayerTableAccess; -use jong_db::join_or_create_lobby; +use jong_db::{ + LobbyTableAccess, UserTableAccess, botQueryTableAccess, + join_or_create_lobby, lobbyQueryTableAccess, player_clockQueryTableAccess, + player_configQueryTableAccess, view_closed_handsQueryTableAccess, view_handQueryTableAccess, +}; use jong_types::GameState; use crate::tui::layout::Overlays; @@ -48,7 +53,42 @@ pub(crate) fn keyboard( TuiState::MainMenu => match key { KeyCode::Char('p') => { // TODO this should be a msg/signal and handle the correct lobby - stdb.reducers().join_or_create_lobby(0).unwrap(); + stdb.reducers() + .join_or_create_lobby_then(0, |ctx, res| { + if res.is_ok() { + let user = + ctx.db().user().identity().find(&ctx.identity()).unwrap(); + let lobby_id = user.lobby_id; + + ctx.subscription_builder() + .on_applied(|ctx| { + // TODO subscribe other player configs? + // ctx.subscription_builder() + // .add_query(|q| { + // q.from.player_config().left_semijoin(right, on) + // }) + // .subscribe(); + }) + .add_query(|q| q.from.lobby().r#where(|l| l.id.eq(lobby_id))) + .add_query(|q| { + q.from.bot().r#where(|b| b.lobby_id.eq(lobby_id)) + }) + .add_query(|q| { + q.from + .player_config() + .r#where(|pc| pc.id.eq(user.config_id)) + }) + .add_query(|q| { + q.from + .player_clock() + .r#where(|pk| pk.player_id.eq(user.config_id)) + }) + .add_query(|q| q.from.view_hand()) + .add_query(|q| q.from.view_closed_hands()) + .subscribe(); + } + }) + .unwrap(); next_tuistate.set(TuiState::InGame); } KeyCode::Char('z') => { diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index 8361571..2d40ff6 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -164,7 +164,7 @@ pub(crate) fn render_main_hand( let hand: Vec<_> = hands .iter() - .find_map(|(h, c)| (main_player.id == h.owner).then_some(c)) + .find_map(|(h, c)| (main_player.id == h.player_id).then_some(c)) .unwrap() .iter() .map(|entity| -> Result<_> { @@ -271,7 +271,7 @@ pub(crate) fn render_main_pond( let pond: Vec<_> = pond .iter() - .find_map(|(p, c)| (main_player.id == p.owner).then_some(c)) + .find_map(|(p, c)| (main_player.id == p.player_id).then_some(c)) .unwrap() .iter() .map(|entity| -> Result<_> { diff --git a/justfile b/justfile index a73beca..4467e36 100644 --- a/justfile +++ b/justfile @@ -18,7 +18,7 @@ spacetime: devenv up spacetime_dev: - spacetime dev --module-bindings-path jong-db/src/db jong-line --delete-data=on-conflict --yes --server-only + spacetime dev --module-bindings-path jong-db/src/db jong-line --delete-data=always --yes --server-only spacetime_generate-bindings: spacetime generate --lang rust --out-dir jong-db/src/db --module-path jong-line