diff --git a/src/game/hand.rs b/src/game/hand.rs index f17b6ba..424d9b4 100644 --- a/src/game/hand.rs +++ b/src/game/hand.rs @@ -1,28 +1,58 @@ -use bevy::{ecs::relationship::RelationshipSourceCollection, prelude::*}; +use bevy::prelude::*; use crate::{ - game::{player::Player /* wall::WallTiles */}, + game::{player::Player, wall::WallTiles}, tiles::Tile, }; #[derive(Component)] pub struct Hand; -// #[derive(Component, Default)] -// enum SortHand { -// #[default] -// Unsorted, -// Sort, -// Manual, -// } +#[derive(Component)] +#[relationship(relationship_target = HandTiles)] +pub struct InHand(pub Entity); -pub(crate) fn sort_hands( - tiles: Populated<&Tile>, - mut hands: Populated<&mut Children, (Changed, Without)>, +#[derive(Component)] +#[relationship_target(relationship = InHand, linked_spawn)] +pub struct HandTiles(Vec); + +pub(crate) fn deal_hands( + mut commands: Commands, + walltiles: Single<&WallTiles>, + walltiles_entity: Single>, + players: Populated>, ) -> Result { - for mut hand in hands { - hand.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit); - debug!("sorted: {hand:?}"); + let mut wall = walltiles.iter().collect::>(); + + for player_entity in players { + let hand = wall.split_off(13); + + commands + .get_entity(*walltiles_entity)? + .remove_children(&hand); + + let handtiles = commands.spawn((Hand, HandTiles(hand))).id(); + + commands + .get_entity(player_entity)? + .add_children(&[handtiles]); } + + trace!("dealt hands"); + Ok(()) +} + +#[allow(clippy::type_complexity)] +pub(crate) fn sort_hands( + mut commands: Commands, + tiles: Populated<&Tile>, + mut hands: Populated<&mut Children, Changed>, +) -> Result { + for (mut children) in hands { + children.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit); + trace!("sorted a hand") + } + + trace!("sort_hands"); Ok(()) } diff --git a/src/game/mod.rs b/src/game/mod.rs index 8d41908..6b3b83f 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,17 +1,12 @@ use bevy::prelude::*; use crate::{ - game::{ - hand::Hand, - player::{MainPlayer, Player}, - wall::Wall, - }, + game::player::MainPlayer, tiles::{self, *}, }; pub mod hand; pub mod player; -pub mod round; pub mod wall; #[derive(States, Default, Hash, Clone, Eq, Debug, PartialEq, Copy)] @@ -19,85 +14,98 @@ pub enum GameState { #[default] None, Setup, - Deal, + // Deal, Play, + Score, } pub struct Riichi; impl Plugin for Riichi { fn build(&self, app: &mut App) { - app - // start stopper - .init_state::() - .init_resource::() - .init_resource::() + app.init_resource::() + .add_systems(Startup, init_match) .add_systems(Startup, tiles::init_tiles) - .add_systems(OnEnter(GameState::Setup), setup) - .add_systems(OnEnter(GameState::Deal), shuffle_deal) - .add_systems(Update, hand::sort_hands.run_if(in_state(GameState::Play))) + .init_state::() + .add_systems(OnEnter(GameState::Setup), (wall::build_wall, hand::deal_hands, setup_done).chain()) + .add_systems(Update, (hand::sort_hands).run_if(in_state(GameState::Play))) // semicolon stopper ; } } -fn shuffle_deal( - mut commands: Commands, - tiles: Populated>, - players: Populated>, - wall_ent: Single>, - mut next_gamestate: ResMut>, -) -> Result { - use rand::seq::SliceRandom; - - let mut rng = rand::rng(); - let mut walltiles: Vec<_> = tiles.iter().collect(); - walltiles.shuffle(&mut rng); - - // commands.get_entity(*wall_ent)?.replace_children(&walltiles); - - for player_ent in players { - 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 - .get_entity(player_ent)? - .replace_children(&[hand_ent]); - - debug!("dealt to player_ent {player_ent:?}"); - } - - commands.get_entity(*wall_ent)?.replace_children(&walltiles); - - next_gamestate.set(GameState::Play); - Ok(()) +fn setup_done(mut next: ResMut>) { + next.set(GameState::Play); + trace!("setup_done"); } -pub(crate) fn setup( - mut commands: Commands, - matchsettings: Res, - // mut compass: ResMut - tiles: Query>, - mut next_gamestate: ResMut>, -) { - for i in 1..=matchsettings.player_count { - let player = player::Player { - name: format!("Player {}", i), - }; - let points = player::Points(matchsettings.starting_points); +#[derive(Component)] +pub(crate) struct Dice(u8, u8); - let bundle = (player, points, Hand); +#[derive(Resource)] +pub(crate) struct Compass { + pub(crate) prevalent_wind: Wind, + pub(crate) round: u8, + pub(crate) dealer_wind: Wind, + pub(crate) riichi: usize, + pub(crate) honba: usize, +} - if i == 1 { - commands.spawn((bundle, MainPlayer)); - } else { - commands.spawn(bundle); +impl Default for Compass { + fn default() -> Self { + Self { + prevalent_wind: Wind::Ton, + round: 1, + dealer_wind: Wind::Ton, + riichi: 0, + honba: 0, } } - - commands.spawn(Wall); - - next_gamestate.set(GameState::Deal); +} + +#[derive(Resource)] +pub(crate) struct MatchSettings { + pub(crate) starting_points: isize, + pub(crate) player_count: u8, +} + +pub(crate) fn next_round(_compass: Res) {} + +pub(crate) fn init_match( + mut commands: Commands, + // mut compass: ResMut +) { + let starting = 25000; + let player_count = 4; + + commands.insert_resource(MatchSettings { + starting_points: starting, + player_count, + }); + + let players = (2..=player_count) + .map(|i| { + ( + player::Player { + name: format!("Player {i}"), + }, + player::Points(starting), + ) + }) + .collect::>(); + commands.spawn_batch(players); + let main_player = ( + player::Player { + name: format!("Player {}", 1), + }, + player::Points(starting), + ); + commands.spawn((main_player, MainPlayer)); + + // *compass = Compass { + // prevalent_wind: Wind::Ton, + // round: 1, + // dealer_wind: todo!(), + // riichi: 0, + // honba: 0, + // } } diff --git a/src/game/player.rs b/src/game/player.rs index c7c3e6c..a99e5cd 100644 --- a/src/game/player.rs +++ b/src/game/player.rs @@ -1,6 +1,11 @@ use bevy::prelude::*; -#[derive(Component, Debug, PartialEq)] +use crate::{ + game::wall::{InWall, Wall}, + tiles::Tile, +}; + +#[derive(Component)] pub struct Player { pub name: String, } diff --git a/src/game/round.rs b/src/game/round.rs deleted file mode 100644 index fa4873b..0000000 --- a/src/game/round.rs +++ /dev/null @@ -1,42 +0,0 @@ -use bevy::prelude::*; - -use crate::tiles::*; - -#[derive(Component)] -pub(crate) struct Dice(u8, u8); - -#[derive(Resource)] -pub(crate) struct Compass { - pub(crate) prevalent_wind: Wind, - pub(crate) round: u8, - pub(crate) dealer_wind: Wind, - pub(crate) riichi: usize, - pub(crate) honba: usize, -} - -impl Default for Compass { - fn default() -> Self { - Self { - prevalent_wind: Wind::Ton, - round: 1, - dealer_wind: Wind::Ton, - riichi: 0, - honba: 0, - } - } -} - -#[derive(Resource)] -pub(crate) struct MatchSettings { - pub(crate) starting_points: isize, - pub(crate) player_count: u8, -} - -impl Default for MatchSettings { - fn default() -> Self { - Self { - starting_points: 25000, - player_count: 4, - } - } -} diff --git a/src/game/wall.rs b/src/game/wall.rs index 4463c26..69e4fd7 100644 --- a/src/game/wall.rs +++ b/src/game/wall.rs @@ -5,3 +5,22 @@ use crate::tiles::Tile; #[derive(Component)] pub struct Wall; + +#[derive(Component)] +#[relationship_target(relationship = InWall, linked_spawn)] +pub struct WallTiles(Vec); + +#[derive(Component)] +#[relationship(relationship_target = WallTiles)] +pub struct InWall(pub Entity); + +pub(crate) fn build_wall(mut commands: Commands, tiles: Query>) { + let mut rng = rand::rng(); + + let mut shuffled = tiles.iter().collect::>(); + shuffled.shuffle(&mut rng); + + commands.spawn((Wall, WallTiles(shuffled))); + + trace!("build_wall"); +} diff --git a/src/tiles.rs b/src/tiles.rs index 40879fe..5147233 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -37,29 +37,27 @@ pub enum Dragon { pub struct Dora; pub fn init_tiles(mut commands: Commands) { - let mut tiles = vec![]; for _ in 0..4 { for i in 1..=9 { - tiles.push(Tile { + commands.spawn(Tile { suit: Suit::Pin(Rank(i)), }); - tiles.push(Tile { + commands.spawn(Tile { suit: Suit::Sou(Rank(i)), }); - tiles.push(Tile { + commands.spawn(Tile { suit: Suit::Man(Rank(i)), }); } for i in 0..4 { - tiles.push(Tile { + commands.spawn(Tile { suit: Suit::Wind(Wind::from_repr(i).unwrap()), }); } for i in 0..3 { - tiles.push(Tile { + commands.spawn(Tile { suit: Suit::Dragon(Dragon::from_repr(i).unwrap()), }); } } - commands.spawn_batch(tiles); } diff --git a/src/tui/input.rs b/src/tui/input.rs deleted file mode 100644 index 44947c3..0000000 --- a/src/tui/input.rs +++ /dev/null @@ -1,79 +0,0 @@ -use bevy::prelude::*; -use bevy_ratatui::event::{KeyMessage, MouseMessage}; - -use jong::game::GameState; - -use crate::tui::{TuiState, console::ConsoleState}; - -#[allow(clippy::too_many_arguments)] -pub(crate) fn kb_input_system( - mut kb_messages: MessageReader, - - curr_tuistate: Res>, - curr_consolestate: Res>, - curr_gamestate: Res>, - - mut next_tuistate: ResMut>, - mut next_consolestate: ResMut>, - mut next_gamestate: ResMut>, - - mut exit: MessageWriter, -) { - use bevy_ratatui::crossterm::event::KeyCode; - - let (ts, cs, gs) = ( - curr_tuistate.get(), - curr_consolestate.get(), - curr_gamestate.get(), - ); - - for message in kb_messages.read() { - if let KeyCode::Char('`') = message.code { - next_consolestate.set(!*curr_consolestate.get()); - continue; - } - - if *cs == ConsoleState::Open { - let mut passthrough = false; - match message.code { - KeyCode::Up => todo!(), - KeyCode::Down => todo!(), - KeyCode::Home => todo!(), - KeyCode::End => todo!(), - KeyCode::PageUp => todo!(), - KeyCode::PageDown => todo!(), - KeyCode::Esc => next_consolestate.set(ConsoleState::Closed), - _ => passthrough = true, - } - if !passthrough { - continue; - } - } - - match ts { - TuiState::MainMenu => match message.code { - KeyCode::Char('p') => { - next_tuistate.set(TuiState::InGame); - next_gamestate.set(GameState::Setup); - } - KeyCode::Char('q') => { - exit.write_default(); - } - _ => {} - }, - TuiState::InGame => match gs { - GameState::Setup => match message.code { - _ => {} - }, - GameState::Play => match message.code { - KeyCode::Char('q') => { - exit.write_default(); - } - _ => {} - }, - _ => todo!(), - _ => unreachable!("TuiState::InGame but GameState invalid"), - }, - } - } -} diff --git a/src/tui/menu.rs b/src/tui/menu.rs index 104e193..3ee27e5 100644 --- a/src/tui/menu.rs +++ b/src/tui/menu.rs @@ -10,8 +10,13 @@ use jong::game::GameState; use crate::tui::TuiState; const MAINMENU_OPTIONS: [&str; 2] = ["(p)lay", "(q)uit"]; +// const MAINMENU_INPUTS: [char;2] = ['p', 'q']; -pub(crate) fn draw_mainmenu(mut tui_ctx: ResMut) { +pub(crate) fn draw_mainmenu( + mut tui_ctx: ResMut, + // mut tui_state: ResMut>, + // mut game_state: ResMut>, +) { let options = MAINMENU_OPTIONS; let layout = Layout::vertical(vec![Constraint::Min(1); options.len()]); diff --git a/src/tui/mod.rs b/src/tui/mod.rs index f96a68a..cde1cf2 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -2,17 +2,17 @@ use std::time::Duration; use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin}; use bevy_ratatui::RatatuiPlugins; +use bevy_ratatui::event::KeyMessage; use ratatui::{text::ToSpan, widgets::Paragraph}; use jong::game::GameState; -// use jong::game::wall::InWall; +use jong::game::wall::InWall; -use crate::tui::{console::ConsoleState, menu::draw_mainmenu, render::ingame::draw_ingame}; +use crate::tui::console::ConsoleState; mod console; mod menu; mod render; -mod input; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)] pub(crate) enum TuiState { @@ -54,12 +54,12 @@ impl Plugin for RiichiTui { // console .init_state::() - .add_systems(Update, console::draw_console.after_ignore_deferred(draw_mainmenu).after_ignore_deferred(draw_ingame).run_if(in_state(console::ConsoleState::Open))) + .add_systems(Update, console::draw_console.run_if(in_state(console::ConsoleState::Open))) // general setup .init_state::() .add_computed_state::() - .add_systems(Update, input::kb_input_system) + .add_systems(Update, input_system) // main menu .add_systems(Update, menu::draw_mainmenu.run_if(in_state(TuiState::MainMenu))) @@ -67,11 +67,62 @@ impl Plugin for RiichiTui { // gaming .init_resource::() .add_systems(Update, render::ingame::draw_ingame.run_if(in_state(InGame))) - .add_systems(Update, render::hand::render_hand.run_if(in_state(InGame).and(in_state(GameState::Play)))) + .add_systems(Update, render::hand::render_changed_hand.run_if(in_state(InGame).and(in_state(GameState::Play)))) // semicolon stopper ; } } +#[allow(clippy::too_many_arguments)] +pub(crate) fn input_system( + mut messages: MessageReader, + + curr_tuistate: Res>, + curr_consolestate: Res>, + curr_gamestate: Res>, + + mut next_tuistate: ResMut>, + mut next_consolestate: ResMut>, + mut next_gamestate: ResMut>, + + mut exit: MessageWriter, +) { + use bevy_ratatui::crossterm::event::KeyCode; + + let (ts, cs, gs) = (curr_tuistate.get(), curr_consolestate.get(), curr_gamestate.get()); + + for message in messages.read() { + if let KeyCode::Char('`') = message.code { + next_consolestate.set(!*curr_consolestate.get()); + continue + } + + match ts { + TuiState::MainMenu => match message.code { + KeyCode::Char('p') => { + next_tuistate.set(TuiState::InGame); + next_gamestate.set(GameState::Setup); + } + KeyCode::Char('q') => { + exit.write_default(); + } + _ => {} + }, + TuiState::InGame => match gs { + GameState::Setup => match message.code { + _ => {} + }, + GameState::Play => match message.code { + KeyCode::Char('q') => { + exit.write_default(); + } + _ => {} + }, + GameState::Score => todo!(), + _ => unreachable!("TuiState::InGame but GameState invalid") + }, + } + } +} diff --git a/src/tui/render/hand.rs b/src/tui/render/hand.rs index a1b5ebc..d2fcc69 100644 --- a/src/tui/render/hand.rs +++ b/src/tui/render/hand.rs @@ -1,35 +1,32 @@ -use bevy::{platform::collections::HashMap, prelude::*}; +use bevy::prelude::*; use ratatui::widgets::Paragraph; -use jong::{ - game::{hand::Hand, player::Player}, - tiles::Tile, -}; +use jong::game::hand::HandTiles; +use jong::tiles::Tile; use crate::tui::render::tiles; #[derive(Resource, Default)] -pub(crate) struct RenderedHand(pub(crate) HashMap>>); +pub(crate) struct RenderedHand(pub(crate) Vec>>); -pub(crate) fn render_hand( +pub(crate) fn render_changed_hand( + hands: Populated<&Children, Changed>, tiles: Populated<&Tile>, - player_hands: Populated<(Entity, &Children), (With, Changed)>, - hands: Populated<&Children, (Changed, Without)>, mut target: ResMut, ) -> Result { - let mut rendered = HashMap::new(); + let mut rendered = vec![]; - for (player_ent, hand) in player_hands { - let hand = hand.iter().next().unwrap(); - let tiles = hands - .get(hand)? + for hand in hands { + let tiles = hand .iter() - .map(|it| tiles.get(it).map(tiles::draw_tile).unwrap()) + .map(|inhand| tiles.get(inhand).map(tiles::draw_tile).unwrap()) .collect(); - rendered.insert(player_ent, tiles); + + rendered.push(tiles); } target.0 = rendered; + trace!("render_changed_hand"); Ok(()) } diff --git a/src/tui/render/ingame.rs b/src/tui/render/ingame.rs index e573cb9..b8e01cc 100644 --- a/src/tui/render/ingame.rs +++ b/src/tui/render/ingame.rs @@ -1,12 +1,10 @@ use bevy::prelude::*; use bevy_ratatui::RatatuiContext; -use jong::game::player::{MainPlayer, Player}; use crate::tui::render::hand; pub(crate) fn draw_ingame( rendered_hand: Res, - main_player: Single, With)>, mut tui_ctx: ResMut, ) -> Result { use ratatui::layout::Flex; @@ -19,9 +17,8 @@ pub(crate) fn draw_ingame( let mut area = frame.area(); area.height = 4; let areas = layout.areas::<13>(area); - // if let Some(hand) = rendered_hand.0.get(&*main_player) { - if let Some(hand) = rendered_hand.0.get(&*main_player) { - for (tile, area) in hand.iter().zip(areas.iter()) { + if let Some(tiles) = rendered_hand.0.first() { + for (tile, area) in tiles.iter().zip(areas.iter()) { frame.render_widget(tile, *area); } }