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 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), SendGameUpdate(Vec), } /// 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, SendGameUpdate(game_ids) => self.send_game_state_update(game_ids), } } /// 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() { // Broadcast game browser update self.state .broadcast_tx .send(meta_games_browser_update(&self.state)) .unwrap(); } 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, player_user_id); self.send_game_state_update(vec![game_id]); } else { tracing::error!("Received judge request for nonexistent judge player!"); } } else { tracing::error!("Received judge request for nonexistent game!"); } } /// 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, player_user_id) { Err(err) => { let message = ChatMessage { text: err }; let tx = self.state.users_tx.clone(); tokio::spawn(async move { let _ = tx.send(DmUser(SendChatMessage(message), Addr(addr))).await; }) } Ok(None) => tokio::spawn(async move { tracing::debug!("None") }), Ok(Some((judge_round, czar_id))) => { let tx = self.state.users_tx.clone(); tokio::spawn(async move { let _ = tx .send(DmUser(SendJudgeRound(judge_round), Id(czar_id))) .await; }) } }; } else { tracing::error!("Nonexistent player tried to submit move for game!"); } } else { tracing::error!("Player tried to submit move for nonexistent game!"); } } // 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, id: String, addr: SocketAddr) { if let Some(this_game) = self.state.games.read().unwrap().get(&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(id.clone(), this_user_id); // Create player this_game.write().unwrap().create_player(this_user.clone()); } else { tracing::error!("Tried to add a nonexistent user to game!"); return; } } else { tracing::error!("User tried to join a nonexistent game!"); return; } tracing::debug!("{:#?}", self.state.games_by_user.read().unwrap()); // Send updates for all players self.send_game_state_update(vec![id]); // Broadcast game browser update self.state .broadcast_tx .send(meta_games_browser_update(&self.state)) .unwrap(); // Broadcast server meta update self.state .broadcast_tx .send(meta_server_summary_update(&self.state)) .unwrap(); } /// Send game state update for all players of a game fn send_game_state_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(), }) .collect::>(); for player in this_game.read().unwrap().players.values() { // Create update for user's game view let meta = GameStateMeta { 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(), 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(), 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 { user_tx.send(msg).await }); } } 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) { // Create manifest let manifest = NewGameManifest { name: new_game.name, host: host.clone(), packs: new_game.packs, }; // 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(vec![game_id]); // Broadcast game browser update self.state .broadcast_tx .send(meta_games_browser_update(&self.state)) .unwrap(); // Broadcast server meta update self.state .broadcast_tx .send(meta_server_summary_update(&self.state)) .unwrap(); } else { tracing::error!("Attempted to create game for nonexistent player!"); } } }