use crate::user_handler::*; use crate::AppState; use crate::GameHandlerMessage::*; use crate::SendUserMessage::*; use crate::User; use crate::UserHandlerMessage::*; use anyhow::{Context, Result}; use lib::*; use rand::prelude::IteratorRandom; use rand::thread_rng; use serde::Deserialize; use std::{ collections::HashMap, fs::read_to_string, net::SocketAddr, sync::{Arc, RwLock}, }; use uuid::Uuid; /// For interacting with the game handler pub enum GameHandlerMessage { NewGame(NewGameRequest, SocketAddr), JoinGame(String, SocketAddr), MoveRequest(PlayerMoveRequest, SocketAddr), JudgeDecision(JudgeDecisionRequest, SocketAddr), DeleteGame(GameDeleteRequest), } /// 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, } } /// Delete game async fn delete_game(&self, request: GameDeleteRequest) { // TODO: add auth lol let _ = self .state .games .write() .unwrap() .remove(&request.delete_game_id) .unwrap(); // Broadcast game browser update self.state .broadcast_tx .send(meta_games_browser_update(&self.state)) .unwrap(); } /// Process judging async fn handle_judging(&self, request: JudgeDecisionRequest, addr: SocketAddr) { // Get pointers let this_user_id = self .state .online_users .read() .unwrap() .get(&addr) .unwrap() .read() .unwrap() .uuid .to_string(); let this_game = self .state .games .read() .unwrap() .get(&request.game_id) .unwrap() .clone(); // Check if this player is czar // Check if player is currently Czar if this_game .read() .unwrap() .czar .read() .unwrap() .uuid .to_string() == this_user_id { // Find user who submitted the card let winning_user_id = this_game .read() .unwrap() .judge_pool .get(&request.winning_cards) .unwrap() .clone(); tracing::debug!("{:#?} Won the round!", winning_user_id); } } /// Process player move request async fn handle_player_move(&self, request: PlayerMoveRequest, addr: SocketAddr) { // Get pointers let this_user_id = self .state .online_users .read() .unwrap() .get(&addr) .unwrap() .read() .unwrap() .uuid .to_string(); let this_game = self .state .games .read() .unwrap() .get(&request.game_id) .unwrap() .clone(); // Do the stuff // Check if player is currently Czar if this_game .read() .unwrap() .czar .read() .unwrap() .uuid .to_string() == this_user_id { // Tell player no let _ = self .state .users_tx .send(DmUserAddr { addr, message: SendChatMessage(ChatMessage { text: "You can't submit cards to judge, you ARE the judge!".to_string(), }), }) .await; } else { // Ignore extra cards let current_round_pick: usize = this_game.read().unwrap().current_black.pick.into(); // TODO: handle not enough cards submitted let trimmed = &request.card_ids[..current_round_pick]; // Put cards into game judge pool this_game .write() .unwrap() .judge_pool .insert(trimmed.to_vec(), this_user_id.clone()); // Check if this is the last player to submit if this_game.read().unwrap().judge_pool.len() == this_game.read().unwrap().players.len() - 1 { let message = SendJudgeRound(JudgeRound { cards_to_judge: this_game .read() .unwrap() .judge_pool .keys() .into_iter() .map(|group| { group .iter() .map(|id| WhiteCardMeta { uuid: self .state .white_cards_by_id .get(id) .unwrap() .uuid .to_string(), text: self .state .white_cards_by_id .get(id) .unwrap() .text .clone(), }) .collect() }) .collect(), }); tracing::debug!("send for judging"); let czar_id = this_game.read().unwrap().czar.read().unwrap().uuid.clone(); let _ = self .state .users_tx .send(DmUserId { id: czar_id, message, }) .await; } } } /// Puts a user in a game async fn join_game(&self, id: String, addr: SocketAddr) { // Get pointers let this_game = self.state.games.read().unwrap().get(&id).unwrap().clone(); let this_user = self .state .online_users .read() .unwrap() .get(&addr) .unwrap() .clone(); // Check if player already exists if !this_game .read() .unwrap() .players .contains_key(&this_user.read().unwrap().uuid) { // Create player this_game.write().unwrap().create_player(this_user); } // Send updates for all players self.send_game_state_update(id).await; // 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 async fn send_game_state_update(&self, game_id: String) { let this_game = self .state .games .read() .unwrap() .get(&game_id) .unwrap() .clone(); 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: this_game .read() .unwrap() .players .values() .map(|player| player.user.read().unwrap().name.clone()) .collect(), czar: this_game.read().unwrap().host.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(), }; // 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 }); } } /// 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; } // Get host pointer let host = self .state .online_users .read() .unwrap() .get(&addr) .unwrap() .clone(); // 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)), ); self.send_game_state_update(game_id).await; // 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(); } } /// Card Set #[derive(Debug)] struct CardSet { white: Option>>, black: Option>>, } /// Card Packs #[derive(Debug)] pub struct CardPacks { official: HashMap, unofficial: HashMap, } /// A raw white card as it exists in the source json #[derive(Debug, Deserialize)] struct CardWhiteFromJSON { /// Card text text: String, /// ID of the pack it came from pack: u8, } /// A raw black card as it exists in the source json #[derive(Debug, Deserialize)] struct CardBlackFromJSON { /// Card text text: String, /// Amount of cards to submit for judging pick: u8, /// ID of the pack it came from pack: u8, } /// A processed white card for use server-side #[derive(Debug)] pub struct CardWhiteWithID { /// Unique identifier pub uuid: Uuid, /// Card text pub text: String, } /// A processed black card for use server-side #[derive(Debug)] struct CardBlackWithID { /// Unique identifier uuid: Uuid, /// Card text text: String, /// Amount of cards to submit for judging pick: u8, } /// A card pack #[derive(Debug, Deserialize)] struct CardPack { /// Name of the pack name: String, /// Whether or not this is an official card pack official: bool, /// White card data white: Option>, /// Black card data black: Option>, } /// Internal manifest for making a new game #[derive(Debug)] struct NewGameManifest { /// Game name name: String, /// Game host host: Arc>, /// Selected game packs packs: Vec, } /// A player #[derive(Debug)] pub struct Player { /// Pointer to user user: Arc>, /// The player's hand white: Vec>, /// The player's wins black: Vec>, } /// The game object #[derive(Debug)] pub struct Game { /// Game's UUID pub uuid: Uuid, /// The name of the game pub name: String, /// The host user of the game pub host: Arc>, /// Current card czar pub czar: Arc>, /// Packs selected for this game pub packs: Vec, /// White draw pile white: Vec>, /// Black draw pile black: Vec>, pub players: HashMap, /// Black card for the current round current_black: Arc, /// Judge pool judge_pool: HashMap, String>, } impl Game { /// Returns a new game object fn new(state: Arc, request: NewGameManifest) -> Self { // Build the decks let mut white = vec![]; let mut black = vec![]; for pack_num in &request.packs { if let Some(pack) = state.packs.official.get(&pack_num) { if let Some(card) = &pack.white { white.extend(card.clone()) } if let Some(card) = &pack.black { black.extend(card.clone()) } } else if let Some(pack) = state.packs.unofficial.get(&pack_num) { if let Some(card) = &pack.white { white.extend(card.clone()) } if let Some(card) = &pack.black { black.extend(card.clone()) } } } // Draw first black card let current_black = black.swap_remove( (0..black.len()) .choose(&mut thread_rng()) .expect("No black cards to draw from!"), ); // These are at the largest size they should ever be white.shrink_to_fit(); black.shrink_to_fit(); // Return game object Game { uuid: Uuid::now_v7(), name: request.name, host: request.host.clone(), czar: request.host.clone(), white, black, players: HashMap::new(), current_black, packs: request.packs.clone(), judge_pool: HashMap::new(), } } /// Draw one white card at random from play deck. fn draw_one_white(&mut self) -> Option> { let deck = &mut self.white; if let Some(index) = (0..deck.len()).choose(&mut thread_rng()) { Some(deck.swap_remove(index)) } else { tracing::error!("Tried to draw white card that doesn't exist!"); None } } /// Create a new player and add them to the game. pub fn create_player(&mut self, user: Arc>) { // Build hand of 10 white cards let mut white = vec![]; for _ in 0..10 { if let Some(card) = self.draw_one_white() { white.push(card); } } // New player object let new_player = Player { user: user.clone(), white, black: vec![], }; // Add player to game self.players .insert(user.read().unwrap().uuid.clone(), new_player); } } /// Parse json for card data pub fn load_cards_from_json( path: &str, ) -> Result<( CardPacks, CardPacksMeta, HashMap>, )> { // Load in json let data: String = read_to_string(path).with_context(|| format!("Invalid JSON path: \"{}\"", path))?; let jayson: Vec = serde_json::from_str(&data) .with_context(|| format!("The contents of \"{path}\" is not valid JSON."))?; // For global state let mut official: HashMap = HashMap::new(); let mut unofficial: HashMap = HashMap::new(); let mut official_meta: Vec = vec![]; let mut unofficial_meta: Vec = vec![]; let mut white_cards_by_id = HashMap::>::new(); // Unpack the json for sets in jayson { let mut num_white = 0; let mut num_black = 0; let mut newset = CardSet { white: Option::None, black: Option::None, }; // No safe default for this so make it an Option let mut pack: Option = Option::None; // Process white cards if there are any if let Some(ref white) = sets.white { num_white = white.len(); if num_white > 0 { pack = Some(white[0].pack); let mut white_buf = vec![]; for card in sets.white.unwrap() { let uuid = Uuid::now_v7(); let new_card = Arc::new(CardWhiteWithID { uuid, text: card.text, }); white_cards_by_id.insert(uuid.to_string(), new_card.clone()); white_buf.push(new_card); } newset.white = Some(white_buf); } } // Process black cards if there are any if let Some(ref black) = sets.black { num_black = black.len(); if num_black > 0 { pack = Some(black[0].pack); let mut black_buf = vec![]; for card in sets.black.unwrap() { black_buf.push(Arc::new(CardBlackWithID { uuid: Uuid::now_v7(), text: card.text, pick: card.pick, })); } newset.black = Some(black_buf); } } // Start repackaging let meta = CardPackMeta { name: sets.name, pack: pack.expect("No card pack number!"), num_white, num_black, }; if sets.official { official_meta.push(meta); official.insert(pack.unwrap(), newset); } else { unofficial_meta.push(meta); unofficial.insert(pack.unwrap(), newset); } } // These are now the largest size they should ever be official.shrink_to_fit(); unofficial.shrink_to_fit(); official_meta.shrink_to_fit(); unofficial_meta.shrink_to_fit(); // Package for export let packs = CardPacks { official, unofficial, }; let packs_meta = CardPacksMeta { official_meta, unofficial_meta, }; Ok((packs, packs_meta, white_cards_by_id)) }