more refactor, start using stdb for everything???

4.5th rewrite for tui
This commit is contained in:
Tao Tien 2026-02-16 00:05:57 -08:00
parent 034e543d40
commit c3686221aa
29 changed files with 478 additions and 744 deletions

View file

@ -1,41 +0,0 @@
use bevy::prelude::*;
use jong_types::*;
#[derive(Component)]
pub struct Hand;
#[derive(Component)]
pub struct Pond;
#[derive(Component)]
pub struct Drawn;
#[derive(Component)]
pub struct Discarded;
// #[derive(Component, Default)]
// enum SortHand {
// #[default]
// Unsorted,
// Sort,
// Manual,
// }
/// assumes hand is sorted
pub(crate) fn check_wincon(_hand: &[Tile; 14], _melds: &[&[Tile]]) -> bool {
// 4x3 + pair
// assume sorted
//
// let melds = hand.iter().array_chunks::<3>().all(|tiles| {
// let suit = discriminant(&tiles[0].suit);
// let starting_rank = tiles[0].suit
// // tiles.iter().all(|t| discriminant(&t.suit) == suit) && tiles.iter().zip(tiles[0].suit.rank())
// }) && melds.iter().all(|meld| todo!());
// let eyeball = todo!();
todo!();
// melds && eyeball
}

View file

@ -1,21 +0,0 @@
use bevy::prelude::*;
#[derive(Component, Debug, PartialEq)]
pub struct Player {
pub name: String,
}
#[derive(Component)]
pub struct Points(pub isize);
#[derive(Component)]
pub struct MainPlayer;
#[derive(Component)]
pub struct CurrentPlayer;
#[derive(Component)]
pub struct Dealer;
#[derive(Component)]
pub struct Tsumo;

View file

@ -1,232 +0,0 @@
use bevy::{platform::collections::HashMap, prelude::*};
use strum::{EnumCount, FromRepr};
use crate::EnumNextCycle;
use jong_types::TurnState;
// #[derive(Resource)]
// pub struct CurrentPlayer(pub Entity);
#[derive(Resource)]
pub(crate) struct MatchSettings {
pub(crate) starting_points: isize,
pub(crate) player_count: u8,
}
#[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,
}
#[derive(Component, Clone, Copy, FromRepr, EnumCount, PartialEq)]
pub enum Wind {
Ton,
Nan,
Shaa,
Pei,
}
pub enum WindRelation {
Shimocha,
Toimen,
Kamicha,
}
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum CallType {
Skip,
Ron,
Chii,
Pon,
Kan,
}
impl Default for MatchSettings {
fn default() -> Self {
Self {
starting_points: 25000,
player_count: 4,
}
}
}
impl Default for Compass {
fn default() -> Self {
Self {
prevalent_wind: Wind::Ton,
round: 1,
dealer_wind: Wind::Ton,
riichi: 0,
honba: 0,
}
}
}
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()
}
}
}
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
}
}
}
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()
}
}
}
// pub(crate) fn tsumo(
// mut commands: Commands,
// // curr_player: Res<CurrentPlayer>,
// curr_player: Single<Entity, With<CurrentPlayer>>,
// wall: Single<Entity, With<Wall>>,
// walltiles: Single<&Children, With<Wall>>,
// curr_turnstate: Res<State<TurnState>>,
// mut next_turnstate: ResMut<NextState<TurnState>>,
// ) {
// let drawn = walltiles.last().unwrap();
// commands.entity(*wall).remove_child(*drawn);
// let drawn = commands.entity(*drawn).insert(Drawn).id();
// commands.entity(*curr_player).add_child(drawn);
// debug!("tsumo for: {:?}, tile: {:?}", *curr_player, drawn);
// next_turnstate.set(curr_turnstate.next());
// }
// pub(crate) fn menzen(
// curr_turnstate: Res<State<TurnState>>,
// mut next_turnstate: ResMut<NextState<TurnState>>,
// ) {
// trace!("menzen check");
// next_turnstate.set(curr_turnstate.next());
// }
// pub(crate) fn riichi_kan(
// curr_turnstate: Res<State<TurnState>>,
// mut next_turnstate: ResMut<NextState<TurnState>>,
// ) {
// trace!("riichi_kan");
// next_turnstate.set(curr_turnstate.next());
// }
// #[allow(clippy::too_many_arguments, irrefutable_let_patterns)]
// pub(crate) fn discard(
// mut commands: Commands,
// mut reader: MessageReader<GameMessage>,
// 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>>,
// curr_turnstate: Res<State<TurnState>>,
// mut next_turnstate: ResMut<NextState<TurnState>>,
// ) -> Result {
// // trace!("discard");
// let (handtiles, hand) = hands.get_mut(players.get(*curr_player)?.iter().next().unwrap())?;
// let mut done = false;
// while let Some(message) = reader.read().next() {
// if let GameMessage::Discarded(discarded) = message {
// debug!("discarded: {discarded:?}");
// if *discarded == *drawn {
// } else if handtiles.contains(discarded) {
// commands
// .entity(hand)
// .remove_child(*discarded)
// .add_child(*drawn);
// } else {
// panic!("current hand nor drawn tile contains discarded tile")
// }
// commands.entity(*drawn).remove::<Drawn>();
// commands.entity(*discarded).insert(Discarded);
// done = true;
// break;
// }
// }
// if done {
// next_turnstate.set(curr_turnstate.next());
// }
// 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(
// 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>>,
// 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());
// }
// pub(crate) fn end(
// curr_turnstate: Res<State<TurnState>>,
// mut next_turnstate: ResMut<NextState<TurnState>>,
// ) {
// next_turnstate.set(curr_turnstate.next());
// }

View file

@ -1,7 +0,0 @@
use bevy::prelude::*;
#[derive(Component)]
pub struct Wall;
#[derive(Component)]
pub struct Dead;

View file

@ -1,29 +0,0 @@
use bevy::{color::palettes::css::GREEN, prelude::*};
pub(crate) fn init_environment(mut commands: Commands) {
commands.spawn((
DirectionalLight {
shadows_enabled: true,
..default()
},
// Transform::from_xyz(),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-200.5, 100., 0.).looking_at(Vec3::ZERO, Vec3::Y),
));
}
pub(crate) fn init_table(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let green: Color = GREEN.into();
let table = Cuboid::new(1000., 5., 1000.);
commands.spawn((
Mesh3d(meshes.add(table)),
MeshMaterial3d(materials.add(green)),
));
}

View file

@ -4,9 +4,7 @@ use bevy::prelude::*;
use bevy_spacetimedb::StdbConnection;
use spacetimedb_sdk::credentials;
pub mod game;
pub mod tile;
pub mod yakus;
pub mod riichi;
trait EnumNextCycle {
fn next(&self) -> Self;
@ -15,6 +13,5 @@ trait EnumNextCycle {
pub type SpacetimeDB<'a> = Res<'a, StdbConnection<jong_db::DbConnection>>;
fn creds_store() -> credentials::File {
credentials::File::new("jongline")
credentials::File::new("jong-line")
}

View file

@ -3,7 +3,7 @@ use clap::{Parser, Subcommand};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod gui;
// mod gui;
mod tui;
#[derive(Parser)]
@ -25,15 +25,11 @@ fn main() {
let mut app = App::new();
let app = match args.mode {
Mode::RunGui => {
app.add_plugins(DefaultPlugins.set(LogPlugin {
filter: FILTERSTRING.into(),
level: Level::TRACE,
// custom_layer: todo!(),
// fmt_layer: todo!(),
..Default::default()
}))
}
Mode::RunGui => app.add_plugins(DefaultPlugins.set(LogPlugin {
filter: FILTERSTRING.into(),
level: Level::TRACE,
..Default::default()
})),
Mode::RunTui => {
tracing_subscriber::registry()
.with(tui_logger::TuiTracingSubscriberLayer)
@ -45,7 +41,7 @@ fn main() {
}
};
app.add_plugins(jong::game::Riichi);
app.add_plugins(jong::riichi::Riichi);
app.run();
}

View file

@ -2,27 +2,22 @@ use bevy::prelude::*;
use bevy_spacetimedb::{
ReadInsertUpdateMessage, ReadStdbConnectedMessage, ReadStdbDisconnectedMessage, StdbPlugin,
};
use spacetimedb_sdk::{DbContext, Table};
use crate::{
SpacetimeDB, creds_store,
game::hand::{Drawn, Hand, Pond},
};
use jong_db::{self, DbConnection, LobbyTableAccess, PlayerTableAccess, RemoteTables};
use jong_db::{add_bot, draw_tile, set_ready, shuffle_deal, skip_call, start_game};
use jong_types::*;
pub mod hand;
pub mod player;
pub mod round;
pub mod wall;
use crate::riichi::player::*;
use crate::{SpacetimeDB, creds_store};
// pub mod round;
pub struct Riichi;
impl Plugin for Riichi {
fn build(&self, app: &mut App) {
let plugins = StdbPlugin::default()
.with_uri("http://localhost:3000")
.with_module_name("jongline")
.with_module_name("jong-line")
.with_run_fn(DbConnection::run_threaded)
// TODO why don't I need to call add_reducer?
@ -41,8 +36,8 @@ impl Plugin for Riichi {
};
app.add_plugins(plugins)
.init_state::<GameState>()
.add_sub_state::<TurnState>()
.init_state::<jong_types::states::GameState>()
.add_sub_state::<jong_types::states::TurnState>()
// .init_resource::<round::MatchSettings>()
// .init_resource::<round::Compass>()
// .add_systems(Startup, tile::init_tiles)
@ -70,6 +65,7 @@ fn on_connect(stdb: SpacetimeDB, mut messages: ReadStdbConnectedMessage, _comman
for msg in messages.read() {
info!("you're now jongline");
// FIXME hack that doesn't work for startup crash?
while stdb.try_identity().is_none() {}
debug!("with identity: {}", stdb.identity());
@ -91,6 +87,7 @@ fn subscriptions(stdb: SpacetimeDB) {
.on_applied(|_| trace!("made all subs!"))
.on_error(|_, err| error!("sub failed: {err}"))
.subscribe([
// TODO until views work
format!(
"SELECT * FROM player p WHERE p.identity = '{}'",
stdb.identity()
@ -100,61 +97,36 @@ fn subscriptions(stdb: SpacetimeDB) {
// .subscribe_to_all_tables();
}
#[derive(Component)]
pub struct TileId(pub u32);
fn on_player_insert_update(
_stdb: SpacetimeDB,
mut messages: ReadInsertUpdateMessage<jong_db::Player>,
mut commands: Commands,
tiles: Query<(&Tile, &TileId, Entity)>,
player: Option<Single<&mut player::Player>>,
hand_ent: Option<Single<Entity, With<Hand>>>,
player: Option<Single<Entity, With<MainPlayer>>>,
hand: Option<Single<Entity, With<Hand>>>,
) {
use player::*;
let hand = if player.is_none() && hand.is_none() {
let hand = commands.spawn(Hand).id();
commands.spawn((Player, MainPlayer)).add_child(hand);
hand
} else {
*hand.unwrap()
};
for msg in messages.read() {
// debug!("player_insert_update msg:\n{:#?}", msg.new);
if let (Some(_player), Some(hand_ent)) = (player.as_ref(), hand_ent.as_ref()) {
// if msg.old.as_ref().is_some_and(|m| !m.ready) && msg.new.ready {
// trace!("entered ready");
// // TODO add a start game button in the future
// stdb.reducers().start_game().unwrap();
// }
let tiles: Vec<_> = msg
.new
.hand
.iter()
.map(|dbt| {
// TODO this seems a lil expensive
if let Some(ent) = tiles
.iter()
.find(|(_, id, _)| id.0 == dbt.id)
.map(|(_, _, e)| e)
{
ent
} else {
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()
}
})
.collect();
commands.entity(**hand_ent).replace_children(&tiles);
if let Some(dbt) = &msg.new.drawn_tile {
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id), Drawn));
}
} else {
let player = Player {
name: msg
.new
.name
.as_ref()
.unwrap_or(&"nameless".to_string())
.clone(),
};
let bundle = (player, Hand, Pond, MainPlayer, CurrentPlayer);
commands.spawn(bundle);
let tiles: Vec<_> = msg
.new
.hand
.iter()
.map(|dbt| {
// drawn tiles will always be new entities to us, until wall isn't fake
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id))).id()
})
.collect();
commands.entity(hand).replace_children(&tiles);
if let Some(dbt) = &msg.new.drawn_tile {
commands.spawn((Tile::from(&dbt.tile), TileId(dbt.id), Drawn));
}
}
}
@ -163,9 +135,9 @@ fn on_lobby_insert_update(
stdb: SpacetimeDB,
mut messages: ReadInsertUpdateMessage<jong_db::Lobby>,
_commands: Commands,
mut next_gamestate: ResMut<NextState<GameState>>,
mut next_turnstate: ResMut<NextState<TurnState>>,
commands: Commands,
mut next_gamestate: ResMut<NextState<jong_types::states::GameState>>,
mut next_turnstate: ResMut<NextState<jong_types::states::TurnState>>,
) {
for msg in messages.read() {
// trace!("on_lobby_insert_update msg:\n{:#?}", msg.new);
@ -212,7 +184,6 @@ fn on_lobby_insert_update(
stdb.reducers().skip_call().unwrap();
}
jong_db::TurnState::End => todo!(),
// _ => todo!(),
}
next_turnstate.set(msg.new.turn_state.into());
}

25
jong/src/riichi/player.rs Normal file
View file

@ -0,0 +1,25 @@
use bevy::prelude::*;
#[derive(Component)]
pub struct Player;
#[derive(Component)]
pub struct MainPlayer;
#[derive(Component)]
pub struct CurrentPlayer;
#[derive(Component)]
pub struct TileId(pub u32);
#[derive(Component)]
pub struct Hand;
#[derive(Component)]
pub struct Pond;
#[derive(Component)]
pub struct Drawn;
#[derive(Component)]
pub struct Discarded;

100
jong/src/riichi/round.rs Normal file
View file

@ -0,0 +1,100 @@
use bevy::{platform::collections::HashMap, prelude::*};
use strum::{EnumCount, FromRepr};
use crate::EnumNextCycle;
use jong_types::states::TurnState;
// #[derive(Resource)]
// pub struct CurrentPlayer(pub Entity);
#[derive(Resource)]
pub(crate) struct MatchSettings {
pub(crate) starting_points: isize,
pub(crate) player_count: u8,
}
#[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,
}
pub enum WindRelation {
Shimocha,
Toimen,
Kamicha,
}
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum CallType {
Skip,
Ron,
Chii,
Pon,
Kan,
}
impl Default for MatchSettings {
fn default() -> Self {
Self {
starting_points: 25000,
player_count: 4,
}
}
}
impl Default for Compass {
fn default() -> Self {
Self {
prevalent_wind: Wind::Ton,
round: 1,
dealer_wind: Wind::Ton,
riichi: 0,
honba: 0,
}
}
}
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()
}
}
}
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
}
}
}
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()
}
}
}
#[derive(Resource)]
pub struct PendingCalls {
eligible: Vec<Entity>,
calls: HashMap<Entity, CallType>,
}

View file

@ -1,11 +0,0 @@
use bevy::prelude::*;
use jong_types::*;
#[derive(Component)]
pub struct Dora;
pub fn init_tiles(mut commands: Commands) {
let tiles = tiles();
commands.spawn_batch(tiles);
}

View file

@ -4,35 +4,17 @@ use std::time::Duration;
use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin};
use bevy_ratatui::RatatuiPlugins;
use jong::game::TileId;
use jong::game::player::MainPlayer;
use tui_logger::TuiWidgetState;
use crate::tui::{input::ConfirmSelect, states::ConsoleWidget};
use jong::{
SpacetimeDB,
game::{
hand::{Drawn, Hand},
player::{CurrentPlayer, Player},
},
};
use jong::{SpacetimeDB, riichi::player::*};
use jong_db::{self, discard_tile as _};
use jong_types::{GameState, TurnState};
use jong_types::states::{GameState, TurnState};
mod input;
mod layout;
mod render;
#[derive(Default)]
pub struct TuiPlugin;
#[derive(Clone, Debug, Eq, Hash, PartialEq, SystemSet)]
pub enum TuiSet {
Input,
Layout,
Render,
}
use input::ConfirmSelect;
use states::ConsoleWidget;
mod states {
use bevy::prelude::*;
use tui_logger::TuiWidgetState;
@ -59,6 +41,16 @@ mod states {
// }
}
#[derive(Default)]
pub struct TuiPlugin;
#[derive(Clone, Debug, Eq, Hash, PartialEq, SystemSet)]
pub enum TuiSet {
Input,
Layout,
Render,
}
impl Plugin for TuiPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
@ -93,7 +85,7 @@ impl Plugin for TuiPlugin {
.add_systems(
Update,
(
render::render_hands.run_if(in_state(GameState::Play)),
render::render_hand.run_if(in_state(GameState::Play)),
render::render,
)
.chain()

View file

@ -7,7 +7,6 @@ use jong_db::start_game;
use tui_logger::TuiWidgetEvent;
use jong::SpacetimeDB;
use jong_types::GameState;
use crate::tui::layout::Overlays;
use crate::tui::states::ConsoleWidget;
@ -21,9 +20,9 @@ pub(crate) fn keyboard(
mut consolewidget: ResMut<ConsoleWidget>,
mut exit: MessageWriter<AppExit>,
curr_gamestate: Res<State<GameState>>,
curr_gamestate: Res<State<jong_types::states::GameState>>,
curr_tuistate: Res<State<TuiState>>,
mut next_gamestate: ResMut<NextState<GameState>>,
mut next_gamestate: ResMut<NextState<jong_types::states::GameState>>,
mut next_tuistate: ResMut<NextState<TuiState>>,
) {
'message: for message in messages.read() {

View file

@ -6,9 +6,9 @@ use ratatui::layout::{Constraint, Flex, Layout, Offset, Rect, Size};
use ratatui::style::{Modifier, Stylize};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use jong::game::hand::{Drawn, Hand};
use jong::game::player::{CurrentPlayer, MainPlayer, Player};
use jong::game::round::Wind;
use jong::riichi::player::{CurrentPlayer, MainPlayer, Player};
use jong::riichi::player::{Drawn, Hand};
// use jong::riichi::round::Wind;
// use jong_types::*;
use crate::tui::input::Hovered;
@ -95,26 +95,9 @@ pub(crate) fn render(
Ok(())
}
// pub(crate) fn render_arg_check(
// mut commands: Commands,
// mut tui: ResMut<RatatuiContext>,
// hovered: Query<Entity, With<Hovered>>,
// layouts: Res<HandLayouts>,
// tiles: Query<&jong_types::Tile>,
// // main_player: Single<(&Player, Entity, &Wind), With<MainPlayer>>,
// curr_player: Single<Entity, With<CurrentPlayer>>,
// players: Query<(&Player, Entity, &Children)>,
// hands: Query<(&Children, Entity), (With<Hand>, Without<Player>)>,
// // drawn_tile: Single<Entity, With<Drawn>>,
// ) {
// // trace!("arg!");
// }
// FIXME we don't care about other players atm
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn render_hands(
pub(crate) fn render_hand(
mut commands: Commands,
mut tui: ResMut<RatatuiContext>,
@ -123,79 +106,66 @@ pub(crate) fn render_hands(
tiles: Query<&jong_types::Tile>,
main_player: Single<(&Player, Entity /* , &Wind */), With<MainPlayer>>,
curr_player: Single<Entity, With<CurrentPlayer>>,
players: Query<(&Player, Entity, &Children)>,
hands: Query<(&Children, Entity), With<Hand>>,
drawn_tile: Single<Entity, With<Drawn>>,
hand: Single<(&Children, Entity), With<Hand>>,
drawn_tile: Option<Single<Entity, With<Drawn>>>,
) -> Result {
let mut frame = tui.get_frame();
debug_blocks(*layouts, &mut frame);
for (hand, hand_ent) in hands {
// debug!("{hand:?}");
// let (player, player_ent, _) = players
// .iter()
// .find(|(_, e, c)| c.contains(&hand_ent))
// .unwrap();
let hand: Vec<_> = hand
.iter()
.map(|entity| -> Result<_> {
let tile = tiles.get(entity).unwrap_or_else(|_| panic!("{entity:?}"));
let hovered = hovered.contains(entity);
let widget = render_tile(tile, hovered);
let hand: Vec<_> = hand
.0
.iter()
.map(|entity| -> Result<_> {
let tile = tiles.get(entity).unwrap_or_else(|_| panic!("{entity:?}"));
let hovered = hovered.contains(entity);
let widget = render_tile(tile, hovered);
Ok((entity, widget, hovered))
})
.collect::<Result<_>>()?;
Ok((entity, widget, hovered))
})
.collect::<Result<_>>()?;
let (player, player_ent) = *main_player;
// if player == main_player.0 {
// split main box into thirds
let mut this_hand = layouts.this_hand;
// 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 hand_draw_meld = Layout::horizontal([
Constraint::Max(hand.len() as u16 * 5),
Constraint::Max(tile_drawn),
Constraint::Fill(1),
])
.flex(Flex::SpaceBetween);
this_hand = this_hand.offset(Offset {
x: 0,
y: this_hand.height.abs_diff(5) as i32 + 1,
});
this_hand = this_hand.resize(Size {
width: this_hand.width,
height: 4,
});
let [hand_area, drawn_area, meld_area] = hand_draw_meld.areas::<3>(this_hand);
let (player, player_ent) = *main_player;
// split main box into thirds
let mut this_hand = layouts.this_hand;
let tile_drawn = if drawn_tile.is_some() { 7 } else { 0 };
let hand_draw_meld = Layout::horizontal([
Constraint::Max(hand.len() as u16 * 5),
Constraint::Max(tile_drawn),
Constraint::Fill(1),
])
.flex(Flex::SpaceBetween);
this_hand = this_hand.offset(Offset {
x: 0,
y: this_hand.height.abs_diff(5) as i32 + 1,
});
this_hand = this_hand.resize(Size {
width: this_hand.width,
height: 4,
});
let [hand_area, drawn_area, meld_area] = hand_draw_meld.areas::<3>(this_hand);
// split hand area into tile areas
let mut constraints = vec![Constraint::Max(5); hand.len()];
constraints.push(Constraint::Fill(1));
let layout = Layout::horizontal(constraints).flex(Flex::Start);
let tile_areas = layout.split(hand_area);
// split hand area into tile areas
let mut constraints = vec![Constraint::Max(5); hand.len()];
constraints.push(Constraint::Fill(1));
let layout = Layout::horizontal(constraints).flex(Flex::Start);
let tile_areas = layout.split(hand_area);
for ((entity, widget, hovered), mut area) in
hand.into_iter().zip(tile_areas.iter().copied())
{
if hovered {
area = area.offset(Offset { x: 0, y: -1 });
let mut hitbox = area.as_size();
hitbox.height += 1;
commands.entity(entity).insert(PickRegion {
area: area.resize(hitbox),
});
} else {
commands.entity(entity).insert(PickRegion { area });
}
frame.render_widget(widget, area);
for ((entity, widget, hovered), mut area) in hand.into_iter().zip(tile_areas.iter().copied()) {
if hovered {
area = area.offset(Offset { x: 0, y: -1 });
let mut hitbox = area.as_size();
hitbox.height += 1;
commands.entity(entity).insert(PickRegion {
area: area.resize(hitbox),
});
} else {
commands.entity(entity).insert(PickRegion { area });
}
frame.render_widget(widget, area);
}
// tsumo tile
// if this_drawer {
// // trace!("this_drawer");
// tsumo tile
if let Some(drawn_tile) = drawn_tile {
let mut area = drawn_area.resize(Size {
width: 5,
height: 4,
@ -214,16 +184,16 @@ pub(crate) fn render_hands(
commands.entity(*drawn_tile).insert(PickRegion { area });
}
frame.render_widget(widget, area);
// }
// TODO draw melds
// } else {
// match mainplayer.1.relate(wind) {
// jong::game::round::WindRelation::Shimocha => todo!(),
// jong::game::round::WindRelation::Toimen => todo!(),
// jong::game::round::WindRelation::Kamicha => todo!(),
// }
// }
}
// TODO draw melds
// } else {
// match mainplayer.1.relate(wind) {
// jong::game::round::WindRelation::Shimocha => todo!(),
// jong::game::round::WindRelation::Toimen => todo!(),
// jong::game::round::WindRelation::Kamicha => todo!(),
// }
// }
Ok(())
}

View file

@ -1 +0,0 @@
// const TSUMO;