refactor jong-line
This commit is contained in:
parent
0c3fe6f87a
commit
147f939179
6 changed files with 242 additions and 278 deletions
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue