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"
tui-logger = { version = "0.18.0", features = ["tracing-support", "crossterm"] }
[dev-dependencies]
bevy = { version = "0.17.3", features = ["track_location"] }
[profile.dev]
opt-level = 1

View file

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

View file

@ -10,11 +10,11 @@ use crate::{
#[derive(Component)]
pub struct Hand;
#[derive(Component, Debug)]
pub struct DrawnTile(pub Entity);
#[derive(Component)]
pub struct Drawn;
#[derive(Component)]
pub struct DiscardedTile(pub Entity);
pub struct Discarded;
// #[derive(Component, Default)]
// enum SortHand {
@ -48,19 +48,16 @@ pub(crate) fn shuffle_deal(
let mut walltiles: Vec<_> = tiles.iter().collect();
walltiles.shuffle(&mut rng);
// commands.get_entity(*wall_ent)?.replace_children(&walltiles);
for player_ent in players {
debug!("deal to player_ent {player_ent:?}");
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();
debug!("hand_ent: {hand_ent:?}");
commands.entity(player_ent).replace_children(&[hand_ent]);
commands.entity(player_ent).add_child(hand_ent);
debug!("deal to player_ent {player_ent:?} {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);
next_gamestate.set(GameState::Play);

View file

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

View file

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

View file

@ -1,13 +1,13 @@
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 jong::game::{
GameMessage, GameState,
hand::{DrawnTile, Hand},
player::{MainPlayer, Player},
round::{CurrentPlayer, Discard},
hand::{Drawn, Hand},
player::{CurrentPlayer, Player},
};
use tracing::instrument;
use tui_logger::TuiWidgetState;
use crate::tui::{input::ConfirmSelect, states::ConsoleWidget};
@ -72,17 +72,25 @@ impl Plugin for TuiPlugin {
fn discard_tile(
mut writer: MessageWriter<GameMessage>,
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>>,
hands: Populated<&Children, (With<Hand>, Without<Player>)>,
) {
let curr = currplayer.0;
let hand_ent = player_hands.get(curr).unwrap().1.iter().next().unwrap();
// trace!("discard_tile");
let hand_ent = player_hands
.get(*curr_player)
.unwrap()
.1
.iter()
.next()
.unwrap();
let hand = hands.get(hand_ent).unwrap();
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));
}

View file

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

View file

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