diff --git a/devenv.nix b/devenv.nix index 3e83f98..72ac11b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -3,6 +3,9 @@ lib, ... }: rec { + env.RUST_LOG = "jong=trace"; + env.RUST_BACKTRACE = "1"; + # https://devenv.sh/processes/ processes.lspmux.exec = "lspmux server"; processes.spacetimedb_start.exec = "spacetime start"; @@ -44,8 +47,6 @@ wayland ]; env.LD_LIBRARY_PATH = lib.makeLibraryPath packages; - env.RUST_LOG = "jong=trace"; - # env.RUST_BACKTRACE = "full"; # https://devenv.sh/languages/ languages.rust = { diff --git a/jong-db/src/db/bot_table.rs b/jong-db/src/db/bot_table.rs index 73bc154..be072da 100644 --- a/jong-db/src/db/bot_table.rs +++ b/jong-db/src/db/bot_table.rs @@ -125,10 +125,41 @@ impl<'ctx> BotIdUnique<'ctx> { } } +/// Access to the `config_id` unique index on the table `bot`, +/// which allows point queries on the field of the same name +/// via the [`BotConfigIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.bot().config_id().find(...)`. +pub struct BotConfigIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> BotTableHandle<'ctx> { + /// Get a handle on the `config_id` unique index on the table `bot`. + pub fn config_id(&self) -> BotConfigIdUnique<'ctx> { + BotConfigIdUnique { + imp: self.imp.get_unique_constraint::("config_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> BotConfigIdUnique<'ctx> { + /// Find the subscribed row whose `config_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &u32) -> Option { + self.imp.find(col_val) + } +} + #[doc(hidden)] pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { let _table = client_cache.get_or_make_table::("bot"); _table.add_unique_constraint::("id", |row| &row.id); + _table.add_unique_constraint::("config_id", |row| &row.config_id); } #[doc(hidden)] diff --git a/jong-db/src/db/bot_type.rs b/jong-db/src/db/bot_type.rs index a49650a..dcb1bb8 100644 --- a/jong-db/src/db/bot_type.rs +++ b/jong-db/src/db/bot_type.rs @@ -40,6 +40,7 @@ impl __sdk::__query_builder::HasCols for Bot { /// /// Provides typed access to indexed columns for query building. pub struct BotIxCols { + pub config_id: __sdk::__query_builder::IxCol, pub id: __sdk::__query_builder::IxCol, pub lobby_id: __sdk::__query_builder::IxCol, } @@ -48,6 +49,7 @@ impl __sdk::__query_builder::HasIxCols for Bot { type IxCols = BotIxCols; fn ix_cols(table_name: &'static str) -> Self::IxCols { BotIxCols { + config_id: __sdk::__query_builder::IxCol::new(table_name, "config_id"), id: __sdk::__query_builder::IxCol::new(table_name, "id"), lobby_id: __sdk::__query_builder::IxCol::new(table_name, "lobby_id"), } diff --git a/jong-line/src/tables.rs b/jong-line/src/tables.rs index 0b09f6c..4c22804 100644 --- a/jong-line/src/tables.rs +++ b/jong-line/src/tables.rs @@ -72,6 +72,7 @@ pub struct Bot { #[index(btree)] pub lobby_id: u32, + #[unique] pub config_id: u32, } @@ -149,16 +150,21 @@ fn view_closed_hands(ctx: &ViewContext) -> Vec { .players .iter() .filter_map(|id| { - ctx.db - .player_hand() - .player_id() - .find(id) - .map(|hand| HandView { - player_id: hand.player_id, - hand_length: hand.hand.len() as u8, - pond: hand.pond, - drawn: hand.turn_state == TurnState::Tsumo && hand.working_tile.is_some(), - }) + if *id == this_player.config_id { + None + } else { + ctx.db + .player_hand() + .player_id() + .find(id) + .map(|hand| HandView { + player_id: hand.player_id, + hand_length: hand.hand.len() as u8, + pond: hand.pond, + drawn: hand.turn_state == TurnState::Tsumo + && hand.working_tile.is_some(), + }) + } }) .collect() } else { diff --git a/jong/src/riichi.rs b/jong/src/riichi.rs index c255b43..bb4b9fd 100644 --- a/jong/src/riichi.rs +++ b/jong/src/riichi.rs @@ -64,8 +64,8 @@ impl Plugin for Riichi { ( on_view_hand_insert, on_view_hand_update, - // on_view_closed_insert, - // on_view_closed_update, + on_view_closed_insert, + on_view_closed_update, ) .after(on_player_config_insert_update) .run_if(in_state(GameState::Play).or(in_state(GameState::Deal))), @@ -315,66 +315,86 @@ fn sync_open_hand( next_turnstate.set(player_hand.turn_state.into()); } -// fn on_view_closed_insert( -// mut messages: ReadInsertMessage, -// mut commands: Commands, -// tiles: Query<(Entity, &TileId)>, -// hands: Query<(Entity, &Hand)>, -// ponds: Query<(Entity, &Pond)>, -// ) { -// for msg in messages.read() { -// sync_closed_hand(&mut commands, tiles, hands, ponds, &msg.row); -// } -// } +fn on_view_closed_insert( + mut messages: ReadInsertMessage, + mut commands: Commands, + players: Populated<(Entity, &Player, Option<&Children>)>, + tiles: Query<(Entity, &TileId)>, + hands: Query<(Entity, &Hand)>, + ponds: Query<(Entity, &Pond)>, +) { + for msg in messages.read() { + trace!("on_view_closed_insert"); + sync_closed_hand(&mut commands, &players, tiles, hands, ponds, &msg.row); + } +} -// fn on_view_closed_update( -// mut messages: ReadUpdateMessage, -// mut commands: Commands, -// tiles: Query<(Entity, &TileId)>, -// hands: Query<(Entity, &Hand)>, -// ponds: Query<(Entity, &Pond)>, -// ) { -// for msg in messages.read() { -// sync_closed_hand(&mut commands, tiles, hands, ponds, &msg.new); -// } -// } +fn on_view_closed_update( + mut messages: ReadUpdateMessage, + mut commands: Commands, + players: Populated<(Entity, &Player, Option<&Children>)>, + tiles: Query<(Entity, &TileId)>, + hands: Query<(Entity, &Hand)>, + ponds: Query<(Entity, &Pond)>, +) { + for msg in messages.read() { + trace!("on_view_closed_update"); + sync_closed_hand(&mut commands, &players, tiles, hands, ponds, &msg.new); + } +} -// fn sync_closed_hand( -// commands: &mut Commands, -// tiles: Query<(Entity, &TileId)>, -// hands: Query<(Entity, &Hand)>, -// ponds: Query<(Entity, &Pond)>, -// hand_view: &HandView, -// ) { -// let player_id = hand_view.player_id; -// let hand_ent = hands -// .iter() -// .find_map(|(e, h)| (h.player_id == player_id).then_some(e)) -// .unwrap_or_else(|| commands.spawn(Hand { player_id }).id()); -// let pond_ent = ponds -// .iter() -// .find_map(|(e, p)| (p.player_id == player_id).then_some(e)) -// .unwrap_or_else(|| commands.spawn(Pond { player_id }).id()); +fn sync_closed_hand( + commands: &mut Commands, + players: &Populated<(Entity, &Player, Option<&Children>)>, + tiles: Query<(Entity, &TileId)>, + hands: Query<(Entity, &Hand)>, + ponds: Query<(Entity, &Pond)>, + hand_view: &HandView, +) { + let player_id = hand_view.player_id; -// let pond: Vec = hand_view -// .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(); + let (player_ent, player_children) = players + .iter() + .find_map(|(e, p, c)| (p.id == player_id).then_some((e, c))) + .unwrap(); -// commands.entity(hand_ent).insert(Closed { -// length: hand_view.hand_length, -// }); -// commands.entity(pond_ent).replace_children(&pond); + let hand_ent = hands + .iter() + .find_map(|(e, _)| player_children.is_some_and(|c| c.contains(&e)).then_some(e)) + .unwrap_or_else(|| { + let id = commands.spawn(Hand).id(); + commands.entity(player_ent).add_child(id); + id + }); + let pond_ent = ponds + .iter() + .find_map(|(e, _)| player_children.is_some_and(|c| c.contains(&e)).then_some(e)) + .unwrap_or_else(|| { + let id = commands.spawn(Pond).id(); + commands.entity(player_ent).add_child(id); + id + }); -// if hand_view.drawn { -// commands.spawn(Drawn); -// } else { -// // TODO clear Drawn from auto-discarded tile -// } -// } + let pond: Vec = hand_view + .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).insert(Closed { + length: hand_view.hand_length, + }); + commands.entity(pond_ent).replace_children(&pond); + + if hand_view.drawn { + let id = commands.spawn(Drawn).id(); + commands.entity(player_ent).add_child(id); + } else { + // TODO clear Drawn from auto-discarded tile + } +} diff --git a/jong/src/tui.rs b/jong/src/tui.rs index fa38da2..db0267b 100644 --- a/jong/src/tui.rs +++ b/jong/src/tui.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - use std::time::Duration; use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin}; @@ -84,7 +82,7 @@ impl Plugin for TuiPlugin { .add_systems( Update, ( - (render::render_main_hand, render::render_main_pond) + (render::render_main_hand, render::render_main_pond, render::render_other_hands) .run_if(in_state(GameState::Play).or(in_state(GameState::Deal))), render::render, ) diff --git a/jong/src/tui/input/keyboard.rs b/jong/src/tui/input/keyboard.rs index 64685d6..88c5c36 100644 --- a/jong/src/tui/input/keyboard.rs +++ b/jong/src/tui/input/keyboard.rs @@ -7,9 +7,9 @@ use tui_logger::TuiWidgetEvent; use jong::SpacetimeDB; use jong_db::{ - LobbyTableAccess, UserTableAccess, botQueryTableAccess, - join_or_create_lobby, lobbyQueryTableAccess, player_clockQueryTableAccess, - player_configQueryTableAccess, view_closed_handsQueryTableAccess, view_handQueryTableAccess, + LobbyTableAccess, UserTableAccess, botQueryTableAccess, join_or_create_lobby, + lobbyQueryTableAccess, player_clockQueryTableAccess, player_configQueryTableAccess, + view_closed_handsQueryTableAccess, view_handQueryTableAccess, }; use jong_types::GameState; @@ -78,6 +78,14 @@ pub(crate) fn keyboard( .player_config() .r#where(|pc| pc.id.eq(user.config_id)) }) + .add_query(|q| { + q.from + .bot() + .r#where(|b| b.lobby_id.eq(lobby_id)) + .right_semijoin(q.from.player_config(), |b, pc| { + b.config_id.eq(pc.id) + }) + }) .add_query(|q| { q.from .player_clock() diff --git a/jong/src/tui/render.rs b/jong/src/tui/render.rs index d8a48f7..83aa271 100644 --- a/jong/src/tui/render.rs +++ b/jong/src/tui/render.rs @@ -7,7 +7,7 @@ use ratatui::style::{Color, Modifier, Style, Stylize}; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Block, Borders, Clear, Paragraph}; -use jong::riichi::player::*; +use jong::riichi::{Lobby, player::*}; use jong_types::*; use crate::tui::input::Hovered; @@ -291,4 +291,51 @@ pub(crate) fn render_main_pond( Ok(()) } -pub(crate) fn render_other_hands() {} +pub(crate) fn render_other_hands( + mut tui: ResMut, + layouts: Res, + + lobby: Res, + + main_player: Single<&Player, With>, + players: Query<(&Player, &Children), Without>, + hands: Query<(Entity, &Closed)>, + ponds: Query<(Entity, &Children), With>, + drawn: Option>>, +) { + let mut frame = tui.get_frame(); + + let main_idx = lobby + .players + .iter() + .position(|id| *id == main_player.id) + .unwrap(); + + let mut player_it = lobby.players.iter().copied().cycle().skip(main_idx + 1); + + let right_id = player_it.next().unwrap(); + let top_id = player_it.next().unwrap(); + let left_id = player_it.next().unwrap(); + + { + let children = players + .iter() + .find_map(|(p, c)| (p.id == right_id).then_some(c)) + .unwrap(); + let hand = hands + .iter() + .find_map(|(e, h)| children.contains(&e).then_some(h)) + .unwrap(); + // let pond = ponds + // .iter() + // .find_map(|(e, c)| children.contains(&e).then_some(c)) + // .unwrap(); + let drawn = drawn.is_some_and(|e| children.contains(&*e)); + + // let hand_draw_meld = Layout::vertical([ + // Constraint::() + // ]) + } + {} + {} +}