From 8c4132e628f7402934ff95548c0812bc74619563 Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:49:09 -0800 Subject: [PATCH] sorta discard a tile --- jong/src/game.rs | 4 +- jong/src/stdb/discard_tile_reducer.rs | 105 ++++++++++++++++++++++++++ jong/src/stdb/mod.rs | 11 +++ jong/src/tui.rs | 55 ++++++++------ spacetimedb/src/reducers/game.rs | 1 + spacetimedb/src/reducers/game/deal.rs | 45 ++++++++++- spacetimedb/src/tables.rs | 4 +- 7 files changed, 199 insertions(+), 26 deletions(-) create mode 100644 jong/src/stdb/discard_tile_reducer.rs diff --git a/jong/src/game.rs b/jong/src/game.rs index b132ec6..4f947ba 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -120,7 +120,7 @@ fn subscriptions(stdb: SpacetimeDB) { } #[derive(Component)] -struct TileId(u32); +pub struct TileId(pub u32); fn on_player_insert_update( stdb: SpacetimeDB, @@ -135,7 +135,7 @@ fn on_player_insert_update( use player::*; for msg in messages.read() { - debug!("player_insert_update msg:\n{:#?}", msg.new); + // debug!("player_insert_update msg:\n{:#?}", msg.new); if let (Some(player), Some(hand_ent)) = (player.as_ref(), hand_ent.as_ref()) { // if msg.old.as_ref().is_some_and(|m| !m.ready) && msg.new.ready { // trace!("entered ready"); diff --git a/jong/src/stdb/discard_tile_reducer.rs b/jong/src/stdb/discard_tile_reducer.rs new file mode 100644 index 0000000..c3bcb15 --- /dev/null +++ b/jong/src/stdb/discard_tile_reducer.rs @@ -0,0 +1,105 @@ +// 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}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct DiscardTileArgs { + pub tile_id: u32, +} + +impl From for super::Reducer { + fn from(args: DiscardTileArgs) -> Self { + Self::DiscardTile { + tile_id: args.tile_id, + } + } +} + +impl __sdk::InModule for DiscardTileArgs { + type Module = super::RemoteModule; +} + +pub struct DiscardTileCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `discard_tile`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait discard_tile { + /// Request that the remote module invoke the reducer `discard_tile` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_discard_tile`] callbacks. + fn discard_tile(&self, tile_id: u32) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `discard_tile`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DiscardTileCallbackId`] can be passed to [`Self::remove_on_discard_tile`] + /// to cancel the callback. + fn on_discard_tile( + &self, + callback: impl FnMut(&super::ReducerEventContext, &u32) + Send + 'static, + ) -> DiscardTileCallbackId; + /// Cancel a callback previously registered by [`Self::on_discard_tile`], + /// causing it not to run in the future. + fn remove_on_discard_tile(&self, callback: DiscardTileCallbackId); +} + +impl discard_tile for super::RemoteReducers { + fn discard_tile(&self, tile_id: u32) -> __sdk::Result<()> { + self.imp + .call_reducer("discard_tile", DiscardTileArgs { tile_id }) + } + fn on_discard_tile( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &u32) + Send + 'static, + ) -> DiscardTileCallbackId { + DiscardTileCallbackId(self.imp.on_reducer( + "discard_tile", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::DiscardTile { tile_id }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, tile_id) + }), + )) + } + fn remove_on_discard_tile(&self, callback: DiscardTileCallbackId) { + self.imp.remove_on_reducer("discard_tile", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `discard_tile`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_discard_tile { + /// Set the call-reducer flags for the reducer `discard_tile` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn discard_tile(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_discard_tile for super::SetReducerFlags { + fn discard_tile(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("discard_tile", flags); + } +} diff --git a/jong/src/stdb/mod.rs b/jong/src/stdb/mod.rs index eea234f..61b46d3 100644 --- a/jong/src/stdb/mod.rs +++ b/jong/src/stdb/mod.rs @@ -11,6 +11,7 @@ pub mod bot_table; pub mod bot_type; pub mod db_tile_type; pub mod db_wall_type; +pub mod discard_tile_reducer; pub mod dragon_type; pub mod draw_tile_reducer; pub mod game_state_type; @@ -37,6 +38,7 @@ pub use bot_table::*; pub use bot_type::Bot; pub use db_tile_type::DbTile; pub use db_wall_type::DbWall; +pub use discard_tile_reducer::{discard_tile, set_flags_for_discard_tile, DiscardTileCallbackId}; pub use dragon_type::Dragon; pub use draw_tile_reducer::{draw_tile, set_flags_for_draw_tile, DrawTileCallbackId}; pub use game_state_type::GameState; @@ -71,6 +73,7 @@ pub use wind_type::Wind; pub enum Reducer { AddBot { lobby_id: u32 }, + DiscardTile { tile_id: u32 }, DrawTile, JoinOrCreateLobby { lobby_id: u32 }, LoginOrAddPlayer, @@ -87,6 +90,7 @@ impl __sdk::Reducer for Reducer { fn reducer_name(&self) -> &'static str { match self { Reducer::AddBot { .. } => "add_bot", + Reducer::DiscardTile { .. } => "discard_tile", Reducer::DrawTile => "draw_tile", Reducer::JoinOrCreateLobby { .. } => "join_or_create_lobby", Reducer::LoginOrAddPlayer => "login_or_add_player", @@ -106,6 +110,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { &value.args, )? .into()), + "discard_tile" => Ok( + __sdk::parse_reducer_args::( + "discard_tile", + &value.args, + )? + .into(), + ), "draw_tile" => Ok( __sdk::parse_reducer_args::( "draw_tile", diff --git a/jong/src/tui.rs b/jong/src/tui.rs index 4a6eebb..e1852f4 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -1,14 +1,22 @@ +#![allow(unused)] + use std::time::Duration; use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin}; use bevy_ratatui::RatatuiPlugins; +use jong::game::TileId; +use jong::game::player::MainPlayer; use tui_logger::TuiWidgetState; use crate::tui::{input::ConfirmSelect, states::ConsoleWidget}; -use jong::game::{ - GameMessage, - hand::{Drawn, Hand}, - player::{CurrentPlayer, Player}, +use jong::stdb::{self, discard_tile as _}; +use jong::{ + SpacetimeDB, + game::{ + GameMessage, + hand::{Drawn, Hand}, + player::{CurrentPlayer, Player}, + }, }; use jong_types::{GameState, TurnState}; @@ -82,10 +90,7 @@ impl Plugin for TuiPlugin { (input::keyboard, input::mouse).in_set(TuiSet::Input), ) .add_systems(Update, layout::layout.in_set(TuiSet::Layout)) - .add_systems( - Update, - discard_tile.run_if(in_state(GameState::Play).and(in_state(TurnState::Tsumo))), - ) + .add_systems(Update, discard_tile.run_if(in_state(TurnState::Tsumo))) .add_systems( Update, ( @@ -99,28 +104,36 @@ impl Plugin for TuiPlugin { } fn discard_tile( + stdb: SpacetimeDB, + mut writer: MessageWriter, mut selected: MessageReader, - drawn: Single>, - curr_player: Single>, - player_hands: Populated<(&Player, &Children), With>, - hands: Populated<&Children, (With, Without)>, + drawn: Single<(Entity, &TileId), With>, + // curr_player: Single>, + // player_hands: Populated<(&Player, &Children), With>, + // hands: Populated<&Children, (With, Without)>, + main_player: Single<(&Player, Entity), With>, + players: Query<(&Player, Entity, &Children)>, + hands: Query<(&Children, Entity), With>, + tiles: Populated<&TileId>, ) { // trace!("discard_tile"); - let hand_ent = player_hands - .get(*curr_player) - .unwrap() - .1 - .iter() - .next() - .unwrap(); - let hand = hands.get(hand_ent).unwrap(); + let (hand, hand_ent) = hands.iter().next().unwrap(); while let Some(message) = selected.read().next() - && (message.0 == *drawn || hand.contains(&message.0)) + // && (message.0 == drawn.0 || hand.contains(&message.0)) { + if message.0 == drawn.0 { + stdb.reducers().discard_tile(drawn.1.0).unwrap(); + } else if let Some(tile_ent) = hand.iter().find(|t| *t == message.0) { + stdb.reducers() + .discard_tile(tiles.get(tile_ent).unwrap().0) + .unwrap(); + } + + // FIXME check if discard actually in hand? writer.write(GameMessage::Discarded(message.0)); } } diff --git a/spacetimedb/src/reducers/game.rs b/spacetimedb/src/reducers/game.rs index 9d01633..4747375 100644 --- a/spacetimedb/src/reducers/game.rs +++ b/spacetimedb/src/reducers/game.rs @@ -24,6 +24,7 @@ pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<( players: vec![PlayerOrBot::Player { id: player.id }], game_state: GameState::Lobby, turn_state: TurnState::None, + // dealt_idx: -1, }); info!("created lobby: {:?}", lobby); diff --git a/spacetimedb/src/reducers/game/deal.rs b/spacetimedb/src/reducers/game/deal.rs index 99069f6..bd871f7 100644 --- a/spacetimedb/src/reducers/game/deal.rs +++ b/spacetimedb/src/reducers/game/deal.rs @@ -1,8 +1,8 @@ -use log::debug; +use log::{debug, trace}; use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; use super::hand::deal_hands; -use crate::tables::*; +use crate::tables::{player::player, *}; use jong_types::*; #[reducer] @@ -40,3 +40,44 @@ pub fn new_shuffled_wall(ctx: &ReducerContext) -> Vec { wall } + +// TODO make sure this can't be called or just error here? +#[reducer] +pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { + trace!("discard_tile"); + + let mut player = ctx.db.player().identity().find(ctx.sender).unwrap(); + let mut lobby = ctx.db.lobby().id().find(player.lobby_id).unwrap(); + + let tile = if let Some(drawn) = player.drawn_tile + && drawn.id == tile_id + { + player.hand.push(drawn); + // lobby.dealt_idx = -1; + + drawn + } else if let Some((i, &hand)) = player + .hand + .iter() + .enumerate() + .find(|(_, t)| t.id == tile_id) + { + player.hand.remove(i); + // lobby.dealt_idx = 0; + + hand + } else { + return Err(format!( + "player {} attempted to deal nonexistant tile {}", + player.id, tile_id + )); + }; + + player.drawn_tile = None; + lobby.turn_state = TurnState::Discard; + + ctx.db.player().id().update(player); + ctx.db.lobby().id().update(lobby); + + Ok(()) +} diff --git a/spacetimedb/src/tables.rs b/spacetimedb/src/tables.rs index 7250513..890eb1e 100644 --- a/spacetimedb/src/tables.rs +++ b/spacetimedb/src/tables.rs @@ -18,6 +18,8 @@ pub struct Lobby { pub game_state: GameState, pub turn_state: TurnState, + + // pub dealt_idx: i8, } #[table(name = wall)] @@ -29,7 +31,7 @@ pub struct DbWall { } #[table(name = tile)] -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct DbTile { #[primary_key] #[auto_inc]