refactor jong-line

This commit is contained in:
Tao Tien 2026-03-02 23:56:30 -08:00
parent 0c3fe6f87a
commit 147f939179
6 changed files with 242 additions and 278 deletions

View file

@ -3,68 +3,46 @@
use log::{debug, trace}; use log::{debug, trace};
use spacetimedb::{ReducerContext, Table, reducer}; use spacetimedb::{ReducerContext, Table, reducer};
use crate::tables::*;
mod reducers; mod reducers;
mod tables; mod tables;
use crate::tables::*;
#[reducer]
pub fn clear_all(ctx: &ReducerContext) {
for row in ctx.db.player().iter() {
ctx.db.player().delete(row);
}
for row in ctx.db.lobby().iter() {
ctx.db.lobby().delete(row);
}
for row in ctx.db.bot().iter() {
ctx.db.bot().delete(row);
}
for row in ctx.db.wall().iter() {
ctx.db.wall().delete(row);
}
for row in ctx.db.tile().iter() {
ctx.db.tile().delete(row);
}
}
#[reducer(client_connected)] #[reducer(client_connected)]
pub fn connect(ctx: &ReducerContext) -> Result<(), String> { pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
let player = if let Some(player) = ctx.db.logged_out_player().identity().find(ctx.sender()) { let player = if let Some(player) = ctx.db.logged_out_user().identity().find(ctx.sender()) {
let player = ctx.db.player().insert(player); let player = ctx.db.user().insert(player);
ctx.db.logged_out_player().identity().delete(ctx.sender()); ctx.db.logged_out_user().identity().delete(ctx.sender());
player player
} else { } else {
debug!("inserting new player with identity {:?}", ctx.sender()); debug!("inserting new user with identity {:?}", ctx.sender());
ctx.db.player().try_insert(Player { ctx.db.user().try_insert(User {
identity: ctx.sender(), identity: ctx.sender(),
id: 0, name: String::new(),
name: None, config_id: 0,
lobby_id: 0, lobby_id: 0,
ready: false,
sort: true,
})? })?
}; };
debug!("player connected: {:?}", player); debug!("user connected: {:?}", player);
Ok(()) Ok(())
} }
#[reducer(client_disconnected)] #[reducer(client_disconnected)]
pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> { pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
let player = ctx let user = ctx
.db .db
.player() .user()
.identity() .identity()
.find(ctx.sender()) .find(ctx.sender())
.ok_or_else(|| format!("can't find player {} to disconnect", ctx.sender()))?; .ok_or_else(|| format!("can't find player {} to disconnect", ctx.sender()))?;
let player = ctx.db.logged_out_player().insert(player); let user = ctx.db.logged_out_user().insert(user);
if !ctx.db.player().identity().delete(ctx.sender()) { if !ctx.db.user().identity().delete(ctx.sender()) {
Err("can't delete row")? Err(format!("can't delete user: {user:?}"))?
} }
debug!("player disconnected: {:?}", player); debug!("user disconnected: {:?}", user);
Ok(()) Ok(())
} }

View file

@ -6,10 +6,10 @@ use spacetimedb::{
}; };
use crate::tables::{ use crate::tables::{
DbTile, DbWall, GameTimer, Lobby, PlayerClock, PlayerHand, bot, game_timer, lobby as _, player, DbTile, Lobby, LobbyTimer, PlayerClock, PlayerHand, Wall, bot, game_timer, lobby as _,
player_clock, player_hand, tile as _, wall, player_clock, player_config, player_hand, tile, user, wall,
}; };
use jong_types::{GameState, PlayerOrBot, TurnState}; use jong_types::{GameState, TurnState};
mod hand; mod hand;
mod lobby; mod lobby;
@ -22,7 +22,7 @@ pub fn advance_game(ctx: &ReducerContext) -> Result<(), String> {
.lobby_id() .lobby_id()
.find( .find(
ctx.db ctx.db
.player() .user()
.identity() .identity()
.find(ctx.sender()) .find(ctx.sender())
.ok_or("player not in lobby")? .ok_or("player not in lobby")?
@ -43,8 +43,7 @@ fn shuffle_wall(ctx: &ReducerContext, lobby: &mut Lobby) {
wall.shuffle(&mut rng); wall.shuffle(&mut rng);
wall wall
}; };
ctx.db.wall().insert(DbWall { ctx.db.wall().insert(Wall {
// id: 0,
lobby_id: lobby.id, lobby_id: lobby.id,
tiles, tiles,
}); });
@ -53,33 +52,17 @@ fn shuffle_wall(ctx: &ReducerContext, lobby: &mut Lobby) {
fn deal_hands(ctx: &ReducerContext, lobby: &mut Lobby) -> Result<(), String> { fn deal_hands(ctx: &ReducerContext, lobby: &mut Lobby) -> Result<(), String> {
let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap(); let mut wall = ctx.db.wall().lobby_id().find(lobby.id).unwrap();
for pob in &lobby.players { for player in &lobby.players {
let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13); let mut tiles = wall.tiles.split_off(wall.tiles.len() - 13);
wall = ctx.db.wall().lobby_id().update(wall); wall = ctx.db.wall().lobby_id().update(wall);
tiles.sort_by_key(|t| t.tile); tiles.sort_by_key(|t| t.tile);
match pob { ctx.db.player_hand().insert(PlayerHand {
PlayerOrBot::Player { id } if let Some(p) = ctx.db.player().id().find(id) => { player_id: *player,
ctx.db.player_hand().insert(PlayerHand { turn_state: TurnState::None,
id: 0, hand: tiles,
player_id: p.id, pond: vec![],
turn_state: jong_types::TurnState::None, working_tile: None,
pond: vec![], });
hand: tiles,
working_tile: None,
});
ctx.db.player_clock().insert(PlayerClock {
id: 0,
player_id: p.id,
renewable: 5,
total: 30,
});
}
PlayerOrBot::Bot { id } if let Some(mut b) = ctx.db.bot().id().find(id) => {
b.hand = tiles;
ctx.db.bot().id().update(b);
}
_ => Err("couldn't find player or bot".to_string())?,
}
} }
lobby.game_state = jong_types::states::GameState::Play; lobby.game_state = jong_types::states::GameState::Play;
@ -87,7 +70,10 @@ fn deal_hands(ctx: &ReducerContext, lobby: &mut Lobby) -> Result<(), String> {
} }
#[reducer] #[reducer]
pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) -> Result<(), String> { pub fn advance_game_private(
ctx: &ReducerContext,
mut game_timer: LobbyTimer,
) -> Result<(), String> {
// checks every second (or more? when users make moves) on whether to advance the game's various states // checks every second (or more? when users make moves) on whether to advance the game's various states
// TODO this, or allow player/debug to call this? // TODO this, or allow player/debug to call this?
@ -99,6 +85,21 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
// TODO keep a count to clear stale lobbies // TODO keep a count to clear stale lobbies
// trace!("shuffle wall"); // trace!("shuffle wall");
shuffle_wall(ctx, &mut lobby); shuffle_wall(ctx, &mut lobby);
lobby.players.shuffle(&mut ctx.rng());
for player_id in lobby
.players
.iter()
.filter(|id| ctx.db.user().config_id().find(*id).is_some())
{
ctx.db.player_clock().insert(PlayerClock {
player_id: *player_id,
renewable: 5,
total: 20,
});
}
ctx.db.lobby().id().update(lobby); ctx.db.lobby().id().update(lobby);
advance_game_private(ctx, game_timer)?; advance_game_private(ctx, game_timer)?;
return Ok(()); return Ok(());
@ -114,62 +115,44 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
GameState::Play => { GameState::Play => {
// trace!("in play"); // trace!("in play");
let curr_player = lobby.players.get(lobby.current_idx as usize).unwrap(); let curr_player = lobby.players.get(lobby.current_idx as usize).unwrap();
match curr_player { let mut hand = ctx.db.player_hand().player_id().find(curr_player).unwrap();
PlayerOrBot::Player { id: player_id } => { match hand.turn_state {
// trace!("current player is {player_id}"); TurnState::None => {
let mut clock = ctx.db.player_clock().player_id().find(player_id).unwrap(); // trace!("draw a tile");
let mut hand = ctx.db.player_hand().player_id().find(player_id).unwrap(); if let Some(mut wall) = ctx.db.wall().lobby_id().find(lobby.id)
match hand.turn_state { && let Some(tile) = wall.tiles.pop()
TurnState::None => { {
// trace!("draw a tile"); hand.working_tile = Some(tile);
if let Some(mut wall) = ctx.db.wall().lobby_id().find(lobby.id) hand.turn_state = TurnState::Tsumo;
&& let Some(tile) = wall.tiles.pop() ctx.db.wall().lobby_id().update(wall);
{ ctx.db.player_hand().player_id().update(hand);
hand.working_tile = Some(tile); } else {
hand.turn_state = TurnState::Tsumo; // TODO out of tiles
ctx.db.wall().lobby_id().update(wall); todo!()
ctx.db.player_hand().id().update(hand);
} else {
// TODO out of tiles
todo!()
}
}
TurnState::Tsumo => {
// trace!("wait for discard");
if clock.tick() {
ctx.db.player_clock().id().update(clock);
} else {
// TODO auto-discard
}
}
TurnState::Menzen => {}
TurnState::RiichiKan => {}
TurnState::RonChiiPonKan => {}
TurnState::End => {}
} }
} }
PlayerOrBot::Bot { id: bot_id } => { TurnState::Tsumo => {
debug!("current bot is {bot_id}"); // only real players have clocks?
let bot = ctx.db.bot().id().find(bot_id).unwrap(); if let Some(mut clock) = ctx.db.player_clock().player_id().find(curr_player)
match bot.turn_state { && clock.tick()
// TurnState::None => todo!(), {
// TurnState::Tsumo => todo!(), ctx.db.player_clock().player_id().update(clock);
// TurnState::Menzen => todo!(), } else {
// TurnState::RiichiKan => todo!(), // TODO bot / auto discard
// TurnState::RonChiiPonKan => todo!(),
// TurnState::End => todo!(),
_ => {}
} }
lobby.next_player();
} }
TurnState::Menzen => {}
TurnState::RiichiKan => {}
TurnState::RonChiiPonKan => {}
TurnState::End => {}
} }
} }
GameState::Exit => { GameState::Exit => {
ctx.db.game_timer().id().delete(game_timer.id); // ctx.db.game_timer().id().delete(game_timer.id);
ctx.db.lobby().id().delete(lobby.id); // ctx.db.lobby().id().delete(lobby.id);
// TODO reset all player lobbies, delete bots, etc? // TODO reset all player lobbies, delete bots, etc?
// is there a way to do this automatically, or rely on elsewhere's checks clearing the state? // is there a way to do this automatically, or rely on elsewhere's checks clearing the state?
return Ok(()); todo!("lobby exit cleanup")
} }
// TODO handle stale lobbies // TODO handle stale lobbies
@ -180,10 +163,10 @@ pub fn advance_game_private(ctx: &ReducerContext, mut game_timer: GameTimer) ->
// ctx.db.game_timer().id().update(game_timer); // ctx.db.game_timer().id().update(game_timer);
ctx.db.lobby().id().update(lobby); ctx.db.lobby().id().update(lobby);
} else { } else {
ctx.db.game_timer().id().delete(game_timer.id); // ctx.db.game_timer().id().delete(game_timer.id);
Err(format!( Err(format!(
"ran schedule {} for empty lobby {}", "ran schedule {} for empty lobby {}",
game_timer.id, game_timer.lobby_id game_timer.scheduled_id, game_timer.lobby_id
))?; ))?;
} }

View file

@ -8,56 +8,63 @@ use crate::tables::*;
// TODO make sure this can't be called or just error here? // TODO make sure this can't be called or just error here?
#[reducer] #[reducer]
pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> { pub fn discard_tile(ctx: &ReducerContext, tile_id: u32) -> Result<(), String> {
let player = ctx.db.player().identity().find(ctx.sender()).unwrap(); let player = ctx.db.user().identity().find(ctx.sender()).unwrap();
let mut hand = ctx.db.player_hand().player_id().find(player.id).unwrap(); let mut hand = ctx
.db
.player_hand()
.player_id()
.find(player.config_id)
.unwrap();
// TODO we can probably remove a buncha these errors // TODO we can probably remove a buncha these errors
let dealt_tile = if let Some(dealt) = ctx.db.tile().id().find(tile_id) { let dealt = ctx.db.tile().id().find(tile_id).unwrap();
if let Some(drawn) = hand.working_tile { let drawn = hand.working_tile.unwrap();
if drawn.id == dealt.id {
// dealt from drawn tile
dealt
} else if let Some((i, _)) = hand.hand.iter().enumerate().find(|(_, t)| t.id == tile_id)
{
// dealt from hand
let dealt = hand.hand.remove(i);
hand.hand.push(drawn);
hand.hand.sort_by_key(|t| t.tile);
dealt let dealt_tile = if dealt.id == drawn.id {
} else { // dealt from drawn tile
return Err(format!( dealt
"player {} attempted to deal tile {} not in hand or drawn", } else if let Some((i, _)) = hand.hand.iter().enumerate().find(|(_, t)| dealt.id == t.id) {
player.id, tile_id // dealt from hand
)); let dealt = hand.hand.remove(i);
} hand.hand.push(drawn);
} else { if ctx
return Err(format!( .db
"player {} attempted to deal tile {} without having drawn", .player_config()
player.id, tile_id .id()
)); .find(player.config_id)
.is_some_and(|c| c.sort)
{
hand.hand.sort_by_key(|t| t.tile);
} }
dealt
} else { } else {
return Err(format!( // ERROR
"player {} attempted to deal nonexistant tile {}", Err("dealt tile is missing")?
player.id, tile_id
));
}; };
hand.pond.push(dealt_tile); hand.pond.push(dealt_tile);
hand.working_tile = None; hand.working_tile = None;
hand.turn_state = TurnState::None; hand.turn_state = TurnState::None;
ctx.db.player_hand().id().update(hand); ctx.db.player_hand().player_id().update(hand);
let mut clock = ctx.db.player_clock().player_id().find(player.id).unwrap(); let mut clock = ctx
.db
.player_clock()
.player_id()
.find(player.config_id)
.unwrap();
clock.renew(); clock.renew();
ctx.db.player_clock().id().update(clock); ctx.db.player_clock().player_id().update(clock);
let mut lobby = ctx.db.lobby().id().find(player.lobby_id).unwrap(); let mut lobby = ctx.db.lobby().id().find(player.lobby_id).unwrap();
lobby.next_player(); lobby.next_player();
ctx.db.lobby().id().update(lobby); ctx.db.lobby().id().update(lobby);
debug!("player {} discarded tile {:?}", player.id, dealt_tile.tile); debug!(
"player {} discarded tile {:?}",
player.identity, dealt_tile.tile
);
Ok(()) Ok(())
} }

View file

@ -1,48 +1,52 @@
use std::time::Duration; use std::time::Duration;
use log::info; use log::{info, warn};
use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer}; use spacetimedb::{ReducerContext, Table, rand::seq::SliceRandom, reducer};
use jong_types::PlayerOrBot;
use crate::{reducers::advance_game_private, tables::*}; use crate::{reducers::advance_game_private, tables::*};
#[reducer] #[reducer]
pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<(), String> { pub fn join_or_create_lobby(ctx: &ReducerContext, mut lobby_id: u32) -> Result<(), String> {
let mut player = ctx let mut user = ctx
.db .db
.player() .user()
.identity() .identity()
.find(ctx.sender()) .find(ctx.sender())
.ok_or(format!("cannot find player {}", ctx.sender()))?; .ok_or(format!("cannot find player {}", ctx.sender()))?;
if lobby_id == 0 && player.lobby_id == 0 { if lobby_id == 0 && user.lobby_id == 0 {
// TODO check first if player is already in a lobby let player = ctx.db.player_config().insert(PlayerConfig {
id: 0,
name: String::new(),
ready: false,
sort: true,
});
let lobby = ctx.db.lobby().insert(Lobby { let lobby = ctx.db.lobby().insert(Lobby {
id: 0, id: 0,
players: vec![PlayerOrBot::Player { id: player.id }], players: vec![player.id],
game_state: jong_types::states::GameState::Lobby, game_state: jong_types::states::GameState::Lobby,
dealer_idx: 0, dealer_idx: 0,
current_idx: 0, current_idx: 0,
}); });
lobby_id = lobby.id;
info!("created lobby: {}", lobby.id); info!("created lobby: {}", lobby.id);
lobby_id = lobby.id; user.config_id = player.id;
player.lobby_id = lobby_id; user.lobby_id = lobby.id;
} else { } else {
let lobby = ctx let lobby = ctx
.db .db
.lobby() .lobby()
.id() .id()
.find(player.lobby_id) .find(user.lobby_id)
.ok_or(format!("can't find lobby {}", player.lobby_id))?; .ok_or(format!("can't find lobby {}", user.lobby_id))?;
lobby_id = lobby.id; lobby_id = lobby.id;
} }
let player = ctx.db.player().identity().update(player); let user = ctx.db.user().identity().update(user);
info!("user {} joined lobby {}", user.name, lobby_id);
info!("player {} joined lobby {}", player.id, lobby_id);
Ok(()) Ok(())
} }
@ -51,19 +55,22 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> {
if lobby_id == 0 { if lobby_id == 0 {
Err("cannot add a bot without a lobby".into()) Err("cannot add a bot without a lobby".into())
} else if let Some(mut lobby) = ctx.db.lobby().id().find(lobby_id) } else if let Some(mut lobby) = ctx.db.lobby().id().find(lobby_id)
&& (ctx.db.player().lobby_id().filter(lobby_id).count() && (ctx.db.user().lobby_id().filter(lobby_id).count()
+ ctx.db.bot().lobby_id().filter(lobby_id).count() + ctx.db.bot().lobby_id().filter(lobby_id).count()
< 4) < 4)
{ {
let player = ctx.db.player_config().insert(PlayerConfig {
id: 0,
name: String::new(),
ready: true,
sort: true,
});
let bot = ctx.db.bot().insert(Bot { let bot = ctx.db.bot().insert(Bot {
id: 0, id: 0,
lobby_id, lobby_id,
hand: vec![], config_id: player.id,
pond: vec![],
working_tile: None,
turn_state: jong_types::TurnState::None,
}); });
lobby.players.push(PlayerOrBot::Bot { id: bot.id }); lobby.players.push(player.id);
ctx.db.lobby().id().update(lobby); ctx.db.lobby().id().update(lobby);
info!("added bot {} to lobby {}", bot.id, lobby_id); info!("added bot {} to lobby {}", bot.id, lobby_id);
Ok(()) Ok(())
@ -74,33 +81,40 @@ pub fn add_bot(ctx: &ReducerContext, lobby_id: u32) -> Result<(), String> {
#[reducer] #[reducer]
pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> { pub fn set_ready(ctx: &ReducerContext, ready: bool) -> Result<(), String> {
let mut player = ctx.db.player().identity().find(ctx.sender()).unwrap(); let mut user = ctx.db.user().identity().find(ctx.sender()).unwrap();
player.ready = ready; let mut player = ctx.db.player_config().id().find(user.config_id).unwrap();
player = ctx.db.player().identity().update(player);
if let Some(mut lobby) = ctx.db.lobby().id().find(player.lobby_id) player.ready = ready;
let player = ctx.db.player_config().id().update(player);
if let Some(mut lobby) = ctx.db.lobby().id().find(user.lobby_id)
&& lobby.players.len() == 4 && lobby.players.len() == 4
&& ctx.db.player().lobby_id().filter(lobby.id).all(|p| p.ready) && lobby.players.iter().all(|id| {
ctx.db
.player_config()
.id()
.find(id)
.is_some_and(|p| p.ready)
})
{ {
lobby.game_state = jong_types::states::GameState::Setup; lobby.game_state = jong_types::states::GameState::Setup;
lobby.players.shuffle(&mut ctx.rng());
let lobby = ctx.db.lobby().id().update(lobby); let lobby = ctx.db.lobby().id().update(lobby);
// TODO should we schedule this outside so that we can clear out stale lobbies? // TODO should we schedule this outside so that we can clear out stale lobbies?
let game_timer = ctx.db.game_timer().insert(GameTimer { let game_timer = ctx.db.game_timer().insert(LobbyTimer {
id: 0,
lobby_id: lobby.id, lobby_id: lobby.id,
scheduled_id: 0,
scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()), scheduled_at: spacetimedb::ScheduleAt::Interval(Duration::from_secs(1).into()),
}); });
advance_game_private(ctx, game_timer)?; advance_game_private(ctx, game_timer)?;
} else { } else {
// if lobby doesn't exist, reset player state // TODO if lobby doesn't exist, reset player state
player.lobby_id = 0;
player.ready = false;
player = ctx.db.player().identity().update(player);
return Err(format!("couldn't find lobby with id: {}", player.lobby_id)); user.lobby_id = 0;
user = ctx.db.user().identity().update(user);
return Err(format!("couldn't find lobby with id: {}", user.lobby_id));
} }
Ok(()) Ok(())

View file

@ -1,13 +1,28 @@
use spacetimedb::{SpacetimeType, ViewContext, table, view}; use spacetimedb::{Identity, SpacetimeType, ViewContext, table, view};
use jong_types::{ use jong_types::{
PlayerOrBot,
states::{GameState, TurnState}, states::{GameState, TurnState},
tiles::Tile, tiles::Tile,
}; };
use crate::reducers::advance_game_private; use crate::reducers::advance_game_private;
#[table(accessor = user)]
#[table(accessor = logged_out_user)]
#[derive(Debug)]
pub struct User {
#[primary_key]
pub identity: Identity,
pub name: String,
#[index(btree)]
pub lobby_id: u32,
#[unique]
pub config_id: u32,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[table(accessor = lobby, public)] #[table(accessor = lobby, public)]
pub struct Lobby { pub struct Lobby {
@ -15,7 +30,7 @@ pub struct Lobby {
#[auto_inc] #[auto_inc]
pub id: u32, pub id: u32,
pub players: Vec<PlayerOrBot>, pub players: Vec<u32>,
pub dealer_idx: u8, pub dealer_idx: u8,
pub current_idx: u8, pub current_idx: u8,
@ -23,8 +38,16 @@ pub struct Lobby {
// pub open_hands: bool, // pub open_hands: bool,
} }
// #[table(accessor = lobby_state, public)]
// pub struct LobbyState {
// #[unique]
// lobby_id: u32,
// current_idx: u8,
// }
#[table(accessor = wall)] #[table(accessor = wall)]
pub struct DbWall { pub struct Wall {
#[primary_key] #[primary_key]
pub lobby_id: u32, pub lobby_id: u32,
@ -41,56 +64,6 @@ pub struct DbTile {
pub tile: Tile, pub tile: Tile,
} }
#[table(accessor = player, public)]
#[table(accessor = logged_out_player)]
#[derive(Debug)]
pub struct Player {
#[unique]
#[auto_inc]
pub id: u32,
#[primary_key]
pub identity: spacetimedb::Identity,
pub name: Option<String>,
#[index(btree)]
pub lobby_id: u32,
pub ready: bool,
pub sort: bool,
}
#[table(accessor = player_clock, public)]
pub struct PlayerClock {
#[primary_key]
pub id: u32,
#[unique]
pub player_id: u32,
pub renewable: u16,
pub total: u16,
}
#[table(accessor = player_hand)]
pub struct PlayerHand {
#[primary_key]
#[auto_inc]
pub id: u32,
#[unique]
pub player_id: u32,
pub turn_state: TurnState,
pub pond: Vec<DbTile>,
pub hand: Vec<DbTile>,
/// drawn or callable tile
pub working_tile: Option<DbTile>,
}
#[table(accessor = bot, public)] #[table(accessor = bot, public)]
pub struct Bot { pub struct Bot {
#[primary_key] #[primary_key]
@ -100,38 +73,68 @@ pub struct Bot {
#[index(btree)] #[index(btree)]
pub lobby_id: u32, pub lobby_id: u32,
pub config_id: u32,
}
#[table(accessor = player_config, public)]
#[derive(Debug)]
pub struct PlayerConfig {
#[primary_key]
#[auto_inc]
pub id: u32,
// TODO randomly generate this from contributor names for bots
pub name: String,
pub ready: bool,
pub sort: bool,
}
#[table(accessor = player_clock, public)]
pub struct PlayerClock {
#[primary_key]
pub player_id: u32,
pub renewable: u16,
pub total: u16,
}
#[table(accessor = player_hand)]
pub struct PlayerHand {
#[primary_key]
pub player_id: u32,
pub turn_state: TurnState, pub turn_state: TurnState,
pub hand: Vec<DbTile>, pub hand: Vec<DbTile>,
pub pond: Vec<DbTile>, pub pond: Vec<DbTile>,
/// drawn or callable tile
pub working_tile: Option<DbTile>, pub working_tile: Option<DbTile>,
} }
#[table(accessor = game_timer, scheduled(advance_game_private), public)] #[table(accessor = game_timer, scheduled(advance_game_private), public)]
pub struct GameTimer { pub struct LobbyTimer {
#[primary_key]
#[auto_inc]
pub id: u64,
#[unique] #[unique]
pub lobby_id: u32, pub lobby_id: u32,
#[primary_key]
#[auto_inc]
pub scheduled_id: u64,
pub scheduled_at: spacetimedb::ScheduleAt, pub scheduled_at: spacetimedb::ScheduleAt,
} }
#[view(accessor = view_hand, public)] #[view(accessor = view_hand, public)]
fn view_hand(ctx: &ViewContext) -> Option<PlayerHand> { fn view_hand(ctx: &ViewContext) -> Option<PlayerHand> {
ctx.db ctx.db
.player() .user()
.identity() .identity()
.find(ctx.sender()) .find(ctx.sender())
.and_then(|p| ctx.db.player_hand().player_id().find(p.id)) .and_then(|p| ctx.db.player_hand().player_id().find(p.config_id))
} }
#[derive(SpacetimeType, Clone)] #[derive(SpacetimeType, Clone)]
pub struct HandView { pub struct HandView {
pub player: PlayerOrBot, pub player_id: u32,
pub hand_length: u8, pub hand_length: u8,
// pub melds: u8, // pub melds: u8,
pub pond: Vec<DbTile>, pub pond: Vec<DbTile>,
@ -140,38 +143,23 @@ pub struct HandView {
#[view(accessor = view_closed_hands, public)] #[view(accessor = view_closed_hands, public)]
fn view_closed_hands(ctx: &ViewContext) -> Vec<HandView> { fn view_closed_hands(ctx: &ViewContext) -> Vec<HandView> {
if let Some(this_player) = ctx.db.player().identity().find(ctx.sender()) if let Some(this_player) = ctx.db.user().identity().find(ctx.sender())
&& let Some(lobby) = ctx.db.lobby().id().find(this_player.lobby_id) && let Some(lobby) = ctx.db.lobby().id().find(this_player.lobby_id)
{ {
lobby lobby
.players .players
.iter() .iter()
.filter_map(|&player| match player { .filter_map(|id| {
PlayerOrBot::Player { id } => { ctx.db
if let Some(player_hand) = ctx.db.player_hand().player_id().find(id) { .player_hand()
Some(HandView { .player_id()
player, .find(id)
hand_length: player_hand.hand.len() as u8, .map(|hand| HandView {
pond: player_hand.pond, player_id: hand.player_id,
drawn: player_hand.turn_state == TurnState::Tsumo hand_length: hand.hand.len() as u8,
&& player_hand.working_tile.is_some(), pond: hand.pond,
}) drawn: hand.turn_state == TurnState::Tsumo && hand.working_tile.is_some(),
} else { })
None
}
}
PlayerOrBot::Bot { id } => {
if let Some(bot) = ctx.db.bot().id().find(id) {
Some(HandView {
player,
hand_length: bot.hand.len() as u8,
pond: bot.pond,
drawn: bot.turn_state == TurnState::Tsumo && bot.working_tile.is_some(),
})
} else {
None
}
}
}) })
.collect() .collect()
} else { } else {

View file

@ -17,9 +17,3 @@ pub mod tiles;
pub use states::*; pub use states::*;
pub use tiles::*; pub use tiles::*;
#[derive(Debug, ..Copy, ..Eq, Hash)]
#[derive(SpacetimeType)]
pub enum PlayerOrBot {
Player { id: u32 },
Bot { id: u32 },
}