advance_game stuff

This commit is contained in:
Tao Tien 2026-02-28 20:40:55 -08:00
parent 151f7a3489
commit a39ad4cf7c
6 changed files with 95 additions and 79 deletions

View file

@ -4,19 +4,13 @@
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::game_timer_type::GameTimer;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct AdvanceGameArgs {
pub game_timer: GameTimer,
}
pub(super) struct AdvanceGameArgs {}
impl From<AdvanceGameArgs> for super::Reducer {
fn from(args: AdvanceGameArgs) -> Self {
Self::AdvanceGame {
game_timer: args.game_timer,
}
Self::AdvanceGame
}
}
@ -35,8 +29,8 @@ pub trait advance_game {
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`advance_game:advance_game_then`] to run a callback after the reducer completes.
fn advance_game(&self, game_timer: GameTimer) -> __sdk::Result<()> {
self.advance_game_then(game_timer, |_, _| {})
fn advance_game(&self) -> __sdk::Result<()> {
self.advance_game_then(|_, _| {})
}
/// Request that the remote module invoke the reducer `advance_game` to run as soon as possible,
@ -47,7 +41,6 @@ pub trait advance_game {
/// and its status can be observed with the `callback`.
fn advance_game_then(
&self,
game_timer: GameTimer,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
@ -58,13 +51,12 @@ pub trait advance_game {
impl advance_game for super::RemoteReducers {
fn advance_game_then(
&self,
game_timer: GameTimer,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(AdvanceGameArgs { game_timer }, callback)
.invoke_reducer_with_callback(AdvanceGameArgs {}, callback)
}
}

View file

@ -77,7 +77,7 @@ pub use wind_type::Wind;
pub enum Reducer {
AddBot { lobby_id: u32 },
AdvanceGame { game_timer: GameTimer },
AdvanceGame,
ClearAll,
DiscardTile { tile_id: u32 },
JoinOrCreateLobby { lobby_id: u32 },
@ -92,7 +92,7 @@ impl __sdk::Reducer for Reducer {
fn reducer_name(&self) -> &'static str {
match self {
Reducer::AddBot { .. } => "add_bot",
Reducer::AdvanceGame { .. } => "advance_game",
Reducer::AdvanceGame => "advance_game",
Reducer::ClearAll => "clear_all",
Reducer::DiscardTile { .. } => "discard_tile",
Reducer::JoinOrCreateLobby { .. } => "join_or_create_lobby",
@ -106,10 +106,8 @@ impl __sdk::Reducer for Reducer {
Reducer::AddBot { lobby_id } => __sats::bsatn::to_vec(&add_bot_reducer::AddBotArgs {
lobby_id: lobby_id.clone(),
}),
Reducer::AdvanceGame { game_timer } => {
__sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs {
game_timer: game_timer.clone(),
})
Reducer::AdvanceGame => {
__sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs {})
}
Reducer::ClearAll => __sats::bsatn::to_vec(&clear_all_reducer::ClearAllArgs {}),
Reducer::DiscardTile { tile_id } => {

View file

@ -15,22 +15,25 @@ mod hand;
mod lobby;
#[reducer]
pub fn advance_game(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> {
pub fn advance_game(ctx: &ReducerContext) -> Result<(), String> {
let game_timer = ctx
.db
.game_timer()
.lobby_id()
.find(
ctx.db
.player()
.identity()
.find(ctx.sender())
.ok_or("player not in lobby")?
.lobby_id,
)
.ok_or("no such lobby")?;
advance_game_private(ctx, game_timer)
}
#[reducer]
pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> {
// checks every second (or more? when users make moves) on whether to advance the game's various states
// TODO this, or allow player/debug to call this?
if let Some(mut lobby) = ctx.db.lobby().id().find(game_timer.lobby_id) {
// trace!("running schedule for lobby {}", lobby.id);
match lobby.game_state {
GameState::Setup => {
// TODO reduce interval beforehand so we don't wait a second?
// TODO keep a count to clear stale lobbies
trace!("shuffle wall");
fn shuffle_wall(ctx: &ReducerContext, lobby: &mut Lobby) {
let tiles = {
let mut rng = ctx.rng();
let mut wall: Vec<_> = jong_types::tiles::tiles()
@ -46,20 +49,16 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
tiles,
});
lobby.game_state = GameState::Deal;
}
GameState::Deal => {
// TODO reduce interval beforehand so this can animate?
// TODO change loop to be per interval somehow?
// trace!("deal hands");
}
fn deal_hands(ctx: &ReducerContext, lobby: &mut Lobby) -> Result<(), String> {
let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap();
for pob in &lobby.players {
let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13);
wall = ctx.db.wall().lobby_id().update(wall);
tiles.sort_by_key(|t| t.tile);
match pob {
PlayerOrBot::Player { id }
if let Some(p) = ctx.db.player().id().find(id) =>
{
PlayerOrBot::Player { id } if let Some(p) = ctx.db.player().id().find(id) => {
ctx.db.player_hand().insert(PlayerHand {
id: 0,
player_id: p.id,
@ -83,7 +82,34 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
}
}
lobby.game_state = jong_types::states::GameState::Play;
// trace!("dealt hands");
Ok(())
}
#[reducer]
pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> {
// checks every second (or more? when users make moves) on whether to advance the game's various states
// TODO this, or allow player/debug to call this?
if let Some(mut lobby) = ctx.db.lobby().id().find(game_timer.lobby_id) {
// trace!("running schedule for lobby {}", lobby.id);
match lobby.game_state {
GameState::Setup => {
// TODO reduce interval beforehand so we don't wait a second?
// TODO keep a count to clear stale lobbies
// trace!("shuffle wall");
shuffle_wall(ctx, &mut lobby);
ctx.db.lobby().id().update(lobby);
advance_game_private(ctx, game_timer)?;
return Ok(());
}
GameState::Deal => {
// TODO reduce interval beforehand so this can animate?
// TODO change loop to be per interval somehow?
deal_hands(ctx, &mut lobby)?;
ctx.db.lobby().id().update(lobby);
advance_game_private(ctx, game_timer)?;
return Ok(());
}
GameState::Play => {
// trace!("in play");

View file

@ -5,7 +5,7 @@ use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer};
use jong_types::PlayerOrBot;
use crate::tables::*;
use crate::{reducers::advance_game_private, tables::*};
#[reducer]
pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<(), String> {
@ -87,11 +87,13 @@ pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> {
let lobby = ctx.db.lobby().id().update(lobby);
// TODO should we schedule this outside so that we can clear out stale lobbies?
ctx.db.game_timer().insert(GameTimer {
let game_timer = ctx.db.game_timer().insert(GameTimer {
id: 0,
lobby_id: lobby.id,
scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()),
});
advance_game_private(ctx, game_timer)?;
} else {
// if lobby doesn't exist, reset player state
player.lobby_id = 0;

View file

@ -4,7 +4,7 @@ use bevy_spacetimedb::{
ReadStdbDisconnectedMessage, ReadUpdateMessage, StdbPlugin,
};
use jong_db::{self, GameTimerTableAccess, add_bot, set_ready};
use jong_db::{self, GameTimerTableAccess, add_bot, advance_game, set_ready};
use jong_db::{
BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables,
ViewClosedHandsTableAccess, ViewHandTableAccess,
@ -25,7 +25,6 @@ impl Plugin for Riichi {
.with_run_fn(DbConnection::run_threaded)
.add_table(RemoteTables::player)
.add_table(RemoteTables::lobby)
.add_table(RemoteTables::game_timer)
// TODO check bevy_spacetimedb PR status
.add_view_with_pk(RemoteTables::view_hand, |p| p.id)
.add_view_with_pk(RemoteTables::view_closed_hands, |p| {
@ -102,7 +101,6 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) {
"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(),
"SELECT g.* FROM game_timer g JOIN player p ON g.lobby_id = p.lobby_id".to_string(),
]);
while let Ok(event) = recv.recv() {
@ -278,6 +276,7 @@ fn on_lobby_insert_update(
stdb.reducers().add_bot(player.lobby_id).unwrap();
}
stdb.reducers().set_ready(true).unwrap();
// stdb.reducers().advance_game().unwrap();
}
}
jong_db::GameState::Setup => {

View file

@ -106,14 +106,13 @@ fn discard_tile(
drawn: Single<(Entity, &TileId), With<Drawn>>,
tiles: Query<&TileId>,
) {
// FIXME why is this not consuming the messages?
// FIXME why is this not consuming the messages? or is it just getting updated too frequently?
// TODO disable this when we're not current player?
while let Some(message) = selected.read().next() {
trace!("{message:?}");
if let Ok(tile_id) = tiles.get(message.0) {
stdb.reducers().discard_tile(tile_id.0).unwrap();
stdb.reducers()
.advance_game(stdb.db().game_timer().iter().next().unwrap())
.unwrap();
stdb.reducers().advance_game().unwrap();
commands.entity(drawn.0).remove::<Drawn>();
}
}