use core::num::NonZeroU8; use rand::Rng; use serde::{Deserialize, Serialize}; use super::Result; use crate::{ character::{Character, CharacterId}, diedto::DiedTo, error::GameError, game::{DateTime, GameOver, GameSettings}, message::{CharacterIdentity, Identification, night::ActionPrompt}, player::PlayerId, role::{Role, RoleTitle}, }; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Village { characters: Box<[Character]>, date_time: DateTime, } impl Village { pub fn new(players: &[Identification], settings: GameSettings) -> Result { if settings.min_players_needed() > players.len() { return Err(GameError::TooManyRoles { players: players.len() as u8, roles: settings.min_players_needed() as u8, }); } let mut characters = settings.assign(players)?; assert_eq!(characters.len(), players.len()); characters.sort_by_key(|l| l.number()); Ok(Self { characters, date_time: DateTime::Night { number: 0 }, }) } pub fn killing_wolf(&self) -> Option<&Character> { let wolves = self.characters.iter().filter(|c| c.is_wolf()); { let ww = wolves .clone() .filter(|w| matches!(w.role_title(), RoleTitle::Werewolf)) .collect::>(); if !ww.is_empty() { return Some(ww[rand::random_range(0..ww.len())]); } } { let wolves = wolves.collect::>(); if wolves.is_empty() { return None; } Some(wolves[rand::random_range(0..wolves.len())]) } } pub fn wolf_pack_kill(&self) -> Option { let night = match self.date_time { DateTime::Day { .. } => return None, DateTime::Night { number } => number, }; let no_kill_due_to_disease = self .characters .iter() .filter(|d| matches!(d.role_title(), RoleTitle::Diseased)) .any(|d| match d.died_to() { Some(DiedTo::Wolfpack { night: diseased_death_night, .. }) => (diseased_death_night.get() + 1) == night, _ => false, }); (night > 0 && !no_kill_due_to_disease).then_some(ActionPrompt::WolfPackKill { marked: None, living_villagers: self.living_villagers(), }) } pub const fn date_time(&self) -> DateTime { self.date_time } pub fn find_by_character_id_mut( &mut self, character_id: CharacterId, ) -> Option<&mut Character> { self.characters .iter_mut() .find(|c| c.character_id() == character_id) } fn living_wolves_count(&self) -> usize { self.characters .iter() .filter(|c| c.is_wolf() && c.alive()) .count() } fn living_villager_count(&self) -> usize { self.characters .iter() .filter(|c| c.is_village() && c.alive()) .count() } pub fn is_game_over(&self) -> Option { let wolves = self.living_wolves_count(); let villagers = self.living_villager_count(); let weightlifters = self .living_characters_by_role(RoleTitle::Weightlifter) .len(); if weightlifters > 0 && wolves == 1 && villagers == 1 { return Some(GameOver::VillageWins); } if wolves == 0 { return Some(GameOver::VillageWins); } if wolves >= villagers { return Some(GameOver::WolvesWin); } None } pub fn execute(&mut self, characters: &[CharacterId]) -> Result> { let day = match self.date_time { DateTime::Day { number } => number, DateTime::Night { number: _ } => return Err(GameError::NoExecutionsAtNight), }; let targets = self .characters .iter_mut() .filter(|c| characters.contains(&c.character_id())) .collect::>(); for t in targets { t.execute(day)?; } self.date_time = self.date_time.next(); Ok(self.is_game_over()) } pub fn to_day(&mut self) -> Result { if self.date_time.is_day() { return Err(GameError::AlreadyDaytime); } self.date_time = self.date_time.next(); Ok(self.date_time) } pub fn living_wolf_pack_players(&self) -> Box<[Character]> { self.characters .iter() .filter(|c| c.is_wolf() && c.alive()) .cloned() .collect() } pub fn killing_wolf_id(&self) -> CharacterId { let wolves = self.living_wolf_pack_players(); if let Some(ww) = wolves .iter() .find(|w| matches!(w.role_title(), RoleTitle::Werewolf)) { ww.character_id() } else if let Some(non_ss_wolf) = wolves .iter() .find(|w| w.is_wolf() && !matches!(w.role_title(), RoleTitle::Shapeshifter)) { non_ss_wolf.character_id() } else { wolves.into_iter().next().unwrap().character_id() } } pub fn living_players(&self) -> Box<[CharacterIdentity]> { self.characters .iter() .filter(|c| c.alive()) .map(Character::identity) .collect() } pub fn target_by_id(&self, character_id: CharacterId) -> Result { self.character_by_id(character_id).map(Character::identity) } pub fn living_villagers(&self) -> Box<[CharacterIdentity]> { self.characters .iter() .filter(|c| c.alive() && c.is_village()) .map(Character::identity) .collect() } pub fn living_players_excluding(&self, exclude: CharacterId) -> Box<[CharacterIdentity]> { self.characters .iter() .filter(|c| c.alive() && c.character_id() != exclude) .map(Character::identity) .collect() } pub fn dead_targets(&self) -> Box<[CharacterIdentity]> { self.characters .iter() .filter(|c| !c.alive()) .map(Character::identity) .collect() } pub fn dead_characters(&self) -> Box<[&Character]> { self.characters.iter().filter(|c| !c.alive()).collect() } pub fn executed_known_elder(&self) -> bool { self.characters.iter().any(|d| { d.known_elder() && d.died_to() .map(|d| matches!(d, DiedTo::Execution { .. })) .unwrap_or_default() }) } pub fn executions_on_day(&self, on_day: NonZeroU8) -> Box<[Character]> { self.characters .iter() .filter(|c| match c.died_to() { Some(DiedTo::Execution { day }) => day.get() == on_day.get(), _ => false, }) .cloned() .collect() } pub fn living_characters_by_role(&self, role: RoleTitle) -> Box<[Character]> { self.characters .iter() .filter(|c| c.role_title() == role) .cloned() .collect() } pub fn living_characters_by_role_mut(&mut self, role: RoleTitle) -> Box<[&mut Character]> { self.characters .iter_mut() .filter(|c| c.role_title() == role) .collect() } pub fn characters(&self) -> Box<[Character]> { self.characters.iter().cloned().collect() } pub fn characters_mut(&mut self) -> Box<[&mut Character]> { self.characters.iter_mut().collect() } pub fn character_by_id_mut(&mut self, character_id: CharacterId) -> Result<&mut Character> { self.characters .iter_mut() .find(|c| c.character_id() == character_id) .ok_or(GameError::InvalidTarget) } pub fn character_by_id(&self, character_id: CharacterId) -> Result<&Character> { self.characters .iter() .find(|c| c.character_id() == character_id) .ok_or(GameError::InvalidTarget) } pub fn character_by_player_id(&self, player_id: PlayerId) -> Option<&Character> { self.characters.iter().find(|c| c.player_id() == player_id) } } impl RoleTitle { pub fn title_to_role_excl_apprentice(self) -> Role { match self { RoleTitle::Villager => Role::Villager, RoleTitle::Scapegoat => Role::Scapegoat { redeemed: rand::random(), }, RoleTitle::Seer => Role::Seer, RoleTitle::Arcanist => Role::Arcanist, RoleTitle::Elder => Role::Elder { knows_on_night: NonZeroU8::new(rand::rng().random_range(1u8..3)).unwrap(), woken_for_reveal: false, lost_protection_night: None, }, RoleTitle::Werewolf => Role::Werewolf, RoleTitle::AlphaWolf => Role::AlphaWolf { killed: None }, RoleTitle::DireWolf => Role::DireWolf, RoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None }, RoleTitle::Protector => Role::Protector { last_protected: None, }, RoleTitle::Gravedigger => Role::Gravedigger, RoleTitle::Hunter => Role::Hunter { target: None }, RoleTitle::Militia => Role::Militia { targeted: None }, RoleTitle::MapleWolf => Role::MapleWolf { last_kill_on_night: 0, }, RoleTitle::Guardian => Role::Guardian { last_protected: None, }, RoleTitle::Apprentice => Role::Villager, RoleTitle::Adjudicator => Role::Adjudicator, RoleTitle::PowerSeer => Role::PowerSeer, RoleTitle::Mortician => Role::Mortician, RoleTitle::Beholder => Role::Beholder, RoleTitle::MasonLeader => Role::MasonLeader { recruits_available: 1, recruits: Box::new([]), }, RoleTitle::Empath => Role::Empath { cursed: false }, RoleTitle::Vindicator => Role::Vindicator, RoleTitle::Diseased => Role::Diseased, RoleTitle::BlackKnight => Role::BlackKnight { attacked: None }, RoleTitle::Weightlifter => Role::Weightlifter, RoleTitle::PyreMaster => Role::PyreMaster { villagers_killed: 0, }, } } }