2026-01-08 20:49:19 -08:00
|
|
|
use std::time::Duration;
|
|
|
|
|
|
2026-01-09 06:54:17 -08:00
|
|
|
use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin};
|
2026-01-09 23:14:29 -08:00
|
|
|
use bevy_ratatui::RatatuiPlugins;
|
2026-01-12 21:07:34 -08:00
|
|
|
use bevy_ratatui::event::KeyMessage;
|
2026-01-09 23:14:29 -08:00
|
|
|
use ratatui::{text::ToSpan, widgets::Paragraph};
|
|
|
|
|
|
2026-01-11 20:10:30 -08:00
|
|
|
use jong::game::GameState;
|
|
|
|
|
use jong::game::wall::InWall;
|
|
|
|
|
|
2026-01-12 21:07:34 -08:00
|
|
|
use crate::tui::console::ConsoleState;
|
|
|
|
|
|
2026-01-09 03:34:54 -08:00
|
|
|
mod console;
|
2026-01-11 23:41:27 -08:00
|
|
|
mod menu;
|
2026-01-09 23:14:29 -08:00
|
|
|
mod render;
|
2026-01-08 20:49:19 -08:00
|
|
|
|
2026-01-11 20:10:30 -08:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)]
|
2026-01-12 21:07:34 -08:00
|
|
|
pub(crate) enum TuiState {
|
2026-01-11 20:10:30 -08:00
|
|
|
#[default]
|
2026-01-11 22:32:30 -08:00
|
|
|
MainMenu,
|
2026-01-11 20:10:30 -08:00
|
|
|
InGame,
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 20:56:30 -08:00
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
|
|
|
struct InGame;
|
|
|
|
|
|
|
|
|
|
impl ComputedStates for InGame {
|
|
|
|
|
type SourceStates = TuiState;
|
|
|
|
|
|
|
|
|
|
fn compute(sources: Self::SourceStates) -> Option<Self> {
|
|
|
|
|
match sources {
|
|
|
|
|
TuiState::MainMenu => None,
|
|
|
|
|
TuiState::InGame => Some(Self),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-11 20:10:30 -08:00
|
|
|
}
|
2026-01-07 00:51:57 -08:00
|
|
|
|
2026-01-12 20:56:30 -08:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct RiichiTui;
|
2026-01-08 20:49:19 -08:00
|
|
|
impl Plugin for RiichiTui {
|
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
|
app.add_plugins((
|
|
|
|
|
MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(
|
2026-01-09 03:34:54 -08:00
|
|
|
1. / 60.,
|
2026-01-08 20:49:19 -08:00
|
|
|
))),
|
|
|
|
|
RatatuiPlugins {
|
|
|
|
|
// enable_kitty_protocol: todo!(),
|
2026-01-11 23:41:27 -08:00
|
|
|
enable_mouse_capture: true,
|
2026-01-12 01:54:59 -08:00
|
|
|
enable_input_forwarding: true,
|
2026-01-08 20:49:19 -08:00
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
))
|
2026-01-09 03:34:54 -08:00
|
|
|
.add_plugins(StatesPlugin)
|
2026-01-12 01:54:59 -08:00
|
|
|
|
|
|
|
|
// console
|
2026-01-09 03:34:54 -08:00
|
|
|
.init_state::<console::ConsoleState>()
|
|
|
|
|
.add_systems(Update, console::draw_console.run_if(in_state(console::ConsoleState::Open)))
|
2026-01-12 01:54:59 -08:00
|
|
|
|
|
|
|
|
// general setup
|
2026-01-09 23:14:29 -08:00
|
|
|
.init_state::<TuiState>()
|
2026-01-11 23:41:27 -08:00
|
|
|
.add_computed_state::<InGame>()
|
2026-01-12 21:07:34 -08:00
|
|
|
.add_systems(Update, input_system)
|
2026-01-12 01:54:59 -08:00
|
|
|
|
2026-01-11 23:41:27 -08:00
|
|
|
// main menu
|
2026-01-12 21:07:34 -08:00
|
|
|
.add_systems(Update, menu::draw_mainmenu.run_if(in_state(TuiState::MainMenu)))
|
2026-01-12 01:54:59 -08:00
|
|
|
|
2026-01-11 23:41:27 -08:00
|
|
|
// gaming
|
2026-01-12 01:54:59 -08:00
|
|
|
.init_resource::<render::hand::RenderedHand>()
|
2026-01-12 20:56:30 -08:00
|
|
|
.add_systems(Update, render::ingame::draw_ingame.run_if(in_state(InGame)))
|
2026-01-11 23:41:27 -08:00
|
|
|
.add_systems(Update, render::hand::render_changed_hand.run_if(in_state(InGame).and(in_state(GameState::Play))))
|
2026-01-12 01:54:59 -08:00
|
|
|
|
2026-01-09 03:34:54 -08:00
|
|
|
// semicolon stopper
|
|
|
|
|
;
|
2026-01-08 20:49:19 -08:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-07 00:51:57 -08:00
|
|
|
|
2026-01-12 21:07:34 -08:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
pub(crate) fn input_system(
|
|
|
|
|
mut messages: MessageReader<KeyMessage>,
|
|
|
|
|
|
|
|
|
|
curr_tuistate: Res<State<TuiState>>,
|
|
|
|
|
curr_consolestate: Res<State<ConsoleState>>,
|
|
|
|
|
curr_gamestate: Res<State<GameState>>,
|
|
|
|
|
|
|
|
|
|
mut next_tuistate: ResMut<NextState<TuiState>>,
|
|
|
|
|
mut next_consolestate: ResMut<NextState<ConsoleState>>,
|
|
|
|
|
mut next_gamestate: ResMut<NextState<GameState>>,
|
|
|
|
|
|
|
|
|
|
mut exit: MessageWriter<AppExit>,
|
|
|
|
|
) {
|
|
|
|
|
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")
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|