use crate::card_loader::*; use crate::game::*; use crate::AppState; use crate::DmUserMethod::*; use crate::GameHandlerMessage::*; use crate::SendUserMessage::*; use crate::UserHandlerMessage::*; use axum::extract::ws::Message; use lib::*; use serde_json::to_string; use std::{collections::HashMap, net::SocketAddr, sync::Arc}; use tokio::sync::mpsc::Sender; /// For interacting with the game handler pub enum GameHandlerMessage { NewGame(NewGameRequest, SocketAddr), JoinGame(String, SocketAddr), MoveRequest(PlayerMoveRequest, SocketAddr), JudgeDecision(JudgeDecisionRequest, SocketAddr), DeleteGame(GameDeleteRequest), SendGameStateUpdate(Vec), SendGameMetaUpdate(Vec), BroadcastGamesUpdate(), SendCardPacks(Sender), } /// Handles game stuff pub struct GameHandler { /// Global state pointer state: Arc, games: HashMap, packs: CardPacks, packs_meta: CardPacksMeta, } 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(); GameHandler { state, games, packs, packs_meta, } } /// Handles incoming messages pub async fn handle(&mut 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), 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_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, addr: SocketAddr) { if let Some(this_game) = self.games.get_mut(&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.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 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 fn handle_player_move(&mut self, request: PlayerMoveRequest, addr: SocketAddr) { if let Some(this_game) = self.games.get_mut(&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.player_move(&request, player_user_id) { 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(DmUser(SendChatMessage(message), Addr(addr))) .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(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(&mut self, game_id: String, addr: SocketAddr) { if self.games.contains_key(&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 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!("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_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(), }; // Send user's update let msg = Message::Text(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.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 msg = Message::Text(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.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 msg = Message::Text(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(&mut self, new_game: NewGameRequest, addr: SocketAddr) { 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; } 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.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 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_game_browser_update(); self.broadcast_game_count(); } else { tracing::error!("Attempted to create game for nonexistent player!"); } } /// 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 msg = to_string::(&GamesUpdate { games }).unwrap(); let tx = self.state.tx_broadcast.clone(); tokio::spawn(async move { if let Err(e) = tx.send(Message::Text(msg)) { tracing::error!("Error broadcasting games update: {}", e); } }); } /// Broadcast updated game count fn broadcast_game_count(&self) { let tx = self.state.tx_broadcast.clone(); let active_games: u32 = self.games.len().try_into().unwrap(); let msg = to_string(&ServerActiveGames { active_games }).unwrap(); tokio::spawn(async move { if let Err(e) = tx.send(Message::Text(msg)) { tracing::error!("Error broadcasting game count: {}", e); } }); } /// Send available card packs to a user async fn send_card_packs(&self, tx: Sender) { let msg = Message::Text(to_string::(&self.packs_meta).unwrap()); if let Err(e) = tx.send(msg).await { tracing::error!("Error sending card packs: {}", e) } } }