2026-01-13 00:01:38 -08:00
|
|
|
use bevy::prelude::*;
|
2026-01-13 12:38:41 -08:00
|
|
|
use strum::{EnumCount, FromRepr};
|
2026-01-13 00:01:38 -08:00
|
|
|
|
2026-01-13 13:07:21 -08:00
|
|
|
use crate::{
|
|
|
|
|
EnumNextCycle,
|
2026-01-16 22:37:05 -08:00
|
|
|
game::{
|
|
|
|
|
GameMessage, GameState,
|
2026-01-18 07:53:33 -08:00
|
|
|
hand::{Discarded, Drawn, Hand},
|
|
|
|
|
player::{CurrentPlayer, Player},
|
2026-01-16 22:37:05 -08:00
|
|
|
wall::Wall,
|
|
|
|
|
},
|
2026-01-13 13:07:21 -08:00
|
|
|
};
|
2026-01-13 00:01:38 -08:00
|
|
|
|
2026-01-18 07:53:33 -08:00
|
|
|
// #[derive(Resource)]
|
|
|
|
|
// pub struct CurrentPlayer(pub Entity);
|
2026-01-16 22:37:05 -08:00
|
|
|
|
|
|
|
|
#[derive(Resource)]
|
|
|
|
|
pub(crate) struct MatchSettings {
|
|
|
|
|
pub(crate) starting_points: isize,
|
|
|
|
|
pub(crate) player_count: u8,
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 00:01:38 -08:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 22:37:05 -08:00
|
|
|
#[derive(Component, Clone, Copy, FromRepr, EnumCount, PartialEq)]
|
|
|
|
|
pub enum Wind {
|
|
|
|
|
Ton,
|
|
|
|
|
Nan,
|
|
|
|
|
Shaa,
|
|
|
|
|
Pei,
|
2026-01-13 00:01:38 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 22:37:05 -08:00
|
|
|
pub enum WindRelation {
|
|
|
|
|
Shimocha,
|
|
|
|
|
Toimen,
|
|
|
|
|
Kamicha,
|
2026-01-13 00:01:38 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 22:37:05 -08:00
|
|
|
#[derive(SubStates, Default, Clone, Copy, PartialEq, Eq, Hash, Debug, FromRepr, EnumCount)]
|
|
|
|
|
#[source(GameState = GameState::Play)]
|
|
|
|
|
pub(crate) enum TurnState {
|
|
|
|
|
#[default]
|
|
|
|
|
Tsumo,
|
|
|
|
|
Menzen,
|
|
|
|
|
RiichiKan,
|
|
|
|
|
Discard,
|
|
|
|
|
RonChiiPonKan,
|
|
|
|
|
End,
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 00:01:38 -08:00
|
|
|
impl Default for MatchSettings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
starting_points: 25000,
|
|
|
|
|
player_count: 4,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-13 12:38:41 -08:00
|
|
|
|
2026-01-16 22:37:05 -08:00
|
|
|
impl Default for Compass {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
prevalent_wind: Wind::Ton,
|
|
|
|
|
round: 1,
|
|
|
|
|
dealer_wind: Wind::Ton,
|
|
|
|
|
riichi: 0,
|
|
|
|
|
honba: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-15 14:00:55 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-13 12:38:41 -08:00
|
|
|
impl EnumNextCycle for Wind {
|
|
|
|
|
fn next(&self) -> Self {
|
|
|
|
|
if (*self as usize + 1) >= Self::COUNT {
|
|
|
|
|
Self::from_repr(0).unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
Self::from_repr(*self as usize + 1).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 14:00:55 -08:00
|
|
|
impl Wind {
|
|
|
|
|
pub fn relate(&self, other: &Self) -> WindRelation {
|
|
|
|
|
if self.next() == *other {
|
|
|
|
|
WindRelation::Shimocha
|
|
|
|
|
} else if other.next() == *self {
|
|
|
|
|
WindRelation::Kamicha
|
|
|
|
|
} else {
|
|
|
|
|
WindRelation::Toimen
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 12:38:41 -08:00
|
|
|
impl EnumNextCycle for TurnState {
|
|
|
|
|
fn next(&self) -> Self {
|
|
|
|
|
if (*self as usize + 1) >= Self::COUNT {
|
|
|
|
|
Self::from_repr(0).unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
Self::from_repr(*self as usize + 1).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-13 13:07:21 -08:00
|
|
|
|
|
|
|
|
pub(crate) fn tsumo(
|
|
|
|
|
mut commands: Commands,
|
2026-01-18 07:53:33 -08:00
|
|
|
|
|
|
|
|
// curr_player: Res<CurrentPlayer>,
|
|
|
|
|
curr_player: Single<Entity, With<CurrentPlayer>>,
|
|
|
|
|
wall: Single<Entity, With<Wall>>,
|
2026-01-13 13:07:21 -08:00
|
|
|
walltiles: Single<&Children, With<Wall>>,
|
2026-01-18 07:53:33 -08:00
|
|
|
|
2026-01-13 13:07:21 -08:00
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
|
|
|
|
) {
|
|
|
|
|
let drawn = walltiles.last().unwrap();
|
2026-01-18 07:53:33 -08:00
|
|
|
commands.entity(*wall).remove_child(*drawn);
|
2026-01-13 13:07:21 -08:00
|
|
|
|
2026-01-18 07:53:33 -08:00
|
|
|
let drawn = commands.entity(*drawn).insert(Drawn).id();
|
|
|
|
|
commands.entity(*curr_player).add_child(drawn);
|
2026-01-13 13:07:21 -08:00
|
|
|
|
2026-01-18 07:53:33 -08:00
|
|
|
debug!("tsumo for: {:?}, tile: {:?}", *curr_player, drawn);
|
2026-01-13 13:07:21 -08:00
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|
2026-01-16 22:37:05 -08:00
|
|
|
|
|
|
|
|
pub(crate) fn menzen(
|
|
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
|
|
|
|
) {
|
2026-01-17 02:22:47 -08:00
|
|
|
trace!("menzen check");
|
2026-01-16 22:37:05 -08:00
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn riichi_kan(
|
|
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
|
|
|
|
) {
|
2026-01-17 02:22:47 -08:00
|
|
|
trace!("riichi_kan");
|
2026-01-16 22:37:05 -08:00
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:22:47 -08:00
|
|
|
#[allow(clippy::too_many_arguments, irrefutable_let_patterns)]
|
2026-01-16 22:37:05 -08:00
|
|
|
pub(crate) fn discard(
|
2026-01-17 02:22:47 -08:00
|
|
|
mut commands: Commands,
|
2026-01-16 22:37:05 -08:00
|
|
|
mut reader: MessageReader<GameMessage>,
|
|
|
|
|
|
2026-01-18 07:53:33 -08:00
|
|
|
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>>,
|
2026-01-16 22:37:05 -08:00
|
|
|
|
|
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
2026-01-18 07:53:33 -08:00
|
|
|
) -> Result {
|
|
|
|
|
// trace!("discard");
|
|
|
|
|
let curr_hand = hands.get_mut(players.get(*curr_player)?.iter().next().unwrap())?;
|
2026-01-16 22:37:05 -08:00
|
|
|
|
2026-01-18 07:53:33 -08:00
|
|
|
let mut done = false;
|
2026-01-16 22:37:05 -08:00
|
|
|
while let Some(message) = reader.read().next() {
|
2026-01-18 07:53:33 -08:00
|
|
|
if let GameMessage::Discarded(discarded) = message {
|
|
|
|
|
if *discarded == *drawn {
|
|
|
|
|
} else if curr_hand.0.contains(discarded) {
|
2026-01-17 02:22:47 -08:00
|
|
|
commands
|
2026-01-18 07:53:33 -08:00
|
|
|
.entity(curr_hand.1)
|
|
|
|
|
.remove_child(*discarded)
|
|
|
|
|
.add_child(*drawn);
|
2026-01-16 22:37:05 -08:00
|
|
|
} else {
|
2026-01-18 07:53:33 -08:00
|
|
|
panic!("current hand nor drawn tile contains discarded tile")
|
2026-01-16 22:37:05 -08:00
|
|
|
}
|
2026-01-18 07:53:33 -08:00
|
|
|
commands
|
|
|
|
|
.entity(*discarded)
|
|
|
|
|
.remove::<Drawn>()
|
|
|
|
|
.insert(Discarded);
|
|
|
|
|
|
|
|
|
|
done = true;
|
2026-01-16 22:37:05 -08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-18 07:53:33 -08:00
|
|
|
|
|
|
|
|
if done {
|
|
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
2026-01-16 22:37:05 -08:00
|
|
|
}
|
2026-01-17 02:22:47 -08:00
|
|
|
|
|
|
|
|
pub(crate) fn ron_chi_pon_kan(
|
|
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
|
|
|
|
) {
|
|
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn end(
|
|
|
|
|
curr_turnstate: Res<State<TurnState>>,
|
|
|
|
|
mut next_turnstate: ResMut<NextState<TurnState>>,
|
|
|
|
|
) {
|
|
|
|
|
next_turnstate.set(curr_turnstate.next());
|
|
|
|
|
}
|