use crate::card_loader::*; use crate::game::*; use crate::AppState; use crate::DmUserMethod::*; use crate::GameHandlerMessage::*; use crate::OutgoingMessageHandlerMessage::*; use crate::SendUserMessage::*; use crate::User; use crate::UserHandlerMessage; use axum::extract::ws::Message; use lib::*; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use tokio::sync::mpsc::Sender; /// For interacting with the game handler pub enum GameHandlerMessage { NewGame(NewGameRequest, Arc>), JoinGame(String, Arc>), MoveRequest(PlayerMoveRequest, Arc>), JudgeDecision(JudgeDecisionRequest, String), DeleteGame(GameDeleteRequest), SendGameStateUpdate(Vec), SendGameMetaUpdate(Vec), SendGameUserUpdate(String), BroadcastGamesUpdate(), SendCardPacks(Sender), } /// Handles game stuff pub struct GameHandler { /// Global state pointer state: Arc, games: HashMap, packs: CardPacks, packs_meta: CardPacksMeta, game_id_by_user_id: HashMap>, } impl GameHandler { /// Returns a new game handler pub fn new(state: Arc) -> Self { let games = HashMap::new(); let (packs, packs_meta) = card_loader("data/cah-cards-full.json").unwrap(); let game_id_by_user_id = HashMap::>::new(); GameHandler { state, games, packs, packs_meta, game_id_by_user_id, } } /// Handles incoming messages pub async fn handle(&mut self, message: GameHandlerMessage) { match message { NewGame(request, host) => self.create_new_game(request, host).await, JoinGame(request, user) => self.join_game(request, user).await, MoveRequest(move_request, player_user) => { self.handle_player_move(move_request, player_user).await } JudgeDecision(request, player_user_id) => { self.handle_judging(request, player_user_id).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), SendGameUserUpdate(user_id) => self.send_game_user_update(user_id), BroadcastGamesUpdate() => self.broadcast_game_browser_update(), SendCardPacks(tx) => self.send_card_packs(tx).await, } } /// Delete game async fn delete_game(&mut self, request: GameDeleteRequest) { // TODO: add auth lol if self.games.remove(&request.delete_game_id).is_some() { self.broadcast_game_browser_update(); } else { tracing::error!("User tried to delete a nonexistent game!"); } } /// Process judging async fn handle_judging(&mut self, request: JudgeDecisionRequest, player_user_id: String) { if let Some(this_game) = self.games.get_mut(&request.game_id) { let game_id = request.game_id.to_string(); // Send to game this_game.judge_round(&request, 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 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( &mut self, request: PlayerMoveRequest, player_user: Arc>, ) { if let Some(this_game) = self.games.get_mut(&request.game_id) { let player_user_id = player_user.read().unwrap().uuid.to_string(); // Do the stuff match this_game.player_move(&request, player_user_id.clone()) { Err(err) => { let message = ChatMessage { text: err }; let users_tx = self.state.tx_user_handler.clone(); tokio::spawn(async move { if let Err(e) = users_tx .send(UserHandlerMessage::DmUser( SendChatMessage(message), Id(player_user_id), )) .await { tracing::error!("Could not send message: {}", e); } }); } Ok(None) => { tracing::debug!("TODO: whatever i'm supposed to do") } Ok(Some((judge_round, czar_id))) => { let users_tx = self.state.tx_user_handler.clone(); tokio::spawn(async move { if let Err(e) = users_tx .send(UserHandlerMessage::DmUser( SendJudgeRound(judge_round), Id(czar_id), )) .await { tracing::error!("Could not send message: {}", e); } }); } }; } else { tracing::error!("Player tried to submit move for nonexistent game!"); } self.send_game_meta_update(vec![request.game_id]); } /// Puts a user in a game async fn join_game(&mut self, game_id: String, this_user: Arc>) { if self.games.contains_key(&game_id) { let this_user_id = this_user.read().unwrap().uuid.clone(); // Register game to user if !self.game_id_by_user_id.contains_key(&this_user_id) { self.game_id_by_user_id .insert(this_user_id.clone(), vec![game_id.clone()]); } else if self.game_id_by_user_id.contains_key(&this_user_id) { self.game_id_by_user_id .get_mut(&this_user_id) .unwrap() .extend(vec![game_id.clone()]); } // Create player self.games .get_mut(&game_id) .unwrap() .create_player(this_user.clone()); // Send cards self.send_game_state_update_single(this_user_id, game_id.clone()) } 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_game_browser_update(); self.broadcast_game_count(); } /// 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.games.get(&game_id) { let players = this_game .players .values() .map(|player| GamePlayerMeta { name: player.user.read().unwrap().name.clone(), score: player.black.len().try_into().unwrap(), submitted: this_game .judge_pile_meta .values() .collect::>() .contains(&&player.user.read().unwrap().uuid), }) .collect::>(); for player in this_game.players.values() { // Create update for user's game view let meta = GameMeta { uuid: game_id.clone(), name: this_game.name.clone(), host: this_game.host.read().unwrap().name.clone(), players: players.clone(), czar: this_game.czar.read().unwrap().name.clone(), packs: this_game.packs.clone(), white_count: this_game.white.len().try_into().unwrap(), black_count: this_game.black.len().try_into().unwrap(), white_discard_count: this_game.white_discard.len().try_into().unwrap(), }; let tx = self.state.tx_outgoing_message_handler.clone(); 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) = tx .send(Unicast((user_tx, ServerToClientMessage::GameMeta(meta)))) .await { tracing::error!("Error sending user update: {}", e) } } }); } } else { tracing::error!("Attempted to create game meta update for nonexistent game!"); } } } /// Send game meta update for all players of a game fn send_game_user_update(&self, user_id: String) { if self.game_id_by_user_id.contains_key(&user_id) { let game_ids = self.game_id_by_user_id.get(&user_id).unwrap().to_vec(); self.send_game_meta_update(game_ids); } } /// 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.games.get(&game_id) { for player in this_game.players.values() { // Create update for user's game view let meta = GameStateMeta { black: Some(BlackCardMeta { text: this_game.current_black.text.clone(), pick: this_game.current_black.pick.try_into().unwrap(), }), white: player .white .iter() .map(|card| WhiteCardMeta { uuid: card.uuid.to_string(), text: card.text.clone(), }) .collect(), }; // Send user's update let tx = self.state.tx_outgoing_message_handler.clone(); let user_tx = player.user.read().unwrap().tx.clone(); tokio::spawn(async move { tx.send(Unicast(( user_tx, ServerToClientMessage::GameStateMeta(meta), ))) .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.games.get(&game_id) { if let Some(player) = this_game.players.get(&user_id) { // Create update for user's game view let meta = GameStateMeta { black: Some(BlackCardMeta { text: this_game.current_black.text.clone(), pick: this_game.current_black.pick.try_into().unwrap(), }), white: player .white .iter() .map(|card| WhiteCardMeta { uuid: card.uuid.to_string(), text: card.text.clone(), }) .collect(), }; // Send user's update let tx = self.state.tx_outgoing_message_handler.clone(); let user_tx = player.user.read().unwrap().tx.clone(); tokio::spawn(async move { tx.send(Unicast(( user_tx, ServerToClientMessage::GameStateMeta(meta), ))) .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(&mut self, new_game: NewGameRequest, host: Arc>) { if new_game.game_packs.is_empty() { tracing::error!("New game cards are empty!"); return; } else if new_game.game_name.is_empty() { tracing::error!("New game name is empty!"); return; } let new_game_name; let max_game_name_len = 32; if new_game.game_name.len() > max_game_name_len { new_game_name = new_game.game_name[..max_game_name_len].to_string() } else { new_game_name = new_game.game_name } // Create manifest let manifest = NewGameManifest { name: new_game_name, host: host.clone(), packs: new_game .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.packs, 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.games .insert(new_game_object.uuid.to_string(), new_game_object); // Register game to user let user_id = host.read().unwrap().uuid.clone(); if !self.game_id_by_user_id.contains_key(&user_id) { self.game_id_by_user_id .insert(user_id.clone(), vec![game_id.clone()]); } else if self.game_id_by_user_id.contains_key(&user_id) { self.game_id_by_user_id .get_mut(&user_id) .unwrap() .extend(vec![game_id.clone()]); } self.send_game_state_update_all(vec![game_id.clone()]); self.send_game_meta_update(vec![game_id]); self.broadcast_game_browser_update(); self.broadcast_game_count(); } /// Generate games list update fn broadcast_game_browser_update(&self) { // TODO: this may get expensive if there are many games let games = self .games .values() .map(|game| GameBrowserMeta { uuid: game.uuid.to_string(), name: game.name.clone(), host: game.host.read().unwrap().name.clone(), players: game.players.len().try_into().unwrap(), packs: game.packs.clone(), }) .collect::>(); let tx = self.state.tx_outgoing_message_handler.clone(); tokio::spawn(async move { if let Err(e) = tx .send(Broadcast(ServerToClientMessage::GamesUpdate(GamesUpdate { games, }))) .await { tracing::error!("Error broadcasting games update: {}", e); } }); } /// Broadcast updated game count fn broadcast_game_count(&self) { let tx = self.state.tx_outgoing_message_handler.clone(); let active_games: u32 = self.games.len().try_into().unwrap(); let msg = ServerActiveGames { active_games }; tokio::spawn(async move { if let Err(e) = tx .send(Broadcast(ServerToClientMessage::ServerActiveGames(msg))) .await { tracing::error!("Error broadcasting game count: {}", e); } }); } /// Send available card packs to a user async fn send_card_packs(&self, tx: Sender) { let msg = ServerToClientMessage::CardPacksMeta(self.packs_meta.clone()); if let Err(e) = self .state .tx_outgoing_message_handler .send(Unicast((tx, msg))) .await { tracing::error!("Error sending card packs: {}", e) } } }