refactor entites to reduce complexity and crashes, switch to Querys

This commit is contained in:
Tao Tien 2026-01-18 07:53:33 -08:00
parent 3182916832
commit c7200b1fd3
8 changed files with 95 additions and 89 deletions

View file

@ -25,9 +25,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22" tracing-subscriber = "0.3.22"
tui-logger = { version = "0.18.0", features = ["tracing-support", "crossterm"] } tui-logger = { version = "0.18.0", features = ["tracing-support", "crossterm"] }
[dev-dependencies]
bevy = { version = "0.17.3", features = ["track_location"] }
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1

View file

@ -1,10 +1,10 @@
use bevy::{ecs::query::QueryData, prelude::*}; use bevy::prelude::*;
use crate::{ use crate::{
game::{ game::{
hand::Hand, hand::Hand,
player::MainPlayer, player::{CurrentPlayer, MainPlayer},
round::{CurrentPlayer, TurnState, Wind}, round::{TurnState, Wind},
wall::Wall, wall::Wall,
}, },
tile::{self}, tile::{self},
@ -60,7 +60,6 @@ pub(crate) fn setup(
matchsettings: Res<round::MatchSettings>, matchsettings: Res<round::MatchSettings>,
// mut compass: ResMut<Compass> // mut compass: ResMut<Compass>
// tiles: Query<Entity, With<Tile>>, // tiles: Query<Entity, With<Tile>>,
curr_gamestate: Res<State<GameState>>,
mut next_gamestate: ResMut<NextState<GameState>>, mut next_gamestate: ResMut<NextState<GameState>>,
) { ) {
for i in 1..=matchsettings.player_count { for i in 1..=matchsettings.player_count {
@ -77,8 +76,8 @@ pub(crate) fn setup(
); );
if i == 1 { if i == 1 {
let player = commands.spawn((bundle, MainPlayer)).id(); let player = commands.spawn((bundle, MainPlayer, CurrentPlayer)).id();
commands.insert_resource(CurrentPlayer(player)); // commands.insert_resource(CurrentPlayer(player));
} else { } else {
commands.spawn(bundle); commands.spawn(bundle);
} }

View file

@ -10,11 +10,11 @@ use crate::{
#[derive(Component)] #[derive(Component)]
pub struct Hand; pub struct Hand;
#[derive(Component, Debug)] #[derive(Component)]
pub struct DrawnTile(pub Entity); pub struct Drawn;
#[derive(Component)] #[derive(Component)]
pub struct DiscardedTile(pub Entity); pub struct Discarded;
// #[derive(Component, Default)] // #[derive(Component, Default)]
// enum SortHand { // enum SortHand {
@ -48,19 +48,16 @@ pub(crate) fn shuffle_deal(
let mut walltiles: Vec<_> = tiles.iter().collect(); let mut walltiles: Vec<_> = tiles.iter().collect();
walltiles.shuffle(&mut rng); walltiles.shuffle(&mut rng);
// commands.get_entity(*wall_ent)?.replace_children(&walltiles);
for player_ent in players { for player_ent in players {
debug!("deal to player_ent {player_ent:?}");
let handtiles = walltiles.split_off(walltiles.len() - 13); let handtiles = walltiles.split_off(walltiles.len() - 13);
// commands.get_entity(*wall_ent)?.remove_children(&handtiles);
let hand_ent = commands.spawn(Hand).add_children(&handtiles).id(); let hand_ent = commands.spawn(Hand).add_children(&handtiles).id();
debug!("hand_ent: {hand_ent:?}"); commands.entity(player_ent).add_child(hand_ent);
debug!("deal to player_ent {player_ent:?} {hand_ent:?}");
commands.entity(player_ent).replace_children(&[hand_ent]);
} }
// don't need to remove hands from wall if we don't insert to wall to begin with
// TODO probably do this later on when animating the draw
debug!("shuffled: {walltiles:?}");
commands.entity(*wall_ent).replace_children(&walltiles); commands.entity(*wall_ent).replace_children(&walltiles);
next_gamestate.set(GameState::Play); next_gamestate.set(GameState::Play);

View file

@ -11,8 +11,11 @@ pub struct Points(pub isize);
#[derive(Component)] #[derive(Component)]
pub struct MainPlayer; pub struct MainPlayer;
#[derive(Component)]
pub struct CurrentPlayer;
#[derive(Component)] #[derive(Component)]
pub struct Dealer; pub struct Dealer;
#[derive(Component)] #[derive(Component)]
pub struct Tsumo(pub Entity); pub struct Tsumo;

View file

@ -5,14 +5,14 @@ use crate::{
EnumNextCycle, EnumNextCycle,
game::{ game::{
GameMessage, GameState, GameMessage, GameState,
hand::{DiscardedTile, DrawnTile, Hand}, hand::{Discarded, Drawn, Hand},
player::Player, player::{CurrentPlayer, Player},
wall::Wall, wall::Wall,
}, },
}; };
#[derive(Resource)] // #[derive(Resource)]
pub struct CurrentPlayer(pub Entity); // pub struct CurrentPlayer(pub Entity);
#[derive(Resource)] #[derive(Resource)]
pub(crate) struct MatchSettings { pub(crate) struct MatchSettings {
@ -58,9 +58,6 @@ pub(crate) enum TurnState {
End, End,
} }
#[derive(EntityEvent)]
pub struct Discard(pub Entity);
impl Default for MatchSettings { impl Default for MatchSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -116,22 +113,22 @@ impl EnumNextCycle for TurnState {
pub(crate) fn tsumo( pub(crate) fn tsumo(
mut commands: Commands, mut commands: Commands,
curr_player: Res<CurrentPlayer>,
// players: Populated<Entity, With<Player>>, // curr_player: Res<CurrentPlayer>,
wall_ent: Single<Entity, With<Wall>>, curr_player: Single<Entity, With<CurrentPlayer>>,
wall: Single<Entity, With<Wall>>,
walltiles: Single<&Children, With<Wall>>, walltiles: Single<&Children, With<Wall>>,
curr_turnstate: Res<State<TurnState>>, curr_turnstate: Res<State<TurnState>>,
mut next_turnstate: ResMut<NextState<TurnState>>, mut next_turnstate: ResMut<NextState<TurnState>>,
) { ) {
debug!("tsumo for: {:?}", curr_player.0);
let drawn = walltiles.last().unwrap(); let drawn = walltiles.last().unwrap();
commands.entity(*wall_ent).remove_child(*drawn); commands.entity(*wall).remove_child(*drawn);
let drawn_ent = commands.spawn(DrawnTile(*drawn)).id();
commands.entity(curr_player.0).add_child(drawn_ent);
debug!("drew: ent: {drawn_ent:?} tile_ent: {:?}", drawn); let drawn = commands.entity(*drawn).insert(Drawn).id();
commands.entity(*curr_player).add_child(drawn);
debug!("tsumo for: {:?}, tile: {:?}", *curr_player, drawn);
next_turnstate.set(curr_turnstate.next()); next_turnstate.set(curr_turnstate.next());
} }
@ -156,39 +153,43 @@ pub(crate) fn discard(
mut commands: Commands, mut commands: Commands,
mut reader: MessageReader<GameMessage>, mut reader: MessageReader<GameMessage>,
currplayer: Res<CurrentPlayer>, curr_player: Single<Entity, With<CurrentPlayer>>,
drawntile: Single<(&DrawnTile, Entity), With<DrawnTile>>, players: Query<&Children, With<Player>>,
player_hands: Populated<(&Player, &Children), With<Hand>>, mut hands: Query<(&Children, Entity), (With<Hand>, Without<Player>)>,
hands: Populated<&Children, (With<Hand>, Without<Player>)>, drawn: Single<Entity, With<Drawn>>,
curr_turnstate: Res<State<TurnState>>, curr_turnstate: Res<State<TurnState>>,
mut next_turnstate: ResMut<NextState<TurnState>>, mut next_turnstate: ResMut<NextState<TurnState>>,
) { ) -> Result {
let curr = currplayer.0; // trace!("discard");
let hand = player_hands.get(curr).unwrap().1.iter().next().unwrap(); let curr_hand = hands.get_mut(players.get(*curr_player)?.iter().next().unwrap())?;
let handtiles = hands.get(hand).unwrap();
let (drawntile, drawn_ent) = *drawntile;
// debug!("discard turn for: {curr:?}");
let mut done = false;
while let Some(message) = reader.read().next() { while let Some(message) = reader.read().next() {
if let GameMessage::Discarded(entity) = message { if let GameMessage::Discarded(discarded) = message {
debug!("{curr:?} discarded: {entity:?}"); if *discarded == *drawn {
// commands.entity(drawn_ent).despawn(); } else if curr_hand.0.contains(discarded) {
if *entity == drawntile.0 {
} else if handtiles.contains(entity) {
commands commands
.entity(hand) .entity(curr_hand.1)
.remove_child(*entity) .remove_child(*discarded)
.add_child(drawntile.0); .add_child(*drawn);
} else { } else {
panic!("discarded illegal player tile?") panic!("current hand nor drawn tile contains discarded tile")
} }
commands.spawn(DiscardedTile(*entity)); commands
next_turnstate.set(curr_turnstate.next()); .entity(*discarded)
.remove::<Drawn>()
.insert(Discarded);
done = true;
break; break;
} }
} }
if done {
next_turnstate.set(curr_turnstate.next());
}
Ok(())
} }
pub(crate) fn ron_chi_pon_kan( pub(crate) fn ron_chi_pon_kan(

View file

@ -1,13 +1,13 @@
use std::time::Duration; use std::time::Duration;
use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin}; use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin, time::TimePlugin};
use bevy_ratatui::RatatuiPlugins; use bevy_ratatui::RatatuiPlugins;
use jong::game::{ use jong::game::{
GameMessage, GameState, GameMessage, GameState,
hand::{DrawnTile, Hand}, hand::{Drawn, Hand},
player::{MainPlayer, Player}, player::{CurrentPlayer, Player},
round::{CurrentPlayer, Discard},
}; };
use tracing::instrument;
use tui_logger::TuiWidgetState; use tui_logger::TuiWidgetState;
use crate::tui::{input::ConfirmSelect, states::ConsoleWidget}; use crate::tui::{input::ConfirmSelect, states::ConsoleWidget};
@ -72,17 +72,25 @@ impl Plugin for TuiPlugin {
fn discard_tile( fn discard_tile(
mut writer: MessageWriter<GameMessage>, mut writer: MessageWriter<GameMessage>,
mut selected: MessageReader<ConfirmSelect>, mut selected: MessageReader<ConfirmSelect>,
drawntile: Single<Entity, With<DrawnTile>>,
currplayer: Res<CurrentPlayer>, drawn: Single<Entity, With<Drawn>>,
curr_player: Single<Entity, With<CurrentPlayer>>,
player_hands: Populated<(&Player, &Children), With<Hand>>, player_hands: Populated<(&Player, &Children), With<Hand>>,
hands: Populated<&Children, (With<Hand>, Without<Player>)>, hands: Populated<&Children, (With<Hand>, Without<Player>)>,
) { ) {
let curr = currplayer.0; // trace!("discard_tile");
let hand_ent = player_hands.get(curr).unwrap().1.iter().next().unwrap();
let hand_ent = player_hands
.get(*curr_player)
.unwrap()
.1
.iter()
.next()
.unwrap();
let hand = hands.get(hand_ent).unwrap(); let hand = hands.get(hand_ent).unwrap();
while let Some(message) = selected.read().next() while let Some(message) = selected.read().next()
&& (message.0 == *drawntile || hand.contains(&message.0)) && (message.0 == *drawn || hand.contains(&message.0))
{ {
writer.write(GameMessage::Discarded(message.0)); writer.write(GameMessage::Discarded(message.0));
} }

View file

@ -12,5 +12,5 @@ pub(crate) struct Hovered;
#[derive(Component)] #[derive(Component)]
pub(crate) struct StartSelect; pub(crate) struct StartSelect;
#[derive(Message)] #[derive(Message, Debug)]
pub(crate) struct ConfirmSelect(pub(crate) Entity); pub(crate) struct ConfirmSelect(pub(crate) Entity);

View file

@ -1,15 +1,13 @@
use std::io::Write as _; use std::io::Write as _;
use std::ops::Sub;
use bevy::platform::collections::HashSet;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_ratatui::RatatuiContext; use bevy_ratatui::RatatuiContext;
use ratatui::layout::{Constraint, Flex, Layout, Offset, Rect, Size}; use ratatui::layout::{Constraint, Flex, Layout, Offset, Rect, Size};
use ratatui::style::{Modifier, Style, Stylize}; use ratatui::style::{Modifier, Stylize};
use ratatui::widgets::{Block, Borders, Clear, Paragraph}; use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use jong::game::hand::{DrawnTile, Hand}; use jong::game::hand::{Drawn, Hand};
use jong::game::player::{MainPlayer, Player}; use jong::game::player::{CurrentPlayer, MainPlayer, Player};
use jong::game::round::Wind; use jong::game::round::Wind;
use jong::tile::Tile; use jong::tile::Tile;
@ -22,7 +20,7 @@ pub(crate) struct PickRegion {
pub(crate) area: Rect, pub(crate) area: Rect,
} }
fn render_tile(tile: &Tile, hovered: bool) -> (Paragraph<'_>) { fn render_tile(tile: &Tile, hovered: bool) -> Paragraph<'_> {
let block = ratatui::widgets::Block::bordered(); let block = ratatui::widgets::Block::bordered();
let mut widget = Paragraph::new(match &tile.suit { let mut widget = Paragraph::new(match &tile.suit {
jong::tile::Suit::Pin(rank) => format!("{}\np", rank.0), jong::tile::Suit::Pin(rank) => format!("{}\np", rank.0),
@ -99,32 +97,35 @@ pub(crate) fn render(
pub(crate) fn render_hands( pub(crate) fn render_hands(
mut commands: Commands, mut commands: Commands,
mut tui: ResMut<RatatuiContext>, mut tui: ResMut<RatatuiContext>,
hovered: Query<Entity, With<Hovered>>, hovered: Query<Entity, With<Hovered>>,
layouts: Res<HandLayouts>, layouts: Res<HandLayouts>,
mainplayer: Single<(&Player, &Wind), With<MainPlayer>>,
players: Populated<(&Player, /* &Wind, */ &Children)>, tiles: Query<&Tile>,
hands: Populated<&Children, (With<Hand>, Without<Player>)>, main_player: Single<(&Player, &Wind), With<MainPlayer>>,
tiles: Populated<&Tile>, curr_player: Single<Entity, With<CurrentPlayer>>,
drawn_tile: Option<Single<(&Player, &DrawnTile, Entity)>>, players: Query<(&Player, &Children)>,
hands: Query<(&Children, Entity), (With<Hand>, Without<Player>)>,
drawn_tile: Option<Single<(&Player, Entity), With<Drawn>>>,
) -> Result { ) -> Result {
let mut frame = tui.get_frame(); let mut frame = tui.get_frame();
debug_blocks(*layouts, &mut frame); debug_blocks(*layouts, &mut frame);
for (player, /* wind, */ hand_ent) in players { for (hand, hand_ent) in hands {
let hand = hand_ent.iter().next().unwrap(); // debug!("{hand:?}");
let hand: Vec<_> = hands let (player, _) = players.iter().find(|(_, c)| c.contains(&hand_ent)).unwrap();
.get(hand)? let hand: Vec<_> = hand
.iter() .iter()
.map(|entity| -> Result<_> { .map(|entity| -> Result<_> {
let tile = tiles.get(entity)?; let tile = tiles.get(entity).unwrap_or_else(|_| panic!("{entity:?}"));
// let paragraph = render_tile(tile,);
let hovered = hovered.contains(entity); let hovered = hovered.contains(entity);
let widget = render_tile(tile, hovered); let widget = render_tile(tile, hovered);
Ok((entity, widget, hovered)) Ok((entity, widget, hovered))
}) })
.collect::<Result<_>>()?; .collect::<Result<_>>()?;
if player == mainplayer.0 { if player == main_player.0 {
// split main box into thirds // split main box into thirds
let mut this_hand = layouts.this_hand; let mut this_hand = layouts.this_hand;
let this_drawer = drawn_tile.as_ref().is_some_and(|dt| dt.0 == player); let this_drawer = drawn_tile.as_ref().is_some_and(|dt| dt.0 == player);
@ -169,14 +170,14 @@ pub(crate) fn render_hands(
// tsumo tile // tsumo tile
if this_drawer { if this_drawer {
let (_, tile, entity) = **drawn_tile.as_ref().unwrap(); let (_, entity) = **drawn_tile.as_ref().unwrap();
let mut area = drawn_area.resize(Size { let mut area = drawn_area.resize(Size {
width: 5, width: 5,
height: 4, height: 4,
}); });
area = area.offset(Offset { x: 2, y: 0 }); area = area.offset(Offset { x: 2, y: 0 });
let hovered = hovered.contains(entity); let hovered = hovered.contains(entity);
let widget = render_tile(tiles.get(tile.0)?, hovered); let widget = render_tile(tiles.get(entity)?, hovered);
if hovered { if hovered {
area = area.offset(Offset { x: 0, y: -1 }); area = area.offset(Offset { x: 0, y: -1 });
let mut hitbox = area.as_size(); let mut hitbox = area.as_size();