jong/jong/src/game.rs
2026-02-13 08:16:38 -08:00

243 lines
8.2 KiB
Rust

#![allow(unused)]
use bevy::prelude::*;
use bevy_spacetimedb::{
ReadInsertUpdateMessage, ReadStdbConnectedMessage, ReadStdbDisconnectedMessage, StdbPlugin,
TableMessages,
};
use spacetimedb::Identity;
use spacetimedb_sdk::{DbContext, Table, credentials};
use crate::game::hand::Drawn;
use crate::stdb::{
self, DbConnection, LobbyTableAccess, PlayerTableAccess, RemoteTables, add_bot, draw_tile,
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},
round::Wind,
wall::Wall,
},
tile::{self},
};
use jong_types::*;
pub mod hand;
pub mod player;
pub mod round;
pub mod wall;
#[derive(Message)]
pub enum GameMessage {
Discarded(Entity),
CallPending,
Called { player: Entity, calltype: Entity },
}
pub struct Riichi;
impl Plugin for Riichi {
fn build(&self, app: &mut App) {
let mut plugins = StdbPlugin::default()
.with_uri("http://localhost:3000")
.with_module_name("jongline")
.with_run_fn(DbConnection::run_threaded)
// TODO why don't I need to call add_reducer?
// TODO do these need to be subscription & vice-versa?
.add_table(RemoteTables::player)
.add_table(RemoteTables::lobby)
// semicolon stopper
;
let plugins =
if let Some(token) = creds_store().load().expect("i/o error loading credentials") {
// FIXME patch plugin so this takes Option?
plugins.with_token(&token)
} else {
plugins
};
app.add_plugins(plugins)
.init_state::<GameState>()
.add_sub_state::<TurnState>()
// .init_resource::<round::MatchSettings>()
// .init_resource::<round::Compass>()
.add_message::<GameMessage>()
// .add_systems(Startup, tile::init_tiles)
// .add_systems(Update, hand::sort_hands.run_if(in_state(GameState::Play)))
// .add_systems(OnEnter(TurnState::Tsumo), round::tsumo)
// .add_systems(OnEnter(TurnState::Menzen), round::menzen)
// .add_systems(Update, round::riichi_kan.run_if(in_state(TurnState::RiichiKan)))
// .add_systems(Update, round::discard.run_if(in_state(TurnState::Discard)))
// .add_systems(OnEnter(TurnState::RonChiiPonKan), round::notify_callable)
// .add_systems(Update, round::ron_chi_pon_kan.run_if(in_state(TurnState::RonChiiPonKan)).after(round::notify_callable))
// .add_systems(OnEnter(TurnState::End), round::end)
// stdb
.add_systems(Startup, subscriptions)
.add_systems(Update, on_connect)
.add_systems(Update, on_disconnect)
.add_systems(Update, on_player_insert_update)
.add_systems(Update, on_lobby_insert_update)
// .add_systems(OnEnter(GameState::Lobby), join_or_create_lobby)
// semicolon stopper
;
}
}
fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage, mut commands: Commands) {
for msg in messages.read() {
info!("you're now jongline");
debug!("with identity: {}", stdb.identity());
creds_store()
.save(&msg.access_token)
.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);
}
}
fn subscriptions(stdb: SpacetimeDB) {
stdb.subscription_builder()
.on_applied(|_| trace!("made all subs!"))
.on_error(|_, err| error!("sub failed: {err}"))
.subscribe([
format!(
"SELECT * FROM player p WHERE p.identity = '{}'",
stdb.identity()
),
"SELECT l.* FROM lobby l JOIN player p ON l.host_player_id = p.id".to_string(),
]);
// .subscribe_to_all_tables();
}
#[derive(Component)]
pub struct TileId(pub u32);
fn on_player_insert_update(
stdb: SpacetimeDB,
mut messages: ReadInsertUpdateMessage<stdb::Player>,
mut commands: Commands,
tiles: Query<(&Tile, &TileId, Entity)>,
mut player: Option<Single<&mut player::Player>>,
mut hand_ent: Option<Single<Entity, With<Hand>>>,
) {
use player::*;
for msg in messages.read() {
// debug!("player_insert_update msg:\n{:#?}", msg.new);
if let (Some(player), Some(hand_ent)) = (player.as_ref(), hand_ent.as_ref()) {
// if msg.old.as_ref().is_some_and(|m| !m.ready) && msg.new.ready {
// trace!("entered ready");
// // TODO add a start game button in the future
// stdb.reducers().start_game().unwrap();
// }
let mut tiles: Vec<_> = msg
.new
.hand
.iter()
.map(|dbt| {
// TODO this seems a lil expensive
if let Some(ent) = tiles
.iter()
.find(|(_, id, _)| id.0 == dbt.id)
.map(|(_, _, e)| e)
{
ent
} else {
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()
}
})
.collect();
commands.entity(**hand_ent).replace_children(&tiles);
if let Some(dbt) = &msg.new.drawn_tile {
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id), Drawn));
}
} 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);
}
}
}
fn on_lobby_insert_update(
stdb: SpacetimeDB,
mut messages: ReadInsertUpdateMessage<stdb::Lobby>,
mut commands: Commands,
mut next_gamestate: ResMut<NextState<GameState>>,
mut next_turnstate: ResMut<NextState<TurnState>>,
) {
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::Lobby => {
trace!("game entered lobby");
if !player.ready {
for _ in 0..3 {
stdb.reducers().add_bot(player.lobby_id).unwrap();
}
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");
}
stdb::GameState::Play => {
trace!("game entered play");
match msg.new.turn_state {
stdb::TurnState::None => {}
stdb::TurnState::Tsumo => {
stdb.reducers().draw_tile().unwrap();
}
stdb::TurnState::Menzen => todo!(),
stdb::TurnState::RiichiKan => todo!(),
stdb::TurnState::Discard => todo!(),
stdb::TurnState::RonChiiPonKan => todo!(),
stdb::TurnState::End => todo!(),
}
next_turnstate.set(msg.new.turn_state.into());
}
stdb::GameState::Exit => {
trace!("game enetered exit");
}
}
next_gamestate.set(msg.new.game_state.into());
}
}