diff --git a/jong-line/src/tables.rs b/jong-line/src/tables.rs index 1df4d31..52c49ba 100644 --- a/jong-line/src/tables.rs +++ b/jong-line/src/tables.rs @@ -140,36 +140,37 @@ pub struct HandView { #[view(name = view_closed_hands, public)] fn view_closed_hands(ctx: &ViewContext) -> Vec { let this_player = ctx.db.player().identity().find(ctx.sender).unwrap(); - ctx.db - .lobby() - .id() - .find(this_player.lobby_id) - .unwrap() - .players - .iter() - .filter_map(|&player| { - let (hand_length, drawn) = match player { + if let Some(lobby) = ctx.db.lobby().id().find(this_player.lobby_id) { + lobby + .players + .iter() + .filter_map(|&player| match player { PlayerOrBot::Player { id } => { - let player_hand = ctx.db.player_hand().player_id().find(id).unwrap(); - ( - player_hand.hand.len() as u8, - player_hand.turn_state == TurnState::Tsumo - && player_hand.working_tile.is_some(), - ) + if let Some(player_hand) = ctx.db.player_hand().player_id().find(id) { + Some(HandView { + player, + hand_length: player_hand.hand.len() as u8, + drawn: player_hand.turn_state == TurnState::Tsumo + && player_hand.working_tile.is_some(), + }) + } else { + None + } } PlayerOrBot::Bot { id } => { - let bot = ctx.db.bot().id().find(id).unwrap(); - ( - bot.hand.len() as u8, - bot.turn_state == TurnState::Tsumo && bot.working_tile.is_some(), - ) + if let Some(bot) = ctx.db.bot().id().find(id) { + Some(HandView { + player, + hand_length: bot.hand.len() as u8, + drawn: bot.turn_state == TurnState::Tsumo && bot.working_tile.is_some(), + }) + } else { + None + } } - }; - Some(HandView { - player, - hand_length, - drawn, }) - }) - .collect() + .collect() + } else { + vec![] + } } diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index 36bbf5b..0a2e319 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -1,3 +1,4 @@ +use bevy::platform::collections::HashMap; use bevy::prelude::*; use bevy_spacetimedb::{ ReadInsertUpdateMessage, ReadStdbConnectedMessage, ReadStdbDisconnectedMessage, @@ -5,8 +6,8 @@ use bevy_spacetimedb::{ }; use jong_db::{ - self, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, RemoteTables, - ViewClosedHandsTableAccess, ViewHandTableAccess as _, + self, BotTableAccess, DbConnection, LobbyTableAccess, PlayerHand, PlayerTableAccess, + RemoteTables, ViewClosedHandsTableAccess, ViewHandTableAccess as _, }; use jong_db::{add_bot, set_ready}; use jong_types::*; @@ -88,10 +89,7 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { .on_error(|_, err| error!("sub failed: {err}")) .subscribe([ // TODO change these to sub/unsub based on being in lobby and some such - format!( - "SELECT * FROM player p WHERE p.identity = '{}'", - stdb.identity() - ), + "SELECT p.* FROM player p JOIN lobby l ON p.lobby_id = l.id".to_string(), "SELECT l.* FROM lobby l JOIN player p ON l.id = p.lobby_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(), @@ -104,24 +102,55 @@ fn subscriptions(stdb: SpacetimeDB, mut commands: Commands) { } } -/// restores (spawns) entities to be consistent with server state +/// spawns entities to be consistent with server state +// TODO figure out a way to call this for later changes in the various on_ins_upd systems fn on_subscribed( _event: On, stdb: SpacetimeDB, mut commands: Commands, - mut next_gamestate: ResMut>, - mut next_turnstate: ResMut>, + mut next_gamestate: ResMut>, + mut next_turnstate: ResMut>, ) { - if let Some(player) = stdb.db().player().iter().next() {} + while let Some(player) = stdb.db().player().iter().next() { + if player.identity == stdb.identity() { + spawn_main_player(&stdb, &mut commands, &mut next_turnstate, &player); + } else { + spawn_other_player(&stdb, &mut commands, &player); + } + } + while let Some(bot) = stdb.db().bot().iter().next() { + let id = PlayerOrBot::Bot { id: bot.id }; + let hand_view = stdb + .db() + .view_closed_hands() + .iter() + .find(|v| PlayerOrBot::from(&v.player) == id) + .unwrap(); + let hand_ent = commands.spawn((Hand, Closed(hand_view.hand_length))).id(); + commands.spawn(Player { id }).add_child(hand_ent); + } if let Some(lobby) = stdb.db().lobby().iter().next() { next_gamestate.set(lobby.game_state.into()); } +} - let hand_ent = commands.spawn(Hand).id(); - let pond_ent = commands.spawn(Pond).id(); +fn spawn_main_player( + stdb: &SpacetimeDB, + commands: &mut Commands, + next_turnstate: &mut ResMut>, + player: &jong_db::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() { let hand_tiles: Vec<_> = player_hand .hand @@ -133,83 +162,32 @@ fn on_subscribed( .iter() .map(|dbt| commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()) .collect(); - commands.entity(pond_ent).add_children(&pond_tiles); - commands.entity(hand_ent).add_children(&hand_tiles); + let pond = commands.spawn(Hand).add_children(&pond_tiles).id(); + let hand = commands.spawn(Pond).add_children(&hand_tiles).id(); + commands.entity(main_player).add_children(&[pond, hand]); if player_hand.turn_state == jong_db::TurnState::Tsumo && let Some(drawn_dbt) = player_hand.working_tile { - commands.spawn((Drawn, Tile::from(&drawn_dbt.tile), TileId(drawn_dbt.id))); + 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 on_view_hand_update( - stdb: SpacetimeDB, - mut messages: ReadUpdateMessage, - - mut commands: Commands, - hand: Single>, - pond: Single>, - // drawn: Option>>, - tiles: Query<(Entity, &TileId)>, - mut next_turnstate: ResMut>, -) { - // trace!("on_view_hand_update"); - - // TODO can this and similar run at startup or on play/reconnect? - for msg in messages.read() { - trace!("new hand: {:?}", msg.new); - - 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(); - commands.entity(*hand).replace_children(&hand_tiles); - - 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(*pond).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()); - } +fn spawn_other_player(stdb: &SpacetimeDB, commands: &mut Commands, player: &jong_db::Player) { + let id = PlayerOrBot::Player { id: player.id }; + let hand_view = stdb + .db() + .view_closed_hands() + .iter() + .find(|v| PlayerOrBot::from(&v.player) == id) + .unwrap(); + 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( @@ -218,18 +196,26 @@ fn on_player_insert_update( mut commands: Commands, - hand: Option>>, - pond: Option>>, -) { - // TODO this should be startup - if hand.is_none() { - commands.spawn(Hand); - } - if pond.is_none() { - commands.spawn(Pond); - } + main_player: Option>, + other_players: Query<&Player>, - for msg in messages.read() {} + mut next_turnstate: ResMut>, +) { + for msg in messages.read() { + if main_player.is_none() && msg.new.identity == stdb.identity() { + spawn_main_player(&stdb, &mut commands, &mut next_turnstate, &msg.new); + } else if other_players.iter().any(|p| { + if let PlayerOrBot::Player { id } = &p.id { + *id == msg.new.id + } else { + false + } + }) { + spawn_other_player(&stdb, &mut commands, &msg.new); + } else { + // TODO update case + } + } } fn on_lobby_insert_update( @@ -280,3 +266,77 @@ fn on_lobby_insert_update( next_gamestate.set(msg.new.game_state.into()); } } + +fn on_view_hand_update( + stdb: SpacetimeDB, + mut messages: ReadUpdateMessage, + + mut commands: Commands, + tiles: Query<(Entity, &TileId)>, + + main_player: Single<&Children, With>, + + hand: Query>, + pond: Query>, + // drawn: Option>>, + mut next_turnstate: ResMut>, +) { + trace!("on_view_hand_update"); + + // TODO can this and similar run at startup or on play/reconnect? + for msg in messages.read() { + trace!("new hand: {:?}", msg.new); + + 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(); + commands + .entity(hand.iter().find(|e| main_player.contains(e)).unwrap()) + .replace_children(&hand_tiles); + + 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(pond.iter().find(|e| main_player.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()); + } +} diff --git a/jong/src/riichi/player.rs b/jong/src/riichi/player.rs index 1622423..a9d7d47 100644 --- a/jong/src/riichi/player.rs +++ b/jong/src/riichi/player.rs @@ -1,7 +1,11 @@ use bevy::prelude::*; +use jong_types::PlayerOrBot; + #[derive(Component)] -pub struct Player; +pub struct Player { + pub(crate) id: PlayerOrBot, +} #[derive(Component)] pub struct MainPlayer; @@ -15,6 +19,9 @@ pub struct TileId(pub u32); #[derive(Component)] pub struct Hand; +#[derive(Component)] +pub struct Closed(pub(crate) u8); + #[derive(Component)] pub struct Pond; diff --git a/jong/src/tui.rs b/jong/src/tui.rs index 5418c21..9f35853 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -85,12 +85,10 @@ impl Plugin for TuiPlugin { .add_systems( Update, ( - ( - render::render_hand, - render::render_pond, - ) + (render::render_main_hand, render::render_main_pond) .run_if(in_state(GameState::Play)), render::render, + query_tester, ) .chain() .in_set(TuiSet::Render), @@ -98,11 +96,31 @@ impl Plugin for TuiPlugin { } } +fn query_tester( + main_player: Single<&Children, With>, + // hand: Query<(&Children, Entity), With>, +) { + trace!("owo"); + // let hand = hand + // .iter() + // .find_map(|(c, e)| { + // if main_player.contains(&e) { + // Some(c) + // } else { + // None + // } + // }) + // .unwrap(); + // debug!("{hand:?}"); +} + fn discard_tile( stdb: SpacetimeDB, mut commands: Commands, mut selected: MessageReader, + // main_player: Single<&Children, With>, + // only main player will have a Drawn tile? drawn: Single<(Entity, &TileId), With>, tiles: Query<&TileId>, ) { diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index c93a578..0e573e0 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -95,23 +95,33 @@ pub(crate) fn render( // FIXME we don't care about other players atm #[allow(clippy::too_many_arguments, clippy::type_complexity)] -pub(crate) fn render_hand( - mut commands: Commands, +pub(crate) fn render_main_hand( mut tui: ResMut, - - hovered: Query>, layouts: Res, + mut commands: Commands, + tiles: Query<&jong_types::Tile>, - // main_player: Single<(&Player, Entity /* , &Wind */), With>, - hand: Single<(&Children, Entity), With>, + hovered: Query>, + + main_player: Single<&Children, With>, + + hand: Query<(&Children, Entity), With>, drawn_tile: Option>>, ) -> Result { let mut frame = tui.get_frame(); debug_blocks(layouts.clone(), &mut frame); let hand: Vec<_> = hand - .0 + .iter() + .find_map(|(c, e)| { + if main_player.contains(&e) { + Some(c) + } else { + None + } + }) + .unwrap() .iter() .map(|entity| -> Result<_> { let tile = tiles.get(entity).unwrap_or_else(|_| panic!("{entity:?}")); @@ -196,20 +206,31 @@ pub(crate) fn render_hand( Ok(()) } -pub(crate) fn render_pond( - mut commands: Commands, +pub(crate) fn render_main_pond( mut tui: ResMut, - - hovered: Query>, layouts: Res, - pond: Single<(&Children, Entity), With>, + mut commands: Commands, + tiles: Query<&Tile>, + hovered: Query>, + + main_player: Single<&Children, With>, + + pond: Query<(&Children, Entity), With>, ) -> Result { let mut frame = tui.get_frame(); let pond: Vec<_> = pond - .0 + .iter() + .find_map(|(c, e)| { + if main_player.contains(&e) { + Some(c) + } else { + None + } + }) + .unwrap() .iter() .map(|entity| -> Result<_> { let tile = tiles.get(entity).unwrap();