From 33cb9c7f972e987e0bd141aee1259e64da32ab30 Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:56:34 -0800 Subject: [PATCH 1/2] refactor layout code a lil --- jong/src/tui/layout.rs | 34 ++++++++++++++++++++++++++-------- jong/src/tui/render.rs | 32 +++----------------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/jong/src/tui/layout.rs b/jong/src/tui/layout.rs index 827c6ea..8c106c7 100644 --- a/jong/src/tui/layout.rs +++ b/jong/src/tui/layout.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_ratatui::RatatuiContext; use ratatui::{ - layout::{Constraint, Layout, Margin}, + layout::{Constraint, Flex, Layout, Margin}, prelude::Rect, }; @@ -12,15 +12,19 @@ pub(crate) struct Overlays { pub(crate) enum Overlay {} -#[derive(Resource, Clone, Copy)] +#[derive(Resource, Clone)] pub(crate) struct HandLayouts { pub(crate) left_pond: Rect, - pub(crate) cross_pond: Rect, - pub(crate) right_pond: Rect, - pub(crate) this_pond: Rect, pub(crate) left_hand: Rect, + + pub(crate) cross_pond: Rect, pub(crate) cross_hand: Rect, + + pub(crate) right_pond: Rect, pub(crate) right_hand: Rect, + + pub(crate) this_pond: Rect, + pub(crate) this_pond_slots: Vec, pub(crate) this_hand: Rect, } @@ -69,14 +73,28 @@ fn tiles_areas(term_area: Rect) -> HandLayouts { right_pond.height = cross_pond.height; right_pond.y += cross_pond.height / 2; + let this_pond_row_constraints = [Constraint::Max(4); 3]; + let this_pond_col_constraints = [Constraint::Max(5); 6]; + let this_pond_row_layouts = Layout::vertical(this_pond_row_constraints).flex(Flex::Start); + let this_pond_col_layouts = Layout::horizontal(this_pond_col_constraints).flex(Flex::Start); + let mut this_pond_rows = this_pond_row_layouts.areas::<3>(this_pond); + let this_pond_slots: Vec<_> = this_pond_rows + .iter() + .flat_map(|row| this_pond_col_layouts.areas::<6>(*row)) + .collect(); + HandLayouts { left_pond, - cross_pond, - right_pond, - this_pond, left_hand, + + cross_pond, cross_hand, + + right_pond, right_hand, + + this_pond, this_hand, + this_pond_slots, } } diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index c12dfdf..c93a578 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -93,21 +93,6 @@ pub(crate) fn render( Ok(()) } -pub(crate) fn query_tester( - mut commands: Commands, - mut tui: ResMut, - - hovered: Query>, - layouts: Res, - - tiles: Query<&jong_types::Tile>, - // main_player: Single<(&Player, Entity /* , &Wind */), With>, - hand: Single<(&Children, Entity), With>, - drawn_tile: Option>>, -) { - trace!("owo") -} - // FIXME we don't care about other players atm #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn render_hand( @@ -123,7 +108,7 @@ pub(crate) fn render_hand( drawn_tile: Option>>, ) -> Result { let mut frame = tui.get_frame(); - debug_blocks(*layouts, &mut frame); + debug_blocks(layouts.clone(), &mut frame); let hand: Vec<_> = hand .0 @@ -234,19 +219,8 @@ pub(crate) fn render_pond( }) .collect::>()?; - let mut this_pond = layouts.this_pond; - let row_constraints = [Constraint::Max(4); 3]; - let col_constraints = [Constraint::Max(5); 6]; - let row_layouts = Layout::vertical(row_constraints).flex(Flex::Start); - let col_layouts = Layout::horizontal(col_constraints).flex(Flex::Start); - let mut rows = row_layouts.areas::<3>(this_pond); - - for (rect, (_, tile)) in rows - .iter() - .flat_map(|row| col_layouts.areas::<6>(*row)) - .zip(pond) - { - frame.render_widget(tile, rect); + for (rect, (_, tile)) in layouts.this_pond_slots.iter().zip(pond) { + frame.render_widget(tile, *rect); } Ok(()) From e7658fd36f9f2f2a6bd828d2e67139e0c45628b2 Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:56:34 -0800 Subject: [PATCH 2/2] TODO render other players --- jong-db/src/db/hand_view_type.rs | 39 ++++++++ jong-db/src/db/mod.rs | 17 ++++ jong-db/src/db/view_closed_hands_table.rs | 112 ++++++++++++++++++++++ jong-db/src/lib.rs | 9 ++ jong-line/src/reducers.rs | 7 +- jong-line/src/reducers/hand.rs | 5 +- jong-line/src/reducers/lobby.rs | 2 + jong-line/src/tables.rs | 53 ++++++++-- jong-types/src/lib.rs | 9 ++ jong/src/riichi.rs | 10 +- 10 files changed, 249 insertions(+), 14 deletions(-) create mode 100644 jong-db/src/db/hand_view_type.rs create mode 100644 jong-db/src/db/view_closed_hands_table.rs diff --git a/jong-db/src/db/hand_view_type.rs b/jong-db/src/db/hand_view_type.rs new file mode 100644 index 0000000..93935a6 --- /dev/null +++ b/jong-db/src/db/hand_view_type.rs @@ -0,0 +1,39 @@ +// 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; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct HandView { + pub player: PlayerOrBot, + pub hand_length: u8, + pub drawn: bool, +} + +impl __sdk::InModule for HandView { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `HandView`. +/// +/// Provides typed access to columns for query building. +pub struct HandViewCols { + pub player: __sdk::__query_builder::Col, + pub hand_length: __sdk::__query_builder::Col, + pub drawn: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for HandView { + type Cols = HandViewCols; + fn cols(table_name: &'static str) -> Self::Cols { + HandViewCols { + player: __sdk::__query_builder::Col::new(table_name, "player"), + hand_length: __sdk::__query_builder::Col::new(table_name, "hand_length"), + drawn: __sdk::__query_builder::Col::new(table_name, "drawn"), + } + } +} diff --git a/jong-db/src/db/mod.rs b/jong-db/src/db/mod.rs index b1eefc3..0f928b7 100644 --- a/jong-db/src/db/mod.rs +++ b/jong-db/src/db/mod.rs @@ -20,6 +20,7 @@ pub mod dragon_type; pub mod game_state_type; pub mod game_timer_table; pub mod game_timer_type; +pub mod hand_view_type; pub mod join_or_create_lobby_reducer; pub mod lobby_table; pub mod lobby_type; @@ -37,6 +38,7 @@ pub mod suit_type; pub mod tile_table; pub mod tile_type; pub mod turn_state_type; +pub mod view_closed_hands_table; pub mod view_hand_table; pub mod wall_table; pub mod wind_type; @@ -55,6 +57,7 @@ pub use dragon_type::Dragon; pub use game_state_type::GameState; pub use game_timer_table::*; pub use game_timer_type::GameTimer; +pub use hand_view_type::HandView; pub use join_or_create_lobby_reducer::{ join_or_create_lobby, set_flags_for_join_or_create_lobby, JoinOrCreateLobbyCallbackId, }; @@ -74,6 +77,7 @@ pub use suit_type::Suit; pub use tile_table::*; pub use tile_type::Tile; pub use turn_state_type::TurnState; +pub use view_closed_hands_table::*; pub use view_hand_table::*; pub use wall_table::*; pub use wind_type::Wind; @@ -190,6 +194,7 @@ pub struct DbUpdate { player_clock: __sdk::TableUpdate, player_hand: __sdk::TableUpdate, tile: __sdk::TableUpdate, + view_closed_hands: __sdk::TableUpdate, view_hand: __sdk::TableUpdate, wall: __sdk::TableUpdate, } @@ -224,6 +229,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "tile" => db_update .tile .append(tile_table::parse_table_update(table_update)?), + "view_closed_hands" => db_update + .view_closed_hands + .append(view_closed_hands_table::parse_table_update(table_update)?), "view_hand" => db_update .view_hand .append(view_hand_table::parse_table_update(table_update)?), @@ -283,6 +291,8 @@ impl __sdk::DbUpdate for DbUpdate { diff.wall = cache .apply_diff_to_table::("wall", &self.wall) .with_updates_by_pk(|row| &row.lobby_id); + 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); diff @@ -301,6 +311,7 @@ pub struct AppliedDiff<'r> { player_clock: __sdk::TableAppliedDiff<'r, PlayerClock>, player_hand: __sdk::TableAppliedDiff<'r, PlayerHand>, tile: __sdk::TableAppliedDiff<'r, DbTile>, + view_closed_hands: __sdk::TableAppliedDiff<'r, HandView>, view_hand: __sdk::TableAppliedDiff<'r, PlayerHand>, wall: __sdk::TableAppliedDiff<'r, DbWall>, __unused: std::marker::PhantomData<&'r ()>, @@ -332,6 +343,11 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { ); callbacks.invoke_table_row_callbacks::("player_hand", &self.player_hand, event); callbacks.invoke_table_row_callbacks::("tile", &self.tile, event); + callbacks.invoke_table_row_callbacks::( + "view_closed_hands", + &self.view_closed_hands, + event, + ); callbacks.invoke_table_row_callbacks::("view_hand", &self.view_hand, event); callbacks.invoke_table_row_callbacks::("wall", &self.wall, event); } @@ -1062,6 +1078,7 @@ impl __sdk::SpacetimeModule for RemoteModule { player_clock_table::register_table(client_cache); player_hand_table::register_table(client_cache); tile_table::register_table(client_cache); + view_closed_hands_table::register_table(client_cache); view_hand_table::register_table(client_cache); wall_table::register_table(client_cache); } diff --git a/jong-db/src/db/view_closed_hands_table.rs b/jong-db/src/db/view_closed_hands_table.rs new file mode 100644 index 0000000..d0ca254 --- /dev/null +++ b/jong-db/src/db/view_closed_hands_table.rs @@ -0,0 +1,112 @@ +// 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_view_type::HandView; +use super::player_or_bot_type::PlayerOrBot; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `view_closed_hands`. +/// +/// Obtain a handle from the [`ViewClosedHandsTableAccess::view_closed_hands`] method on [`super::RemoteTables`], +/// like `ctx.db.view_closed_hands()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.view_closed_hands().on_insert(...)`. +pub struct ViewClosedHandsTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `view_closed_hands`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait ViewClosedHandsTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`ViewClosedHandsTableHandle`], which mediates access to the table `view_closed_hands`. + fn view_closed_hands(&self) -> ViewClosedHandsTableHandle<'_>; +} + +impl ViewClosedHandsTableAccess for super::RemoteTables { + fn view_closed_hands(&self) -> ViewClosedHandsTableHandle<'_> { + ViewClosedHandsTableHandle { + imp: self.imp.get_table::("view_closed_hands"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct ViewClosedHandsInsertCallbackId(__sdk::CallbackId); +pub struct ViewClosedHandsDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for ViewClosedHandsTableHandle<'ctx> { + type Row = HandView; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = ViewClosedHandsInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> ViewClosedHandsInsertCallbackId { + ViewClosedHandsInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: ViewClosedHandsInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = ViewClosedHandsDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> ViewClosedHandsDeleteCallbackId { + ViewClosedHandsDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: ViewClosedHandsDeleteCallbackId) { + 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_closed_hands"); +} + +#[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() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `HandView`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait view_closed_handsQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `HandView`. + fn view_closed_hands(&self) -> __sdk::__query_builder::Table; +} + +impl view_closed_handsQueryTableAccess for __sdk::QueryTableAccessor { + fn view_closed_hands(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("view_closed_hands") + } +} diff --git a/jong-db/src/lib.rs b/jong-db/src/lib.rs index 0d3befb..46735ab 100644 --- a/jong-db/src/lib.rs +++ b/jong-db/src/lib.rs @@ -55,4 +55,13 @@ mod conversions { Self::from_repr(value as usize).unwrap() } } + + impl From<&crate::db::PlayerOrBot> for jong_types::PlayerOrBot { + fn from(value: &crate::db::PlayerOrBot) -> Self { + match value { + crate::PlayerOrBot::Player(id) => Self::Player { id: *id }, + crate::PlayerOrBot::Bot(id) => Self::Bot { id: *id }, + } + } + } } diff --git a/jong-line/src/reducers.rs b/jong-line/src/reducers.rs index eec0d3a..83fad96 100644 --- a/jong-line/src/reducers.rs +++ b/jong-line/src/reducers.rs @@ -5,12 +5,11 @@ use spacetimedb::{ ReducerContext, ScheduleAt::Interval, Table as _, rand::seq::SliceRandom, reducer, }; -use jong_types::{GameState, TurnState}; - use crate::tables::{ - DbTile, DbWall, GameTimer, Lobby, PlayerClock, PlayerHand, PlayerOrBot, bot, game_timer, - lobby as _, player, player_clock, player_hand, tile as _, wall, + DbTile, DbWall, GameTimer, Lobby, PlayerClock, PlayerHand, bot, game_timer, lobby as _, player, + player_clock, player_hand, tile as _, wall, }; +use jong_types::{GameState, PlayerOrBot, TurnState}; mod hand; mod lobby; diff --git a/jong-line/src/reducers/hand.rs b/jong-line/src/reducers/hand.rs index b5189fc..fd2f861 100644 --- a/jong-line/src/reducers/hand.rs +++ b/jong-line/src/reducers/hand.rs @@ -47,9 +47,12 @@ pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { hand.pond.push(dealt_tile); hand.working_tile = None; hand.turn_state = TurnState::None; - ctx.db.player_hand().id().update(hand); + let mut clock = ctx.db.player_clock().player_id().find(player.id).unwrap(); + clock.renew(); + ctx.db.player_clock().player_id().update(clock); + let mut lobby = ctx.db.lobby().id().find(player.lobby_id).unwrap(); lobby.next_player(); ctx.db.lobby().id().update(lobby); diff --git a/jong-line/src/reducers/lobby.rs b/jong-line/src/reducers/lobby.rs index 269ed02..05e9c84 100644 --- a/jong-line/src/reducers/lobby.rs +++ b/jong-line/src/reducers/lobby.rs @@ -3,6 +3,8 @@ use std::time::Duration; use log::info; use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; +use jong_types::PlayerOrBot; + use crate::tables::*; #[reducer] diff --git a/jong-line/src/tables.rs b/jong-line/src/tables.rs index 4dfc472..1df4d31 100644 --- a/jong-line/src/tables.rs +++ b/jong-line/src/tables.rs @@ -1,6 +1,7 @@ use spacetimedb::{SpacetimeType, ViewContext, table, view}; use jong_types::{ + PlayerOrBot, states::{GameState, TurnState}, tiles::Tile, }; @@ -19,6 +20,7 @@ pub struct Lobby { pub current_idx: u8, pub game_state: GameState, + // pub open_hands: bool, } #[table(name = wall)] @@ -39,12 +41,6 @@ pub struct DbTile { pub tile: Tile, } -#[derive(Debug, Clone, SpacetimeType)] -pub enum PlayerOrBot { - Player { id: u32 }, - Bot { id: u32 }, -} - #[table(name = player, public)] #[table(name = logged_out_player)] #[derive(Debug)] @@ -132,3 +128,48 @@ fn view_hand(ctx: &ViewContext) -> Option { .find(ctx.sender) .and_then(|p| ctx.db.player_hand().player_id().find(p.id)) } + +#[derive(SpacetimeType, Clone, Copy)] +pub struct HandView { + pub player: PlayerOrBot, + pub hand_length: u8, + // pub melds: u8, + pub drawn: bool, +} + +#[view(name = view_closed_hands, public)] +fn view_closed_hands(ctx: &ViewContext) -> Vec { + let this_player = ctx.db.player().identity().find(ctx.sender).unwrap(); + ctx.db + .lobby() + .id() + .find(this_player.lobby_id) + .unwrap() + .players + .iter() + .filter_map(|&player| { + let (hand_length, drawn) = match player { + PlayerOrBot::Player { id } => { + let player_hand = ctx.db.player_hand().player_id().find(id).unwrap(); + ( + player_hand.hand.len() as u8, + player_hand.turn_state == TurnState::Tsumo + && player_hand.working_tile.is_some(), + ) + } + PlayerOrBot::Bot { id } => { + let bot = ctx.db.bot().id().find(id).unwrap(); + ( + bot.hand.len() as u8, + bot.turn_state == TurnState::Tsumo && bot.working_tile.is_some(), + ) + } + }; + Some(HandView { + player, + hand_length, + drawn, + }) + }) + .collect() +} diff --git a/jong-types/src/lib.rs b/jong-types/src/lib.rs index e8c26f3..6eecb7e 100644 --- a/jong-types/src/lib.rs +++ b/jong-types/src/lib.rs @@ -9,8 +9,17 @@ mod derive_alias { } use derive_aliases::derive; +use spacetimedb::SpacetimeType; + pub mod states; pub mod tiles; pub use states::*; pub use tiles::*; + +#[derive(Debug, ..Copy, ..Eq, Hash)] +#[derive(SpacetimeType)] +pub enum PlayerOrBot { + Player { id: u32 }, + Bot { id: u32 }, +} diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index 6c9cbf0..36bbf5b 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -6,7 +6,7 @@ use bevy_spacetimedb::{ use jong_db::{ self, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables, - ViewHandTableAccess as _, + ViewClosedHandsTableAccess, ViewHandTableAccess as _, }; use jong_db::{add_bot, set_ready}; use jong_types::*; @@ -27,7 +27,10 @@ impl Plugin for Riichi { .add_table(RemoteTables::player) .add_table(RemoteTables::lobby) // TODO check bevy_spacetimedb PR status - .add_view_with_pk(RemoteTables::view_hand, |p| p.id); + .add_view_with_pk(RemoteTables::view_hand, |p| p.id) + .add_view_with_pk(RemoteTables::view_closed_hands, |p| { + PlayerOrBot::from(&p.player) + }); let plugins = if let Some(token) = creds_store().load().expect("i/o error loading credentials") { // FIXME patch plugin so this takes Option? @@ -91,8 +94,9 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { ), "SELECT l.* FROM lobby l JOIN player p ON l.id = p.lobby_id".to_string(), "SELECT c.* FROM player_clock c JOIN player p ON c.player_id = p.id".to_string(), - "SELECT * FROM view_hand".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(), ]); while let Ok(event) = recv.recv() {