use crate::AppState; use crate::User; use lib::*; use rand::prelude::IteratorRandom; use rand::thread_rng; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use uuid::Uuid; /// Internal manifest for making a new game #[derive(Debug)] pub struct NewGameManifest { /// Game name pub name: String, /// Game host pub host: Arc>, /// Selected game packs pub packs: Vec, } /// 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)] pub struct CardBlackWithID { /// Unique identifier pub uuid: Uuid, /// Card text pub text: String, /// Amount of cards to submit for judging pub pick: u8, } /// A player #[derive(Debug)] pub struct Player { /// Pointer to user pub user: Arc>, /// The player's hand pub white: Vec>, /// The player's wins pub 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, /// List of players in the game pub players: HashMap, /// Black card for the current round pub current_black: Arc, /// Judge pool pub judge_pile_meta: HashMap, String>, /// Judge pile that contains cards judge_pile: HashMap>, /// White draw pile pub white: Vec>, /// White discard pile pub white_discard: Vec>, /// Black draw pile pub black: Vec>, // TODO: do this better rotation: Vec, rotation_index: usize, } impl Game { /// Returns a new game object pub 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_pile_meta: HashMap::new(), judge_pile: HashMap::new(), white_discard: vec![], rotation: vec![], // this gets set in create_user() rotation_index: 0, } } /// Judge Game pub fn judge_round(&mut self, request: JudgeDecisionRequest, player_user_id: String) { // Check if player is czar if self.czar.read().unwrap().uuid.to_string() == player_user_id { let winner_id = self .judge_pile_meta .get(&request.winning_cards) .unwrap() .clone(); self.end_round(winner_id) } else { tracing::error!("Player is not czar!"); } } /// Process player move pub fn player_move( &mut self, request: PlayerMoveRequest, player_user_id: String, ) -> Result, String> { if self.czar.read().unwrap().uuid == player_user_id { tracing::debug!("Player is czar"); Err("You can't submit cards to judge, you ARE the judge!".to_string()) } else { // Error if not enough cards if request.card_ids.len() < self.current_black.pick.into() { return Err("You didn't pick enough cards!".to_string()); } // Ignore extra cards let trimmed = request.card_ids[..self.current_black.pick.into()].to_vec(); tracing::debug!("Trimmed: {:#?}", trimmed); // Move card from player's hand to judge pile for id in &trimmed { let index = self .players .get(&player_user_id) .unwrap() .white .iter() .position(|card| card.uuid.to_string() == *id) .unwrap(); let card = self .players .get_mut(&player_user_id) .unwrap() .white .remove(index); self.judge_pile.insert(card.uuid.to_string(), card); } // Meta for convenience // TODO: don't do this extra work self.judge_pile_meta.insert(trimmed, player_user_id.clone()); // Start judging if this is last player to submit if self.judge_pile_meta.len() == self.players.len() - 1 { Ok(Some(( JudgeRound { cards_to_judge: self .judge_pile_meta .keys() .into_iter() .map(|group| { group .iter() .map(|id| { let card = self.judge_pile.get(id).unwrap().clone(); WhiteCardMeta { uuid: card.uuid.to_string(), text: card.text.clone(), } }) .collect() }) .collect(), }, self.czar.read().unwrap().uuid.clone(), ))) } else { // User submitted cards // TODO: player submitted move indication Ok(None) } } } /// Tick forward fn end_round(&mut self, winner_id: String) { // Top off player hands let user_ids: Vec = self.judge_pile_meta.drain().map(|pair| pair.1).collect(); for id in user_ids { for _ in 0..self.current_black.pick.into() { let card = self.draw_one_white().unwrap(); let player = self.players.get_mut(&id).unwrap(); player.white.push(card); } } // Award card/point to winning player self.players .get_mut(&winner_id) .unwrap() .black .push(self.current_black.clone()); // Draw new black card self.current_black = self.draw_one_black().unwrap(); // End of round clean-up self.white_discard.extend( self.judge_pile .drain() .map(|pair| pair.1) .collect::>>(), ); // Choose new czar if self.rotation_index == self.rotation.len() - 1 { self.rotation_index = 0; } else { self.rotation_index += 1; } self.czar = self .players .get(&self.rotation[self.rotation_index]) .unwrap() .user .clone(); } /// Draw one black card at random from play deck. fn draw_one_black(&mut self) -> Option> { let deck = &mut self.black; 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 } } /// 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>) { // Check if player already exists if !self.players.contains_key(&user.read().unwrap().uuid) { // 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); // Add player to rotation self.rotation.push(user.read().unwrap().uuid.to_string()); } else { tracing::debug!("User already has a player in this game!"); } } }