Compare commits

...

3 commits

Author SHA1 Message Date
Tao Tien
7cbe10c19e stash 2026-01-19 22:41:07 -08:00
Tao Tien
faa653b012 render drawn, discard drawn 2026-01-18 12:28:09 -08:00
Tao Tien
50ccd7fcb2 detect hand children change instead 2026-01-18 11:05:37 -08:00
5 changed files with 90 additions and 26 deletions

View file

@ -2,7 +2,7 @@ use bevy::prelude::*;
use crate::{ use crate::{
game::{ game::{
hand::Hand, hand::{Hand, Pond},
player::{CurrentPlayer, MainPlayer}, player::{CurrentPlayer, MainPlayer},
round::{TurnState, Wind}, round::{TurnState, Wind},
wall::Wall, wall::Wall,
@ -27,6 +27,17 @@ pub enum GameState {
#[derive(Message)] #[derive(Message)]
pub enum GameMessage { pub enum GameMessage {
Discarded(Entity), Discarded(Entity),
CallPending,
Called { player: Entity, calltype: Entity },
}
impl GameMessage {
pub(crate) fn is_called(&self) -> bool {
match self {
GameMessage::Called { .. } => true,
_ => false,
}
}
} }
pub struct Riichi; pub struct Riichi;
@ -47,9 +58,9 @@ impl Plugin for Riichi {
.add_systems(OnEnter(TurnState::Menzen), round::menzen) .add_systems(OnEnter(TurnState::Menzen), round::menzen)
.add_systems(Update, round::riichi_kan.run_if(in_state(TurnState::RiichiKan))) .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(Update, round::discard.run_if(in_state(TurnState::Discard)))
.add_systems(Update, round::ron_chi_pon_kan.run_if(in_state(TurnState::RonChiiPonKan))) .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) .add_systems(OnEnter(TurnState::End), round::end)
// .add_systems(Update, systems)
// semicolon stopper // semicolon stopper
; ;
} }
@ -72,6 +83,7 @@ pub(crate) fn setup(
player, player,
points, points,
Hand, Hand,
Pond,
Wind::from_repr((i - 1) as usize).unwrap(), Wind::from_repr((i - 1) as usize).unwrap(),
); );

View file

@ -10,6 +10,9 @@ use crate::{
#[derive(Component)] #[derive(Component)]
pub struct Hand; pub struct Hand;
#[derive(Component)]
pub struct Pond;
#[derive(Component)] #[derive(Component)]
pub struct Drawn; pub struct Drawn;
@ -25,8 +28,8 @@ pub struct Discarded;
// } // }
pub(crate) fn sort_hands( pub(crate) fn sort_hands(
tiles: Populated<&Tile>, tiles: Query<&Tile>,
hands: Populated<&mut Children, (Changed<Hand>, Without<Player>)>, hands: Query<&mut Children, (Changed<Children>, With<Hand>, Without<Player>)>,
) -> Result { ) -> Result {
for mut hand in hands { for mut hand in hands {
hand.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit); hand.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit);

View file

@ -1,11 +1,13 @@
use bevy::prelude::*; use std::rc::Weak;
use bevy::{platform::collections::HashMap, prelude::*};
use strum::{EnumCount, FromRepr}; use strum::{EnumCount, FromRepr};
use crate::{ use crate::{
EnumNextCycle, EnumNextCycle,
game::{ game::{
GameMessage, GameState, GameMessage, GameState,
hand::{Discarded, Drawn, Hand}, hand::{Discarded, Drawn, Hand, Pond},
player::{CurrentPlayer, Player}, player::{CurrentPlayer, Player},
wall::Wall, wall::Wall,
}, },
@ -58,6 +60,15 @@ pub(crate) enum TurnState {
End, End,
} }
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum CallType {
Skip,
Ron,
Chii,
Pon,
Kan,
}
impl Default for MatchSettings { impl Default for MatchSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -162,24 +173,23 @@ pub(crate) fn discard(
mut next_turnstate: ResMut<NextState<TurnState>>, mut next_turnstate: ResMut<NextState<TurnState>>,
) -> Result { ) -> Result {
// trace!("discard"); // trace!("discard");
let curr_hand = hands.get_mut(players.get(*curr_player)?.iter().next().unwrap())?; let (handtiles, hand) = hands.get_mut(players.get(*curr_player)?.iter().next().unwrap())?;
let mut done = false; let mut done = false;
while let Some(message) = reader.read().next() { while let Some(message) = reader.read().next() {
if let GameMessage::Discarded(discarded) = message { if let GameMessage::Discarded(discarded) = message {
debug!("discarded: {discarded:?}");
if *discarded == *drawn { if *discarded == *drawn {
} else if curr_hand.0.contains(discarded) { } else if handtiles.contains(discarded) {
commands commands
.entity(curr_hand.1) .entity(hand)
.remove_child(*discarded) .remove_child(*discarded)
.add_child(*drawn); .add_child(*drawn);
} else { } else {
panic!("current hand nor drawn tile contains discarded tile") panic!("current hand nor drawn tile contains discarded tile")
} }
commands commands.entity(*drawn).remove::<Drawn>();
.entity(*discarded) commands.entity(*discarded).insert(Discarded);
.remove::<Drawn>()
.insert(Discarded);
done = true; done = true;
break; break;
@ -192,10 +202,45 @@ pub(crate) fn discard(
Ok(()) Ok(())
} }
#[derive(Resource)]
pub struct PendingCalls {
eligible: Vec<Entity>,
calls: HashMap<Entity, CallType>,
}
pub(crate) fn notify_callable() {}
pub(crate) fn ron_chi_pon_kan( pub(crate) fn ron_chi_pon_kan(
mut commands: Commands,
mut reader: MessageReader<GameMessage>,
discarded: Single<Entity, With<Discarded>>,
mut ponds: Query<(&Children, Entity), (With<Pond>, Without<Player>)>,
calls: Query<&CallType>,
curr_turnstate: Res<State<TurnState>>, curr_turnstate: Res<State<TurnState>>,
mut next_turnstate: ResMut<NextState<TurnState>>, mut next_turnstate: ResMut<NextState<TurnState>>,
) { ) {
// check if can call?
// message players?
// collect then prioritize
// let mut received = vec![];
let mut received: Vec<_> = reader
.read()
.filter_map(|m| {
if let GameMessage::Called { player, calltype } = m
&& let Ok(calltype) = calls.get(*calltype)
{
Some((calltype, player))
} else {
None
}
})
.collect();
received.sort_unstable_by_key(|(c, t)| c);
// received.sort_unstable_by_key(|m| m.);
next_turnstate.set(curr_turnstate.next()); next_turnstate.set(curr_turnstate.next());
} }

View file

@ -1,13 +1,12 @@
use std::time::Duration; use std::time::Duration;
use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin, time::TimePlugin}; use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin};
use bevy_ratatui::RatatuiPlugins; use bevy_ratatui::RatatuiPlugins;
use jong::game::{ use jong::game::{
GameMessage, GameState, GameMessage, GameState,
hand::{Drawn, Hand}, hand::{Drawn, Hand},
player::{CurrentPlayer, Player}, player::{CurrentPlayer, Player},
}; };
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};

View file

@ -94,6 +94,7 @@ pub(crate) fn render(
Ok(()) Ok(())
} }
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn render_hands( pub(crate) fn render_hands(
mut commands: Commands, mut commands: Commands,
mut tui: ResMut<RatatuiContext>, mut tui: ResMut<RatatuiContext>,
@ -102,18 +103,21 @@ pub(crate) fn render_hands(
layouts: Res<HandLayouts>, layouts: Res<HandLayouts>,
tiles: Query<&Tile>, tiles: Query<&Tile>,
main_player: Single<(&Player, &Wind), With<MainPlayer>>, main_player: Single<(&Player, Entity, &Wind), With<MainPlayer>>,
curr_player: Single<Entity, With<CurrentPlayer>>, curr_player: Single<Entity, With<CurrentPlayer>>,
players: Query<(&Player, &Children)>, players: Query<(&Player, Entity, &Children)>,
hands: Query<(&Children, Entity), (With<Hand>, Without<Player>)>, hands: Query<(&Children, Entity), (With<Hand>, Without<Player>)>,
drawn_tile: Option<Single<(&Player, Entity), With<Drawn>>>, drawn_tile: Single<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 (hand, hand_ent) in hands { for (hand, hand_ent) in hands {
// debug!("{hand:?}"); // debug!("{hand:?}");
let (player, _) = players.iter().find(|(_, c)| c.contains(&hand_ent)).unwrap(); let (player, player_ent, _) = players
.iter()
.find(|(_, e, c)| c.contains(&hand_ent))
.unwrap();
let hand: Vec<_> = hand let hand: Vec<_> = hand
.iter() .iter()
.map(|entity| -> Result<_> { .map(|entity| -> Result<_> {
@ -128,7 +132,8 @@ pub(crate) fn render_hands(
if player == main_player.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..is_some_and(|dt| dt.0 == player);
let this_drawer = player_ent == *curr_player;
let tile_drawn = if this_drawer { 7 } else { 0 }; let tile_drawn = if this_drawer { 7 } else { 0 };
let hand_draw_meld = Layout::horizontal([ let hand_draw_meld = Layout::horizontal([
Constraint::Max(hand.len() as u16 * 5), Constraint::Max(hand.len() as u16 * 5),
@ -170,23 +175,23 @@ pub(crate) fn render_hands(
// tsumo tile // tsumo tile
if this_drawer { if this_drawer {
let (_, entity) = **drawn_tile.as_ref().unwrap(); // trace!("this_drawer");
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(*drawn_tile);
let widget = render_tile(tiles.get(entity)?, hovered); let widget = render_tile(tiles.get(*drawn_tile)?, 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();
hitbox.height += 1; hitbox.height += 1;
commands.entity(entity).insert(PickRegion { commands.entity(*drawn_tile).insert(PickRegion {
area: area.resize(hitbox), area: area.resize(hitbox),
}); });
} else { } else {
commands.entity(entity).insert(PickRegion { area }); commands.entity(*drawn_tile).insert(PickRegion { area });
} }
frame.render_widget(widget, area); frame.render_widget(widget, area);
} }