From 6f3d27a6f6cbd28991f9a13c4a0b8f117a228c3d Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:01:01 -0800 Subject: [PATCH 1/5] view_hand to bevy hand children logic --- jong/src/game.rs | 76 +++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/jong/src/game.rs b/jong/src/game.rs index c45ee3b..128b97b 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -9,7 +9,8 @@ use spacetimedb::Identity; use spacetimedb_sdk::{DbContext, Table, credentials}; use crate::{ - SpacetimeDB, creds_store, game, + SpacetimeDB, creds_store, + game::{self, player::Player}, stdb::{ self, DbConnection, HandTableAccess, LobbyTableAccess, PlayerTableAccess, RemoteTables, ViewPlayerHandTableAccess, add_bot, join_or_create_lobby, login_or_add_player, @@ -85,22 +86,10 @@ impl Plugin for Riichi { .add_systems(Update, on_lobby_insert_update) .add_systems(OnEnter(GameState::Setup), join_or_create_lobby) .add_systems(Update, view_hand.run_if(in_state(GameState::Play))) - .add_systems(Update, (view_hand).run_if(in_state(GameState::Play))) // semicolon stopper ; } } - -fn view_hand( - stdb: SpacetimeDB, - mut commands: Commands, - // hand: Populated<&mut Children, With>, -) { - if let Some(view) = stdb.db().view_player_hand().iter().next() { - // hand.get_mut(entity) - } -} - fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage, mut commands: Commands) { for msg in messages.read() { info!("you're now jongline"); @@ -138,9 +127,26 @@ fn on_player_insert_update( mut messages: ReadInsertUpdateMessage, mut commands: Commands, + mut player: Option>, ) { + use player::*; + for msg in messages.read() { debug!("player_insert_update msg:\n{:#?}", msg.new); + if let Some(ref player) = player { + // player.as_mut() = msg.new + } else { + let player = Player { + name: msg + .new + .name + .as_ref() + .unwrap_or(&"nameless".to_string()) + .clone(), + }; + let bundle = (player, Hand, Pond, MainPlayer, CurrentPlayer); + commands.spawn(bundle); + } } } @@ -198,20 +204,6 @@ fn on_lobby_insert_update( } stdb::GameState::Play => { trace!("game entered play"); - - // TODO this should run once to spawn the hand? or do elsewhere - if let Some(hand) = stdb.db().view_player_hand().iter().next() { - debug!("hand: {hand:?}"); - let tiles = hand - .tiles - .iter() - .map(Into::into) - .map(|t: Tile| commands.spawn(t).id()) - .collect::>(); - commands.spawn(Hand).add_children(&tiles); - } else { - error!("entered play without a hand") - } } stdb::GameState::Exit => { trace!("game enetered exit"); @@ -219,3 +211,33 @@ fn on_lobby_insert_update( } } } + +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() { + debug!("view_hand: {view:?}"); + + 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); + } +} From 5497872aff0a5a218e0c20d56661df223724525f Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:29:22 -0800 Subject: [PATCH 2/5] begin FOURTH time lmao --- jong/src/game.rs | 4 +--- jong/src/tui.rs | 54 +++++++++++++++++++++--------------------- jong/src/tui/render.rs | 19 +++++++++++++++ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/jong/src/game.rs b/jong/src/game.rs index 128b97b..e72112c 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -219,10 +219,8 @@ fn view_hand( tiles: Query<(&Tile, Entity)>, hand_ent: Single>, ) { - trace!("view_hand"); + // trace!("view_hand"); if let Some(view) = stdb.db().view_player_hand().iter().next() { - debug!("view_hand: {view:?}"); - let mut view = view.tiles.iter().map(Tile::from).collect::>(); let tiles = tiles diff --git a/jong/src/tui.rs b/jong/src/tui.rs index b0d60d4..3c2bade 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -26,6 +26,32 @@ pub enum TuiSet { Render, } +mod states { + use bevy::prelude::*; + use tui_logger::TuiWidgetState; + + #[derive(Resource)] + pub(crate) struct ConsoleWidget { + pub(crate) state: TuiWidgetState, + pub(crate) open: bool, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)] + pub(crate) enum TuiState { + #[default] + MainMenu, + InGame, + } + + // #[derive(SubStates, Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] + // #[source(TuiState = TuiState::MainMenu)] + // pub(crate) enum ZenState { + // #[default] + // Menu, + // Zen, + // } +} + impl Plugin for TuiPlugin { fn build(&self, app: &mut App) { app.add_plugins(( @@ -60,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() @@ -95,29 +121,3 @@ fn discard_tile( writer.write(GameMessage::Discarded(message.0)); } } - -mod states { - use bevy::prelude::*; - use tui_logger::TuiWidgetState; - - #[derive(Resource)] - pub(crate) struct ConsoleWidget { - pub(crate) state: TuiWidgetState, - pub(crate) open: bool, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)] - pub(crate) enum TuiState { - #[default] - MainMenu, - InGame, - } - - // #[derive(SubStates, Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] - // #[source(TuiState = TuiState::MainMenu)] - // pub(crate) enum ZenState { - // #[default] - // Menu, - // Zen, - // } -} diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index a81588d..0fd0ced 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -95,6 +95,23 @@ pub(crate) fn render( Ok(()) } +pub(crate) fn render_arg_check( + mut commands: Commands, + mut tui: ResMut, + + 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!"); +} + #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn render_hands( mut commands: Commands, @@ -110,6 +127,8 @@ pub(crate) fn render_hands( hands: Query<(&Children, Entity), (With, Without)>, drawn_tile: Single>, ) -> Result { + trace!("render_hands"); + let mut frame = tui.get_frame(); debug_blocks(*layouts, &mut frame); From f6361b9fa1c0dd5e2272f99381f531d52b57240a Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:30:45 -0800 Subject: [PATCH 3/5] lspmux --- .helix/config.toml | 3 +-- .helix/languages.toml | 6 ++++++ devenv.nix | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .helix/languages.toml diff --git a/.helix/config.toml b/.helix/config.toml index 07545b2..8b13789 100644 --- a/.helix/config.toml +++ b/.helix/config.toml @@ -1,2 +1 @@ -[editor] -workspace-lsp-roots = ["jongline"] + diff --git a/.helix/languages.toml b/.helix/languages.toml new file mode 100644 index 0000000..a35a590 --- /dev/null +++ b/.helix/languages.toml @@ -0,0 +1,6 @@ +[language-server.lspmux] +command = "lspmux" + +[[language]] +name = "rust" +language-servers = ["lspmux"] diff --git a/devenv.nix b/devenv.nix index 89cd3ec..e1f7c55 100644 --- a/devenv.nix +++ b/devenv.nix @@ -4,6 +4,7 @@ ... }: rec { # https://devenv.sh/processes/ + processes.lspmux.exec = "lspmux server"; processes.spacetimedb_start.exec = "spacetime start"; processes.spacetimedb_generate_bindings = { exec = "spacetime dev --module-bindings-path jong/src/stdb jongline --delete-data=always"; @@ -17,7 +18,7 @@ # https://devenv.sh/packages/ packages = with pkgs; [ - # process-compose + lspmux pkg-config # spacetimedb From 1e6a3ca84b9039ce22b7dc3a4d49b196e0af55bb Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:40:13 -0800 Subject: [PATCH 4/5] turn_state logic --- jong-types/src/lib.rs | 23 ++++++++++++++++++++++- jong/src/game.rs | 4 ++-- jong/src/game/round.rs | 13 +------------ jong/src/stdb/lobby_table.rs | 1 + jong/src/stdb/lobby_type.rs | 2 ++ jong/src/stdb/mod.rs | 2 ++ jong/src/stdb/turn_state_type.rs | 28 ++++++++++++++++++++++++++++ spacetimedb/src/game.rs | 1 + spacetimedb/src/game/wall.rs | 2 +- spacetimedb/src/tables.rs | 1 + 10 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 jong/src/stdb/turn_state_type.rs diff --git a/jong-types/src/lib.rs b/jong-types/src/lib.rs index bc2c1b4..dadb0b0 100644 --- a/jong-types/src/lib.rs +++ b/jong-types/src/lib.rs @@ -11,7 +11,7 @@ use derive_aliases::derive; use bevy::prelude::*; use spacetimedb::SpacetimeType; -use strum::FromRepr; +use strum::{EnumCount, FromRepr}; #[derive(..Base, Hash, Default)] #[derive(States, SpacetimeType)] @@ -112,3 +112,24 @@ pub fn tiles() -> Vec { } tiles } + +#[derive( + Default, + ..Copy, + PartialEq, + Eq, + Hash, + Debug, +)] +#[derive(SubStates, FromRepr, EnumCount, SpacetimeType)] +#[source(GameState = GameState::Play)] +pub enum TurnState { + #[default] + None, + Tsumo, + Menzen, + RiichiKan, + Discard, + RonChiiPonKan, + End, +} diff --git a/jong/src/game.rs b/jong/src/game.rs index e72112c..d84c597 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -21,7 +21,7 @@ use crate::{ game::{ hand::{Hand, Pond}, player::{CurrentPlayer, MainPlayer}, - round::{TurnState, Wind}, + round::Wind, wall::Wall, }, tile::{self}, @@ -65,7 +65,7 @@ impl Plugin for Riichi { app.add_plugins(plugins) .init_state::() - // .add_sub_state::() + .add_sub_state::() // .init_resource::() // .init_resource::() .add_message::() diff --git a/jong/src/game/round.rs b/jong/src/game/round.rs index 9a76ac1..9953c57 100644 --- a/jong/src/game/round.rs +++ b/jong/src/game/round.rs @@ -12,6 +12,7 @@ use crate::{ wall::Wall, }, }; +use jong_types::TurnState; // #[derive(Resource)] // pub struct CurrentPlayer(pub Entity); @@ -48,18 +49,6 @@ pub enum WindRelation { Kamicha, } -#[derive(SubStates, Default, Clone, Copy, PartialEq, Eq, Hash, Debug, FromRepr, EnumCount)] -#[source(GameState = GameState::Play)] -pub(crate) enum TurnState { - #[default] - Tsumo, - Menzen, - RiichiKan, - Discard, - RonChiiPonKan, - End, -} - #[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum CallType { Skip, diff --git a/jong/src/stdb/lobby_table.rs b/jong/src/stdb/lobby_table.rs index ab1d881..98b9f62 100644 --- a/jong/src/stdb/lobby_table.rs +++ b/jong/src/stdb/lobby_table.rs @@ -5,6 +5,7 @@ use super::game_state_type::GameState; use super::lobby_type::Lobby; use super::player_or_bot_type::PlayerOrBot; +use super::turn_state_type::TurnState; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; /// Table handle for the table `lobby`. diff --git a/jong/src/stdb/lobby_type.rs b/jong/src/stdb/lobby_type.rs index 110a274..ce82250 100644 --- a/jong/src/stdb/lobby_type.rs +++ b/jong/src/stdb/lobby_type.rs @@ -6,6 +6,7 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; use super::game_state_type::GameState; use super::player_or_bot_type::PlayerOrBot; +use super::turn_state_type::TurnState; #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] @@ -14,6 +15,7 @@ pub struct Lobby { pub host_player_id: u32, pub players: Vec, pub game_state: GameState, + pub turn_state: TurnState, } impl __sdk::InModule for Lobby { diff --git a/jong/src/stdb/mod.rs b/jong/src/stdb/mod.rs index 6c87060..d46c734 100644 --- a/jong/src/stdb/mod.rs +++ b/jong/src/stdb/mod.rs @@ -26,6 +26,7 @@ pub mod rank_type; pub mod shuffle_deal_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; @@ -55,6 +56,7 @@ pub use rank_type::Rank; pub use shuffle_deal_reducer::{set_flags_for_shuffle_deal, shuffle_deal, ShuffleDealCallbackId}; 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; diff --git a/jong/src/stdb/turn_state_type.rs b/jong/src/stdb/turn_state_type.rs new file mode 100644 index 0000000..1bbd7d5 --- /dev/null +++ b/jong/src/stdb/turn_state_type.rs @@ -0,0 +1,28 @@ +// 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)] +#[derive(Copy, Eq, Hash)] +pub enum TurnState { + None, + + Tsumo, + + Menzen, + + RiichiKan, + + Discard, + + RonChiiPonKan, + + End, +} + +impl __sdk::InModule for TurnState { + type Module = super::RemoteModule; +} diff --git a/spacetimedb/src/game.rs b/spacetimedb/src/game.rs index 1266e64..b122bcf 100644 --- a/spacetimedb/src/game.rs +++ b/spacetimedb/src/game.rs @@ -23,6 +23,7 @@ pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<( host_player_id: player.id, players: vec![PlayerOrBot::Player { id: player.id }], game_state: GameState::Setup, + turn_state: TurnState::None, }); info!("created lobby: {:?}", lobby); diff --git a/spacetimedb/src/game/wall.rs b/spacetimedb/src/game/wall.rs index dca40d4..d46b92c 100644 --- a/spacetimedb/src/game/wall.rs +++ b/spacetimedb/src/game/wall.rs @@ -13,7 +13,6 @@ pub fn shuffle_deal(ctx: &ReducerContext, lobby_id: u32) { lobby.game_state = GameState::Deal; let mut lobby = ctx.db.lobby().id().update(lobby); - let tiles = new_shuffled_wall(ctx); ctx.db.wall().insert(Wall { // id: 0, @@ -24,6 +23,7 @@ pub fn shuffle_deal(ctx: &ReducerContext, lobby_id: u32) { deal_hands(ctx, lobby_id); lobby.game_state = GameState::Play; + lobby.turn_state = TurnState::Tsumo; ctx.db.lobby().id().update(lobby); } diff --git a/spacetimedb/src/tables.rs b/spacetimedb/src/tables.rs index c53daf7..e3a3928 100644 --- a/spacetimedb/src/tables.rs +++ b/spacetimedb/src/tables.rs @@ -50,6 +50,7 @@ pub struct Lobby { pub players: Vec, pub game_state: GameState, + pub turn_state: TurnState, } #[table(name = wall)] From d183f5d993f8b0e3b605fcdd45673dfdf53a86b2 Mon Sep 17 00:00:00 2001 From: Tao Tien <29749622+taotien@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:38:41 -0800 Subject: [PATCH 5/5] all clients trigger ready, then advance gamestate on last ready --- .helix/languages.toml | 10 +-- devenv.nix | 4 +- jong-types/src/lib.rs | 1 + jong/src/game.rs | 82 +++++++++++----------- jong/src/lib.rs | 4 +- jong/src/stdb/game_state_type.rs | 2 + jong/src/stdb/mod.rs | 22 ++++++ jong/src/stdb/player_type.rs | 1 + jong/src/stdb/set_ready_reducer.rs | 102 ++++++++++++++++++++++++++++ jong/src/stdb/start_game_reducer.rs | 100 +++++++++++++++++++++++++++ jong/src/tui/input/keyboard.rs | 13 ++-- jong/src/tui/render.rs | 2 +- justfile | 4 +- spacetimedb/src/game.rs | 35 ++++++++-- spacetimedb/src/game/wall.rs | 25 ++++--- spacetimedb/src/lib.rs | 8 +-- spacetimedb/src/tables.rs | 2 + states | 18 +++++ 18 files changed, 358 insertions(+), 77 deletions(-) create mode 100644 jong/src/stdb/set_ready_reducer.rs create mode 100644 jong/src/stdb/start_game_reducer.rs create mode 100644 states diff --git a/.helix/languages.toml b/.helix/languages.toml index a35a590..3b9df02 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -1,6 +1,6 @@ -[language-server.lspmux] -command = "lspmux" +# [language-server.lspmux] +# command = "lspmux" -[[language]] -name = "rust" -language-servers = ["lspmux"] +# [[language]] +# name = "rust" +# language-servers = ["lspmux"] diff --git a/devenv.nix b/devenv.nix index e1f7c55..656bae8 100644 --- a/devenv.nix +++ b/devenv.nix @@ -4,7 +4,7 @@ ... }: rec { # https://devenv.sh/processes/ - processes.lspmux.exec = "lspmux server"; + # processes.lspmux.exec = "lspmux server"; processes.spacetimedb_start.exec = "spacetime start"; processes.spacetimedb_generate_bindings = { exec = "spacetime dev --module-bindings-path jong/src/stdb jongline --delete-data=always"; @@ -18,7 +18,7 @@ # https://devenv.sh/packages/ packages = with pkgs; [ - lspmux + # lspmux pkg-config # spacetimedb diff --git a/jong-types/src/lib.rs b/jong-types/src/lib.rs index dadb0b0..4d38b2d 100644 --- a/jong-types/src/lib.rs +++ b/jong-types/src/lib.rs @@ -18,6 +18,7 @@ use strum::{EnumCount, FromRepr}; pub enum GameState { #[default] None, + Lobby, Setup, Deal, Play, diff --git a/jong/src/game.rs b/jong/src/game.rs index d84c597..cb4d61f 100644 --- a/jong/src/game.rs +++ b/jong/src/game.rs @@ -8,19 +8,17 @@ use bevy_spacetimedb::{ use spacetimedb::Identity; use spacetimedb_sdk::{DbContext, Table, credentials}; -use crate::{ - SpacetimeDB, creds_store, - game::{self, player::Player}, - stdb::{ - self, DbConnection, HandTableAccess, LobbyTableAccess, PlayerTableAccess, RemoteTables, - ViewPlayerHandTableAccess, add_bot, join_or_create_lobby, login_or_add_player, - shuffle_deal, - }, +use crate::stdb::{ + 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, game::{ + self, hand::{Hand, Pond}, - player::{CurrentPlayer, MainPlayer}, + player::{CurrentPlayer, MainPlayer, Player}, round::Wind, wall::Wall, }, @@ -84,7 +82,7 @@ impl Plugin for Riichi { .add_systems(Update, on_disconnect) .add_systems(Update, on_player_insert_update) .add_systems(Update, on_lobby_insert_update) - .add_systems(OnEnter(GameState::Setup), join_or_create_lobby) + // .add_systems(OnEnter(GameState::Lobby), join_or_create_lobby) .add_systems(Update, view_hand.run_if(in_state(GameState::Play))) // semicolon stopper ; @@ -134,7 +132,11 @@ fn on_player_insert_update( for msg in messages.read() { debug!("player_insert_update msg:\n{:#?}", msg.new); if let Some(ref player) = player { - // player.as_mut() = msg.new + // if msg.old.as_ref().is_some_and(|m| !m.ready) && msg.new.ready { + // trace!("entered ready"); + // // TODO add a start game button in the future + // stdb.reducers().start_game().unwrap(); + // } } else { let player = Player { name: msg @@ -150,20 +152,20 @@ fn on_player_insert_update( } } -fn join_or_create_lobby(stdb: SpacetimeDB) { - let player = stdb - .db() - .player() - .identity() - .find(&stdb.identity()) - .unwrap(); +// 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) - } -} +// 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, @@ -175,30 +177,32 @@ fn on_lobby_insert_update( for msg in messages.read() { trace!("on_lobby_insert_update msg:\n{:#?}", msg.new); + let player = stdb + .db() + .player() + .identity() + .find(&stdb.identity()) + .unwrap(); + next_gamestate.set(msg.new.game_state.into()); match msg.new.game_state { stdb::GameState::None => { trace!("game entered none"); } - stdb::GameState::Setup => { - trace!("game entered setup"); - - let player = stdb - .db() - .player() - .identity() - .find(&stdb.identity()) - .unwrap(); - - if player.lobby_id == msg.new.id { - stdb.reducers().shuffle_deal(player.lobby_id).unwrap(); + stdb::GameState::Lobby => { + trace!("game in lobby"); + if !player.ready { for _ in 0..3 { stdb.reducers().add_bot(player.lobby_id).unwrap(); } - } else { - error!("no player but game in setup") + stdb.reducers().set_ready(true).unwrap(); + stdb.reducers().start_game().unwrap(); } } + stdb::GameState::Setup => { + trace!("game entered setup"); + stdb.reducers().shuffle_deal(player.lobby_id).unwrap(); + } stdb::GameState::Deal => { trace!("game entered deal"); } @@ -209,6 +213,8 @@ fn on_lobby_insert_update( trace!("game enetered exit"); } } + + next_gamestate.set(msg.new.game_state.into()); } } diff --git a/jong/src/lib.rs b/jong/src/lib.rs index ac78c87..dc56646 100644 --- a/jong/src/lib.rs +++ b/jong/src/lib.rs @@ -5,11 +5,10 @@ use bevy_spacetimedb::StdbConnection; use spacetimedb_sdk::credentials; pub mod game; +pub mod stdb; pub mod tile; pub mod yakus; -mod stdb; - trait EnumNextCycle { fn next(&self) -> Self; } @@ -24,6 +23,7 @@ impl From for jong_types::GameState { fn from(value: stdb::GameState) -> Self { match value { stdb::GameState::None => Self::None, + stdb::GameState::Lobby => Self::Lobby, stdb::GameState::Setup => Self::Setup, stdb::GameState::Deal => Self::Deal, stdb::GameState::Play => Self::Play, diff --git a/jong/src/stdb/game_state_type.rs b/jong/src/stdb/game_state_type.rs index 2e26076..b8cafa4 100644 --- a/jong/src/stdb/game_state_type.rs +++ b/jong/src/stdb/game_state_type.rs @@ -10,6 +10,8 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; pub enum GameState { None, + Lobby, + Setup, Deal, diff --git a/jong/src/stdb/mod.rs b/jong/src/stdb/mod.rs index d46c734..356ab0c 100644 --- a/jong/src/stdb/mod.rs +++ b/jong/src/stdb/mod.rs @@ -23,7 +23,9 @@ 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; +pub mod start_game_reducer; pub mod suit_type; pub mod tile_type; pub mod turn_state_type; @@ -53,7 +55,9 @@ 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}; +pub use start_game_reducer::{set_flags_for_start_game, start_game, StartGameCallbackId}; pub use suit_type::Suit; pub use tile_type::Tile; pub use turn_state_type::TurnState; @@ -73,7 +77,9 @@ pub enum Reducer { AddBot { lobby_id: u32 }, JoinOrCreateLobby { lobby_id: u32 }, LoginOrAddPlayer, + SetReady { ready: bool }, ShuffleDeal { lobby_id: u32 }, + StartGame, } impl __sdk::InModule for Reducer { @@ -86,7 +92,9 @@ impl __sdk::Reducer for Reducer { Reducer::AddBot { .. } => "add_bot", Reducer::JoinOrCreateLobby { .. } => "join_or_create_lobby", Reducer::LoginOrAddPlayer => "login_or_add_player", + Reducer::SetReady { .. } => "set_ready", Reducer::ShuffleDeal { .. } => "shuffle_deal", + Reducer::StartGame => "start_game", _ => unreachable!(), } } @@ -108,6 +116,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { login_or_add_player_reducer::LoginOrAddPlayerArgs, >("login_or_add_player", &value.args)? .into()), + "set_ready" => Ok( + __sdk::parse_reducer_args::( + "set_ready", + &value.args, + )? + .into(), + ), "shuffle_deal" => Ok( __sdk::parse_reducer_args::( "shuffle_deal", @@ -115,6 +130,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { )? .into(), ), + "start_game" => Ok( + __sdk::parse_reducer_args::( + "start_game", + &value.args, + )? + .into(), + ), unknown => { Err( __sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo") diff --git a/jong/src/stdb/player_type.rs b/jong/src/stdb/player_type.rs index 0e651ec..311ac9b 100644 --- a/jong/src/stdb/player_type.rs +++ b/jong/src/stdb/player_type.rs @@ -13,6 +13,7 @@ pub struct Player { pub lobby_id: u32, pub hand_id: u32, pub pond_id: u32, + pub ready: bool, } impl __sdk::InModule for Player { diff --git a/jong/src/stdb/set_ready_reducer.rs b/jong/src/stdb/set_ready_reducer.rs new file mode 100644 index 0000000..bebd7cf --- /dev/null +++ b/jong/src/stdb/set_ready_reducer.rs @@ -0,0 +1,102 @@ +// 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 SetReadyArgs { + pub ready: bool, +} + +impl From for super::Reducer { + fn from(args: SetReadyArgs) -> Self { + Self::SetReady { ready: args.ready } + } +} + +impl __sdk::InModule for SetReadyArgs { + type Module = super::RemoteModule; +} + +pub struct SetReadyCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `set_ready`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait set_ready { + /// Request that the remote module invoke the reducer `set_ready` 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_set_ready`] callbacks. + fn set_ready(&self, ready: bool) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `set_ready`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`SetReadyCallbackId`] can be passed to [`Self::remove_on_set_ready`] + /// to cancel the callback. + fn on_set_ready( + &self, + callback: impl FnMut(&super::ReducerEventContext, &bool) + Send + 'static, + ) -> SetReadyCallbackId; + /// Cancel a callback previously registered by [`Self::on_set_ready`], + /// causing it not to run in the future. + fn remove_on_set_ready(&self, callback: SetReadyCallbackId); +} + +impl set_ready for super::RemoteReducers { + fn set_ready(&self, ready: bool) -> __sdk::Result<()> { + self.imp.call_reducer("set_ready", SetReadyArgs { ready }) + } + fn on_set_ready( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &bool) + Send + 'static, + ) -> SetReadyCallbackId { + SetReadyCallbackId(self.imp.on_reducer( + "set_ready", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::SetReady { ready }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, ready) + }), + )) + } + fn remove_on_set_ready(&self, callback: SetReadyCallbackId) { + self.imp.remove_on_reducer("set_ready", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `set_ready`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_set_ready { + /// Set the call-reducer flags for the reducer `set_ready` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn set_ready(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_set_ready for super::SetReducerFlags { + fn set_ready(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("set_ready", flags); + } +} diff --git a/jong/src/stdb/start_game_reducer.rs b/jong/src/stdb/start_game_reducer.rs new file mode 100644 index 0000000..5eb59c1 --- /dev/null +++ b/jong/src/stdb/start_game_reducer.rs @@ -0,0 +1,100 @@ +// 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 StartGameArgs {} + +impl From for super::Reducer { + fn from(args: StartGameArgs) -> Self { + Self::StartGame + } +} + +impl __sdk::InModule for StartGameArgs { + type Module = super::RemoteModule; +} + +pub struct StartGameCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `start_game`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait start_game { + /// Request that the remote module invoke the reducer `start_game` 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_start_game`] callbacks. + fn start_game(&self) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `start_game`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`StartGameCallbackId`] can be passed to [`Self::remove_on_start_game`] + /// to cancel the callback. + fn on_start_game( + &self, + callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> StartGameCallbackId; + /// Cancel a callback previously registered by [`Self::on_start_game`], + /// causing it not to run in the future. + fn remove_on_start_game(&self, callback: StartGameCallbackId); +} + +impl start_game for super::RemoteReducers { + fn start_game(&self) -> __sdk::Result<()> { + self.imp.call_reducer("start_game", StartGameArgs {}) + } + fn on_start_game( + &self, + mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> StartGameCallbackId { + StartGameCallbackId(self.imp.on_reducer( + "start_game", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::StartGame {}, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx) + }), + )) + } + fn remove_on_start_game(&self, callback: StartGameCallbackId) { + self.imp.remove_on_reducer("start_game", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `start_game`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_start_game { + /// Set the call-reducer flags for the reducer `start_game` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn start_game(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_start_game for super::SetReducerFlags { + fn start_game(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("start_game", flags); + } +} diff --git a/jong/src/tui/input/keyboard.rs b/jong/src/tui/input/keyboard.rs index d2415bc..722d6fb 100644 --- a/jong/src/tui/input/keyboard.rs +++ b/jong/src/tui/input/keyboard.rs @@ -1,15 +1,21 @@ use bevy::prelude::*; use bevy_ratatui::crossterm::event::KeyCode; use bevy_ratatui::event::KeyMessage; -// use ratatui::crossterm::event::KeyCode; +use jong::stdb::PlayerTableAccess; +use jong::stdb::join_or_create_lobby; +use jong::stdb::start_game; use tui_logger::TuiWidgetEvent; +use jong::SpacetimeDB; +use jong_types::GameState; + use crate::tui::layout::Overlays; use crate::tui::states::ConsoleWidget; use crate::tui::states::TuiState; -use jong_types::GameState; pub(crate) fn keyboard( + stdb: SpacetimeDB, + mut messages: MessageReader, mut overlays: ResMut, mut consolewidget: ResMut, @@ -17,7 +23,6 @@ pub(crate) fn keyboard( curr_gamestate: Res>, curr_tuistate: Res>, - mut next_gamestate: ResMut>, mut next_tuistate: ResMut>, ) { @@ -38,8 +43,8 @@ pub(crate) fn keyboard( match curr_tuistate.get() { TuiState::MainMenu => match key { KeyCode::Char('p') => { + stdb.reducers().join_or_create_lobby(0).unwrap(); next_tuistate.set(TuiState::InGame); - next_gamestate.set(GameState::Setup); } KeyCode::Char('z') => { // if let Some(ref curr_zenstate) = curr_zenstate { diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index 0fd0ced..42a0fd2 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -109,7 +109,7 @@ pub(crate) fn render_arg_check( hands: Query<(&Children, Entity), (With, Without)>, // drawn_tile: Single>, ) { - trace!("arg!"); + // trace!("arg!"); } #[allow(clippy::too_many_arguments, clippy::type_complexity)] 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 b122bcf..37a6a69 100644 --- a/spacetimedb/src/game.rs +++ b/spacetimedb/src/game.rs @@ -1,5 +1,5 @@ -use log::info; -use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; +use log::{info, trace}; +use spacetimedb::{ReducerContext, Table, reducer}; use crate::tables::*; use jong_types::*; @@ -22,7 +22,7 @@ pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<( id: 0, host_player_id: player.id, players: vec![PlayerOrBot::Player { id: player.id }], - game_state: GameState::Setup, + game_state: GameState::Lobby, turn_state: TurnState::None, }); info!("created lobby: {:?}", lobby); @@ -42,10 +42,10 @@ pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<( pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { if lobby_id == 0 { Err("cannot add a bot without a lobby".into()) - } else if let Some(lobby) = ctx.db.lobby().id().find(lobby_id) + } else if let Some(mut lobby) = ctx.db.lobby().id().find(lobby_id) && (ctx.db.player().lobby_id().filter(lobby_id).count() + ctx.db.bot().lobby_id().filter(lobby_id).count() - <= 4) + < 4) { let bot = ctx.db.bot().insert(Bot { id: 0, @@ -53,9 +53,34 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> { hand_id: 0, pond_id: 0, }); + lobby.players.push(PlayerOrBot::Bot { id: bot.id }); + ctx.db.lobby().id().update(lobby); info!("added bot {} to lobby {}", bot.id, lobby_id); Ok(()) } else { Err("lobby doesn't exist".into()) } } + +#[reducer] +pub fn set_ready(ctx: &ReducerContext, ready: bool) { + let mut player = ctx.db.player().identity().find(ctx.sender).unwrap(); + player.ready = ready; + + let player = ctx.db.player().identity().update(player); +} + +#[reducer] +pub fn start_game(ctx: &ReducerContext) { + let player = ctx.db.player().identity().find(ctx.sender).unwrap(); + if let Some(mut lobby) = ctx.db.lobby().host_player_id().find(player.id) + && lobby.players.len() == 4 + && lobby.players.iter().all(|p| match p { + PlayerOrBot::Player { id } => ctx.db.player().id().find(id).is_some_and(|p| p.ready), + PlayerOrBot::Bot { id } => ctx.db.bot().id().find(id).is_some(), + }) + { + lobby.game_state = GameState::Deal; + ctx.db.lobby().id().update(lobby); + } +} diff --git a/spacetimedb/src/game/wall.rs b/spacetimedb/src/game/wall.rs index d46b92c..f33c898 100644 --- a/spacetimedb/src/game/wall.rs +++ b/spacetimedb/src/game/wall.rs @@ -10,21 +10,20 @@ 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(); - lobby.game_state = GameState::Deal; - let mut 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, + lobby_id, + tiles, + }); - let tiles = new_shuffled_wall(ctx); - ctx.db.wall().insert(Wall { - // id: 0, - lobby_id, - tiles, - }); + deal_hands(ctx, lobby_id); - deal_hands(ctx, lobby_id); - - lobby.game_state = GameState::Play; - lobby.turn_state = TurnState::Tsumo; - ctx.db.lobby().id().update(lobby); + lobby.game_state = GameState::Play; + lobby.turn_state = TurnState::Tsumo; + ctx.db.lobby().id().update(lobby); + } } pub fn new_shuffled_wall(ctx: &ReducerContext) -> Vec { diff --git a/spacetimedb/src/lib.rs b/spacetimedb/src/lib.rs index e1669d6..505db51 100644 --- a/spacetimedb/src/lib.rs +++ b/spacetimedb/src/lib.rs @@ -1,10 +1,7 @@ -use log::{debug, info}; -use spacetimedb::{ - ReducerContext, Table, ViewContext, rand::seq::SliceRandom, reducer, table, view, -}; +use log::info; +use spacetimedb::{ReducerContext, Table, reducer}; use crate::tables::*; -use jong_types::*; mod game; mod tables; @@ -21,6 +18,7 @@ pub fn login_or_add_player(ctx: &ReducerContext) { lobby_id: 0, hand_id: 0, pond_id: 0, + ready: false, }) { info!("added player: {:?}", player); } else { diff --git a/spacetimedb/src/tables.rs b/spacetimedb/src/tables.rs index e3a3928..02efc8e 100644 --- a/spacetimedb/src/tables.rs +++ b/spacetimedb/src/tables.rs @@ -18,6 +18,8 @@ pub struct Player { pub lobby_id: u32, pub hand_id: u32, pub pond_id: u32, + + pub ready: bool, } #[table(name = bot)] diff --git a/states b/states new file mode 100644 index 0000000..13e0595 --- /dev/null +++ b/states @@ -0,0 +1,18 @@ +gamestate + + none + join_or_create_lobby() + + lobby + set_ready() + add_bot() + start_game() + + setup + shuffle_deal() + + deal + animations?? + + play + discards??