Compare commits
5 commits
deabee9466
...
7ffef5522b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ffef5522b | ||
|
|
71ec40ee29 | ||
|
|
a39ad4cf7c | ||
|
|
151f7a3489 | ||
|
|
edd389c787 |
12 changed files with 384 additions and 356 deletions
|
|
@ -4,19 +4,13 @@
|
||||||
#![allow(unused, clippy::all)]
|
#![allow(unused, clippy::all)]
|
||||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
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)]
|
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||||
#[sats(crate = __lib)]
|
#[sats(crate = __lib)]
|
||||||
pub(super) struct AdvanceGameArgs {
|
pub(super) struct AdvanceGameArgs {}
|
||||||
pub game_timer: GameTimer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AdvanceGameArgs> for super::Reducer {
|
impl From<AdvanceGameArgs> for super::Reducer {
|
||||||
fn from(args: AdvanceGameArgs) -> Self {
|
fn from(args: AdvanceGameArgs) -> Self {
|
||||||
Self::AdvanceGame {
|
Self::AdvanceGame
|
||||||
game_timer: args.game_timer,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,8 +29,8 @@ pub trait advance_game {
|
||||||
/// The reducer will run asynchronously in the future,
|
/// The reducer will run asynchronously in the future,
|
||||||
/// and this method provides no way to listen for its completion status.
|
/// 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.
|
/// /// Use [`advance_game:advance_game_then`] to run a callback after the reducer completes.
|
||||||
fn advance_game(&self, game_timer: GameTimer) -> __sdk::Result<()> {
|
fn advance_game(&self) -> __sdk::Result<()> {
|
||||||
self.advance_game_then(game_timer, |_, _| {})
|
self.advance_game_then(|_, _| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request that the remote module invoke the reducer `advance_game` to run as soon as possible,
|
/// 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`.
|
/// and its status can be observed with the `callback`.
|
||||||
fn advance_game_then(
|
fn advance_game_then(
|
||||||
&self,
|
&self,
|
||||||
game_timer: GameTimer,
|
|
||||||
|
|
||||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||||
+ Send
|
+ Send
|
||||||
|
|
@ -58,13 +51,12 @@ pub trait advance_game {
|
||||||
impl advance_game for super::RemoteReducers {
|
impl advance_game for super::RemoteReducers {
|
||||||
fn advance_game_then(
|
fn advance_game_then(
|
||||||
&self,
|
&self,
|
||||||
game_timer: GameTimer,
|
|
||||||
|
|
||||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||||
+ Send
|
+ Send
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> __sdk::Result<()> {
|
) -> __sdk::Result<()> {
|
||||||
self.imp
|
self.imp
|
||||||
.invoke_reducer_with_callback(AdvanceGameArgs { game_timer }, callback)
|
.invoke_reducer_with_callback(AdvanceGameArgs {}, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ pub use wind_type::Wind;
|
||||||
|
|
||||||
pub enum Reducer {
|
pub enum Reducer {
|
||||||
AddBot { lobby_id: u32 },
|
AddBot { lobby_id: u32 },
|
||||||
AdvanceGame { game_timer: GameTimer },
|
AdvanceGame,
|
||||||
ClearAll,
|
ClearAll,
|
||||||
DiscardTile { tile_id: u32 },
|
DiscardTile { tile_id: u32 },
|
||||||
JoinOrCreateLobby { lobby_id: u32 },
|
JoinOrCreateLobby { lobby_id: u32 },
|
||||||
|
|
@ -92,7 +92,7 @@ impl __sdk::Reducer for Reducer {
|
||||||
fn reducer_name(&self) -> &'static str {
|
fn reducer_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Reducer::AddBot { .. } => "add_bot",
|
Reducer::AddBot { .. } => "add_bot",
|
||||||
Reducer::AdvanceGame { .. } => "advance_game",
|
Reducer::AdvanceGame => "advance_game",
|
||||||
Reducer::ClearAll => "clear_all",
|
Reducer::ClearAll => "clear_all",
|
||||||
Reducer::DiscardTile { .. } => "discard_tile",
|
Reducer::DiscardTile { .. } => "discard_tile",
|
||||||
Reducer::JoinOrCreateLobby { .. } => "join_or_create_lobby",
|
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 {
|
Reducer::AddBot { lobby_id } => __sats::bsatn::to_vec(&add_bot_reducer::AddBotArgs {
|
||||||
lobby_id: lobby_id.clone(),
|
lobby_id: lobby_id.clone(),
|
||||||
}),
|
}),
|
||||||
Reducer::AdvanceGame { game_timer } => {
|
Reducer::AdvanceGame => {
|
||||||
__sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs {
|
__sats::bsatn::to_vec(&advance_game_reducer::AdvanceGameArgs {})
|
||||||
game_timer: game_timer.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Reducer::ClearAll => __sats::bsatn::to_vec(&clear_all_reducer::ClearAllArgs {}),
|
Reducer::ClearAll => __sats::bsatn::to_vec(&clear_all_reducer::ClearAllArgs {}),
|
||||||
Reducer::DiscardTile { tile_id } => {
|
Reducer::DiscardTile { tile_id } => {
|
||||||
|
|
|
||||||
|
|
@ -15,82 +15,108 @@ mod hand;
|
||||||
mod lobby;
|
mod lobby;
|
||||||
|
|
||||||
#[reducer]
|
#[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)
|
advance_game_private(ctx, game_timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shuffle_wall(ctx: &ReducerContext, lobby: &mut Lobby) {
|
||||||
|
let tiles = {
|
||||||
|
let mut rng = ctx.rng();
|
||||||
|
let mut wall: Vec<_> = jong_types::tiles::tiles()
|
||||||
|
.into_iter()
|
||||||
|
.map(|tile| ctx.db.tile().insert(DbTile { id: 0, tile }))
|
||||||
|
.collect();
|
||||||
|
wall.shuffle(&mut rng);
|
||||||
|
wall
|
||||||
|
};
|
||||||
|
ctx.db.wall().insert(DbWall {
|
||||||
|
// id: 0,
|
||||||
|
lobby_id: lobby.id,
|
||||||
|
tiles,
|
||||||
|
});
|
||||||
|
lobby.game_state = GameState::Deal;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
ctx.db.player_hand().insert(PlayerHand {
|
||||||
|
id: 0,
|
||||||
|
player_id: p.id,
|
||||||
|
turn_state: jong_types::TurnState::None,
|
||||||
|
pond: vec![],
|
||||||
|
hand: tiles,
|
||||||
|
working_tile: None,
|
||||||
|
});
|
||||||
|
ctx.db.player_clock().insert(PlayerClock {
|
||||||
|
id: 0,
|
||||||
|
player_id: p.id,
|
||||||
|
renewable: 5,
|
||||||
|
total: 30,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PlayerOrBot::Bot { id } if let Some(mut b) = ctx.db.bot().id().find(id) => {
|
||||||
|
b.hand = tiles;
|
||||||
|
ctx.db.bot().id().update(b);
|
||||||
|
}
|
||||||
|
_ => Err("couldn't find player or bot".to_string())?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lobby.game_state = jong_types::states::GameState::Play;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[reducer]
|
#[reducer]
|
||||||
pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> {
|
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
|
// 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?
|
// TODO this, or allow player/debug to call this?
|
||||||
|
|
||||||
if let Some(mut lobby) = ctx.db.lobby().id().find(game_timer.lobby_id) {
|
if let Some(mut lobby) = ctx.db.lobby().id().find(game_timer.lobby_id) {
|
||||||
trace!("running schedule for lobby {}", lobby.id);
|
// trace!("running schedule for lobby {}", lobby.id);
|
||||||
match lobby.game_state {
|
match lobby.game_state {
|
||||||
GameState::Setup => {
|
GameState::Setup => {
|
||||||
// TODO reduce interval beforehand so we don't wait a second?
|
// TODO reduce interval beforehand so we don't wait a second?
|
||||||
// TODO keep a count to clear stale lobbies
|
// TODO keep a count to clear stale lobbies
|
||||||
trace!("shuffle wall");
|
// trace!("shuffle wall");
|
||||||
let tiles = {
|
shuffle_wall(ctx, &mut lobby);
|
||||||
let mut rng = ctx.rng();
|
ctx.db.lobby().id().update(lobby);
|
||||||
let mut wall: Vec<_> = jong_types::tiles::tiles()
|
advance_game_private(ctx, game_timer)?;
|
||||||
.into_iter()
|
return Ok(());
|
||||||
.map(|tile| ctx.db.tile().insert(DbTile { id: 0, tile }))
|
|
||||||
.collect();
|
|
||||||
wall.shuffle(&mut rng);
|
|
||||||
wall
|
|
||||||
};
|
|
||||||
ctx.db.wall().insert(DbWall {
|
|
||||||
// id: 0,
|
|
||||||
lobby_id: lobby.id,
|
|
||||||
tiles,
|
|
||||||
});
|
|
||||||
lobby.game_state = GameState::Deal;
|
|
||||||
}
|
}
|
||||||
GameState::Deal => {
|
GameState::Deal => {
|
||||||
// TODO reduce interval beforehand so this can animate?
|
// TODO reduce interval beforehand so this can animate?
|
||||||
// TODO change loop to be per interval somehow?
|
// TODO change loop to be per interval somehow?
|
||||||
trace!("deal hands");
|
deal_hands(ctx, &mut lobby)?;
|
||||||
let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap();
|
ctx.db.lobby().id().update(lobby);
|
||||||
for pob in &lobby.players {
|
advance_game_private(ctx, game_timer)?;
|
||||||
let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13);
|
return Ok(());
|
||||||
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) =>
|
|
||||||
{
|
|
||||||
ctx.db.player_hand().insert(PlayerHand {
|
|
||||||
id: 0,
|
|
||||||
player_id: p.id,
|
|
||||||
turn_state: jong_types::TurnState::None,
|
|
||||||
pond: vec![],
|
|
||||||
hand: tiles,
|
|
||||||
working_tile: None,
|
|
||||||
});
|
|
||||||
ctx.db.player_clock().insert(PlayerClock {
|
|
||||||
id: 0,
|
|
||||||
player_id: p.id,
|
|
||||||
renewable: 5,
|
|
||||||
total: 30,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
PlayerOrBot::Bot { id } if let Some(mut b) = ctx.db.bot().id().find(id) => {
|
|
||||||
b.hand = tiles;
|
|
||||||
ctx.db.bot().id().update(b);
|
|
||||||
}
|
|
||||||
_ => Err("couldn't find player or bot".to_string())?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lobby.game_state = jong_types::states::GameState::Play;
|
|
||||||
trace!("dealt hands");
|
|
||||||
}
|
}
|
||||||
GameState::Play => {
|
GameState::Play => {
|
||||||
trace!("in play");
|
// trace!("in play");
|
||||||
let curr_player = lobby.players.get(lobby.current_idx as usize).unwrap();
|
let curr_player = lobby.players.get(lobby.current_idx as usize).unwrap();
|
||||||
match curr_player {
|
match curr_player {
|
||||||
PlayerOrBot::Player { id: player_id } => {
|
PlayerOrBot::Player { id: player_id } => {
|
||||||
trace!("current player is {player_id}");
|
// trace!("current player is {player_id}");
|
||||||
let mut clock = ctx.db.player_clock().player_id().find(player_id).unwrap();
|
let mut clock = ctx.db.player_clock().player_id().find(player_id).unwrap();
|
||||||
let mut hand = ctx.db.player_hand().player_id().find(player_id).unwrap();
|
let mut hand = ctx.db.player_hand().player_id().find(player_id).unwrap();
|
||||||
match hand.turn_state {
|
match hand.turn_state {
|
||||||
|
|
@ -109,7 +135,7 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TurnState::Tsumo => {
|
TurnState::Tsumo => {
|
||||||
trace!("wait for discard");
|
// trace!("wait for discard");
|
||||||
if clock.tick() {
|
if clock.tick() {
|
||||||
ctx.db.player_clock().id().update(clock);
|
ctx.db.player_clock().id().update(clock);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer};
|
||||||
|
|
||||||
use jong_types::PlayerOrBot;
|
use jong_types::PlayerOrBot;
|
||||||
|
|
||||||
use crate::tables::*;
|
use crate::{reducers::advance_game_private, tables::*};
|
||||||
|
|
||||||
#[reducer]
|
#[reducer]
|
||||||
pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<(), String> {
|
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);
|
let lobby = ctx.db.lobby().id().update(lobby);
|
||||||
|
|
||||||
// TODO should we schedule this outside so that we can clear out stale lobbies?
|
// 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,
|
id: 0,
|
||||||
lobby_id: lobby.id,
|
lobby_id: lobby.id,
|
||||||
scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()),
|
scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
advance_game_private(ctx, game_timer)?;
|
||||||
} else {
|
} else {
|
||||||
// if lobby doesn't exist, reset player state
|
// if lobby doesn't exist, reset player state
|
||||||
player.lobby_id = 0;
|
player.lobby_id = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_spacetimedb::{
|
use bevy_spacetimedb::{ReadInsertMessage, ReadInsertUpdateMessage, ReadUpdateMessage, StdbPlugin};
|
||||||
ReadInsertUpdateMessage, ReadStdbConnectedMessage, ReadStdbDisconnectedMessage,
|
|
||||||
ReadUpdateMessage, StdbPlugin,
|
|
||||||
};
|
|
||||||
|
|
||||||
use jong_db::{self, GameTimerTableAccess, add_bot, set_ready};
|
use jong_db::{
|
||||||
|
self, GameTimerTableAccess, PlayerClockTableAccess, add_bot, advance_game, set_ready,
|
||||||
|
};
|
||||||
use jong_db::{
|
use jong_db::{
|
||||||
BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables,
|
BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables,
|
||||||
ViewClosedHandsTableAccess, ViewHandTableAccess,
|
ViewClosedHandsTableAccess, ViewHandTableAccess,
|
||||||
|
|
@ -12,10 +11,10 @@ use jong_db::{
|
||||||
use jong_types::*;
|
use jong_types::*;
|
||||||
use spacetimedb_sdk::Table;
|
use spacetimedb_sdk::Table;
|
||||||
|
|
||||||
|
mod connection;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
use crate::riichi::player::*;
|
use crate::riichi::player::*;
|
||||||
use crate::{SpacetimeDB, creds_store};
|
use crate::{SpacetimeDB, creds_store};
|
||||||
// pub mod round;
|
|
||||||
|
|
||||||
pub struct Riichi;
|
pub struct Riichi;
|
||||||
impl Plugin for Riichi {
|
impl Plugin for Riichi {
|
||||||
|
|
@ -24,9 +23,10 @@ impl Plugin for Riichi {
|
||||||
.with_uri("http://localhost:3000")
|
.with_uri("http://localhost:3000")
|
||||||
.with_module_name("jong-line")
|
.with_module_name("jong-line")
|
||||||
.with_run_fn(DbConnection::run_threaded)
|
.with_run_fn(DbConnection::run_threaded)
|
||||||
.add_table(RemoteTables::player)
|
|
||||||
.add_table(RemoteTables::lobby)
|
.add_table(RemoteTables::lobby)
|
||||||
.add_table(RemoteTables::game_timer)
|
.add_table(RemoteTables::player)
|
||||||
|
.add_table(RemoteTables::bot)
|
||||||
|
.add_table(RemoteTables::player_clock)
|
||||||
// TODO check bevy_spacetimedb PR status
|
// 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| {
|
.add_view_with_pk(RemoteTables::view_closed_hands, |p| {
|
||||||
|
|
@ -43,50 +43,42 @@ impl Plugin for Riichi {
|
||||||
app.add_plugins(plugins)
|
app.add_plugins(plugins)
|
||||||
.init_state::<jong_types::states::GameState>()
|
.init_state::<jong_types::states::GameState>()
|
||||||
.add_sub_state::<jong_types::states::TurnState>()
|
.add_sub_state::<jong_types::states::TurnState>()
|
||||||
|
.add_message::<SyncPlayer>()
|
||||||
|
.add_message::<SyncOpenHand>()
|
||||||
.add_systems(Startup, subscriptions)
|
.add_systems(Startup, subscriptions)
|
||||||
.add_observer(on_subscribed)
|
.add_systems(Update, (connection::on_connect, connection::on_disconnect))
|
||||||
.add_systems(Update, (on_connect, on_disconnect))
|
|
||||||
.add_systems(Update, (on_lobby_insert_update, on_player_insert_update))
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(on_view_hand_update)
|
(
|
||||||
|
(on_player_insert_update, on_lobby_insert_update),
|
||||||
|
(sync_player),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
((on_view_hand_insert, on_view_hand_update), (sync_open_hand))
|
||||||
.run_if(in_state(GameState::Play).or(in_state(GameState::Deal))),
|
.run_if(in_state(GameState::Play).or(in_state(GameState::Deal))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage) {
|
/// on subscribe we need to check:
|
||||||
for msg in messages.read() {
|
/// if we're in a game already
|
||||||
info!("you're now jongline");
|
/// spawn (restore) all current game state
|
||||||
|
/// spawn all players and hands
|
||||||
// FIXME hack that doesn't work for startup crash?
|
/// else
|
||||||
while stdb.try_identity().is_none() {}
|
/// spawn self player
|
||||||
|
/// then
|
||||||
debug!("with identity: {}", stdb.identity());
|
/// wait for lobbies
|
||||||
creds_store()
|
/// spawn other players
|
||||||
.save(&msg.access_token)
|
/// spawn all hands and ponds
|
||||||
.expect("i/o error saving token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO how reconnect?
|
|
||||||
fn on_disconnect(_stdb: SpacetimeDB, mut messages: ReadStdbDisconnectedMessage) {
|
|
||||||
for msg in messages.read() {
|
|
||||||
warn!("lost connection: {:#?}", msg.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO we can make this hold more info in the future
|
|
||||||
#[derive(Event)]
|
|
||||||
struct Subscribed;
|
|
||||||
|
|
||||||
fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) {
|
fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) {
|
||||||
// commands.queue(command);
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let (send, recv) = std::sync::mpsc::channel::<Subscribed>();
|
|
||||||
stdb.subscription_builder()
|
stdb.subscription_builder()
|
||||||
.on_applied(move |_| {
|
.on_applied(move |_| {
|
||||||
trace!("subs succeeded");
|
trace!("subs succeeded");
|
||||||
send.send(Subscribed).unwrap();
|
tx.send(()).unwrap();
|
||||||
})
|
})
|
||||||
.on_error(|_, err| {
|
.on_error(|_, err| {
|
||||||
error!("subs failed: {err}");
|
error!("subs failed: {err}");
|
||||||
|
|
@ -97,160 +89,164 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) {
|
||||||
"SELECT p.* FROM player p WHERE p.identity = '{}'",
|
"SELECT p.* FROM player p WHERE p.identity = '{}'",
|
||||||
stdb.identity()
|
stdb.identity()
|
||||||
),
|
),
|
||||||
"SELECT p.* FROM player p JOIN lobby l ON p.lobby_id = l.id".to_string(),
|
// TODO add filter for lobby id for all of these later
|
||||||
"SELECT l.* FROM lobby l JOIN player p ON l.id = p.lobby_id".to_string(),
|
"SELECT l.* FROM lobby l JOIN player p ON l.id = p.lobby_id".to_string(),
|
||||||
|
"SELECT p.* FROM player p JOIN lobby l ON p.lobby_id = l.id".to_string(),
|
||||||
"SELECT c.* FROM player_clock c JOIN player p ON c.player_id = p.id".to_string(),
|
"SELECT c.* FROM player_clock c JOIN player p ON c.player_id = p.id".to_string(),
|
||||||
"SELECT b.* FROM bot b JOIN lobby l ON l.id = b.lobby_id".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_hand".to_string(),
|
||||||
"SELECT * FROM view_closed_hands".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() {
|
while let Ok(()) = rx.recv() {
|
||||||
commands.trigger(event);
|
// todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// spawns entities to be consistent with server state
|
#[derive(Message)]
|
||||||
// TODO figure out a way to call this for later changes in the various on_ins_upd systems
|
struct SyncPlayer(u32);
|
||||||
fn on_subscribed(
|
|
||||||
_event: On<Subscribed>,
|
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
struct SyncOpenHand(u32);
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
struct SyncClosedHand(PlayerOrBot);
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
struct SyncPlayerClock(u32);
|
||||||
|
|
||||||
|
fn sync_player(
|
||||||
stdb: SpacetimeDB,
|
stdb: SpacetimeDB,
|
||||||
|
|
||||||
|
mut messages: MessageReader<SyncPlayer>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut next_gamestate: ResMut<NextState<GameState>>,
|
|
||||||
|
players: Query<(Entity, &Player)>,
|
||||||
|
) {
|
||||||
|
for SyncPlayer(id) in messages.read() {
|
||||||
|
trace!("sync_player");
|
||||||
|
let Some(player) = stdb.db().player().id().find(id) else {
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let player_ent = players
|
||||||
|
.iter()
|
||||||
|
.find_map(|(e, p)| (p.id == PlayerOrBot::Player { id: player.id }).then_some(e))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
commands
|
||||||
|
.spawn(Player {
|
||||||
|
id: PlayerOrBot::Player { id: player.id },
|
||||||
|
})
|
||||||
|
.id()
|
||||||
|
});
|
||||||
|
|
||||||
|
if player.identity == stdb.identity() {
|
||||||
|
commands.entity(player_ent).insert(MainPlayer);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_open_hand(
|
||||||
|
stdb: SpacetimeDB,
|
||||||
|
|
||||||
|
mut messages: MessageReader<SyncOpenHand>,
|
||||||
|
mut commands: Commands,
|
||||||
|
|
||||||
|
tiles: Query<(Entity, &TileId)>,
|
||||||
|
hands: Query<(Entity, &Hand)>,
|
||||||
|
ponds: Query<(Entity, &Pond)>,
|
||||||
|
|
||||||
mut next_turnstate: ResMut<NextState<TurnState>>,
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
||||||
) {
|
) {
|
||||||
trace!("on_subscribed");
|
for SyncOpenHand(id) in messages.read() {
|
||||||
for player in stdb.db().player().iter() {
|
trace!("sync_open_hand");
|
||||||
if player.identity == stdb.identity() {
|
let Some(player_hand) = stdb.db().view_hand().iter().find(|hand| hand.id == *id) else {
|
||||||
// trace!("spawn_main_player");
|
todo!()
|
||||||
spawn_main_player(&stdb, &mut commands, &mut next_turnstate, &player);
|
};
|
||||||
} else {
|
|
||||||
// trace!("spawn_other_player");
|
|
||||||
spawn_other_player(&stdb, &mut commands, &player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for bot in stdb.db().bot().iter() {
|
let hand_ent = hands
|
||||||
let id = PlayerOrBot::Bot { id: bot.id };
|
|
||||||
let hand_view = stdb
|
|
||||||
.db()
|
|
||||||
.view_closed_hands()
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|v| PlayerOrBot::from(&v.player) == id)
|
.find_map(|(e, h)| (h.owner == PlayerOrBot::Player { id: *id }).then_some(e))
|
||||||
.unwrap();
|
.unwrap_or_else(|| {
|
||||||
let hand_ent = commands.spawn((Hand, Closed(hand_view.hand_length))).id();
|
commands
|
||||||
commands.spawn(Player { id }).add_child(hand_ent);
|
.spawn(Hand {
|
||||||
}
|
owner: PlayerOrBot::Player { id: *id },
|
||||||
|
})
|
||||||
|
.id()
|
||||||
|
});
|
||||||
|
let pond_ent = ponds
|
||||||
|
.iter()
|
||||||
|
.find_map(|(e, h)| (h.owner == PlayerOrBot::Player { id: *id }).then_some(e))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
commands
|
||||||
|
.spawn(Pond {
|
||||||
|
owner: PlayerOrBot::Player { id: *id },
|
||||||
|
})
|
||||||
|
.id()
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(lobby) = stdb.db().lobby().iter().next() {
|
// hand and pond both still need ability to spawn for the reconnect case
|
||||||
next_gamestate.set(lobby.game_state.into());
|
let hand: Vec<Entity> = player_hand
|
||||||
|
.hand
|
||||||
|
.iter()
|
||||||
|
.map(|dbt| {
|
||||||
|
tiles
|
||||||
|
.iter()
|
||||||
|
.find_map(|(e, i)| (i.0 == dbt.id).then_some(e))
|
||||||
|
.unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let pond: Vec<Entity> = player_hand
|
||||||
|
.pond
|
||||||
|
.iter()
|
||||||
|
.map(|dbt| {
|
||||||
|
tiles
|
||||||
|
.iter()
|
||||||
|
.find_map(|(e, i)| (i.0 == dbt.id).then_some(e))
|
||||||
|
.unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
commands.entity(hand_ent).replace_children(&hand);
|
||||||
|
commands.entity(pond_ent).replace_children(&pond);
|
||||||
|
|
||||||
|
if let Some(dbt) = player_hand.working_tile
|
||||||
|
&& player_hand.turn_state == jong_db::TurnState::Tsumo
|
||||||
|
{
|
||||||
|
commands.spawn((Drawn, Tile::from(&dbt.tile), TileId(dbt.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
next_turnstate.set(player_hand.turn_state.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_main_player(
|
fn sync_closed_hand(
|
||||||
stdb: &SpacetimeDB,
|
|
||||||
|
|
||||||
commands: &mut Commands,
|
|
||||||
next_turnstate: &mut ResMut<NextState<TurnState>>,
|
|
||||||
|
|
||||||
player: &jong_db::Player,
|
|
||||||
) {
|
|
||||||
// trace!("spawn_main_player");
|
|
||||||
let main_player = commands
|
|
||||||
.spawn((
|
|
||||||
Player {
|
|
||||||
id: PlayerOrBot::Player { id: player.id },
|
|
||||||
},
|
|
||||||
MainPlayer,
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
if let Some(player_hand) = stdb.db().view_hand().iter().next() {
|
|
||||||
spawn_main_hand(commands, next_turnstate, main_player, &player_hand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_main_hand(
|
|
||||||
commands: &mut Commands,
|
|
||||||
next_turnstate: &mut ResMut<NextState<TurnState>>,
|
|
||||||
main_player: Entity,
|
|
||||||
|
|
||||||
player_hand: &PlayerHand,
|
|
||||||
) {
|
|
||||||
let hand_tiles: Vec<_> = player_hand
|
|
||||||
.hand
|
|
||||||
.iter()
|
|
||||||
.map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
|
||||||
.collect();
|
|
||||||
let pond_tiles: Vec<_> = player_hand
|
|
||||||
.pond
|
|
||||||
.iter()
|
|
||||||
.map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
|
||||||
.collect();
|
|
||||||
let hand = commands.spawn(Hand).add_children(&hand_tiles).id();
|
|
||||||
let pond = commands.spawn(Pond).add_children(&pond_tiles).id();
|
|
||||||
commands.entity(main_player).add_children(&[hand, pond]);
|
|
||||||
|
|
||||||
debug!("main_hand: {:?}\n main_pond: {:?}", hand_tiles, pond_tiles);
|
|
||||||
|
|
||||||
if player_hand.turn_state == jong_db::TurnState::Tsumo
|
|
||||||
&& let Some(drawn_dbt) = &player_hand.working_tile
|
|
||||||
{
|
|
||||||
let drawn = commands
|
|
||||||
.spawn((Drawn, Tile::from(&drawn_dbt.tile), TileId(drawn_dbt.id)))
|
|
||||||
.id();
|
|
||||||
commands.entity(main_player).add_child(drawn);
|
|
||||||
}
|
|
||||||
next_turnstate.set(player_hand.turn_state.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_other_player(stdb: &SpacetimeDB, commands: &mut Commands, player: &jong_db::Player) {
|
|
||||||
let id = PlayerOrBot::Player { id: player.id };
|
|
||||||
if let Some(hand_view) = stdb
|
|
||||||
.db()
|
|
||||||
.view_closed_hands()
|
|
||||||
.iter()
|
|
||||||
.find(|v| PlayerOrBot::from(&v.player) == id)
|
|
||||||
{
|
|
||||||
let hand_ent = commands.spawn((Hand, Closed(hand_view.hand_length))).id();
|
|
||||||
commands.spawn(Player { id }).add_child(hand_ent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_player_insert_update(
|
|
||||||
stdb: SpacetimeDB,
|
stdb: SpacetimeDB,
|
||||||
mut messages: ReadInsertUpdateMessage<jong_db::Player>,
|
|
||||||
|
|
||||||
|
mut events: MessageReader<SyncClosedHand>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
|
||||||
main_player: Option<Single<&MainPlayer>>,
|
hands: Query<&mut Closed, With<Hand>>,
|
||||||
other_players: Query<&Player, Without<MainPlayer>>,
|
ponds: Query<&mut Children, With<Pond>>,
|
||||||
|
|
||||||
mut next_turnstate: ResMut<NextState<jong_types::states::TurnState>>,
|
|
||||||
) {
|
) {
|
||||||
for msg in messages.read() {
|
}
|
||||||
debug!("on_player_insert_update: {:?}", msg.new);
|
|
||||||
assert_eq!(msg.new.identity, stdb.identity());
|
fn sync_player_clock() {}
|
||||||
if main_player.is_none() && msg.new.identity == stdb.identity() {
|
|
||||||
// trace!("spawn_main_player");
|
fn on_player_insert_update(
|
||||||
spawn_main_player(&stdb, &mut commands, &mut next_turnstate, &msg.new);
|
mut db_messages: ReadInsertUpdateMessage<jong_db::Player>,
|
||||||
} else if other_players.iter().any(|p| {
|
|
||||||
if let PlayerOrBot::Player { id } = &p.id {
|
mut writer: MessageWriter<SyncPlayer>,
|
||||||
*id == msg.new.id
|
) {
|
||||||
} else {
|
for msg in db_messages.read() {
|
||||||
false
|
trace!("on_player_insert_update");
|
||||||
}
|
writer.write(SyncPlayer(msg.new.id));
|
||||||
}) {
|
|
||||||
trace!("spawn_other_player");
|
|
||||||
spawn_other_player(&stdb, &mut commands, &msg.new);
|
|
||||||
} else {
|
|
||||||
// TODO update case
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_bot_insert_update() {}
|
||||||
|
|
||||||
fn on_lobby_insert_update(
|
fn on_lobby_insert_update(
|
||||||
stdb: SpacetimeDB,
|
stdb: SpacetimeDB,
|
||||||
mut messages: ReadInsertUpdateMessage<jong_db::Lobby>,
|
mut messages: ReadInsertUpdateMessage<jong_db::Lobby>,
|
||||||
|
|
@ -268,7 +264,6 @@ fn on_lobby_insert_update(
|
||||||
.find(&stdb.identity())
|
.find(&stdb.identity())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
next_gamestate.set(msg.new.game_state.into());
|
|
||||||
match msg.new.game_state {
|
match msg.new.game_state {
|
||||||
jong_db::GameState::None => {
|
jong_db::GameState::None => {
|
||||||
trace!("game entered none");
|
trace!("game entered none");
|
||||||
|
|
@ -280,6 +275,7 @@ fn on_lobby_insert_update(
|
||||||
stdb.reducers().add_bot(player.lobby_id).unwrap();
|
stdb.reducers().add_bot(player.lobby_id).unwrap();
|
||||||
}
|
}
|
||||||
stdb.reducers().set_ready(true).unwrap();
|
stdb.reducers().set_ready(true).unwrap();
|
||||||
|
// stdb.reducers().advance_game().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jong_db::GameState::Setup => {
|
jong_db::GameState::Setup => {
|
||||||
|
|
@ -300,88 +296,84 @@ fn on_lobby_insert_update(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_view_hand_insert(
|
||||||
|
mut messages: ReadInsertMessage<jong_db::PlayerHand>,
|
||||||
|
mut writer: MessageWriter<SyncOpenHand>,
|
||||||
|
) {
|
||||||
|
for msg in messages.read() {
|
||||||
|
trace!("on_view_hand_insert");
|
||||||
|
writer.write(SyncOpenHand(msg.row.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_view_hand_update(
|
fn on_view_hand_update(
|
||||||
stdb: SpacetimeDB,
|
|
||||||
mut messages: ReadUpdateMessage<jong_db::PlayerHand>,
|
mut messages: ReadUpdateMessage<jong_db::PlayerHand>,
|
||||||
|
mut writer: MessageWriter<SyncOpenHand>,
|
||||||
|
// mut commands: Commands,
|
||||||
|
// tiles: Query<(Entity, &TileId)>,
|
||||||
|
|
||||||
mut commands: Commands,
|
// main_player: Single<(Entity, &Children), With<MainPlayer>>,
|
||||||
tiles: Query<(Entity, &TileId)>,
|
|
||||||
|
|
||||||
main_player: Single<(Entity, Option<&Children>), With<MainPlayer>>,
|
// hand: Query<Entity, With<Hand>>,
|
||||||
|
// pond: Query<Entity, With<Pond>>,
|
||||||
hand: Query<Entity, With<Hand>>,
|
// // drawn: Option<Single<Entity, With<Drawn>>>,
|
||||||
pond: Query<Entity, With<Pond>>,
|
// mut next_turnstate: ResMut<NextState<jong_types::states::TurnState>>,
|
||||||
// drawn: Option<Single<Entity, With<Drawn>>>,
|
|
||||||
mut next_turnstate: ResMut<NextState<jong_types::states::TurnState>>,
|
|
||||||
) {
|
) {
|
||||||
// TODO can this and similar run at startup or on play/reconnect?
|
// TODO can this and similar run at startup or on play/reconnect?
|
||||||
for msg in messages.read() {
|
for msg in messages.read() {
|
||||||
// trace!("new hand: {:?}", msg.new);
|
trace!("on_view_hand_update");
|
||||||
|
writer.write(SyncOpenHand(msg.new.player_id));
|
||||||
if main_player.1.is_none() {
|
|
||||||
// trace!("spawn_main_hand, {:?}", *main_player);
|
|
||||||
spawn_main_hand(&mut commands, &mut next_turnstate, main_player.0, &msg.new);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hand_tiles: Vec<_> = msg
|
|
||||||
.new
|
|
||||||
.hand
|
|
||||||
.iter()
|
|
||||||
.map(|dbt| {
|
|
||||||
tiles
|
|
||||||
.iter()
|
|
||||||
.find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None })
|
|
||||||
.unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let pond_tiles: Vec<_> = msg
|
|
||||||
.new
|
|
||||||
.pond
|
|
||||||
.iter()
|
|
||||||
.map(|dbt| {
|
|
||||||
tiles
|
|
||||||
.iter()
|
|
||||||
.find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None })
|
|
||||||
.unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
commands
|
|
||||||
.entity(
|
|
||||||
hand.iter()
|
|
||||||
.find(|e| main_player.1.is_some_and(|mp| mp.contains(e)))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.replace_children(&hand_tiles);
|
|
||||||
commands
|
|
||||||
.entity(
|
|
||||||
pond.iter()
|
|
||||||
.find(|e| main_player.1.is_some_and(|mp| mp.contains(e)))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.replace_children(&pond_tiles);
|
|
||||||
|
|
||||||
match msg.new.turn_state {
|
|
||||||
jong_db::TurnState::None => {
|
|
||||||
trace!("turnstate none");
|
|
||||||
// TODO do we reconcile hand state here or in ::End?
|
|
||||||
}
|
|
||||||
jong_db::TurnState::Tsumo => {
|
|
||||||
trace!("turnstate tsumo");
|
|
||||||
let dbt = msg
|
|
||||||
.new
|
|
||||||
.working_tile
|
|
||||||
.as_ref()
|
|
||||||
.expect("entered tsumo without a drawn tile");
|
|
||||||
commands.spawn((Drawn, Tile::from(&dbt.tile), TileId(dbt.id)));
|
|
||||||
}
|
|
||||||
jong_db::TurnState::Menzen => todo!(),
|
|
||||||
jong_db::TurnState::RiichiKan => todo!(),
|
|
||||||
jong_db::TurnState::RonChiiPonKan => todo!(),
|
|
||||||
jong_db::TurnState::End => todo!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
next_turnstate.set(msg.new.turn_state.into());
|
|
||||||
}
|
}
|
||||||
|
// let hand_tiles: Vec<_> = msg
|
||||||
|
// .new
|
||||||
|
// .hand
|
||||||
|
// .iter()
|
||||||
|
// .map(|dbt| {
|
||||||
|
// tiles
|
||||||
|
// .iter()
|
||||||
|
// .find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None })
|
||||||
|
// .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
||||||
|
// })
|
||||||
|
// .collect();
|
||||||
|
// let pond_tiles: Vec<_> = msg
|
||||||
|
// .new
|
||||||
|
// .pond
|
||||||
|
// .iter()
|
||||||
|
// .map(|dbt| {
|
||||||
|
// tiles
|
||||||
|
// .iter()
|
||||||
|
// .find_map(|(e, t)| if *t == TileId(dbt.id) { Some(e) } else { None })
|
||||||
|
// .unwrap_or_else(|| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id())
|
||||||
|
// })
|
||||||
|
// .collect();
|
||||||
|
|
||||||
|
// commands
|
||||||
|
// .entity(hand.iter().find(|e| main_player.1.contains(e)).unwrap())
|
||||||
|
// .replace_children(&hand_tiles);
|
||||||
|
// commands
|
||||||
|
// .entity(pond.iter().find(|e| main_player.1.contains(e)).unwrap())
|
||||||
|
// .replace_children(&pond_tiles);
|
||||||
|
|
||||||
|
// match msg.new.turn_state {
|
||||||
|
// jong_db::TurnState::None => {
|
||||||
|
// trace!("turnstate none");
|
||||||
|
// // TODO do we reconcile hand state here or in ::End?
|
||||||
|
// }
|
||||||
|
// jong_db::TurnState::Tsumo => {
|
||||||
|
// trace!("turnstate tsumo");
|
||||||
|
// let dbt = msg
|
||||||
|
// .new
|
||||||
|
// .working_tile
|
||||||
|
// .as_ref()
|
||||||
|
// .expect("entered tsumo without a drawn tile");
|
||||||
|
// commands.spawn((Drawn, Tile::from(&dbt.tile), TileId(dbt.id)));
|
||||||
|
// }
|
||||||
|
// jong_db::TurnState::Menzen => todo!(),
|
||||||
|
// jong_db::TurnState::RiichiKan => todo!(),
|
||||||
|
// jong_db::TurnState::RonChiiPonKan => todo!(),
|
||||||
|
// jong_db::TurnState::End => todo!(),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// next_turnstate.set(msg.new.turn_state.into());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
jong/src/riichi/connection.rs
Normal file
26
jong/src/riichi/connection.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use bevy_spacetimedb::{ReadStdbConnectedMessage, ReadStdbDisconnectedMessage};
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
|
||||||
|
use crate::SpacetimeDB;
|
||||||
|
use crate::creds_store;
|
||||||
|
|
||||||
|
pub(crate) fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage) {
|
||||||
|
for msg in messages.read() {
|
||||||
|
info!("you're now jongline");
|
||||||
|
|
||||||
|
// FIXME hack that doesn't work for startup crash?
|
||||||
|
while stdb.try_identity().is_none() {}
|
||||||
|
|
||||||
|
debug!("with identity: {}", stdb.identity());
|
||||||
|
creds_store()
|
||||||
|
.save(&msg.access_token)
|
||||||
|
.expect("i/o error saving token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO how reconnect?
|
||||||
|
pub(crate) fn on_disconnect(_stdb: SpacetimeDB, mut messages: ReadStdbDisconnectedMessage) {
|
||||||
|
for msg in messages.read() {
|
||||||
|
warn!("lost connection: {:#?}", msg.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ use jong_types::PlayerOrBot;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub(crate) id: PlayerOrBot,
|
pub id: PlayerOrBot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
|
@ -17,13 +17,20 @@ pub struct CurrentPlayer;
|
||||||
pub struct TileId(pub u32);
|
pub struct TileId(pub u32);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Hand;
|
pub struct Hand {
|
||||||
|
pub owner: PlayerOrBot,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Closed(pub(crate) u8);
|
pub struct Closed {
|
||||||
|
pub(crate) owner: PlayerOrBot,
|
||||||
|
pub(crate) length: u8,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Pond;
|
pub struct Pond {
|
||||||
|
pub owner: PlayerOrBot,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Drawn;
|
pub struct Drawn;
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ impl Plugin for TuiPlugin {
|
||||||
open: true,
|
open: true,
|
||||||
})
|
})
|
||||||
.init_state::<states::TuiState>()
|
.init_state::<states::TuiState>()
|
||||||
.add_message::<ConfirmSelect>()
|
|
||||||
.configure_sets(
|
.configure_sets(
|
||||||
Update,
|
Update,
|
||||||
(TuiSet::Input, TuiSet::Layout, TuiSet::Render).chain(),
|
(TuiSet::Input, TuiSet::Layout, TuiSet::Render).chain(),
|
||||||
|
|
@ -82,39 +81,38 @@ impl Plugin for TuiPlugin {
|
||||||
(input::keyboard, input::mouse).in_set(TuiSet::Input),
|
(input::keyboard, input::mouse).in_set(TuiSet::Input),
|
||||||
)
|
)
|
||||||
.add_systems(Update, layout::layout.in_set(TuiSet::Layout))
|
.add_systems(Update, layout::layout.in_set(TuiSet::Layout))
|
||||||
.add_systems(Update, discard_tile.run_if(in_state(TurnState::Tsumo)))
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
(render::render_main_hand, render::render_main_pond)
|
(render::render_main_hand, render::render_main_pond)
|
||||||
.run_if(in_state(GameState::Play)),
|
.run_if(in_state(GameState::Play).or(in_state(GameState::Deal))),
|
||||||
render::render,
|
render::render,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(TuiSet::Render),
|
.in_set(TuiSet::Render),
|
||||||
);
|
)
|
||||||
|
// .add_systems(Update, discard_tile.run_if(in_state(TurnState::Tsumo)));
|
||||||
|
.add_observer(discard_tile) // TODO check run_if here feature is out
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discard_tile(
|
fn discard_tile(
|
||||||
|
selected: On<ConfirmSelect>,
|
||||||
|
|
||||||
stdb: SpacetimeDB,
|
stdb: SpacetimeDB,
|
||||||
|
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut selected: MessageReader<ConfirmSelect>,
|
|
||||||
// main_player: Single<&Children, With<MainPlayer>>,
|
// main_player: Single<&Children, With<MainPlayer>>,
|
||||||
// only main player will have a Drawn tile?
|
// only main player will have a Drawn tile?
|
||||||
drawn: Single<(Entity, &TileId), With<Drawn>>,
|
drawn: Single<(Entity, &TileId), With<Drawn>>,
|
||||||
tiles: Query<&TileId>,
|
tiles: Query<&TileId>,
|
||||||
) {
|
) {
|
||||||
// FIXME why is this not consuming the messages?
|
|
||||||
// TODO disable this when we're not current player?
|
// TODO disable this when we're not current player?
|
||||||
while let Some(message) = selected.read().next() {
|
if let Ok(tile_id) = tiles.get(selected.0) {
|
||||||
if let Ok(tile_id) = tiles.get(message.0) {
|
trace!("{:?}, {tile_id:?}", selected.0);
|
||||||
stdb.reducers().discard_tile(tile_id.0).unwrap();
|
stdb.reducers().discard_tile(tile_id.0).unwrap();
|
||||||
stdb.reducers()
|
stdb.reducers().advance_game().unwrap();
|
||||||
.advance_game(stdb.db().game_timer().iter().next().unwrap())
|
commands.entity(drawn.0).remove::<Drawn>();
|
||||||
.unwrap();
|
|
||||||
commands.entity(drawn.0).remove::<Drawn>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ pub(crate) struct Hovered;
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub(crate) struct StartSelect;
|
pub(crate) struct StartSelect;
|
||||||
|
|
||||||
#[derive(Message, Debug)]
|
#[derive(Event, Debug)]
|
||||||
pub(crate) struct ConfirmSelect(pub(crate) Entity);
|
pub(crate) struct ConfirmSelect(pub(crate) Entity);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ use crate::tui::{
|
||||||
pub(crate) fn mouse(
|
pub(crate) fn mouse(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut mouse_reader: MessageReader<MouseMessage>,
|
mut mouse_reader: MessageReader<MouseMessage>,
|
||||||
mut event_writer: MessageWriter<ConfirmSelect>,
|
|
||||||
entities: Query<(Entity, &PickRegion)>,
|
entities: Query<(Entity, &PickRegion)>,
|
||||||
hovered: Query<(Entity, &PickRegion), With<Hovered>>,
|
hovered: Query<(Entity, &PickRegion), With<Hovered>>,
|
||||||
startselected: Query<(Entity, &PickRegion), With<StartSelect>>,
|
startselected: Query<(Entity, &PickRegion), With<StartSelect>>,
|
||||||
|
|
@ -52,7 +51,7 @@ pub(crate) fn mouse(
|
||||||
for (entity, region) in &startselected {
|
for (entity, region) in &startselected {
|
||||||
if region.area.contains(position) {
|
if region.area.contains(position) {
|
||||||
commands.entity(entity).remove::<StartSelect>();
|
commands.entity(entity).remove::<StartSelect>();
|
||||||
event_writer.write(ConfirmSelect(entity));
|
commands.trigger(ConfirmSelect(entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,9 @@ pub(crate) fn render_main_hand(
|
||||||
tiles: Query<&jong_types::Tile>,
|
tiles: Query<&jong_types::Tile>,
|
||||||
hovered: Query<Entity, With<Hovered>>,
|
hovered: Query<Entity, With<Hovered>>,
|
||||||
|
|
||||||
main_player: Single<&Children, With<MainPlayer>>,
|
main_player: Single<&Player, With<MainPlayer>>,
|
||||||
|
|
||||||
hand: Query<(&Children, Entity), With<Hand>>,
|
hand: Query<(&Hand, &Children)>,
|
||||||
drawn_tile: Option<Single<Entity, With<Drawn>>>,
|
drawn_tile: Option<Single<Entity, With<Drawn>>>,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
let mut frame = tui.get_frame();
|
let mut frame = tui.get_frame();
|
||||||
|
|
@ -164,14 +164,7 @@ pub(crate) fn render_main_hand(
|
||||||
|
|
||||||
let hand: Vec<_> = hand
|
let hand: Vec<_> = hand
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(c, e)| {
|
.find_map(|(h, c)| (main_player.id == h.owner).then_some(c))
|
||||||
// debug!("main_player children: {:?}", *main_player);
|
|
||||||
if main_player.contains(&e) {
|
|
||||||
Some(c)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entity| -> Result<_> {
|
.map(|entity| -> Result<_> {
|
||||||
|
|
@ -266,9 +259,9 @@ pub(crate) fn render_main_pond(
|
||||||
tiles: Query<&Tile>,
|
tiles: Query<&Tile>,
|
||||||
hovered: Query<Entity, With<Hovered>>,
|
hovered: Query<Entity, With<Hovered>>,
|
||||||
|
|
||||||
main_player: Single<&Children, With<MainPlayer>>,
|
main_player: Single<&Player, With<MainPlayer>>,
|
||||||
|
|
||||||
pond: Query<(&Children, Entity), With<Pond>>,
|
pond: Query<(&Pond, &Children)>,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
let mut frame = tui.get_frame();
|
let mut frame = tui.get_frame();
|
||||||
|
|
||||||
|
|
@ -278,13 +271,7 @@ pub(crate) fn render_main_pond(
|
||||||
|
|
||||||
let pond: Vec<_> = pond
|
let pond: Vec<_> = pond
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(c, e)| {
|
.find_map(|(p, c)| (main_player.id == p.owner).then_some(c))
|
||||||
if main_player.contains(&e) {
|
|
||||||
Some(c)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entity| -> Result<_> {
|
.map(|entity| -> Result<_> {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"dev": {
|
"dev": {
|
||||||
"run": ""
|
"run": ""
|
||||||
},
|
},
|
||||||
|
"_source-config": "spacetime.local.json",
|
||||||
"module-path": "jong-line",
|
"module-path": "jong-line",
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"database": "jong-line"
|
"database": "jong-line"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue