diff --git a/.ignore b/.ignore deleted file mode 100644 index c380951..0000000 --- a/.ignore +++ /dev/null @@ -1 +0,0 @@ -jong/src/stdb diff --git a/Cargo.toml b/Cargo.toml index eb2e720..c283329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,7 @@ bevy.default-features = false bevy_ratatui = "0.10.0" bevy_spacetimedb = "0.7" -spacetimedb.version = "1.11.*" -spacetimedb.features = ["unstable"] - +spacetimedb = "1.11.*" spacetimedb-sdk = "1.11.*" strum.version = "0.27.2" diff --git a/jong/src/game.rs b/jong/src/game.rs index 85ff3cd..cb4d61f 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -9,8 +9,9 @@ use spacetimedb::Identity; use spacetimedb_sdk::{DbContext, Table, credentials}; use crate::stdb::{ - self, DbConnection, LobbyTableAccess, PlayerTableAccess, RemoteTables, add_bot, - join_or_create_lobby, login_or_add_player, set_ready, shuffle_deal, start_game, + self, DbConnection, HandTableAccess, LobbyTableAccess, PlayerTableAccess, RemoteTables, + ViewPlayerHandTableAccess, add_bot, join_or_create_lobby, login_or_add_player, set_ready, + shuffle_deal, start_game, }; use crate::{ SpacetimeDB, creds_store, @@ -44,11 +45,11 @@ impl Plugin for Riichi { .with_uri("http://localhost:3000") .with_module_name("jongline") .with_run_fn(DbConnection::run_threaded) - // TODO why don't I need to call add_reducer? // TODO do these need to be subscription & vice-versa? .add_table(RemoteTables::player) .add_table(RemoteTables::lobby) + .add_table(RemoteTables::hand) // semicolon stopper ; @@ -82,7 +83,7 @@ impl Plugin for Riichi { .add_systems(Update, on_player_insert_update) .add_systems(Update, on_lobby_insert_update) // .add_systems(OnEnter(GameState::Lobby), join_or_create_lobby) - + .add_systems(Update, view_hand.run_if(in_state(GameState::Play))) // semicolon stopper ; } @@ -114,6 +115,7 @@ fn subscriptions(stdb: SpacetimeDB) { stdb.identity() ), "SELECT l.* FROM lobby l JOIN player p ON l.host_player_id = p.id".to_string(), + "SELECT * FROM view_player_hand vph".to_string(), ]); // .subscribe_to_all_tables(); } @@ -123,10 +125,7 @@ fn on_player_insert_update( mut messages: ReadInsertUpdateMessage, mut commands: Commands, - - tiles: Query<(&Tile, Entity)>, mut player: Option>, - mut hand_ent: Option>>, ) { use player::*; @@ -138,28 +137,6 @@ fn on_player_insert_update( // // TODO add a start game button in the future // stdb.reducers().start_game().unwrap(); // } - let mut view: Vec<_> = msg.new.hand.iter().map(Tile::from).collect(); - // let mut tiles = tiles - // .iter() - // .filter(|(tt, _)| { - // if let Some((i, _)) = view.iter().enumerate().find(|(_, t)| t == tt) { - // view.swap_remove(i); - // true - // } else { - // false - // } - // }) - // // .map(|(t, e)| e) - // .collect::>(); - // tiles.sort_by_key(|(t, e)| **t); - // tiles.get_many(entities) - - - let tiles = tiles.into_iter().map(|(_, e)| e).collect::>(); - - commands - .entity(**hand_ent.as_ref().unwrap()) - .replace_children(&tiles); } else { let player = Player { name: msg @@ -175,6 +152,21 @@ fn on_player_insert_update( } } +// fn join_or_create_lobby(stdb: SpacetimeDB) { +// let player = stdb +// .db() +// .player() +// .identity() +// .find(&stdb.identity()) +// .unwrap(); + +// if player.lobby_id == 0 { +// stdb.reducers().join_or_create_lobby(0).unwrap(); +// } else { +// info!("in lobby: {}", player.lobby_id) +// } +// } + fn on_lobby_insert_update( stdb: SpacetimeDB, mut messages: ReadInsertUpdateMessage, @@ -183,7 +175,7 @@ fn on_lobby_insert_update( mut next_gamestate: ResMut>, ) { for msg in messages.read() { - // trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); + trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); let player = stdb .db() @@ -198,7 +190,7 @@ fn on_lobby_insert_update( trace!("game entered none"); } stdb::GameState::Lobby => { - trace!("game entered lobby"); + trace!("game in lobby"); if !player.ready { for _ in 0..3 { stdb.reducers().add_bot(player.lobby_id).unwrap(); @@ -225,3 +217,31 @@ fn on_lobby_insert_update( next_gamestate.set(msg.new.game_state.into()); } } + +fn view_hand( + stdb: SpacetimeDB, + + mut commands: Commands, + tiles: Query<(&Tile, Entity)>, + hand_ent: Single>, +) { + // trace!("view_hand"); + if let Some(view) = stdb.db().view_player_hand().iter().next() { + let mut view = view.tiles.iter().map(Tile::from).collect::>(); + + let tiles = tiles + .iter() + .filter(|(tt, _)| { + if let Some((i, _)) = view.iter().enumerate().find(|(_, t)| t == tt) { + view.swap_remove(i); + true + } else { + false + } + }) + .map(|(_, e)| e) + .collect::>(); + + commands.entity(*hand_ent).replace_children(&tiles); + } +} diff --git a/jong/src/stdb/bot_table.rs b/jong/src/stdb/bot_table.rs index 6d53305..9e4c080 100644 --- a/jong/src/stdb/bot_table.rs +++ b/jong/src/stdb/bot_table.rs @@ -3,7 +3,6 @@ #![allow(unused, clippy::all)] use super::bot_type::Bot; -use super::tile_type::Tile; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; /// Table handle for the table `bot`. diff --git a/jong/src/stdb/bot_type.rs b/jong/src/stdb/bot_type.rs index deaeada..0d4fd2c 100644 --- a/jong/src/stdb/bot_type.rs +++ b/jong/src/stdb/bot_type.rs @@ -4,15 +4,13 @@ #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; -use super::tile_type::Tile; - #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] pub struct Bot { pub id: u32, pub lobby_id: u32, - pub hand: Vec, - pub pond: Vec, + pub hand_id: u32, + pub pond_id: u32, } impl __sdk::InModule for Bot { diff --git a/jong/src/stdb/hand_table.rs b/jong/src/stdb/hand_table.rs new file mode 100644 index 0000000..d967559 --- /dev/null +++ b/jong/src/stdb/hand_table.rs @@ -0,0 +1,144 @@ +// 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::hand_type::Hand; +use super::player_or_bot_type::PlayerOrBot; +use super::tile_type::Tile; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `hand`. +/// +/// Obtain a handle from the [`HandTableAccess::hand`] method on [`super::RemoteTables`], +/// like `ctx.db.hand()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.hand().on_insert(...)`. +pub struct HandTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `hand`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait HandTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`HandTableHandle`], which mediates access to the table `hand`. + fn hand(&self) -> HandTableHandle<'_>; +} + +impl HandTableAccess for super::RemoteTables { + fn hand(&self) -> HandTableHandle<'_> { + HandTableHandle { + imp: self.imp.get_table::("hand"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct HandInsertCallbackId(__sdk::CallbackId); +pub struct HandDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for HandTableHandle<'ctx> { + type Row = Hand; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = HandInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> HandInsertCallbackId { + HandInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: HandInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = HandDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> HandDeleteCallbackId { + HandDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: HandDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("hand"); + _table.add_unique_constraint::("id", |row| &row.id); +} +pub struct HandUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for HandTableHandle<'ctx> { + type UpdateCallbackId = HandUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> HandUpdateCallbackId { + HandUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: HandUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +/// Access to the `id` unique index on the table `hand`, +/// which allows point queries on the field of the same name +/// via the [`HandIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.hand().id().find(...)`. +pub struct HandIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> HandTableHandle<'ctx> { + /// Get a handle on the `id` unique index on the table `hand`. + pub fn id(&self) -> HandIdUnique<'ctx> { + HandIdUnique { + imp: self.imp.get_unique_constraint::("id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> HandIdUnique<'ctx> { + /// Find the subscribed row whose `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) + } +} diff --git a/jong/src/stdb/hand_type.rs b/jong/src/stdb/hand_type.rs new file mode 100644 index 0000000..2c4f86b --- /dev/null +++ b/jong/src/stdb/hand_type.rs @@ -0,0 +1,21 @@ +// 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 spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::player_or_bot_type::PlayerOrBot; +use super::tile_type::Tile; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct Hand { + pub id: u32, + pub owner: PlayerOrBot, + pub sort: bool, + pub tiles: Vec, +} + +impl __sdk::InModule for Hand { + type Module = super::RemoteModule; +} diff --git a/jong/src/stdb/mod.rs b/jong/src/stdb/mod.rs index 5c7e382..356ab0c 100644 --- a/jong/src/stdb/mod.rs +++ b/jong/src/stdb/mod.rs @@ -11,6 +11,8 @@ pub mod bot_table; pub mod bot_type; pub mod dragon_type; pub mod game_state_type; +pub mod hand_table; +pub mod hand_type; pub mod join_or_create_lobby_reducer; pub mod lobby_table; pub mod lobby_type; @@ -18,6 +20,8 @@ pub mod login_or_add_player_reducer; pub mod player_or_bot_type; pub mod player_table; pub mod player_type; +pub mod pond_table; +pub mod pond_type; pub mod rank_type; pub mod set_ready_reducer; pub mod shuffle_deal_reducer; @@ -25,6 +29,7 @@ pub mod start_game_reducer; pub mod suit_type; pub mod tile_type; pub mod turn_state_type; +pub mod view_player_hand_table; pub mod wall_table; pub mod wall_type; pub mod wind_type; @@ -34,6 +39,8 @@ pub use bot_table::*; pub use bot_type::Bot; pub use dragon_type::Dragon; pub use game_state_type::GameState; +pub use hand_table::*; +pub use hand_type::Hand; pub use join_or_create_lobby_reducer::{ join_or_create_lobby, set_flags_for_join_or_create_lobby, JoinOrCreateLobbyCallbackId, }; @@ -45,6 +52,8 @@ pub use login_or_add_player_reducer::{ pub use player_or_bot_type::PlayerOrBot; pub use player_table::*; pub use player_type::Player; +pub use pond_table::*; +pub use pond_type::Pond; pub use rank_type::Rank; pub use set_ready_reducer::{set_flags_for_set_ready, set_ready, SetReadyCallbackId}; pub use shuffle_deal_reducer::{set_flags_for_shuffle_deal, shuffle_deal, ShuffleDealCallbackId}; @@ -52,6 +61,7 @@ pub use start_game_reducer::{set_flags_for_start_game, start_game, StartGameCall pub use suit_type::Suit; pub use tile_type::Tile; pub use turn_state_type::TurnState; +pub use view_player_hand_table::*; pub use wall_table::*; pub use wall_type::Wall; pub use wind_type::Wind; @@ -142,8 +152,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { #[doc(hidden)] pub struct DbUpdate { bot: __sdk::TableUpdate, + hand: __sdk::TableUpdate, lobby: __sdk::TableUpdate, player: __sdk::TableUpdate, + pond: __sdk::TableUpdate, + view_player_hand: __sdk::TableUpdate, wall: __sdk::TableUpdate, } @@ -156,12 +169,21 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "bot" => db_update .bot .append(bot_table::parse_table_update(table_update)?), + "hand" => db_update + .hand + .append(hand_table::parse_table_update(table_update)?), "lobby" => db_update .lobby .append(lobby_table::parse_table_update(table_update)?), "player" => db_update .player .append(player_table::parse_table_update(table_update)?), + "pond" => db_update + .pond + .append(pond_table::parse_table_update(table_update)?), + "view_player_hand" => db_update + .view_player_hand + .append(view_player_hand_table::parse_table_update(table_update)?), "wall" => db_update .wall .append(wall_table::parse_table_update(table_update)?), @@ -194,15 +216,23 @@ impl __sdk::DbUpdate for DbUpdate { diff.bot = cache .apply_diff_to_table::("bot", &self.bot) .with_updates_by_pk(|row| &row.id); + diff.hand = cache + .apply_diff_to_table::("hand", &self.hand) + .with_updates_by_pk(|row| &row.id); diff.lobby = cache .apply_diff_to_table::("lobby", &self.lobby) .with_updates_by_pk(|row| &row.id); diff.player = cache .apply_diff_to_table::("player", &self.player) .with_updates_by_pk(|row| &row.identity); + diff.pond = cache + .apply_diff_to_table::("pond", &self.pond) + .with_updates_by_pk(|row| &row.id); diff.wall = cache .apply_diff_to_table::("wall", &self.wall) .with_updates_by_pk(|row| &row.lobby_id); + diff.view_player_hand = + cache.apply_diff_to_table::("view_player_hand", &self.view_player_hand); diff } @@ -213,8 +243,11 @@ impl __sdk::DbUpdate for DbUpdate { #[doc(hidden)] pub struct AppliedDiff<'r> { bot: __sdk::TableAppliedDiff<'r, Bot>, + hand: __sdk::TableAppliedDiff<'r, Hand>, lobby: __sdk::TableAppliedDiff<'r, Lobby>, player: __sdk::TableAppliedDiff<'r, Player>, + pond: __sdk::TableAppliedDiff<'r, Pond>, + view_player_hand: __sdk::TableAppliedDiff<'r, Hand>, wall: __sdk::TableAppliedDiff<'r, Wall>, __unused: std::marker::PhantomData<&'r ()>, } @@ -230,8 +263,15 @@ 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::("hand", &self.hand, event); callbacks.invoke_table_row_callbacks::("lobby", &self.lobby, event); callbacks.invoke_table_row_callbacks::("player", &self.player, event); + callbacks.invoke_table_row_callbacks::("pond", &self.pond, event); + callbacks.invoke_table_row_callbacks::( + "view_player_hand", + &self.view_player_hand, + event, + ); callbacks.invoke_table_row_callbacks::("wall", &self.wall, event); } } @@ -953,8 +993,11 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { bot_table::register_table(client_cache); + hand_table::register_table(client_cache); lobby_table::register_table(client_cache); player_table::register_table(client_cache); + pond_table::register_table(client_cache); + view_player_hand_table::register_table(client_cache); wall_table::register_table(client_cache); } } diff --git a/jong/src/stdb/player_table.rs b/jong/src/stdb/player_table.rs index 2cfb5b3..93eb034 100644 --- a/jong/src/stdb/player_table.rs +++ b/jong/src/stdb/player_table.rs @@ -3,7 +3,6 @@ #![allow(unused, clippy::all)] use super::player_type::Player; -use super::tile_type::Tile; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; /// Table handle for the table `player`. diff --git a/jong/src/stdb/player_type.rs b/jong/src/stdb/player_type.rs index 269c160..311ac9b 100644 --- a/jong/src/stdb/player_type.rs +++ b/jong/src/stdb/player_type.rs @@ -4,8 +4,6 @@ #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; -use super::tile_type::Tile; - #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] pub struct Player { @@ -13,10 +11,9 @@ pub struct Player { pub id: u32, pub name: Option, pub lobby_id: u32, + pub hand_id: u32, + pub pond_id: u32, pub ready: bool, - pub sort: bool, - pub hand: Vec, - pub pond: Vec, } impl __sdk::InModule for Player { diff --git a/jong/src/stdb/pond_table.rs b/jong/src/stdb/pond_table.rs new file mode 100644 index 0000000..9f079c2 --- /dev/null +++ b/jong/src/stdb/pond_table.rs @@ -0,0 +1,144 @@ +// 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::player_or_bot_type::PlayerOrBot; +use super::pond_type::Pond; +use super::tile_type::Tile; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `pond`. +/// +/// Obtain a handle from the [`PondTableAccess::pond`] method on [`super::RemoteTables`], +/// like `ctx.db.pond()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.pond().on_insert(...)`. +pub struct PondTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `pond`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PondTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PondTableHandle`], which mediates access to the table `pond`. + fn pond(&self) -> PondTableHandle<'_>; +} + +impl PondTableAccess for super::RemoteTables { + fn pond(&self) -> PondTableHandle<'_> { + PondTableHandle { + imp: self.imp.get_table::("pond"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PondInsertCallbackId(__sdk::CallbackId); +pub struct PondDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PondTableHandle<'ctx> { + type Row = Pond; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PondInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PondInsertCallbackId { + PondInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PondInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PondDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PondDeleteCallbackId { + PondDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PondDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("pond"); + _table.add_unique_constraint::("id", |row| &row.id); +} +pub struct PondUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PondTableHandle<'ctx> { + type UpdateCallbackId = PondUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PondUpdateCallbackId { + PondUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PondUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +/// Access to the `id` unique index on the table `pond`, +/// which allows point queries on the field of the same name +/// via the [`PondIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.pond().id().find(...)`. +pub struct PondIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> PondTableHandle<'ctx> { + /// Get a handle on the `id` unique index on the table `pond`. + pub fn id(&self) -> PondIdUnique<'ctx> { + PondIdUnique { + imp: self.imp.get_unique_constraint::("id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> PondIdUnique<'ctx> { + /// Find the subscribed row whose `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) + } +} diff --git a/jong/src/stdb/pond_type.rs b/jong/src/stdb/pond_type.rs new file mode 100644 index 0000000..bf08228 --- /dev/null +++ b/jong/src/stdb/pond_type.rs @@ -0,0 +1,20 @@ +// 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 spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::player_or_bot_type::PlayerOrBot; +use super::tile_type::Tile; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct Pond { + pub id: u32, + pub owner: PlayerOrBot, + pub tiles: Vec, +} + +impl __sdk::InModule for Pond { + type Module = super::RemoteModule; +} diff --git a/jong/src/stdb/view_player_hand_table.rs b/jong/src/stdb/view_player_hand_table.rs new file mode 100644 index 0000000..82cfb91 --- /dev/null +++ b/jong/src/stdb/view_player_hand_table.rs @@ -0,0 +1,97 @@ +// 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::hand_type::Hand; +use super::player_or_bot_type::PlayerOrBot; +use super::tile_type::Tile; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `view_player_hand`. +/// +/// Obtain a handle from the [`ViewPlayerHandTableAccess::view_player_hand`] method on [`super::RemoteTables`], +/// like `ctx.db.view_player_hand()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.view_player_hand().on_insert(...)`. +pub struct ViewPlayerHandTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `view_player_hand`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait ViewPlayerHandTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`ViewPlayerHandTableHandle`], which mediates access to the table `view_player_hand`. + fn view_player_hand(&self) -> ViewPlayerHandTableHandle<'_>; +} + +impl ViewPlayerHandTableAccess for super::RemoteTables { + fn view_player_hand(&self) -> ViewPlayerHandTableHandle<'_> { + ViewPlayerHandTableHandle { + imp: self.imp.get_table::("view_player_hand"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct ViewPlayerHandInsertCallbackId(__sdk::CallbackId); +pub struct ViewPlayerHandDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for ViewPlayerHandTableHandle<'ctx> { + type Row = Hand; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = ViewPlayerHandInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> ViewPlayerHandInsertCallbackId { + ViewPlayerHandInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: ViewPlayerHandInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = ViewPlayerHandDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> ViewPlayerHandDeleteCallbackId { + ViewPlayerHandDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: ViewPlayerHandDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("view_player_hand"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/jong/src/tui.rs b/jong/src/tui.rs index 821e3a1..3c2bade 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -86,7 +86,7 @@ impl Plugin for TuiPlugin { .add_systems( Update, ( - render::render_hands.run_if(in_state(GameState::Play)), + (render::render_hands, render::render_arg_check).run_if(in_state(GameState::Play)), render::render, ) .chain() diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index 2377516..42a0fd2 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -95,24 +95,23 @@ pub(crate) fn render( Ok(()) } -// pub(crate) fn render_arg_check( -// mut commands: Commands, -// mut tui: ResMut, +pub(crate) fn render_arg_check( + mut commands: Commands, + mut tui: ResMut, -// hovered: Query>, -// layouts: Res, + hovered: Query>, + layouts: Res, -// tiles: Query<&jong_types::Tile>, -// // main_player: Single<(&Player, Entity, &Wind), With>, -// curr_player: Single>, -// players: Query<(&Player, Entity, &Children)>, -// hands: Query<(&Children, Entity), (With, Without)>, -// // drawn_tile: Single>, -// ) { -// // trace!("arg!"); -// } + tiles: Query<&jong_types::Tile>, + // main_player: Single<(&Player, Entity, &Wind), With>, + curr_player: Single>, + players: Query<(&Player, Entity, &Children)>, + hands: Query<(&Children, Entity), (With, Without)>, + // drawn_tile: Single>, +) { + // trace!("arg!"); +} -// FIXME we don't care about other players atm #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn render_hands( mut commands: Commands, @@ -122,21 +121,23 @@ pub(crate) fn render_hands( layouts: Res, tiles: Query<&jong_types::Tile>, - main_player: Single<(&Player, Entity /* , &Wind */), With>, + main_player: Single<(&Player, Entity, &Wind), With>, curr_player: Single>, players: Query<(&Player, Entity, &Children)>, - hands: Query<(&Children, Entity), With>, - // drawn_tile: Single>, + hands: Query<(&Children, Entity), (With, Without)>, + drawn_tile: Single>, ) -> Result { + trace!("render_hands"); + let mut frame = tui.get_frame(); debug_blocks(*layouts, &mut frame); for (hand, hand_ent) in hands { // debug!("{hand:?}"); - // let (player, player_ent, _) = players - // .iter() - // .find(|(_, e, c)| c.contains(&hand_ent)) - // .unwrap(); + let (player, player_ent, _) = players + .iter() + .find(|(_, e, c)| c.contains(&hand_ent)) + .unwrap(); let hand: Vec<_> = hand .iter() .map(|entity| -> Result<_> { @@ -148,81 +149,80 @@ pub(crate) fn render_hands( }) .collect::>()?; - let (player, player_ent) = *main_player; - // if player == main_player.0 { - // split main box into thirds - let mut this_hand = layouts.this_hand; - // let this_drawer = drawn_tile..is_some_and(|dt| dt.0 == player); - let this_drawer = player_ent == *curr_player; - let tile_drawn = if this_drawer { 7 } else { 0 }; - let hand_draw_meld = Layout::horizontal([ - Constraint::Max(hand.len() as u16 * 5), - Constraint::Max(tile_drawn), - Constraint::Fill(1), - ]) - .flex(Flex::SpaceBetween); - this_hand = this_hand.offset(Offset { - x: 0, - y: this_hand.height.abs_diff(5) as i32 + 1, - }); - this_hand = this_hand.resize(Size { - width: this_hand.width, - height: 4, - }); - let [hand_area, drawn_area, meld_area] = hand_draw_meld.areas::<3>(this_hand); + if player == main_player.0 { + // split main box into thirds + let mut this_hand = layouts.this_hand; + // let this_drawer = drawn_tile..is_some_and(|dt| dt.0 == player); + let this_drawer = player_ent == *curr_player; + let tile_drawn = if this_drawer { 7 } else { 0 }; + let hand_draw_meld = Layout::horizontal([ + Constraint::Max(hand.len() as u16 * 5), + Constraint::Max(tile_drawn), + Constraint::Fill(1), + ]) + .flex(Flex::SpaceBetween); + this_hand = this_hand.offset(Offset { + x: 0, + y: this_hand.height.abs_diff(5) as i32 + 1, + }); + this_hand = this_hand.resize(Size { + width: this_hand.width, + height: 4, + }); + let [hand_area, drawn_area, meld_area] = hand_draw_meld.areas::<3>(this_hand); - // split hand area into tile areas - let mut constraints = vec![Constraint::Max(5); hand.len()]; - constraints.push(Constraint::Fill(1)); - let layout = Layout::horizontal(constraints).flex(Flex::Start); - let tile_areas = layout.split(hand_area); + // split hand area into tile areas + let mut constraints = vec![Constraint::Max(5); hand.len()]; + constraints.push(Constraint::Fill(1)); + let layout = Layout::horizontal(constraints).flex(Flex::Start); + let tile_areas = layout.split(hand_area); - for ((entity, widget, hovered), mut area) in - hand.into_iter().zip(tile_areas.iter().copied()) - { - if hovered { - area = area.offset(Offset { x: 0, y: -1 }); - let mut hitbox = area.as_size(); - hitbox.height += 1; - commands.entity(entity).insert(PickRegion { - area: area.resize(hitbox), - }); - } else { - commands.entity(entity).insert(PickRegion { area }); + for ((entity, widget, hovered), mut area) in + hand.into_iter().zip(tile_areas.iter().copied()) + { + if hovered { + area = area.offset(Offset { x: 0, y: -1 }); + let mut hitbox = area.as_size(); + hitbox.height += 1; + commands.entity(entity).insert(PickRegion { + area: area.resize(hitbox), + }); + } else { + commands.entity(entity).insert(PickRegion { area }); + } + frame.render_widget(widget, area); } - frame.render_widget(widget, area); - } - // tsumo tile - // if this_drawer { - // // trace!("this_drawer"); - // let mut area = drawn_area.resize(Size { - // width: 5, - // height: 4, - // }); - // area = area.offset(Offset { x: 2, y: 0 }); - // let hovered = hovered.contains(*drawn_tile); - // let widget = render_tile(tiles.get(*drawn_tile)?, hovered); - // if hovered { - // area = area.offset(Offset { x: 0, y: -1 }); - // let mut hitbox = area.as_size(); - // hitbox.height += 1; - // commands.entity(*drawn_tile).insert(PickRegion { - // area: area.resize(hitbox), - // }); - // } else { - // commands.entity(*drawn_tile).insert(PickRegion { area }); - // } - // frame.render_widget(widget, area); - // } - // TODO draw melds - // } else { - // match mainplayer.1.relate(wind) { - // jong::game::round::WindRelation::Shimocha => todo!(), - // jong::game::round::WindRelation::Toimen => todo!(), - // jong::game::round::WindRelation::Kamicha => todo!(), - // } - // } + // tsumo tile + if this_drawer { + // trace!("this_drawer"); + let mut area = drawn_area.resize(Size { + width: 5, + height: 4, + }); + area = area.offset(Offset { x: 2, y: 0 }); + let hovered = hovered.contains(*drawn_tile); + let widget = render_tile(tiles.get(*drawn_tile)?, hovered); + if hovered { + area = area.offset(Offset { x: 0, y: -1 }); + let mut hitbox = area.as_size(); + hitbox.height += 1; + commands.entity(*drawn_tile).insert(PickRegion { + area: area.resize(hitbox), + }); + } else { + commands.entity(*drawn_tile).insert(PickRegion { area }); + } + frame.render_widget(widget, area); + } + // TODO draw melds + } else { + // match mainplayer.1.relate(wind) { + // jong::game::round::WindRelation::Shimocha => todo!(), + // jong::game::round::WindRelation::Toimen => todo!(), + // jong::game::round::WindRelation::Kamicha => todo!(), + // } + } } Ok(()) diff --git a/justfile b/justfile index 0d4b9f7..8962781 100644 --- a/justfile +++ b/justfile @@ -8,8 +8,8 @@ default: just --list run-tui: - mprocs -s localhost:4050 --ctl $"({c: restart-proc, name: spacetimedb_generate_bindings} | to yaml)" - sleep 3sec + # mprocs -s localhost:4050 --ctl $"({c: restart-proc, name: spacetimedb_generate_bindings} | to yaml)" + # sleep 3sec cargo run -- run-tui update: diff --git a/spacetimedb/src/game.rs b/spacetimedb/src/game.rs index 8777e2d..37a6a69 100644 --- a/spacetimedb/src/game.rs +++ b/spacetimedb/src/game.rs @@ -1,7 +1,7 @@ use log::{info, trace}; use spacetimedb::{ReducerContext, Table, reducer}; -use crate::tables::{player::player, *}; +use crate::tables::*; use jong_types::*; mod hand; @@ -50,8 +50,8 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { let bot = ctx.db.bot().insert(Bot { id: 0, lobby_id, - hand: vec![], - pond: vec![], + hand_id: 0, + pond_id: 0, }); lobby.players.push(PlayerOrBot::Bot { id: bot.id }); ctx.db.lobby().id().update(lobby); @@ -80,7 +80,7 @@ pub fn start_game(ctx: &ReducerContext) { PlayerOrBot::Bot { id } => ctx.db.bot().id().find(id).is_some(), }) { - lobby.game_state = GameState::Setup; + lobby.game_state = GameState::Deal; ctx.db.lobby().id().update(lobby); } } diff --git a/spacetimedb/src/game/hand.rs b/spacetimedb/src/game/hand.rs index 294b6d9..546ad66 100644 --- a/spacetimedb/src/game/hand.rs +++ b/spacetimedb/src/game/hand.rs @@ -1,6 +1,6 @@ use spacetimedb::{ReducerContext, Table, ViewContext, reducer, view}; -use crate::tables::{player::player, *}; +use crate::tables::*; use jong_types::*; pub fn deal_hands(ctx: &ReducerContext, lobby_id: u32) { @@ -12,28 +12,40 @@ pub fn deal_hands(ctx: &ReducerContext, lobby_id: u32) { // FIXME rectify deal orders for mut player in players { let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13); - wall = ctx.db.wall().lobby_id().update(wall); tiles.sort(); - player.hand = tiles; + wall = ctx.db.wall().lobby_id().update(wall); + let hand = ctx.db.hand().insert(Hand { + id: 0, + owner: PlayerOrBot::Player { id: player.id }, + sort: true, + tiles, + }); + player.hand_id = hand.id; ctx.db.player().id().update(player); } for mut bot in bots { let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13); - wall = ctx.db.wall().lobby_id().update(wall); tiles.sort(); - bot.hand = tiles; + wall = ctx.db.wall().lobby_id().update(wall); + let hand = ctx.db.hand().insert(Hand { + id: 0, + owner: PlayerOrBot::Bot { id: bot.id }, + sort: true, + tiles, + }); + bot.hand_id = hand.id; ctx.db.bot().id().update(bot); } } -// #[view(name = view_player_hand, public)] -// pub fn view_player_hand(ctx: &ViewContext) -> Option { -// ctx.db -// .player() -// .identity() -// .find(ctx.sender) -// .map(|p| ctx.db.hand().id().find(p.hand_id))? -// } +#[view(name = view_player_hand, public)] +pub fn view_player_hand(ctx: &ViewContext) -> Option { + ctx.db + .player() + .identity() + .find(ctx.sender) + .map(|p| ctx.db.hand().id().find(p.hand_id))? +} // #[reducer] // pub fn sort_hand(ctx: &ReducerContext) { diff --git a/spacetimedb/src/game/wall.rs b/spacetimedb/src/game/wall.rs index 7096559..f33c898 100644 --- a/spacetimedb/src/game/wall.rs +++ b/spacetimedb/src/game/wall.rs @@ -10,10 +10,7 @@ pub fn shuffle_deal(ctx: &ReducerContext, lobby_id: u32) { debug!("lobby_id: {lobby_id}"); let mut lobby = ctx.db.lobby().id().find(lobby_id).unwrap(); - if lobby.game_state == GameState::Setup { - lobby.game_state = GameState::Deal; - lobby = ctx.db.lobby().id().update(lobby); - + if lobby.game_state == GameState::Deal { let tiles = new_shuffled_wall(ctx); ctx.db.wall().insert(Wall { // id: 0, diff --git a/spacetimedb/src/lib.rs b/spacetimedb/src/lib.rs index 2c6d8d1..505db51 100644 --- a/spacetimedb/src/lib.rs +++ b/spacetimedb/src/lib.rs @@ -1,7 +1,7 @@ use log::info; use spacetimedb::{ReducerContext, Table, reducer}; -use crate::tables::{player::player, *}; +use crate::tables::*; mod game; mod tables; @@ -16,10 +16,9 @@ pub fn login_or_add_player(ctx: &ReducerContext) { id: 0, name: None, lobby_id: 0, + hand_id: 0, + pond_id: 0, ready: false, - sort: true, - hand: vec![], - pond: vec![], }) { info!("added player: {:?}", player); } else { diff --git a/spacetimedb/src/tables.rs b/spacetimedb/src/tables.rs index 4e84432..02efc8e 100644 --- a/spacetimedb/src/tables.rs +++ b/spacetimedb/src/tables.rs @@ -1,9 +1,44 @@ -use spacetimedb::{Filter, SpacetimeType, client_visibility_filter, table}; +use spacetimedb::{Identity, SpacetimeType, table}; use jong_types::*; -pub mod player; -pub use player::*; +#[derive(Debug)] +#[table(name = player, public)] +pub struct Player { + #[primary_key] + pub identity: Identity, + + #[unique] + #[auto_inc] + pub id: u32, + + pub name: Option, + + #[index(btree)] + pub lobby_id: u32, + pub hand_id: u32, + pub pond_id: u32, + + pub ready: bool, +} + +#[table(name = bot)] +pub struct Bot { + #[primary_key] + #[auto_inc] + pub id: u32, + + #[index(btree)] + pub lobby_id: u32, + pub hand_id: u32, + pub pond_id: u32, +} + +#[derive(Debug, Clone, SpacetimeType)] +pub enum PlayerOrBot { + Player { id: u32 }, + Bot { id: u32 }, +} #[derive(Debug, Clone)] #[table(name = lobby, public)] @@ -14,7 +49,7 @@ pub struct Lobby { #[unique] pub host_player_id: u32, - pub players: Vec, + pub players: Vec, pub game_state: GameState, pub turn_state: TurnState, @@ -27,3 +62,26 @@ pub struct Wall { pub tiles: Vec, } + +#[table(name = hand)] +pub struct Hand { + #[primary_key] + #[auto_inc] + pub id: u32, + + pub owner: PlayerOrBot, + + pub sort: bool, + pub tiles: Vec, +} + +#[table(name = pond, public)] +pub struct Pond { + #[primary_key] + #[auto_inc] + pub id: u32, + + pub owner: PlayerOrBot, + + pub tiles: Vec, +} diff --git a/spacetimedb/src/tables/player.rs b/spacetimedb/src/tables/player.rs deleted file mode 100644 index ab03a06..0000000 --- a/spacetimedb/src/tables/player.rs +++ /dev/null @@ -1,45 +0,0 @@ -use spacetimedb::Identity; -use spacetimedb::{SpacetimeType, table}; - -use jong_types::*; - -#[derive(Debug)] -#[table(name = player, public)] -pub struct Player { - #[primary_key] - pub identity: Identity, - - #[unique] - #[auto_inc] - pub id: u32, - - pub name: Option, - - #[index(btree)] - pub lobby_id: u32, - pub ready: bool, - - pub sort: bool, - - pub hand: Vec, - pub pond: Vec, -} - -#[table(name = bot)] -pub struct Bot { - #[primary_key] - #[auto_inc] - pub id: u32, - - #[index(btree)] - pub lobby_id: u32, - - pub hand: Vec, - pub pond: Vec, -} - -#[derive(Debug, Clone, SpacetimeType)] -pub enum PlayerOrBot { - Player { id: u32 }, - Bot { id: u32 }, -} diff --git a/docs/states b/states similarity index 100% rename from docs/states rename to states