create a workspace

This commit is contained in:
Tao Tien 2026-01-22 14:24:34 -08:00
parent dedeb39304
commit bcbaab6909
38 changed files with 771 additions and 905 deletions

28
jong/Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "jong"
version = "0.1.0"
edition = "2024"
# license = "Source-First-1.1" # pending crates.io support
license-file = "LICENSE.typ"
description = "riichi mahjong"
readme = false
[lib]
[dependencies]
bevy = { version = "0.17.3", features = ["dynamic_linking"] }
# bevy_ratatui = { git = "https://github.com/kenianbei/bevy_ratatui.git", rev = "e4b022308e08ab360ef89eca8e9f8b1c969e9a56" }
bevy_ratatui = { path = "/home/tao/clones/bevy_ratatui" }
bevy_spacetimedb = "0.7"
clap = { version = "4.5.54", features = ["derive"] }
jongline = { version = "0.1.0", path = "../jongline" }
log = { version = "0.4.29", features = [
"release_max_level_error",
"max_level_trace",
] }
rand = "0.9.2"
ratatui = "0.30.0"
strum = { version = "0.27.2", features = ["derive"] }
tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tui-logger = { version = "0.18.0", features = ["tracing-support", "crossterm"] }

101
jong/src/game.rs Normal file
View file

@ -0,0 +1,101 @@
use bevy::prelude::*;
use crate::{
game::{
hand::{Hand, Pond},
player::{CurrentPlayer, MainPlayer},
round::{TurnState, Wind},
wall::Wall,
},
tile::{self},
};
pub mod hand;
pub mod player;
pub mod round;
pub mod wall;
#[derive(States, Default, Hash, Clone, Eq, Debug, PartialEq, Copy)]
pub enum GameState {
#[default]
None,
Setup,
Deal,
Play,
}
#[derive(Message)]
pub enum GameMessage {
Discarded(Entity),
CallPending,
Called { player: Entity, calltype: Entity },
}
impl GameMessage {
pub(crate) fn is_called(&self) -> bool {
match self {
GameMessage::Called { .. } => true,
_ => false,
}
}
}
pub struct Riichi;
impl Plugin for Riichi {
fn build(&self, app: &mut App) {
app
// start stopper
.init_state::<GameState>()
.add_sub_state::<TurnState>()
.init_resource::<round::MatchSettings>()
.init_resource::<round::Compass>()
.add_message::<GameMessage>()
.add_systems(Startup, tile::init_tiles)
.add_systems(OnEnter(GameState::Setup), setup)
.add_systems(OnEnter(GameState::Deal), hand::shuffle_deal)
.add_systems(Update, hand::sort_hands.run_if(in_state(GameState::Play)))
.add_systems(OnEnter(TurnState::Tsumo), round::tsumo)
.add_systems(OnEnter(TurnState::Menzen), round::menzen)
.add_systems(Update, round::riichi_kan.run_if(in_state(TurnState::RiichiKan)))
.add_systems(Update, round::discard.run_if(in_state(TurnState::Discard)))
.add_systems(OnEnter(TurnState::RonChiiPonKan), round::notify_callable)
.add_systems(Update, round::ron_chi_pon_kan.run_if(in_state(TurnState::RonChiiPonKan)).after(round::notify_callable))
.add_systems(OnEnter(TurnState::End), round::end)
// semicolon stopper
;
}
}
pub(crate) fn setup(
mut commands: Commands,
matchsettings: Res<round::MatchSettings>,
// mut compass: ResMut<Compass>
// tiles: Query<Entity, With<Tile>>,
mut next_gamestate: ResMut<NextState<GameState>>,
) {
for i in 1..=matchsettings.player_count {
let player = player::Player {
name: format!("Player {}", i),
};
let points = player::Points(matchsettings.starting_points);
let bundle = (
player,
points,
Hand,
Pond,
Wind::from_repr((i - 1) as usize).unwrap(),
);
if i == 1 {
let player = commands.spawn((bundle, MainPlayer, CurrentPlayer)).id();
// commands.insert_resource(CurrentPlayer(player));
} else {
commands.spawn(bundle);
}
}
commands.spawn(Wall);
next_gamestate.set(GameState::Deal);
}

84
jong/src/game/hand.rs Normal file
View file

@ -0,0 +1,84 @@
use std::mem::discriminant;
use bevy::prelude::*;
use crate::{
game::{GameState, player::Player, wall::Wall},
tile::Tile,
};
#[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,
// }
pub(crate) fn sort_hands(
tiles: Query<&Tile>,
hands: Query<&mut Children, (Changed<Children>, With<Hand>, Without<Player>)>,
) -> Result {
for mut hand in hands {
hand.sort_unstable_by_key(|e| tiles.get(*e).unwrap().suit);
debug!("sorted: {hand:?}");
}
Ok(())
}
pub(crate) fn shuffle_deal(
mut commands: Commands,
tiles: Populated<Entity, With<Tile>>,
players: Populated<Entity, With<Player>>,
wall_ent: Single<Entity, With<Wall>>,
mut next_gamestate: ResMut<NextState<GameState>>,
) {
use rand::seq::SliceRandom;
let mut rng = rand::rng();
let mut walltiles: Vec<_> = tiles.iter().collect();
walltiles.shuffle(&mut rng);
for player_ent in players {
let handtiles = walltiles.split_off(walltiles.len() - 13);
let hand_ent = commands.spawn(Hand).add_children(&handtiles).id();
commands.entity(player_ent).add_child(hand_ent);
debug!("deal to player_ent {player_ent:?} {hand_ent:?}");
}
// don't need to remove hands from wall if we don't insert to wall to begin with
// TODO probably do this later on when animating the draw
debug!("shuffled: {walltiles:?}");
commands.entity(*wall_ent).replace_children(&walltiles);
next_gamestate.set(GameState::Play);
}
/// 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
}

21
jong/src/game/player.rs Normal file
View file

@ -0,0 +1,21 @@
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;

252
jong/src/game/round.rs Normal file
View file

@ -0,0 +1,252 @@
use std::rc::Weak;
use bevy::{platform::collections::HashMap, prelude::*};
use strum::{EnumCount, FromRepr};
use crate::{
EnumNextCycle,
game::{
GameMessage, GameState,
hand::{Discarded, Drawn, Hand, Pond},
player::{CurrentPlayer, Player},
wall::Wall,
},
};
// #[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(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,
}
#[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());
}

7
jong/src/game/wall.rs Normal file
View file

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

29
jong/src/gui/mod.rs Normal file
View file

@ -0,0 +1,29 @@
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

@ -0,0 +1,105 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct CreatePlayerArgs {
pub username: String,
}
impl From<CreatePlayerArgs> for super::Reducer {
fn from(args: CreatePlayerArgs) -> Self {
Self::CreatePlayer {
username: args.username,
}
}
}
impl __sdk::InModule for CreatePlayerArgs {
type Module = super::RemoteModule;
}
pub struct CreatePlayerCallbackId(__sdk::CallbackId);
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `create_player`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait create_player {
/// Request that the remote module invoke the reducer `create_player` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed by listening for [`Self::on_create_player`] callbacks.
fn create_player(&self, username: String) -> __sdk::Result<()>;
/// Register a callback to run whenever we are notified of an invocation of the reducer `create_player`.
///
/// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`]
/// to determine the reducer's status.
///
/// The returned [`CreatePlayerCallbackId`] can be passed to [`Self::remove_on_create_player`]
/// to cancel the callback.
fn on_create_player(
&self,
callback: impl FnMut(&super::ReducerEventContext, &String) + Send + 'static,
) -> CreatePlayerCallbackId;
/// Cancel a callback previously registered by [`Self::on_create_player`],
/// causing it not to run in the future.
fn remove_on_create_player(&self, callback: CreatePlayerCallbackId);
}
impl create_player for super::RemoteReducers {
fn create_player(&self, username: String) -> __sdk::Result<()> {
self.imp
.call_reducer("create_player", CreatePlayerArgs { username })
}
fn on_create_player(
&self,
mut callback: impl FnMut(&super::ReducerEventContext, &String) + Send + 'static,
) -> CreatePlayerCallbackId {
CreatePlayerCallbackId(self.imp.on_reducer(
"create_player",
Box::new(move |ctx: &super::ReducerEventContext| {
#[allow(irrefutable_let_patterns)]
let super::ReducerEventContext {
event:
__sdk::ReducerEvent {
reducer: super::Reducer::CreatePlayer { username },
..
},
..
} = ctx
else {
unreachable!()
};
callback(ctx, username)
}),
))
}
fn remove_on_create_player(&self, callback: CreatePlayerCallbackId) {
self.imp.remove_on_reducer("create_player", callback.0)
}
}
#[allow(non_camel_case_types)]
#[doc(hidden)]
/// Extension trait for setting the call-flags for the reducer `create_player`.
///
/// Implemented for [`super::SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub trait set_flags_for_create_player {
/// Set the call-reducer flags for the reducer `create_player` to `flags`.
///
/// This type is currently unstable and may be removed without a major version bump.
fn create_player(&self, flags: __ws::CallReducerFlags);
}
impl set_flags_for_create_player for super::SetReducerFlags {
fn create_player(&self, flags: __ws::CallReducerFlags) {
self.imp.set_call_reducer_flags("create_player", flags);
}
}

View file

@ -0,0 +1,95 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::drawn_type::Drawn;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `drawn`.
///
/// Obtain a handle from the [`DrawnTableAccess::drawn`] method on [`super::RemoteTables`],
/// like `ctx.db.drawn()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.drawn().on_insert(...)`.
pub struct DrawnTableHandle<'ctx> {
imp: __sdk::TableHandle<Drawn>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `drawn`.
///
/// Implemented for [`super::RemoteTables`].
pub trait DrawnTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`DrawnTableHandle`], which mediates access to the table `drawn`.
fn drawn(&self) -> DrawnTableHandle<'_>;
}
impl DrawnTableAccess for super::RemoteTables {
fn drawn(&self) -> DrawnTableHandle<'_> {
DrawnTableHandle {
imp: self.imp.get_table::<Drawn>("drawn"),
ctx: std::marker::PhantomData,
}
}
}
pub struct DrawnInsertCallbackId(__sdk::CallbackId);
pub struct DrawnDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for DrawnTableHandle<'ctx> {
type Row = Drawn;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Drawn> + '_ {
self.imp.iter()
}
type InsertCallbackId = DrawnInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DrawnInsertCallbackId {
DrawnInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: DrawnInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = DrawnDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DrawnDeleteCallbackId {
DrawnDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: DrawnDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Drawn>("drawn");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
) -> __sdk::Result<__sdk::TableUpdate<Drawn>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Drawn>", "TableUpdate")
.with_cause(e)
.into()
})
}

View file

@ -0,0 +1,13 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Drawn {}
impl __sdk::InModule for Drawn {
type Module = super::RemoteModule;
}

View file

@ -0,0 +1,95 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::hand_type::Hand;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `hand`.
///
/// Obtain a handle from the [`HandTableAccess::hand`] method on [`super::RemoteTables`],
/// like `ctx.db.hand()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.hand().on_insert(...)`.
pub struct HandTableHandle<'ctx> {
imp: __sdk::TableHandle<Hand>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `hand`.
///
/// Implemented for [`super::RemoteTables`].
pub trait HandTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`HandTableHandle`], which mediates access to the table `hand`.
fn hand(&self) -> HandTableHandle<'_>;
}
impl HandTableAccess for super::RemoteTables {
fn hand(&self) -> HandTableHandle<'_> {
HandTableHandle {
imp: self.imp.get_table::<Hand>("hand"),
ctx: std::marker::PhantomData,
}
}
}
pub struct HandInsertCallbackId(__sdk::CallbackId);
pub struct HandDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for HandTableHandle<'ctx> {
type Row = Hand;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Hand> + '_ {
self.imp.iter()
}
type InsertCallbackId = HandInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> HandInsertCallbackId {
HandInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: HandInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = HandDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> HandDeleteCallbackId {
HandDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: HandDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Hand>("hand");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
) -> __sdk::Result<__sdk::TableUpdate<Hand>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Hand>", "TableUpdate")
.with_cause(e)
.into()
})
}

View file

@ -0,0 +1,13 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Hand {}
impl __sdk::InModule for Hand {
type Module = super::RemoteModule;
}

View file

@ -0,0 +1,101 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct IdentityConnectedArgs {}
impl From<IdentityConnectedArgs> for super::Reducer {
fn from(args: IdentityConnectedArgs) -> Self {
Self::IdentityConnected
}
}
impl __sdk::InModule for IdentityConnectedArgs {
type Module = super::RemoteModule;
}
pub struct IdentityConnectedCallbackId(__sdk::CallbackId);
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `identity_connected`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait identity_connected {
/// Request that the remote module invoke the reducer `identity_connected` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed by listening for [`Self::on_identity_connected`] callbacks.
fn identity_connected(&self) -> __sdk::Result<()>;
/// Register a callback to run whenever we are notified of an invocation of the reducer `identity_connected`.
///
/// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`]
/// to determine the reducer's status.
///
/// The returned [`IdentityConnectedCallbackId`] can be passed to [`Self::remove_on_identity_connected`]
/// to cancel the callback.
fn on_identity_connected(
&self,
callback: impl FnMut(&super::ReducerEventContext) + Send + 'static,
) -> IdentityConnectedCallbackId;
/// Cancel a callback previously registered by [`Self::on_identity_connected`],
/// causing it not to run in the future.
fn remove_on_identity_connected(&self, callback: IdentityConnectedCallbackId);
}
impl identity_connected for super::RemoteReducers {
fn identity_connected(&self) -> __sdk::Result<()> {
self.imp
.call_reducer("identity_connected", IdentityConnectedArgs {})
}
fn on_identity_connected(
&self,
mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static,
) -> IdentityConnectedCallbackId {
IdentityConnectedCallbackId(self.imp.on_reducer(
"identity_connected",
Box::new(move |ctx: &super::ReducerEventContext| {
#[allow(irrefutable_let_patterns)]
let super::ReducerEventContext {
event:
__sdk::ReducerEvent {
reducer: super::Reducer::IdentityConnected {},
..
},
..
} = ctx
else {
unreachable!()
};
callback(ctx)
}),
))
}
fn remove_on_identity_connected(&self, callback: IdentityConnectedCallbackId) {
self.imp.remove_on_reducer("identity_connected", callback.0)
}
}
#[allow(non_camel_case_types)]
#[doc(hidden)]
/// Extension trait for setting the call-flags for the reducer `identity_connected`.
///
/// Implemented for [`super::SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub trait set_flags_for_identity_connected {
/// Set the call-reducer flags for the reducer `identity_connected` to `flags`.
///
/// This type is currently unstable and may be removed without a major version bump.
fn identity_connected(&self, flags: __ws::CallReducerFlags);
}
impl set_flags_for_identity_connected for super::SetReducerFlags {
fn identity_connected(&self, flags: __ws::CallReducerFlags) {
self.imp.set_call_reducer_flags("identity_connected", flags);
}
}

View file

@ -0,0 +1,103 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct IdentityDisconnectedArgs {}
impl From<IdentityDisconnectedArgs> for super::Reducer {
fn from(args: IdentityDisconnectedArgs) -> Self {
Self::IdentityDisconnected
}
}
impl __sdk::InModule for IdentityDisconnectedArgs {
type Module = super::RemoteModule;
}
pub struct IdentityDisconnectedCallbackId(__sdk::CallbackId);
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `identity_disconnected`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait identity_disconnected {
/// Request that the remote module invoke the reducer `identity_disconnected` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed by listening for [`Self::on_identity_disconnected`] callbacks.
fn identity_disconnected(&self) -> __sdk::Result<()>;
/// Register a callback to run whenever we are notified of an invocation of the reducer `identity_disconnected`.
///
/// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`]
/// to determine the reducer's status.
///
/// The returned [`IdentityDisconnectedCallbackId`] can be passed to [`Self::remove_on_identity_disconnected`]
/// to cancel the callback.
fn on_identity_disconnected(
&self,
callback: impl FnMut(&super::ReducerEventContext) + Send + 'static,
) -> IdentityDisconnectedCallbackId;
/// Cancel a callback previously registered by [`Self::on_identity_disconnected`],
/// causing it not to run in the future.
fn remove_on_identity_disconnected(&self, callback: IdentityDisconnectedCallbackId);
}
impl identity_disconnected for super::RemoteReducers {
fn identity_disconnected(&self) -> __sdk::Result<()> {
self.imp
.call_reducer("identity_disconnected", IdentityDisconnectedArgs {})
}
fn on_identity_disconnected(
&self,
mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static,
) -> IdentityDisconnectedCallbackId {
IdentityDisconnectedCallbackId(self.imp.on_reducer(
"identity_disconnected",
Box::new(move |ctx: &super::ReducerEventContext| {
#[allow(irrefutable_let_patterns)]
let super::ReducerEventContext {
event:
__sdk::ReducerEvent {
reducer: super::Reducer::IdentityDisconnected {},
..
},
..
} = ctx
else {
unreachable!()
};
callback(ctx)
}),
))
}
fn remove_on_identity_disconnected(&self, callback: IdentityDisconnectedCallbackId) {
self.imp
.remove_on_reducer("identity_disconnected", callback.0)
}
}
#[allow(non_camel_case_types)]
#[doc(hidden)]
/// Extension trait for setting the call-flags for the reducer `identity_disconnected`.
///
/// Implemented for [`super::SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub trait set_flags_for_identity_disconnected {
/// Set the call-reducer flags for the reducer `identity_disconnected` to `flags`.
///
/// This type is currently unstable and may be removed without a major version bump.
fn identity_disconnected(&self, flags: __ws::CallReducerFlags);
}
impl set_flags_for_identity_disconnected for super::SetReducerFlags {
fn identity_disconnected(&self, flags: __ws::CallReducerFlags) {
self.imp
.set_call_reducer_flags("identity_disconnected", flags);
}
}

View file

@ -0,0 +1,916 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
// This was generated using spacetimedb cli version 1.11.3 (commit 02449737ca3b29e7e39679fccbef541a50f32094).
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
pub mod create_player_reducer;
pub mod drawn_table;
pub mod drawn_type;
pub mod hand_table;
pub mod hand_type;
pub mod identity_connected_reducer;
pub mod identity_disconnected_reducer;
pub mod player_table;
pub mod player_type;
pub mod table_table;
pub mod table_type;
pub mod wall_table;
pub mod wall_type;
pub use create_player_reducer::{
create_player, set_flags_for_create_player, CreatePlayerCallbackId,
};
pub use drawn_table::*;
pub use drawn_type::Drawn;
pub use hand_table::*;
pub use hand_type::Hand;
pub use identity_connected_reducer::{
identity_connected, set_flags_for_identity_connected, IdentityConnectedCallbackId,
};
pub use identity_disconnected_reducer::{
identity_disconnected, set_flags_for_identity_disconnected, IdentityDisconnectedCallbackId,
};
pub use player_table::*;
pub use player_type::Player;
pub use table_table::*;
pub use table_type::Table;
pub use wall_table::*;
pub use wall_type::Wall;
#[derive(Clone, PartialEq, Debug)]
/// One of the reducers defined by this module.
///
/// Contained within a [`__sdk::ReducerEvent`] in [`EventContext`]s for reducer events
/// to indicate which reducer caused the event.
pub enum Reducer {
CreatePlayer { username: String },
IdentityConnected,
IdentityDisconnected,
}
impl __sdk::InModule for Reducer {
type Module = RemoteModule;
}
impl __sdk::Reducer for Reducer {
fn reducer_name(&self) -> &'static str {
match self {
Reducer::CreatePlayer { .. } => "create_player",
Reducer::IdentityConnected => "identity_connected",
Reducer::IdentityDisconnected => "identity_disconnected",
_ => unreachable!(),
}
}
}
impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer {
type Error = __sdk::Error;
fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result<Self> {
match &value.reducer_name[..] {
"create_player" => Ok(__sdk::parse_reducer_args::<
create_player_reducer::CreatePlayerArgs,
>("create_player", &value.args)?
.into()),
"identity_connected" => Ok(__sdk::parse_reducer_args::<
identity_connected_reducer::IdentityConnectedArgs,
>("identity_connected", &value.args)?
.into()),
"identity_disconnected" => Ok(__sdk::parse_reducer_args::<
identity_disconnected_reducer::IdentityDisconnectedArgs,
>("identity_disconnected", &value.args)?
.into()),
unknown => {
Err(
__sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo")
.into(),
)
}
}
}
}
#[derive(Default)]
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct DbUpdate {
drawn: __sdk::TableUpdate<Drawn>,
hand: __sdk::TableUpdate<Hand>,
player: __sdk::TableUpdate<Player>,
table: __sdk::TableUpdate<Table>,
wall: __sdk::TableUpdate<Wall>,
}
impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate {
type Error = __sdk::Error;
fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result<Self, Self::Error> {
let mut db_update = DbUpdate::default();
for table_update in raw.tables {
match &table_update.table_name[..] {
"drawn" => db_update
.drawn
.append(drawn_table::parse_table_update(table_update)?),
"hand" => db_update
.hand
.append(hand_table::parse_table_update(table_update)?),
"player" => db_update
.player
.append(player_table::parse_table_update(table_update)?),
"table" => db_update
.table
.append(table_table::parse_table_update(table_update)?),
"wall" => db_update
.wall
.append(wall_table::parse_table_update(table_update)?),
unknown => {
return Err(__sdk::InternalError::unknown_name(
"table",
unknown,
"DatabaseUpdate",
)
.into());
}
}
}
Ok(db_update)
}
}
impl __sdk::InModule for DbUpdate {
type Module = RemoteModule;
}
impl __sdk::DbUpdate for DbUpdate {
fn apply_to_client_cache(
&self,
cache: &mut __sdk::ClientCache<RemoteModule>,
) -> AppliedDiff<'_> {
let mut diff = AppliedDiff::default();
diff.drawn = cache.apply_diff_to_table::<Drawn>("drawn", &self.drawn);
diff.hand = cache.apply_diff_to_table::<Hand>("hand", &self.hand);
diff.player = cache.apply_diff_to_table::<Player>("player", &self.player);
diff.table = cache.apply_diff_to_table::<Table>("table", &self.table);
diff.wall = cache.apply_diff_to_table::<Wall>("wall", &self.wall);
diff
}
}
#[derive(Default)]
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct AppliedDiff<'r> {
drawn: __sdk::TableAppliedDiff<'r, Drawn>,
hand: __sdk::TableAppliedDiff<'r, Hand>,
player: __sdk::TableAppliedDiff<'r, Player>,
table: __sdk::TableAppliedDiff<'r, Table>,
wall: __sdk::TableAppliedDiff<'r, Wall>,
__unused: std::marker::PhantomData<&'r ()>,
}
impl __sdk::InModule for AppliedDiff<'_> {
type Module = RemoteModule;
}
impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
fn invoke_row_callbacks(
&self,
event: &EventContext,
callbacks: &mut __sdk::DbCallbacks<RemoteModule>,
) {
callbacks.invoke_table_row_callbacks::<Drawn>("drawn", &self.drawn, event);
callbacks.invoke_table_row_callbacks::<Hand>("hand", &self.hand, event);
callbacks.invoke_table_row_callbacks::<Player>("player", &self.player, event);
callbacks.invoke_table_row_callbacks::<Table>("table", &self.table, event);
callbacks.invoke_table_row_callbacks::<Wall>("wall", &self.wall, event);
}
}
#[doc(hidden)]
pub struct RemoteModule;
impl __sdk::InModule for RemoteModule {
type Module = Self;
}
/// The `reducers` field of [`EventContext`] and [`DbConnection`],
/// with methods provided by extension traits for each reducer defined by the module.
pub struct RemoteReducers {
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::InModule for RemoteReducers {
type Module = RemoteModule;
}
/// The `procedures` field of [`DbConnection`] and other [`DbContext`] types,
/// with methods provided by extension traits for each procedure defined by the module.
pub struct RemoteProcedures {
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::InModule for RemoteProcedures {
type Module = RemoteModule;
}
#[doc(hidden)]
/// The `set_reducer_flags` field of [`DbConnection`],
/// with methods provided by extension traits for each reducer defined by the module.
/// Each method sets the flags for the reducer with the same name.
///
/// This type is currently unstable and may be removed without a major version bump.
pub struct SetReducerFlags {
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::InModule for SetReducerFlags {
type Module = RemoteModule;
}
/// The `db` field of [`EventContext`] and [`DbConnection`],
/// with methods provided by extension traits for each table defined by the module.
pub struct RemoteTables {
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::InModule for RemoteTables {
type Module = RemoteModule;
}
/// A connection to a remote module, including a materialized view of a subset of the database.
///
/// Connect to a remote module by calling [`DbConnection::builder`]
/// and using the [`__sdk::DbConnectionBuilder`] builder-pattern constructor.
///
/// You must explicitly advance the connection by calling any one of:
///
/// - [`DbConnection::frame_tick`].
/// - [`DbConnection::run_threaded`].
/// - [`DbConnection::run_async`].
/// - [`DbConnection::advance_one_message`].
/// - [`DbConnection::advance_one_message_blocking`].
/// - [`DbConnection::advance_one_message_async`].
///
/// Which of these methods you should call depends on the specific needs of your application,
/// but you must call one of them, or else the connection will never progress.
pub struct DbConnection {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
#[doc(hidden)]
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::InModule for DbConnection {
type Module = RemoteModule;
}
impl __sdk::DbContext for DbConnection {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl DbConnection {
/// Builder-pattern constructor for a connection to a remote module.
///
/// See [`__sdk::DbConnectionBuilder`] for required and optional configuration for the new connection.
pub fn builder() -> __sdk::DbConnectionBuilder<RemoteModule> {
__sdk::DbConnectionBuilder::new()
}
/// If any WebSocket messages are waiting, process one of them.
///
/// Returns `true` if a message was processed, or `false` if the queue is empty.
/// Callers should invoke this message in a loop until it returns `false`
/// or for as much time is available to process messages.
///
/// Returns an error if the connection is disconnected.
/// If the disconnection in question was normal,
/// i.e. the result of a call to [`__sdk::DbContext::disconnect`],
/// the returned error will be downcastable to [`__sdk::DisconnectedError`].
///
/// This is a low-level primitive exposed for power users who need significant control over scheduling.
/// Most applications should call [`Self::frame_tick`] each frame
/// to fully exhaust the queue whenever time is available.
pub fn advance_one_message(&self) -> __sdk::Result<bool> {
self.imp.advance_one_message()
}
/// Process one WebSocket message, potentially blocking the current thread until one is received.
///
/// Returns an error if the connection is disconnected.
/// If the disconnection in question was normal,
/// i.e. the result of a call to [`__sdk::DbContext::disconnect`],
/// the returned error will be downcastable to [`__sdk::DisconnectedError`].
///
/// This is a low-level primitive exposed for power users who need significant control over scheduling.
/// Most applications should call [`Self::run_threaded`] to spawn a thread
/// which advances the connection automatically.
pub fn advance_one_message_blocking(&self) -> __sdk::Result<()> {
self.imp.advance_one_message_blocking()
}
/// Process one WebSocket message, `await`ing until one is received.
///
/// Returns an error if the connection is disconnected.
/// If the disconnection in question was normal,
/// i.e. the result of a call to [`__sdk::DbContext::disconnect`],
/// the returned error will be downcastable to [`__sdk::DisconnectedError`].
///
/// This is a low-level primitive exposed for power users who need significant control over scheduling.
/// Most applications should call [`Self::run_async`] to run an `async` loop
/// which advances the connection when polled.
pub async fn advance_one_message_async(&self) -> __sdk::Result<()> {
self.imp.advance_one_message_async().await
}
/// Process all WebSocket messages waiting in the queue,
/// then return without `await`ing or blocking the current thread.
pub fn frame_tick(&self) -> __sdk::Result<()> {
self.imp.frame_tick()
}
/// Spawn a thread which processes WebSocket messages as they are received.
pub fn run_threaded(&self) -> std::thread::JoinHandle<()> {
self.imp.run_threaded()
}
/// Run an `async` loop which processes WebSocket messages when polled.
pub async fn run_async(&self) -> __sdk::Result<()> {
self.imp.run_async().await
}
}
impl __sdk::DbConnection for DbConnection {
fn new(imp: __sdk::DbContextImpl<RemoteModule>) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
imp,
}
}
}
/// A handle on a subscribed query.
// TODO: Document this better after implementing the new subscription API.
#[derive(Clone)]
pub struct SubscriptionHandle {
imp: __sdk::SubscriptionHandleImpl<RemoteModule>,
}
impl __sdk::InModule for SubscriptionHandle {
type Module = RemoteModule;
}
impl __sdk::SubscriptionHandle for SubscriptionHandle {
fn new(imp: __sdk::SubscriptionHandleImpl<RemoteModule>) -> Self {
Self { imp }
}
/// Returns true if this subscription has been terminated due to an unsubscribe call or an error.
fn is_ended(&self) -> bool {
self.imp.is_ended()
}
/// Returns true if this subscription has been applied and has not yet been unsubscribed.
fn is_active(&self) -> bool {
self.imp.is_active()
}
/// Unsubscribe from the query controlled by this `SubscriptionHandle`,
/// then run `on_end` when its rows are removed from the client cache.
fn unsubscribe_then(self, on_end: __sdk::OnEndedCallback<RemoteModule>) -> __sdk::Result<()> {
self.imp.unsubscribe_then(Some(on_end))
}
fn unsubscribe(self) -> __sdk::Result<()> {
self.imp.unsubscribe_then(None)
}
}
/// Alias trait for a [`__sdk::DbContext`] connected to this module,
/// with that trait's associated types bounded to this module's concrete types.
///
/// Users can use this trait as a boundary on definitions which should accept
/// either a [`DbConnection`] or an [`EventContext`] and operate on either.
pub trait RemoteDbContext:
__sdk::DbContext<
DbView = RemoteTables,
Reducers = RemoteReducers,
SetReducerFlags = SetReducerFlags,
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
>
{
}
impl<
Ctx: __sdk::DbContext<
DbView = RemoteTables,
Reducers = RemoteReducers,
SetReducerFlags = SetReducerFlags,
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
>,
> RemoteDbContext for Ctx
{
}
/// An [`__sdk::DbContext`] augmented with a [`__sdk::Event`],
/// passed to [`__sdk::Table::on_insert`], [`__sdk::Table::on_delete`] and [`__sdk::TableWithPrimaryKey::on_update`] callbacks.
pub struct EventContext {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
/// The event which caused these callbacks to run.
pub event: __sdk::Event<Reducer>,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::AbstractEventContext for EventContext {
type Event = __sdk::Event<Reducer>;
fn event(&self) -> &Self::Event {
&self.event
}
fn new(imp: __sdk::DbContextImpl<RemoteModule>, event: Self::Event) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
event,
imp,
}
}
}
impl __sdk::InModule for EventContext {
type Module = RemoteModule;
}
impl __sdk::DbContext for EventContext {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl __sdk::EventContext for EventContext {}
/// An [`__sdk::DbContext`] augmented with a [`__sdk::ReducerEvent`],
/// passed to on-reducer callbacks.
pub struct ReducerEventContext {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
/// The event which caused these callbacks to run.
pub event: __sdk::ReducerEvent<Reducer>,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::AbstractEventContext for ReducerEventContext {
type Event = __sdk::ReducerEvent<Reducer>;
fn event(&self) -> &Self::Event {
&self.event
}
fn new(imp: __sdk::DbContextImpl<RemoteModule>, event: Self::Event) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
event,
imp,
}
}
}
impl __sdk::InModule for ReducerEventContext {
type Module = RemoteModule;
}
impl __sdk::DbContext for ReducerEventContext {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl __sdk::ReducerEventContext for ReducerEventContext {}
/// An [`__sdk::DbContext`] passed to procedure callbacks.
pub struct ProcedureEventContext {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::AbstractEventContext for ProcedureEventContext {
type Event = ();
fn event(&self) -> &Self::Event {
&()
}
fn new(imp: __sdk::DbContextImpl<RemoteModule>, _event: Self::Event) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
imp,
}
}
}
impl __sdk::InModule for ProcedureEventContext {
type Module = RemoteModule;
}
impl __sdk::DbContext for ProcedureEventContext {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl __sdk::ProcedureEventContext for ProcedureEventContext {}
/// An [`__sdk::DbContext`] passed to [`__sdk::SubscriptionBuilder::on_applied`] and [`SubscriptionHandle::unsubscribe_then`] callbacks.
pub struct SubscriptionEventContext {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::AbstractEventContext for SubscriptionEventContext {
type Event = ();
fn event(&self) -> &Self::Event {
&()
}
fn new(imp: __sdk::DbContextImpl<RemoteModule>, _event: Self::Event) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
imp,
}
}
}
impl __sdk::InModule for SubscriptionEventContext {
type Module = RemoteModule;
}
impl __sdk::DbContext for SubscriptionEventContext {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl __sdk::SubscriptionEventContext for SubscriptionEventContext {}
/// An [`__sdk::DbContext`] augmented with a [`__sdk::Error`],
/// passed to [`__sdk::DbConnectionBuilder::on_disconnect`], [`__sdk::DbConnectionBuilder::on_connect_error`] and [`__sdk::SubscriptionBuilder::on_error`] callbacks.
pub struct ErrorContext {
/// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
pub db: RemoteTables,
/// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
pub reducers: RemoteReducers,
/// Access to setting the call-flags of each reducer defined for each reducer defined by the module
/// via extension traits implemented for [`SetReducerFlags`].
///
/// This type is currently unstable and may be removed without a major version bump.
pub set_reducer_flags: SetReducerFlags,
/// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`].
pub procedures: RemoteProcedures,
/// The event which caused these callbacks to run.
pub event: Option<__sdk::Error>,
imp: __sdk::DbContextImpl<RemoteModule>,
}
impl __sdk::AbstractEventContext for ErrorContext {
type Event = Option<__sdk::Error>;
fn event(&self) -> &Self::Event {
&self.event
}
fn new(imp: __sdk::DbContextImpl<RemoteModule>, event: Self::Event) -> Self {
Self {
db: RemoteTables { imp: imp.clone() },
reducers: RemoteReducers { imp: imp.clone() },
set_reducer_flags: SetReducerFlags { imp: imp.clone() },
procedures: RemoteProcedures { imp: imp.clone() },
event,
imp,
}
}
}
impl __sdk::InModule for ErrorContext {
type Module = RemoteModule;
}
impl __sdk::DbContext for ErrorContext {
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type Procedures = RemoteProcedures;
type SetReducerFlags = SetReducerFlags;
fn db(&self) -> &Self::DbView {
&self.db
}
fn reducers(&self) -> &Self::Reducers {
&self.reducers
}
fn procedures(&self) -> &Self::Procedures {
&self.procedures
}
fn set_reducer_flags(&self) -> &Self::SetReducerFlags {
&self.set_reducer_flags
}
fn is_active(&self) -> bool {
self.imp.is_active()
}
fn disconnect(&self) -> __sdk::Result<()> {
self.imp.disconnect()
}
type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
fn subscription_builder(&self) -> Self::SubscriptionBuilder {
__sdk::SubscriptionBuilder::new(&self.imp)
}
fn try_identity(&self) -> Option<__sdk::Identity> {
self.imp.try_identity()
}
fn connection_id(&self) -> __sdk::ConnectionId {
self.imp.connection_id()
}
fn try_connection_id(&self) -> Option<__sdk::ConnectionId> {
self.imp.try_connection_id()
}
}
impl __sdk::ErrorContext for ErrorContext {}
impl __sdk::SpacetimeModule for RemoteModule {
type DbConnection = DbConnection;
type EventContext = EventContext;
type ReducerEventContext = ReducerEventContext;
type ProcedureEventContext = ProcedureEventContext;
type SubscriptionEventContext = SubscriptionEventContext;
type ErrorContext = ErrorContext;
type Reducer = Reducer;
type DbView = RemoteTables;
type Reducers = RemoteReducers;
type SetReducerFlags = SetReducerFlags;
type DbUpdate = DbUpdate;
type AppliedDiff<'r> = AppliedDiff<'r>;
type SubscriptionHandle = SubscriptionHandle;
fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {
drawn_table::register_table(client_cache);
hand_table::register_table(client_cache);
player_table::register_table(client_cache);
table_table::register_table(client_cache);
wall_table::register_table(client_cache);
}
}

View file

@ -0,0 +1,95 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::player_type::Player;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `player`.
///
/// Obtain a handle from the [`PlayerTableAccess::player`] method on [`super::RemoteTables`],
/// like `ctx.db.player()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.player().on_insert(...)`.
pub struct PlayerTableHandle<'ctx> {
imp: __sdk::TableHandle<Player>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `player`.
///
/// Implemented for [`super::RemoteTables`].
pub trait PlayerTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`PlayerTableHandle`], which mediates access to the table `player`.
fn player(&self) -> PlayerTableHandle<'_>;
}
impl PlayerTableAccess for super::RemoteTables {
fn player(&self) -> PlayerTableHandle<'_> {
PlayerTableHandle {
imp: self.imp.get_table::<Player>("player"),
ctx: std::marker::PhantomData,
}
}
}
pub struct PlayerInsertCallbackId(__sdk::CallbackId);
pub struct PlayerDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for PlayerTableHandle<'ctx> {
type Row = Player;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Player> + '_ {
self.imp.iter()
}
type InsertCallbackId = PlayerInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> PlayerInsertCallbackId {
PlayerInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: PlayerInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = PlayerDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> PlayerDeleteCallbackId {
PlayerDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: PlayerDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Player>("player");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
) -> __sdk::Result<__sdk::TableUpdate<Player>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Player>", "TableUpdate")
.with_cause(e)
.into()
})
}

View file

@ -0,0 +1,17 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Player {
pub username: String,
pub identity: __sdk::Identity,
pub connection_id: __sdk::ConnectionId,
}
impl __sdk::InModule for Player {
type Module = super::RemoteModule;
}

View file

@ -0,0 +1,97 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::hand_type::Hand;
use super::player_type::Player;
use super::table_type::Table;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `table`.
///
/// Obtain a handle from the [`TableTableAccess::table`] method on [`super::RemoteTables`],
/// like `ctx.db.table()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.table().on_insert(...)`.
pub struct TableTableHandle<'ctx> {
imp: __sdk::TableHandle<Table>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `table`.
///
/// Implemented for [`super::RemoteTables`].
pub trait TableTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`TableTableHandle`], which mediates access to the table `table`.
fn table(&self) -> TableTableHandle<'_>;
}
impl TableTableAccess for super::RemoteTables {
fn table(&self) -> TableTableHandle<'_> {
TableTableHandle {
imp: self.imp.get_table::<Table>("table"),
ctx: std::marker::PhantomData,
}
}
}
pub struct TableInsertCallbackId(__sdk::CallbackId);
pub struct TableDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for TableTableHandle<'ctx> {
type Row = Table;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Table> + '_ {
self.imp.iter()
}
type InsertCallbackId = TableInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TableInsertCallbackId {
TableInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: TableInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = TableDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TableDeleteCallbackId {
TableDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: TableDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Table>("table");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
) -> __sdk::Result<__sdk::TableUpdate<Table>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Table>", "TableUpdate")
.with_cause(e)
.into()
})
}

View file

@ -0,0 +1,19 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::hand_type::Hand;
use super::player_type::Player;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Table {
pub players: Player,
pub hands: Vec<Hand>,
}
impl __sdk::InModule for Table {
type Module = super::RemoteModule;
}

View file

@ -0,0 +1,95 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::wall_type::Wall;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `wall`.
///
/// Obtain a handle from the [`WallTableAccess::wall`] method on [`super::RemoteTables`],
/// like `ctx.db.wall()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.wall().on_insert(...)`.
pub struct WallTableHandle<'ctx> {
imp: __sdk::TableHandle<Wall>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `wall`.
///
/// Implemented for [`super::RemoteTables`].
pub trait WallTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`WallTableHandle`], which mediates access to the table `wall`.
fn wall(&self) -> WallTableHandle<'_>;
}
impl WallTableAccess for super::RemoteTables {
fn wall(&self) -> WallTableHandle<'_> {
WallTableHandle {
imp: self.imp.get_table::<Wall>("wall"),
ctx: std::marker::PhantomData,
}
}
}
pub struct WallInsertCallbackId(__sdk::CallbackId);
pub struct WallDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for WallTableHandle<'ctx> {
type Row = Wall;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Wall> + '_ {
self.imp.iter()
}
type InsertCallbackId = WallInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> WallInsertCallbackId {
WallInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: WallInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = WallDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> WallDeleteCallbackId {
WallDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: WallDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Wall>("wall");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
) -> __sdk::Result<__sdk::TableUpdate<Wall>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Wall>", "TableUpdate")
.with_cause(e)
.into()
})
}

View file

@ -0,0 +1,13 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Wall {}
impl __sdk::InModule for Wall {
type Module = super::RemoteModule;
}

17
jong/src/lib.rs Normal file
View file

@ -0,0 +1,17 @@
#![feature(stmt_expr_attributes, iter_array_chunks)]
use bevy::prelude::*;
use bevy_spacetimedb::StdbConnection;
mod jongline_bindings;
use jongline_bindings::*;
pub mod game;
pub mod tile;
pub mod yakus;
trait EnumNextCycle {
fn next(&self) -> Self;
}
pub type SpacetimeDb<'a> = Res<'a, StdbConnection<DbConnection>>;

61
jong/src/main.rs Normal file
View file

@ -0,0 +1,61 @@
use bevy::{log::LogPlugin, prelude::*};
use bevy_spacetimedb::{StdbConnection, StdbPlugin, TableMessages};
use clap::{Parser, Subcommand};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod gui;
mod tui;
#[derive(Parser)]
struct Args {
#[command(subcommand)]
mode: Mode,
}
#[derive(Subcommand)]
enum Mode {
RunGui,
RunTui,
}
const FILTERSTRING: &str = "warn,jong=trace";
fn main() {
let args = Args::parse();
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::RunTui => {
tracing_subscriber::registry()
.with(tui_logger::TuiTracingSubscriberLayer)
.init();
tui_logger::init_logger(tui_logger::LevelFilter::Trace).unwrap();
tui_logger::set_env_filter_from_string(FILTERSTRING);
// app.add_plugins(tui::RiichiTui)
app.add_plugins(tui::TuiPlugin)
}
};
app.add_plugins(
StdbPlugin::default()
.with_uri("http://localhost:3000")
.with_module_name("jongline")
.with_run_fn(DbConnection::run_threaded)
.add_partial_table(RemoteTables::players, TableMessages::no_update()),
);
app.add_plugins(jong::game::Riichi);
app.run();
}

77
jong/src/tile.rs Normal file
View file

@ -0,0 +1,77 @@
use bevy::prelude::*;
use strum::FromRepr;
#[derive(Component, Debug, Clone, Copy)]
pub struct Tile {
pub suit: Suit,
}
#[derive(/* MapEntities, */ Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
pub enum Suit {
Man(Rank),
Pin(Rank),
Sou(Rank),
Wind(Wind),
Dragon(Dragon),
}
impl Suit {
pub fn rank(&self) -> Option<Rank> {
match self {
Suit::Man(rank) => Some(*rank),
Suit::Pin(rank) => Some(*rank),
Suit::Sou(rank) => Some(*rank),
// Suit::Wind(wind) | Suit::Dragon(dragon) => None,
_ => None,
}
}
}
#[derive(Deref, DerefMut, Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
pub struct Rank(pub u8);
#[derive(FromRepr, Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
pub enum Wind {
Ton,
Nan,
Shaa,
Pei,
}
#[derive(Debug, FromRepr, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
pub enum Dragon {
Haku,
Hatsu,
Chun,
}
#[derive(Component)]
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 {
suit: Suit::Pin(Rank(i)),
});
tiles.push(Tile {
suit: Suit::Sou(Rank(i)),
});
tiles.push(Tile {
suit: Suit::Man(Rank(i)),
});
}
for i in 0..4 {
tiles.push(Tile {
suit: Suit::Wind(Wind::from_repr(i).unwrap()),
});
}
for i in 0..3 {
tiles.push(Tile {
suit: Suit::Dragon(Dragon::from_repr(i).unwrap()),
});
}
}
commands.spawn_batch(tiles);
}

122
jong/src/tui.rs Normal file
View file

@ -0,0 +1,122 @@
use std::time::Duration;
use bevy::{app::ScheduleRunnerPlugin, prelude::*, state::app::StatesPlugin};
use bevy_ratatui::RatatuiPlugins;
use jong::game::{
GameMessage, GameState,
hand::{Drawn, Hand},
player::{CurrentPlayer, Player},
};
use tui_logger::TuiWidgetState;
use crate::tui::{input::ConfirmSelect, states::ConsoleWidget};
mod input;
mod layout;
mod render;
#[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((
MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(
1. / 60.,
))),
RatatuiPlugins {
// enable_kitty_protocol: todo!(),
enable_mouse_capture: true,
enable_input_forwarding: true,
..Default::default()
},
))
.add_plugins(StatesPlugin)
.init_resource::<layout::Overlays>()
.insert_resource(ConsoleWidget {
state: TuiWidgetState::new(),
open: true,
})
.init_state::<states::TuiState>()
.configure_sets(
Update,
(TuiSet::Input, TuiSet::Layout, TuiSet::Render).chain(),
)
.add_message::<ConfirmSelect>()
.add_systems(
Update,
(input::keyboard, input::mouse).in_set(TuiSet::Input),
)
.add_systems(Update, layout::layout.in_set(TuiSet::Layout))
.add_systems(Update, discard_tile.run_if(in_state(GameState::Play)))
.add_systems(
Update,
(
render::render_hands.run_if(in_state(GameState::Play)),
render::render,
)
.chain()
.in_set(TuiSet::Render),
);
}
}
fn discard_tile(
mut writer: MessageWriter<GameMessage>,
mut selected: MessageReader<ConfirmSelect>,
drawn: Single<Entity, With<Drawn>>,
curr_player: Single<Entity, With<CurrentPlayer>>,
player_hands: Populated<(&Player, &Children), With<Hand>>,
hands: Populated<&Children, (With<Hand>, Without<Player>)>,
) {
// trace!("discard_tile");
let hand_ent = player_hands
.get(*curr_player)
.unwrap()
.1
.iter()
.next()
.unwrap();
let hand = hands.get(hand_ent).unwrap();
while let Some(message) = selected.read().next()
&& (message.0 == *drawn || hand.contains(&message.0))
{
writer.write(GameMessage::Discarded(message.0));
}
}
mod states {
use bevy::prelude::*;
use tui_logger::TuiWidgetState;
#[derive(Resource)]
pub(crate) struct ConsoleWidget {
pub(crate) state: TuiWidgetState,
pub(crate) open: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)]
pub(crate) enum TuiState {
#[default]
MainMenu,
InGame,
}
// #[derive(SubStates, Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
// #[source(TuiState = TuiState::MainMenu)]
// pub(crate) enum ZenState {
// #[default]
// Menu,
// Zen,
// }
}

16
jong/src/tui/input.rs Normal file
View file

@ -0,0 +1,16 @@
use bevy::prelude::*;
pub(crate) use keyboard::keyboard;
pub(crate) use mouse::mouse;
pub(crate) mod keyboard;
pub(crate) mod mouse;
#[derive(Component)]
pub(crate) struct Hovered;
#[derive(Component)]
pub(crate) struct StartSelect;
#[derive(Message, Debug)]
pub(crate) struct ConfirmSelect(pub(crate) Entity);

View file

@ -0,0 +1,85 @@
use bevy::prelude::*;
use bevy_ratatui::event::KeyMessage;
use ratatui::crossterm::event::KeyCode;
use tui_logger::TuiWidgetEvent;
use jong::game::GameState;
use crate::tui::layout::Overlays;
use crate::tui::states::ConsoleWidget;
use crate::tui::states::TuiState;
pub(crate) fn keyboard(
mut messages: MessageReader<KeyMessage>,
mut overlays: ResMut<Overlays>,
mut consolewidget: ResMut<ConsoleWidget>,
mut exit: MessageWriter<AppExit>,
curr_gamestate: Res<State<GameState>>,
curr_tuistate: Res<State<TuiState>>,
mut next_gamestate: ResMut<NextState<GameState>>,
mut next_tuistate: ResMut<NextState<TuiState>>,
) {
'message: for message in messages.read() {
let key = message.code;
if consolewidget.handle_keyboard(key) {
continue 'message;
}
for overlay in overlays.stack.iter().rev() {
let consumed = match overlay {
_ => false,
};
if consumed {
continue 'message;
}
}
match curr_tuistate.get() {
TuiState::MainMenu => match key {
KeyCode::Char('p') => {
next_tuistate.set(TuiState::InGame);
next_gamestate.set(GameState::Setup);
}
KeyCode::Char('z') => {
// if let Some(ref curr_zenstate) = curr_zenstate {
// match curr_zenstate.get() {
// ZenState::Menu => next_zenstate.set(ZenState::Zen),
// ZenState::Zen => next_zenstate.set(ZenState::Menu),
// }
// }
}
KeyCode::Char('q') => {
exit.write_default();
}
_ => {}
},
TuiState::InGame => { /* debug!("unhandled keyboard event") */ }
}
}
}
impl ConsoleWidget {
pub(crate) fn handle_keyboard(&mut self, key: KeyCode) -> bool {
if key == KeyCode::Char('`') {
self.open = !self.open;
return true;
}
if self.open {
match key {
KeyCode::Up => self.state.transition(TuiWidgetEvent::UpKey),
KeyCode::Down => self.state.transition(TuiWidgetEvent::DownKey),
// KeyCode::Home => self.state.transition(TuiWidgetEvent::),
// KeyCode::End => self.state.transition(TuiWidgetEvent::),
KeyCode::PageUp => self.state.transition(TuiWidgetEvent::PrevPageKey),
KeyCode::PageDown => self.state.transition(TuiWidgetEvent::NextPageKey),
KeyCode::Esc => {
self.open = false;
return true;
}
_ => return false,
}
}
self.open
}
}

View file

@ -0,0 +1,83 @@
use bevy::prelude::*;
use bevy_ratatui::event::MouseMessage;
use ratatui::layout::Position;
use crate::tui::{
input::{ConfirmSelect, Hovered, StartSelect},
render::PickRegion,
};
pub(crate) fn mouse(
mut commands: Commands,
mut mouse_reader: MessageReader<MouseMessage>,
mut event_writer: MessageWriter<ConfirmSelect>,
entities: Query<(Entity, &PickRegion)>,
hovered: Query<(Entity, &PickRegion), With<Hovered>>,
startselected: Query<(Entity, &PickRegion), With<StartSelect>>,
) {
for message in mouse_reader.read() {
let event = message.0;
// let term_size = context.size().unwrap();
let position = Position::new(event.column, event.row);
match event.kind {
#[allow(clippy::single_match)]
ratatui::crossterm::event::MouseEventKind::Down(mouse_button) => match mouse_button {
ratatui::crossterm::event::MouseButton::Left => {
for (entity, region) in &entities {
if region.area.contains(position) {
commands.entity(entity).insert(StartSelect);
}
}
}
// ratatui::crossterm::event::MouseButton::Right => debug!("unhandled mouse event"),
// ratatui::crossterm::event::MouseButton::Middle => debug!("unhandled mouse event"),
_ => {}
},
ratatui::crossterm::event::MouseEventKind::Moved => {
for (entity, region) in &hovered {
if !region.area.contains(position) {
commands.entity(entity).remove::<Hovered>();
}
}
for (entity, region) in &entities {
if region.area.contains(position) {
commands.entity(entity).insert(Hovered);
}
}
}
ratatui::crossterm::event::MouseEventKind::Up(mouse_button) => match mouse_button {
ratatui::crossterm::event::MouseButton::Left => {
for (entity, region) in &startselected {
if region.area.contains(position) {
commands.entity(entity).remove::<StartSelect>();
event_writer.write(ConfirmSelect(entity));
}
}
}
ratatui::crossterm::event::MouseButton::Right => {
for (entity, _) in &startselected {
commands.entity(entity).remove::<StartSelect>();
}
}
// ratatui::crossterm::event::MouseButton::Middle => debug!("unhandled mouse event"),
_ => {}
},
ratatui::crossterm::event::MouseEventKind::Drag(mouse_button) => {
// debug!("unhandled mouse event")
}
ratatui::crossterm::event::MouseEventKind::ScrollDown => {
// debug!("unhandled mouse event")
}
ratatui::crossterm::event::MouseEventKind::ScrollUp => {
// debug!("unhandled mouse event")
}
ratatui::crossterm::event::MouseEventKind::ScrollLeft => {
// debug!("unhandled mouse event")
}
ratatui::crossterm::event::MouseEventKind::ScrollRight => {
// debug!("unhandled mouse event")
}
}
}
}

82
jong/src/tui/layout.rs Normal file
View file

@ -0,0 +1,82 @@
use bevy::prelude::*;
use bevy_ratatui::RatatuiContext;
use ratatui::{
layout::{Constraint, Layout, Margin},
prelude::Rect,
};
#[derive(Resource, Default)]
pub(crate) struct Overlays {
pub(crate) stack: Vec<Overlay>,
}
pub(crate) enum Overlay {}
#[derive(Resource, Clone, Copy)]
pub(crate) struct HandLayouts {
pub(crate) left_pond: Rect,
pub(crate) cross_pond: Rect,
pub(crate) right_pond: Rect,
pub(crate) this_pond: Rect,
pub(crate) left_hand: Rect,
pub(crate) cross_hand: Rect,
pub(crate) right_hand: Rect,
pub(crate) this_hand: Rect,
}
pub(crate) enum LayoutError {
TerminalTooSmall,
}
pub(crate) fn layout(mut commands: Commands, mut tui: ResMut<RatatuiContext>) -> Result {
tui.autoresize()?;
let frame = tui.get_frame();
let term_area = frame.area();
let tiles = tiles_areas(term_area);
commands.insert_resource(tiles);
Ok(())
}
// TODO make fallible to warn area too small
fn tiles_areas(term_area: Rect) -> HandLayouts {
let constraints = [Constraint::Fill(3), Constraint::Fill(18)];
let horizontal_slicer_right = Layout::horizontal(constraints);
let vertical_slicer_bottom = Layout::vertical(constraints);
let horizontal_slicer_left = Layout::horizontal(constraints.iter().rev());
let vertical_slicer_top = Layout::vertical(constraints.iter().rev());
let [left_hand, this_hand] = horizontal_slicer_right.areas::<2>(term_area);
let [_, left_hand] = vertical_slicer_bottom.areas::<2>(left_hand);
let [_, this_hand] = vertical_slicer_top.areas::<2>(this_hand);
let [cross_hand, right_hand] = horizontal_slicer_left.areas::<2>(term_area);
let [right_hand, _] = vertical_slicer_top.areas::<2>(right_hand);
let [cross_hand, _] = vertical_slicer_bottom.areas::<2>(cross_hand);
let margin = Margin::new(
(left_hand.width + right_hand.width) / 2,
(cross_hand.height + this_hand.height) / 2,
);
let pond_area = term_area.inner(margin);
let all_pond = Layout::horizontal([Constraint::Fill(1); 3]);
let cross_pond =
Layout::vertical([Constraint::Fill(1), Constraint::Max(1), Constraint::Fill(1)]);
let [mut left_pond, center, mut right_pond] = all_pond.areas::<3>(pond_area);
let [cross_pond, _compass, this_pond] = cross_pond.areas::<3>(center);
// let shift = left_pond.height - cross_pond.height;
left_pond.height = cross_pond.height;
left_pond.y += cross_pond.height / 2;
right_pond.height = cross_pond.height;
right_pond.y += cross_pond.height / 2;
HandLayouts {
left_pond,
cross_pond,
right_pond,
this_pond,
left_hand,
cross_hand,
right_hand,
this_hand,
}
}

209
jong/src/tui/render.rs Normal file
View file

@ -0,0 +1,209 @@
use std::io::Write as _;
use bevy::prelude::*;
use bevy_ratatui::RatatuiContext;
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::tile::Tile;
use crate::tui::input::Hovered;
use crate::tui::layout::*;
use crate::tui::states::ConsoleWidget;
#[derive(Component)]
pub(crate) struct PickRegion {
pub(crate) area: Rect,
}
fn render_tile(tile: &Tile, hovered: bool) -> Paragraph<'_> {
let block = ratatui::widgets::Block::bordered();
let mut widget = Paragraph::new(match &tile.suit {
jong::tile::Suit::Pin(rank) => format!("{}\np", rank.0),
jong::tile::Suit::Sou(rank) => format!("{}\ns", rank.0),
jong::tile::Suit::Man(rank) => format!("{}\nm", rank.0),
jong::tile::Suit::Wind(wind) => (match wind {
jong::tile::Wind::Ton => "e\nw",
jong::tile::Wind::Nan => "s\nw",
jong::tile::Wind::Shaa => "w\nw",
jong::tile::Wind::Pei => "n\nw",
})
.into(),
jong::tile::Suit::Dragon(dragon) => (match dragon {
jong::tile::Dragon::Haku => "w\nd",
jong::tile::Dragon::Hatsu => "g\nd",
jong::tile::Dragon::Chun => "r\nd",
})
.into(),
})
.block(block)
.centered();
if hovered {
widget = widget.add_modifier(Modifier::BOLD);
}
widget
}
fn debug_blocks(layouts: HandLayouts, frame: &mut ratatui::Frame<'_>) {
let debug_block = Block::new().borders(Borders::ALL);
frame.render_widget(debug_block.clone(), layouts.this_hand);
frame.render_widget(debug_block.clone(), layouts.left_hand);
frame.render_widget(debug_block.clone(), layouts.cross_hand);
frame.render_widget(debug_block.clone(), layouts.right_hand);
frame.render_widget(debug_block.clone(), layouts.this_pond);
frame.render_widget(debug_block.clone(), layouts.left_pond);
frame.render_widget(debug_block.clone(), layouts.cross_pond);
frame.render_widget(debug_block.clone(), layouts.right_pond);
}
pub(crate) fn render(
mut tui: ResMut<RatatuiContext>,
layouts: Res<HandLayouts>,
overlays: Res<Overlays>,
consolewidget: Res<ConsoleWidget>,
) -> Result {
let mut frame = tui.get_frame();
for overlay in overlays.stack.iter() {
match overlay {
_ => {}
}
}
if consolewidget.open {
let block = Block::bordered().title("console [press esc to close]");
frame.render_widget(Clear, frame.area());
frame.render_widget(
tui_logger::TuiLoggerWidget::default()
.block(block)
.state(&consolewidget.state),
frame.area(), /* .inner(Margin { horizontal: 8, vertical: 8 }) */
);
}
tui.hide_cursor()?;
tui.flush()?;
tui.swap_buffers();
tui.backend_mut().flush()?;
Ok(())
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn render_hands(
mut commands: Commands,
mut tui: ResMut<RatatuiContext>,
hovered: Query<Entity, With<Hovered>>,
layouts: Res<HandLayouts>,
tiles: Query<&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>>,
) -> 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);
Ok((entity, widget, hovered))
})
.collect::<Result<_>>()?;
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);
// 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);
}
// tsumo tile
if this_drawer {
// trace!("this_drawer");
let mut area = drawn_area.resize(Size {
width: 5,
height: 4,
});
area = area.offset(Offset { x: 2, y: 0 });
let hovered = hovered.contains(*drawn_tile);
let widget = render_tile(tiles.get(*drawn_tile)?, hovered);
if hovered {
area = area.offset(Offset { x: 0, y: -1 });
let mut hitbox = area.as_size();
hitbox.height += 1;
commands.entity(*drawn_tile).insert(PickRegion {
area: area.resize(hitbox),
});
} else {
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!(),
// }
}
}
Ok(())
}

1
jong/src/yakus.rs Normal file
View file

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