diff --git a/src/game/hand.rs b/src/game/hand.rs index 424d9b4..f17b6ba 100644 --- a/src/game/hand.rs +++ b/src/game/hand.rs @@ -1,58 +1,28 @@ -use bevy::prelude::*; +use bevy::{ecs::relationship::RelationshipSourceCollection, prelude::*}; use crate::{ - game::{player::Player, wall::WallTiles}, + game::{player::Player /* wall::WallTiles */}, tiles::Tile, }; #[derive(Component)] pub struct Hand; -#[derive(Component)] -#[relationship(relationship_target = HandTiles)] -pub struct InHand(pub Entity); +// #[derive(Component, Default)] +// enum SortHand { +// #[default] +// Unsorted, +// Sort, +// Manual, +// } -#[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 { - 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>, + mut hands: Populated<&mut Children, (Changed, Without)>, ) -> Result { - for (mut children) in hands { - children.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit); - trace!("sorted a hand") + for mut hand in hands { + hand.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit); + debug!("sorted: {hand:?}"); } - - trace!("sort_hands"); Ok(()) } diff --git a/src/game/mod.rs b/src/game/mod.rs index 6b3b83f..8d41908 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,12 +1,17 @@ use bevy::prelude::*; use crate::{ - game::player::MainPlayer, + game::{ + hand::Hand, + player::{MainPlayer, Player}, + wall::Wall, + }, tiles::{self, *}, }; pub mod hand; pub mod player; +pub mod round; pub mod wall; #[derive(States, Default, Hash, Clone, Eq, Debug, PartialEq, Copy)] @@ -14,98 +19,85 @@ pub enum GameState { #[default] None, Setup, - // Deal, + Deal, Play, - Score, } pub struct Riichi; impl Plugin for Riichi { fn build(&self, app: &mut App) { - app.init_resource::() - .add_systems(Startup, init_match) - .add_systems(Startup, tiles::init_tiles) + app + // start stopper .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))) + .init_resource::() + .init_resource::() + .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))) // semicolon stopper ; } } -fn setup_done(mut next: ResMut>) { - next.set(GameState::Play); - trace!("setup_done"); +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(()) } -#[derive(Component)] -pub(crate) struct Dice(u8, u8); +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(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, -} + let bundle = (player, points, Hand); -impl Default for Compass { - fn default() -> Self { - Self { - prevalent_wind: Wind::Ton, - round: 1, - dealer_wind: Wind::Ton, - riichi: 0, - honba: 0, + if i == 1 { + commands.spawn((bundle, MainPlayer)); + } else { + commands.spawn(bundle); } } -} - -#[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, - // } + + commands.spawn(Wall); + + next_gamestate.set(GameState::Deal); } diff --git a/src/game/player.rs b/src/game/player.rs index a99e5cd..c7c3e6c 100644 --- a/src/game/player.rs +++ b/src/game/player.rs @@ -1,11 +1,6 @@ use bevy::prelude::*; -use crate::{ - game::wall::{InWall, Wall}, - tiles::Tile, -}; - -#[derive(Component)] +#[derive(Component, Debug, PartialEq)] pub struct Player { pub name: String, } diff --git a/src/game/round.rs b/src/game/round.rs new file mode 100644 index 0000000..fa4873b --- /dev/null +++ b/src/game/round.rs @@ -0,0 +1,42 @@ +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 69e4fd7..4463c26 100644 --- a/src/game/wall.rs +++ b/src/game/wall.rs @@ -5,22 +5,3 @@ 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 5147233..40879fe 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -37,27 +37,29 @@ 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 { - commands.spawn(Tile { + tiles.push(Tile { suit: Suit::Pin(Rank(i)), }); - commands.spawn(Tile { + tiles.push(Tile { suit: Suit::Sou(Rank(i)), }); - commands.spawn(Tile { + tiles.push(Tile { suit: Suit::Man(Rank(i)), }); } for i in 0..4 { - commands.spawn(Tile { + tiles.push(Tile { suit: Suit::Wind(Wind::from_repr(i).unwrap()), }); } for i in 0..3 { - commands.spawn(Tile { + tiles.push(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 new file mode 100644 index 0000000..44947c3 --- /dev/null +++ b/src/tui/input.rs @@ -0,0 +1,79 @@ +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 3ee27e5..104e193 100644 --- a/src/tui/menu.rs +++ b/src/tui/menu.rs @@ -10,13 +10,8 @@ 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, - // mut tui_state: ResMut>, - // mut game_state: ResMut>, -) { +pub(crate) fn draw_mainmenu(mut tui_ctx: 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 cde1cf2..f96a68a 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; +use crate::tui::{console::ConsoleState, menu::draw_mainmenu, render::ingame::draw_ingame}; 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.run_if(in_state(console::ConsoleState::Open))) + .add_systems(Update, console::draw_console.after_ignore_deferred(draw_mainmenu).after_ignore_deferred(draw_ingame).run_if(in_state(console::ConsoleState::Open))) // general setup .init_state::() .add_computed_state::() - .add_systems(Update, input_system) + .add_systems(Update, input::kb_input_system) // main menu .add_systems(Update, menu::draw_mainmenu.run_if(in_state(TuiState::MainMenu))) @@ -67,62 +67,11 @@ 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_changed_hand.run_if(in_state(InGame).and(in_state(GameState::Play)))) + .add_systems(Update, render::hand::render_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 d2fcc69..a1b5ebc 100644 --- a/src/tui/render/hand.rs +++ b/src/tui/render/hand.rs @@ -1,32 +1,35 @@ -use bevy::prelude::*; +use bevy::{platform::collections::HashMap, prelude::*}; use ratatui::widgets::Paragraph; -use jong::game::hand::HandTiles; -use jong::tiles::Tile; +use jong::{ + game::{hand::Hand, player::Player}, + tiles::Tile, +}; use crate::tui::render::tiles; #[derive(Resource, Default)] -pub(crate) struct RenderedHand(pub(crate) Vec>>); +pub(crate) struct RenderedHand(pub(crate) HashMap>>); -pub(crate) fn render_changed_hand( - hands: Populated<&Children, Changed>, +pub(crate) fn render_hand( tiles: Populated<&Tile>, + player_hands: Populated<(Entity, &Children), (With, Changed)>, + hands: Populated<&Children, (Changed, Without)>, mut target: ResMut, ) -> Result { - let mut rendered = vec![]; + let mut rendered = HashMap::new(); - for hand in hands { - let tiles = hand + for (player_ent, hand) in player_hands { + let hand = hand.iter().next().unwrap(); + let tiles = hands + .get(hand)? .iter() - .map(|inhand| tiles.get(inhand).map(tiles::draw_tile).unwrap()) + .map(|it| tiles.get(it).map(tiles::draw_tile).unwrap()) .collect(); - - rendered.push(tiles); + rendered.insert(player_ent, tiles); } target.0 = rendered; - trace!("render_changed_hand"); Ok(()) } diff --git a/src/tui/render/ingame.rs b/src/tui/render/ingame.rs index b8e01cc..e573cb9 100644 --- a/src/tui/render/ingame.rs +++ b/src/tui/render/ingame.rs @@ -1,10 +1,12 @@ 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; @@ -17,8 +19,9 @@ pub(crate) fn draw_ingame( let mut area = frame.area(); area.height = 4; let areas = layout.areas::<13>(area); - if let Some(tiles) = rendered_hand.0.first() { - for (tile, area) in tiles.iter().zip(areas.iter()) { + // 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()) { frame.render_widget(tile, *area); } }