use crate::game::*; use crate::user_handler::*; use crate::AppState; use crate::DmUserMethod::*; use crate::GameHandlerMessage::*; use crate::SendUserMessage::*; use crate::UserHandlerMessage::*; use lib::*; use serde_json::to_string; use std::{ net::SocketAddr, sync::{Arc, RwLock}, }; /// For interacting with the game handler #[derive(Debug)] pub enum GameHandlerMessage { NewGame(NewGameRequest, SocketAddr), JoinGame(String, SocketAddr), MoveRequest(PlayerMoveRequest, SocketAddr), JudgeDecision(JudgeDecisionRequest, SocketAddr), DeleteGame(GameDeleteRequest), SendGameStateUpdate(Vec), SendGameMetaUpdate(Vec), BroadcastGamesUpdate(), } /// Handles game stuff pub struct GameHandler { /// Global state pointer state: Arc, } impl GameHandler { /// Returns a new game handler pub fn new(state: Arc) -> Self { GameHandler { state } } /// Handles incoming messages pub async fn handle(&self, message: GameHandlerMessage) { match message { NewGame(request, addr) => self.create_new_game(request, addr).await, JoinGame(request, addr) => self.join_game(request, addr).await, MoveRequest(request, addr) => self.handle_player_move(request, addr).await, JudgeDecision(request, addr) => self.handle_judging(request, addr).await, DeleteGame(request) => self.delete_game(request).await, SendGameStateUpdate(game_ids) => self.send_game_state_update_all(game_ids), SendGameMetaUpdate(game_ids) => self.send_game_meta_update(game_ids), BroadcastGamesUpdate() => self.broadcast_games_update(), } } /// Delete game async fn delete_game(&self, request: GameDeleteRequest) { // TODO: add auth lol if self .state .games .write() .unwrap() .remove(&request.delete_game_id) .is_some() { self.broadcast_games_update(); } else { tracing::error!("User tried to delete a nonexistent game!"); } } /// Process judging async fn handle_judging(&self, request: JudgeDecisionRequest, addr: SocketAddr) { if let Some(this_game) = self.state.games.read().unwrap().get(&request.game_id) { if let Some(player_user) = self.state.online_users.read().unwrap().get(&addr) { let player_user_id = player_user.read().unwrap().uuid.to_string(); let game_id = request.game_id.to_string(); // Send to game this_game .write() .unwrap() .judge_round(request.clone(), player_user_id); self.send_game_state_update_all(vec![game_id.clone()]); self.send_game_meta_update(vec![game_id]); } else { tracing::error!("Received judge request for nonexistent judge player!"); } } else { tracing::error!("Received judge request for nonexistent game!"); } // Send updates for all players self.send_game_state_update_all(vec![request.game_id.clone()]); self.send_game_meta_update(vec![request.game_id]); } /// Process player move request async fn handle_player_move(&self, request: PlayerMoveRequest, addr: SocketAddr) { if let Some(this_game) = self.state.games.read().unwrap().get(&request.game_id) { if let Some(player_user) = self.state.online_users.read().unwrap().get(&addr) { let player_user_id = player_user.read().unwrap().uuid.to_string(); // Do the stuff match this_game .write() .unwrap() .player_move(request.clone(), player_user_id) { Err(err) => { let message = ChatMessage { text: err }; let tx = self.state.users_tx.clone(); tokio::spawn(async move { if let Err(e) = tx.send(DmUser(SendChatMessage(message), Addr(addr))).await { tracing::error!("Could not send message: {}", e); } }) } Ok(None) => { tokio::spawn( async move { tracing::debug!("TODO: whatever i'm supposed to do") }, ) } Ok(Some((judge_round, czar_id))) => { let tx = self.state.users_tx.clone(); tokio::spawn(async move { if let Err(e) = tx .send(DmUser(SendJudgeRound(judge_round), Id(czar_id))) .await { tracing::error!("Could not send message: {}", e); } }) } }; } else { tracing::error!("Nonexistent player tried to submit move for game!"); } } else { tracing::error!("Player tried to submit move for nonexistent game!"); } self.send_game_meta_update(vec![request.game_id]); } // Ties game ids to user for easier lookup fn register_user_in_game(&self, game_id: String, user_id: String) { if !self .state .games_by_user .read() .unwrap() .contains_key(&user_id) { self.state .games_by_user .write() .unwrap() .insert(user_id, vec![game_id]); } else if self .state .games_by_user .read() .unwrap() .contains_key(&user_id) { self.state .games_by_user .write() .unwrap() .get_mut(&user_id) .unwrap() .extend(vec![game_id]); } } /// Puts a user in a game async fn join_game(&self, game_id: String, addr: SocketAddr) { if let Some(this_game) = self.state.games.read().unwrap().get(&game_id) { if let Some(this_user) = self.state.online_users.read().unwrap().get(&addr) { let this_user_id = this_user.read().unwrap().uuid.clone(); // Register game to user self.register_user_in_game(game_id.clone(), this_user_id.clone()); // Create player this_game.write().unwrap().create_player(this_user.clone()); // Send cards self.send_game_state_update_single(this_user_id, game_id.clone()) } else { tracing::error!("Tried to add a nonexistent user to game!"); return; } } else { tracing::error!("User tried to join a nonexistent game!"); return; } // Send updates for all players self.send_game_meta_update(vec![game_id.clone()]); self.broadcast_games_update(); // Broadcast server meta update if let Err(e) = self .state .broadcast_tx .send(meta_server_summary_update(&self.state)) { tracing::error!("Could not broadcast server meta update: {}", e); }; } /// Send game meta update for all players of a game fn send_game_meta_update(&self, game_ids: Vec) { for game_id in game_ids { if let Some(this_game) = self.state.games.read().unwrap().get(&game_id) { let players = this_game .read() .unwrap() .players .values() .map(|player| GamePlayerMeta { name: player.user.read().unwrap().name.clone(), score: player.black.len(), submitted: this_game .read() .unwrap() .judge_pile_meta .values() .collect::>() .contains(&&player.user.read().unwrap().uuid), }) .collect::>(); for player in this_game.read().unwrap().players.values() { // Create update for user's game view let meta = GameMeta { uuid: game_id.clone(), name: this_game.read().unwrap().name.clone(), host: this_game.read().unwrap().host.read().unwrap().name.clone(), players: players.clone(), czar: this_game.read().unwrap().czar.read().unwrap().name.clone(), packs: this_game.read().unwrap().packs.clone(), white_count: this_game.read().unwrap().white.len(), black_count: this_game.read().unwrap().black.len(), white_discard_count: this_game.read().unwrap().white_discard.len(), }; // Send user's update let msg = serde_json::to_string(&meta).unwrap(); let user_tx = player.user.read().unwrap().tx.clone(); tokio::spawn(async move { // channel can still close after this point but it'll catch most of it // TODO: handle this later? make this situation impossible to begin with? if !user_tx.is_closed() { if let Err(e) = user_tx.send(msg).await { tracing::error!("Error sending user update: {}", e) } } }); } } else { tracing::error!("Attempted to create game meta update for nonexistent game!"); } } } /// Send game state update for all players of a game fn send_game_state_update_all(&self, game_ids: Vec) { for game_id in game_ids { if let Some(this_game) = self.state.games.read().unwrap().get(&game_id) { for player in this_game.read().unwrap().players.values() { // Create update for user's game view let meta = GameStateMeta { black: ( this_game.read().unwrap().current_black.text.clone(), this_game.read().unwrap().current_black.pick, ), white: player .white .iter() .map(|card| WhiteCardMeta { uuid: card.uuid.to_string(), text: card.text.clone(), }) .collect(), }; // Send user's update let msg = serde_json::to_string(&meta).unwrap(); let user_tx = player.user.read().unwrap().tx.clone(); tokio::spawn(async move { user_tx.send(msg).await }); } } else { tracing::error!("Attempted to create game state update for nonexistent game!"); } } } /// Send game state update for a single user fn send_game_state_update_single(&self, user_id: String, game_id: String) { if let Some(this_game) = self.state.games.read().unwrap().get(&game_id) { if let Some(player) = this_game.read().unwrap().players.get(&user_id) { // Create update for user's game view let meta = GameStateMeta { black: ( this_game.read().unwrap().current_black.text.clone(), this_game.read().unwrap().current_black.pick, ), white: player .white .iter() .map(|card| WhiteCardMeta { uuid: card.uuid.to_string(), text: card.text.clone(), }) .collect(), }; // Send user's update let msg = serde_json::to_string(&meta).unwrap(); let user_tx = player.user.read().unwrap().tx.clone(); tokio::spawn(async move { user_tx.send(msg).await }); } else { tracing::error!("Attempted to create game state update for nonexistent player!"); } } else { tracing::error!("Attempted to create game state update for nonexistent game!"); } } /// Creates a new game async fn create_new_game(&self, new_game: NewGameRequest, addr: SocketAddr) { if new_game.packs.is_empty() { tracing::error!("New game cards are empty!"); return; } else if new_game.name.is_empty() { tracing::error!("New game name is empty!"); return; } if let Some(host) = self.state.online_users.read().unwrap().get(&addr) { let new_game_name; let max_game_name_len = 32; if new_game.name.len() > max_game_name_len { new_game_name = new_game.name[..max_game_name_len].to_string() } else { new_game_name = new_game.name } // Create manifest let manifest = NewGameManifest { name: new_game_name, host: host.clone(), packs: new_game .packs .into_iter() .map(|pack| u8::from_str_radix(&pack, 10).unwrap()) .collect(), }; // Create game using manifest let mut new_game_object = Game::new(self.state.clone(), manifest); // Don't forget to create the host player!!! new_game_object.create_player(host.clone()); let game_id = new_game_object.uuid.to_string(); // Add game to active list self.state.games.write().unwrap().insert( new_game_object.uuid.to_string(), Arc::new(RwLock::new(new_game_object)), ); // Register game to user self.register_user_in_game(game_id.clone(), host.read().unwrap().uuid.clone()); self.send_game_state_update_all(vec![game_id.clone()]); self.send_game_meta_update(vec![game_id]); self.broadcast_games_update(); // Broadcast server meta update let msg = meta_server_summary_update(&self.state); let tx = self.state.broadcast_tx.clone(); tokio::spawn(async move { if let Err(e) = tx.send(msg) { tracing::error!("Could not broadcast server meta update: {}", e); } }); } else { tracing::error!("Attempted to create game for nonexistent player!"); } } /// Generate games list update fn broadcast_games_update(&self) { // TODO: this may get expensive if there are many games let games = self .state .games .read() .unwrap() .values() .map(|game| GameBrowserMeta { uuid: game.read().unwrap().uuid.to_string(), name: game.read().unwrap().name.clone(), host: game.read().unwrap().host.read().unwrap().name.clone(), players: game.read().unwrap().players.len(), packs: game.read().unwrap().packs.clone(), }) .collect::>(); let msg = to_string::(&GamesUpdate { games }).unwrap(); let tx = self.state.broadcast_tx.clone(); tokio::spawn(async move { if let Err(e) = tx.send(msg) { tracing::error!("Error broadcasting games update: {}", e); } }); } }