From 5e3a31471a5a8e1e79cb7f6ea0f0ccdfcf6fb852 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 21 Feb 2025 19:03:20 -0500 Subject: [PATCH 01/23] Optimized remove Added keep_lesser, and keep_greater Added retain_mut Added with_capacity --- server/src/vec_map.rs | 70 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/server/src/vec_map.rs b/server/src/vec_map.rs index a67d5211c..abd9f12dd 100644 --- a/server/src/vec_map.rs +++ b/server/src/vec_map.rs @@ -1,3 +1,5 @@ + + use serde::{Deserialize, Serialize}; #[derive(Clone, Debug)] @@ -21,6 +23,9 @@ impl VecMap where K: Eq { } out } + pub fn with_capacity(capacity: usize) -> Self{ + VecMap { vec: Vec::with_capacity(capacity) } + } /// returns the old value if the key already exists pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)>{ @@ -66,18 +71,12 @@ impl VecMap where K: Eq { } pub fn remove(&mut self, key: &K) -> Option<(K, V)> { - let mut index = None; for (i, (k, _)) in self.vec.iter().enumerate() { if k == key { - index = Some(i); - break; + return Some(self.vec.swap_remove(i)); } } - if let Some(i) = index { - Some(self.vec.remove(i)) - } else { - None - } + return None; } pub fn iter(&self) -> impl Iterator { @@ -104,6 +103,10 @@ impl VecMap where K: Eq { self.vec.retain(|(k, v)| f(k, v)); } + pub fn retain_mut(&mut self, mut f: F) where F: FnMut(&mut K, &mut V) -> bool { + self.vec.retain_mut(|(k, v)| f(k, v)); + } + pub fn keys(&self) -> impl Iterator { self.vec.iter().map(|(k, _)| k) } @@ -120,6 +123,57 @@ impl VecMap where K: Eq { } } +impl VecMap where K: Eq, V: PartialOrd { + /// Returns the old value if the key already exists, the last value of the tuple is the true if the old value was replaced + /// If the old value is greater than value, then it is not replaced + pub fn keep_greater(&mut self, key: K, value: V) -> Option<(K, V, bool)>{ + + if let Some((old_key, old_val)) = self.vec.iter_mut().find(|(k, _)| *k == key) { + if *old_val > value { + Some(( + key, + value, + false, + )) + } else { + Some(( + std::mem::replace(old_key, key), + std::mem::replace(old_val, value), + true, + )) + } + + }else{ + self.vec.push((key, value)); + None + } + } + + /// Returns the old value if the key already exists, the last value of the tuple is the true if the old value was replaced + /// If the old value is lesser than value, then it is not replaced + pub fn keep_lesser(&mut self, key: K, value: V) -> Option<(K, V, bool)>{ + if let Some((old_key, old_val)) = self.vec.iter_mut().find(|(k, _)| *k == key) { + if *old_val < value { + Some(( + key, + value, + false, + )) + } else { + Some(( + std::mem::replace(old_key, key), + std::mem::replace(old_val, value), + true, + )) + } + + }else{ + self.vec.push((key, value)); + None + } + } +} + impl PartialEq for VecMap where K: Eq, V: Eq { fn eq(&self, other: &Self) -> bool { self.vec.iter().all(|(k, v)| other.get(k) == Some(v)) From d60aa2858c745580c37454167bbdf42dad98921f Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:37:49 -0500 Subject: [PATCH 02/23] Added red herrings --- server/src/game/components/confused.rs | 50 +++++++++++++++++++----- server/src/game/components/drunk_aura.rs | 46 +++++++++++++++------- server/src/game/components/mod.rs | 1 + server/src/game/event/on_phase_start.rs | 6 +-- server/src/game/role/detective.rs | 13 +++++- server/src/game/role/drunk.rs | 7 ++-- server/src/game/role/gossip.rs | 18 ++++++--- server/src/game/role/philosopher.rs | 20 ++++++++-- 8 files changed, 122 insertions(+), 39 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index ae43e2ff2..184552126 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -1,12 +1,15 @@ -use std::collections::HashSet; +use rand::seq::IteratorRandom; -use crate::game::{player::PlayerReference, Game}; +use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; +use super::duration::Duration; -#[derive(Default)] -pub struct Confused{ - players: HashSet +#[derive(Default, Clone)] +pub struct Confused { + pub players_durations: VecMap, } + + impl Confused{ fn confused<'a>(game: &'a Game)->&'a Self{ &game.confused @@ -15,18 +18,47 @@ impl Confused{ &mut game.confused } + pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ + let confused = Self::confused_mut(game); + confused.players_durations.insert(player, Duration::Permanent); + } - pub fn add_player(game: &mut Game, player: PlayerReference){ + pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ let confused = Self::confused_mut(game); - confused.players.insert(player); + confused.players_durations.keep_greater(player, Duration::Temporary(duration)); } + pub fn remove_player(game: &mut Game, player: PlayerReference){ let confused = Self::confused_mut(game); - confused.players.remove(&player); + confused.players_durations.remove(&player); } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ let confused = Self::confused(game); - confused.players.contains(&player) + confused.players_durations.contains(&player) + } + + /// Decrements confusion durations and removes players whose durations are up + pub fn on_phase_start(game: &mut Game, phase: PhaseState){ + match phase { + //feel free to change the phase, right now there aren't any ways to temporarily confuse a player so I chose Night mostly arbitrarily + PhaseState::Night => { + game.confused.players_durations.retain_mut( + |_, duration| duration.decrement() + ); + }, + _=>{} + } + } +} + +impl PlayerReference { + pub fn generate_red_herring(self, game: &Game) -> Option{ + return PlayerReference::all_players(game) + .filter(|player| + player.alive(game) && + *player != self + ) + .choose(&mut rand::rng()) } } \ No newline at end of file diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index c87a1868e..91fb70053 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -1,12 +1,10 @@ -use std::collections::HashSet; +use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; -use crate::game::{player::PlayerReference, Game}; - -use super::confused::Confused; +use super::{duration::Duration, confused::Confused}; #[derive(Default, Clone)] pub struct DrunkAura { - pub players: HashSet, + pub players_durations: VecMap, } impl DrunkAura { @@ -17,21 +15,41 @@ impl DrunkAura { &mut game.drunk_aura } - pub fn add_player(game: &mut Game, player: PlayerReference) { - Self::drunk_aura_mut(game).players.insert(player); + pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.insert(player, Duration::Permanent); } - pub fn remove_player(game: &mut Game, player: PlayerReference) { - Self::drunk_aura_mut(game).players.remove(&player); + + pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.keep_greater(player, Duration::Temporary(duration)); } + pub fn remove_player(game: &mut Game, player: PlayerReference){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.remove(&player); + } + pub fn has_drunk_aura(game: &Game, player: PlayerReference) -> bool { - Self::drunk_aura(game).players.contains(&player) + let drunk_aura = Self::drunk_aura(game); + drunk_aura.players_durations.contains(&player) } pub fn on_role_switch(game: &mut Game, player: PlayerReference) { - if Self::has_drunk_aura(game, player) { - Self::remove_player(game, player); - Confused::remove_player(game, player); + Self::remove_player(game, player); + Confused::remove_player(game, player); + } + + ///Decrements drunk aura durations and removes players whose durations are up + pub fn on_phase_start(game: &mut Game, phase: PhaseState){ + match phase { + //feel free to change the phase, right now there aren't any ways to temporarily give a player drunk aura so I chose Night mostly arbitrarily + PhaseState::Night => { + game.drunk_aura.players_durations.retain_mut( + |_, duration| duration.decrement() + ); + }, + _=>{} } } -} \ No newline at end of file +} diff --git a/server/src/game/components/mod.rs b/server/src/game/components/mod.rs index e459b1328..d44778b31 100644 --- a/server/src/game/components/mod.rs +++ b/server/src/game/components/mod.rs @@ -11,6 +11,7 @@ pub mod insider_group; pub mod detained; pub mod confused; pub mod drunk_aura; +pub mod duration; pub mod forfeit_vote; pub mod night_visits; pub mod syndicate_gun_item; diff --git a/server/src/game/event/on_phase_start.rs b/server/src/game/event/on_phase_start.rs index fb1fce080..f3c90b582 100644 --- a/server/src/game/event/on_phase_start.rs +++ b/server/src/game/event/on_phase_start.rs @@ -1,8 +1,6 @@ use crate::game::{ ability_input::saved_controllers_map::SavedControllersMap, components::{ - cult::Cult, detained::Detained, - mafia::Mafia, night_visits::NightVisits, - verdicts_today::VerdictsToday + confused::Confused, cult::Cult, detained::Detained, drunk_aura::DrunkAura, mafia::Mafia, night_visits::NightVisits, verdicts_today::VerdictsToday }, modifiers::Modifiers, phase::PhaseState, player::PlayerReference, Game }; @@ -26,6 +24,8 @@ impl OnPhaseStart{ Cult::on_phase_start(game, self.phase.phase()); SavedControllersMap::on_phase_start(game, self.phase.phase()); Modifiers::on_phase_start(game, self.phase.clone()); + Confused::on_phase_start(game, self.phase.clone()); + DrunkAura::on_phase_start(game, self.phase.clone()); game.on_phase_start(self.phase.phase()); } diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index 842c1e050..b2e44ab38 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -2,6 +2,7 @@ use serde::Serialize; use crate::game::ability_input::ControllerID; use crate::game::components::confused::Confused; +use crate::game::role::RoleState; use crate::game::{attack_power::DefensePower, chat::ChatMessageVariant}; use crate::game::game_conclusion::GameConclusion; use crate::game::player::PlayerReference; @@ -15,7 +16,9 @@ pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Detective; +pub struct Detective { + red_herring: Option, +} impl RoleStateImpl for Detective { type ClientRoleState = Detective; @@ -25,7 +28,7 @@ impl RoleStateImpl for Detective { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ let suspicious = if Confused::is_confused(game, actor_ref) { - false + self.red_herring.is_some_and(|red_herring| red_herring == visit.target) }else{ Detective::player_is_suspicious(game, visit.target) }; @@ -55,6 +58,12 @@ impl RoleStateImpl for Detective { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Detective(Detective{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Detective { diff --git a/server/src/game/role/drunk.rs b/server/src/game/role/drunk.rs index e44f08848..6b3ea450f 100644 --- a/server/src/game/role/drunk.rs +++ b/server/src/game/role/drunk.rs @@ -25,13 +25,14 @@ impl RoleStateImpl for Drunk { //special case here. I don't want to use set_role because it alerts the player their role changed //NOTE: It will still send a packet to the player that their role state updated, - //so it might be deducable that there is a recruiter + //so it might be deductible that there is a recruiter + // Sammy wrote the above, I have no idea why recruiter has anything to do with this, I just fixed his typo (deducable -> deductible) if let Some(random_town_role) = possible_roles.choose(&mut rand::rng()) { actor_ref.set_role_state(game, random_town_role.new_state(game)); } - Confused::add_player(game, actor_ref); - DrunkAura::add_player(game, actor_ref); + Confused::add_player_permanent(game, actor_ref); + DrunkAura::add_player_permanent(game, actor_ref); } } impl Drunk{ diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index 8726f59a7..1ae030c4b 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -8,14 +8,16 @@ use crate::game::visit::Visit; use crate::game::Game; use super::detective::Detective; -use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; +use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Gossip; +pub struct Gossip { + red_herring: Option, +} impl RoleStateImpl for Gossip { type ClientRoleState = Gossip; @@ -25,9 +27,9 @@ impl RoleStateImpl for Gossip { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ - let enemies = if Confused::is_confused(game, actor_ref){ - false - }else{ + let enemies = if Confused::is_confused(game, actor_ref) { + self.red_herring.is_some_and(|red_herring| red_herring == visit.target) + } else { Gossip::enemies(game, visit.target) }; @@ -54,6 +56,12 @@ impl RoleStateImpl for Gossip { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Gossip(Gossip{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Gossip { diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index 849415b3c..e4f8e331c 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -10,10 +10,12 @@ use crate::game::visit::Visit; use crate::game::Game; use crate::vec_set; -use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; +use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; #[derive(Clone, Debug, Serialize, Default)] -pub struct Philosopher; +pub struct Philosopher{ + red_herring: Option, +} pub(super) const MAXIMUM_COUNT: Option = None; @@ -28,8 +30,14 @@ impl RoleStateImpl for Philosopher { let Some(first_visit) = actor_visits.get(0) else {return;}; let Some(second_visit) = actor_visits.get(1) else {return;}; - let enemies = if Confused::is_confused(game, actor_ref) { + let enemies = + if first_visit.target == second_visit.target { false + } else if Confused::is_confused(game, actor_ref) { + self.red_herring.is_some_and(|red_herring| + red_herring == first_visit.target || + red_herring == second_visit.target + ) } else { Philosopher::players_are_enemies(game, first_visit.target, second_visit.target) }; @@ -70,6 +78,12 @@ impl RoleStateImpl for Philosopher { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Philosopher(Philosopher{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Philosopher{ pub fn players_are_enemies(game: &Game, a: PlayerReference, b: PlayerReference) -> bool { From e39c5da63b7d55a96f1954d64b77b80414e9ea98 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:37:49 -0500 Subject: [PATCH 03/23] Added red herrings --- server/src/game/components/confused.rs | 50 +++++++++++++++++++----- server/src/game/components/drunk_aura.rs | 46 +++++++++++++++------- server/src/game/components/mod.rs | 1 + server/src/game/event/on_phase_start.rs | 6 +-- server/src/game/role/detective.rs | 13 +++++- server/src/game/role/drunk.rs | 7 ++-- server/src/game/role/gossip.rs | 18 ++++++--- server/src/game/role/philosopher.rs | 20 ++++++++-- 8 files changed, 122 insertions(+), 39 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index ae43e2ff2..184552126 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -1,12 +1,15 @@ -use std::collections::HashSet; +use rand::seq::IteratorRandom; -use crate::game::{player::PlayerReference, Game}; +use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; +use super::duration::Duration; -#[derive(Default)] -pub struct Confused{ - players: HashSet +#[derive(Default, Clone)] +pub struct Confused { + pub players_durations: VecMap, } + + impl Confused{ fn confused<'a>(game: &'a Game)->&'a Self{ &game.confused @@ -15,18 +18,47 @@ impl Confused{ &mut game.confused } + pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ + let confused = Self::confused_mut(game); + confused.players_durations.insert(player, Duration::Permanent); + } - pub fn add_player(game: &mut Game, player: PlayerReference){ + pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ let confused = Self::confused_mut(game); - confused.players.insert(player); + confused.players_durations.keep_greater(player, Duration::Temporary(duration)); } + pub fn remove_player(game: &mut Game, player: PlayerReference){ let confused = Self::confused_mut(game); - confused.players.remove(&player); + confused.players_durations.remove(&player); } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ let confused = Self::confused(game); - confused.players.contains(&player) + confused.players_durations.contains(&player) + } + + /// Decrements confusion durations and removes players whose durations are up + pub fn on_phase_start(game: &mut Game, phase: PhaseState){ + match phase { + //feel free to change the phase, right now there aren't any ways to temporarily confuse a player so I chose Night mostly arbitrarily + PhaseState::Night => { + game.confused.players_durations.retain_mut( + |_, duration| duration.decrement() + ); + }, + _=>{} + } + } +} + +impl PlayerReference { + pub fn generate_red_herring(self, game: &Game) -> Option{ + return PlayerReference::all_players(game) + .filter(|player| + player.alive(game) && + *player != self + ) + .choose(&mut rand::rng()) } } \ No newline at end of file diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index c87a1868e..91fb70053 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -1,12 +1,10 @@ -use std::collections::HashSet; +use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; -use crate::game::{player::PlayerReference, Game}; - -use super::confused::Confused; +use super::{duration::Duration, confused::Confused}; #[derive(Default, Clone)] pub struct DrunkAura { - pub players: HashSet, + pub players_durations: VecMap, } impl DrunkAura { @@ -17,21 +15,41 @@ impl DrunkAura { &mut game.drunk_aura } - pub fn add_player(game: &mut Game, player: PlayerReference) { - Self::drunk_aura_mut(game).players.insert(player); + pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.insert(player, Duration::Permanent); } - pub fn remove_player(game: &mut Game, player: PlayerReference) { - Self::drunk_aura_mut(game).players.remove(&player); + + pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.keep_greater(player, Duration::Temporary(duration)); } + pub fn remove_player(game: &mut Game, player: PlayerReference){ + let drunk_aura = Self::drunk_aura_mut(game); + drunk_aura.players_durations.remove(&player); + } + pub fn has_drunk_aura(game: &Game, player: PlayerReference) -> bool { - Self::drunk_aura(game).players.contains(&player) + let drunk_aura = Self::drunk_aura(game); + drunk_aura.players_durations.contains(&player) } pub fn on_role_switch(game: &mut Game, player: PlayerReference) { - if Self::has_drunk_aura(game, player) { - Self::remove_player(game, player); - Confused::remove_player(game, player); + Self::remove_player(game, player); + Confused::remove_player(game, player); + } + + ///Decrements drunk aura durations and removes players whose durations are up + pub fn on_phase_start(game: &mut Game, phase: PhaseState){ + match phase { + //feel free to change the phase, right now there aren't any ways to temporarily give a player drunk aura so I chose Night mostly arbitrarily + PhaseState::Night => { + game.drunk_aura.players_durations.retain_mut( + |_, duration| duration.decrement() + ); + }, + _=>{} } } -} \ No newline at end of file +} diff --git a/server/src/game/components/mod.rs b/server/src/game/components/mod.rs index e459b1328..d44778b31 100644 --- a/server/src/game/components/mod.rs +++ b/server/src/game/components/mod.rs @@ -11,6 +11,7 @@ pub mod insider_group; pub mod detained; pub mod confused; pub mod drunk_aura; +pub mod duration; pub mod forfeit_vote; pub mod night_visits; pub mod syndicate_gun_item; diff --git a/server/src/game/event/on_phase_start.rs b/server/src/game/event/on_phase_start.rs index fb1fce080..f3c90b582 100644 --- a/server/src/game/event/on_phase_start.rs +++ b/server/src/game/event/on_phase_start.rs @@ -1,8 +1,6 @@ use crate::game::{ ability_input::saved_controllers_map::SavedControllersMap, components::{ - cult::Cult, detained::Detained, - mafia::Mafia, night_visits::NightVisits, - verdicts_today::VerdictsToday + confused::Confused, cult::Cult, detained::Detained, drunk_aura::DrunkAura, mafia::Mafia, night_visits::NightVisits, verdicts_today::VerdictsToday }, modifiers::Modifiers, phase::PhaseState, player::PlayerReference, Game }; @@ -26,6 +24,8 @@ impl OnPhaseStart{ Cult::on_phase_start(game, self.phase.phase()); SavedControllersMap::on_phase_start(game, self.phase.phase()); Modifiers::on_phase_start(game, self.phase.clone()); + Confused::on_phase_start(game, self.phase.clone()); + DrunkAura::on_phase_start(game, self.phase.clone()); game.on_phase_start(self.phase.phase()); } diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index 842c1e050..b2e44ab38 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -2,6 +2,7 @@ use serde::Serialize; use crate::game::ability_input::ControllerID; use crate::game::components::confused::Confused; +use crate::game::role::RoleState; use crate::game::{attack_power::DefensePower, chat::ChatMessageVariant}; use crate::game::game_conclusion::GameConclusion; use crate::game::player::PlayerReference; @@ -15,7 +16,9 @@ pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Detective; +pub struct Detective { + red_herring: Option, +} impl RoleStateImpl for Detective { type ClientRoleState = Detective; @@ -25,7 +28,7 @@ impl RoleStateImpl for Detective { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ let suspicious = if Confused::is_confused(game, actor_ref) { - false + self.red_herring.is_some_and(|red_herring| red_herring == visit.target) }else{ Detective::player_is_suspicious(game, visit.target) }; @@ -55,6 +58,12 @@ impl RoleStateImpl for Detective { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Detective(Detective{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Detective { diff --git a/server/src/game/role/drunk.rs b/server/src/game/role/drunk.rs index e44f08848..6b3ea450f 100644 --- a/server/src/game/role/drunk.rs +++ b/server/src/game/role/drunk.rs @@ -25,13 +25,14 @@ impl RoleStateImpl for Drunk { //special case here. I don't want to use set_role because it alerts the player their role changed //NOTE: It will still send a packet to the player that their role state updated, - //so it might be deducable that there is a recruiter + //so it might be deductible that there is a recruiter + // Sammy wrote the above, I have no idea why recruiter has anything to do with this, I just fixed his typo (deducable -> deductible) if let Some(random_town_role) = possible_roles.choose(&mut rand::rng()) { actor_ref.set_role_state(game, random_town_role.new_state(game)); } - Confused::add_player(game, actor_ref); - DrunkAura::add_player(game, actor_ref); + Confused::add_player_permanent(game, actor_ref); + DrunkAura::add_player_permanent(game, actor_ref); } } impl Drunk{ diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index 8726f59a7..1ae030c4b 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -8,14 +8,16 @@ use crate::game::visit::Visit; use crate::game::Game; use super::detective::Detective; -use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; +use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Gossip; +pub struct Gossip { + red_herring: Option, +} impl RoleStateImpl for Gossip { type ClientRoleState = Gossip; @@ -25,9 +27,9 @@ impl RoleStateImpl for Gossip { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ - let enemies = if Confused::is_confused(game, actor_ref){ - false - }else{ + let enemies = if Confused::is_confused(game, actor_ref) { + self.red_herring.is_some_and(|red_herring| red_herring == visit.target) + } else { Gossip::enemies(game, visit.target) }; @@ -54,6 +56,12 @@ impl RoleStateImpl for Gossip { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Gossip(Gossip{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Gossip { diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index 849415b3c..e4f8e331c 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -10,10 +10,12 @@ use crate::game::visit::Visit; use crate::game::Game; use crate::vec_set; -use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; +use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; #[derive(Clone, Debug, Serialize, Default)] -pub struct Philosopher; +pub struct Philosopher{ + red_herring: Option, +} pub(super) const MAXIMUM_COUNT: Option = None; @@ -28,8 +30,14 @@ impl RoleStateImpl for Philosopher { let Some(first_visit) = actor_visits.get(0) else {return;}; let Some(second_visit) = actor_visits.get(1) else {return;}; - let enemies = if Confused::is_confused(game, actor_ref) { + let enemies = + if first_visit.target == second_visit.target { false + } else if Confused::is_confused(game, actor_ref) { + self.red_herring.is_some_and(|red_herring| + red_herring == first_visit.target || + red_herring == second_visit.target + ) } else { Philosopher::players_are_enemies(game, first_visit.target, second_visit.target) }; @@ -70,6 +78,12 @@ impl RoleStateImpl for Philosopher { false ) } + + fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { + actor_ref.set_role_state(game, RoleState::Philosopher(Philosopher{ + red_herring: PlayerReference::generate_red_herring(actor_ref, game) + })); + } } impl Philosopher{ pub fn players_are_enemies(game: &Game, a: PlayerReference, b: PlayerReference) -> bool { From 3d3af8c2b380c6cd240c9b37a0437764ab7f3d0f Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:52:01 -0500 Subject: [PATCH 04/23] forgot to add file last commit --- server/src/game/components/duration.rs | 115 +++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 server/src/game/components/duration.rs diff --git a/server/src/game/components/duration.rs b/server/src/game/components/duration.rs new file mode 100644 index 000000000..37fa9433f --- /dev/null +++ b/server/src/game/components/duration.rs @@ -0,0 +1,115 @@ +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +/// All operators are saturating +pub enum Duration { + Temporary(u8), + Permanent, +} + +impl Duration { + ///Returns true if the duration is not 0 + pub fn decrement(&mut self) -> bool { + match self { + Self::Permanent => true, + Self::Temporary(duration) => { + if *duration == 0 { + return false; + } + *duration -= 1; + return *duration > 0; + } + } + } + + pub fn increment(&mut self) { + match self { + Self::Permanent => (), + Self::Temporary(duration) => *duration += 1, + } + } + + pub fn is_over(&self) -> bool { + match self { + Self::Permanent => false, + Self::Temporary(duration) => { + return *duration == 0; + } + } + } +} + +impl Add for Duration { + type Output = Self; + fn add(self, rhs: Duration) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary(duration) => { + let rhs_duration = duration; + match rhs { + Self::Permanent => Self::Permanent, + Self::Temporary(duration) => Self::Temporary(rhs_duration.saturating_add(duration)), + } + } + } + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Self) { + match rhs { + Self::Permanent => *self = Self::Permanent, + Self::Temporary(duration) => { + let rhs_duration = duration; + match self { + Self::Permanent => return, + Self::Temporary (duration) => { + *duration = rhs_duration.saturating_add(*duration); + } + } + } + } + } +} + +impl Add for Duration { + type Output = Duration; + fn add(self, rhs: u8) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary (duration) => Self::Temporary(rhs.saturating_add(duration)) + } + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: u8) { + match self { + Self::Permanent => return, + Self::Temporary(duration) => { + *duration = rhs.saturating_add(*duration); + } + } + } +} + +impl Sub for Duration { + type Output = Duration; + fn sub(self, rhs: u8) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary (duration) => Self::Temporary(duration.saturating_sub(rhs)) + } + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: u8) { + match self { + Self::Permanent => return, + Self::Temporary(duration) => { + *duration = duration.saturating_sub(rhs); + } + } + } +} \ No newline at end of file From 828d780d3e428135f49c59e6d5aaaf5d01b3cea1 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:53:51 -0500 Subject: [PATCH 05/23] Add files via upload --- server/src/game/components/duration.rs | 115 +++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 server/src/game/components/duration.rs diff --git a/server/src/game/components/duration.rs b/server/src/game/components/duration.rs new file mode 100644 index 000000000..49ff96d91 --- /dev/null +++ b/server/src/game/components/duration.rs @@ -0,0 +1,115 @@ +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +/// All operators are saturating +pub enum Duration { + Temporary(u8), + Permanent, +} + +impl Duration { + ///Returns true if the duration is not 0 + pub fn decrement(&mut self) -> bool { + match self { + Self::Permanent => true, + Self::Temporary(duration) => { + if *duration == 0 { + return false; + } + *duration -= 1; + return *duration > 0; + } + } + } + + pub fn increment(&mut self) { + match self { + Self::Permanent => (), + Self::Temporary(duration) => *duration += 1, + } + } + + pub fn is_over(&self) -> bool { + match self { + Self::Permanent => false, + Self::Temporary(duration) => { + return *duration == 0; + } + } + } +} + +impl Add for Duration { + type Output = Self; + fn add(self, rhs: Duration) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary(duration) => { + let rhs_duration = duration; + match rhs { + Self::Permanent => Self::Permanent, + Self::Temporary(duration) => Self::Temporary(rhs_duration.saturating_add(duration)), + } + } + } + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Self) { + match rhs { + Self::Permanent => *self = Self::Permanent, + Self::Temporary(duration) => { + let rhs_duration = duration; + match self { + Self::Permanent => return, + Self::Temporary (duration) => { + *duration = rhs_duration.saturating_add(*duration); + } + } + } + } + } +} + +impl Add for Duration { + type Output = Duration; + fn add(self, rhs: u8) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary (duration) => Self::Temporary(rhs.saturating_add(duration)) + } + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: u8) { + match self { + Self::Permanent => return, + Self::Temporary(duration) => { + *duration = rhs.saturating_add(*duration); + } + } + } +} + +impl Sub for Duration { + type Output = Duration; + fn sub(self, rhs: u8) -> Self::Output { + match self { + Self::Permanent => Self::Permanent, + Self::Temporary (duration) => Self::Temporary(duration.saturating_sub(rhs)) + } + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: u8) { + match self { + Self::Permanent => return, + Self::Temporary(duration) => { + *duration = duration.saturating_sub(rhs); + } + } + } +} \ No newline at end of file From 9369a40c2b6146f4188f229b58b81eaa286138e2 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 23:00:16 -0500 Subject: [PATCH 06/23] Made confused players think framed players are evil. Added tests. --- server/src/game/chat/chat_message_variant.rs | 4 +- server/src/game/role/detective.rs | 3 +- server/src/game/role/gossip.rs | 23 +- server/src/game/role/philosopher.rs | 14 +- server/tests/kit/mod.rs | 24 +- server/tests/kit/player.rs | 10 +- server/tests/role.rs | 294 +++++++++++++++++-- 7 files changed, 317 insertions(+), 55 deletions(-) diff --git a/server/src/game/chat/chat_message_variant.rs b/server/src/game/chat/chat_message_variant.rs index 4738d7f4b..7c732f705 100644 --- a/server/src/game/chat/chat_message_variant.rs +++ b/server/src/game/chat/chat_message_variant.rs @@ -161,10 +161,10 @@ pub enum ChatMessageVariant { Wardblocked, - SheriffResult {suspicious: bool}, + DetectiveResult {suspicious: bool}, LookoutResult{players: Vec}, TrackerResult{players: Vec}, - SeerResult{enemies: bool}, + PhilosopherResult{enemies: bool}, SpyMafiaVisit{players: Vec}, SpyBug{bug: SpyBug}, PsychicGood{player: PlayerReference}, diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index b2e44ab38..d2d075d59 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -28,12 +28,13 @@ impl RoleStateImpl for Detective { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ let suspicious = if Confused::is_confused(game, actor_ref) { + visit.target.night_framed(game) || self.red_herring.is_some_and(|red_herring| red_herring == visit.target) }else{ Detective::player_is_suspicious(game, visit.target) }; - let message = ChatMessageVariant::SheriffResult { + let message = ChatMessageVariant::DetectiveResult { suspicious }; diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index 1ae030c4b..db78ffcac 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -24,19 +24,18 @@ impl RoleStateImpl for Gossip { fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) { if priority != Priority::Investigative {return;} + + let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ - let enemies = if Confused::is_confused(game, actor_ref) { - self.red_herring.is_some_and(|red_herring| red_herring == visit.target) - } else { - Gossip::enemies(game, visit.target) - }; - + let enemies = self.enemies(game, visit.target, actor_ref); + let message = ChatMessageVariant::GossipResult{ enemies }; actor_ref.push_night_message(game, message); } + } fn controller_parameters_map(self, game: &Game, actor_ref: PlayerReference) -> ControllerParametersMap { crate::game::role::common_role::controller_parameters_map_player_list_night_typical( @@ -65,16 +64,20 @@ impl RoleStateImpl for Gossip { } impl Gossip { - pub fn enemies(game: &Game, player_ref: PlayerReference) -> bool { - + pub fn enemies(self, game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { match player_ref.night_appeared_visits(game) { Some(x) => x.clone(), None => player_ref.all_night_visits_cloned(game), } .iter() .map(|v|v.target.clone()) - .any(|targets_target| - Detective::player_is_suspicious(game, targets_target) + .any( + |targets_target: PlayerReference| + if Confused::is_confused(game, actor_ref) { + targets_target.night_framed(game) || self.red_herring.is_some_and(|red_herring| red_herring == targets_target) + } else { + Detective::player_is_suspicious(game, targets_target) + } ) } } \ No newline at end of file diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index e4f8e331c..5c5657c67 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -14,7 +14,7 @@ use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerPara #[derive(Clone, Debug, Serialize, Default)] pub struct Philosopher{ - red_herring: Option, + pub red_herring: Option, } @@ -26,25 +26,31 @@ impl RoleStateImpl for Philosopher { fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) { if priority != Priority::Investigative {return;} + println!("phil do_night_action"); + let actor_visits = actor_ref.untagged_night_visits_cloned(game); let Some(first_visit) = actor_visits.get(0) else {return;}; let Some(second_visit) = actor_visits.get(1) else {return;}; + + let enemies = if first_visit.target == second_visit.target { false } else if Confused::is_confused(game, actor_ref) { self.red_herring.is_some_and(|red_herring| - red_herring == first_visit.target || - red_herring == second_visit.target + (red_herring == first_visit.target || first_visit.target.night_framed(game)) ^ + (red_herring == second_visit.target || second_visit.target.night_framed(game)) ) } else { Philosopher::players_are_enemies(game, first_visit.target, second_visit.target) }; - let message = ChatMessageVariant::SeerResult{ enemies }; + let message = ChatMessageVariant::PhilosopherResult{ enemies }; actor_ref.push_night_message(game, message); + + println!("enemies: {}", enemies); } fn controller_parameters_map(self, game: &Game, actor_ref: PlayerReference) -> ControllerParametersMap { diff --git a/server/tests/kit/mod.rs b/server/tests/kit/mod.rs index 919783409..fc667aead 100644 --- a/server/tests/kit/mod.rs +++ b/server/tests/kit/mod.rs @@ -1,9 +1,5 @@ use mafia_server::game::{ - player::PlayerReference, - Game, - role::RoleState, - settings::Settings, - test::mock_game + chat::ChatMessageVariant, player::PlayerReference, role::RoleState, settings::Settings, test::mock_game, Game }; pub mod player; @@ -60,6 +56,22 @@ macro_rules! assert_not_contains { #[allow(unused)] pub(crate) use {scenario, assert_contains, assert_not_contains}; +//Formats messages in a way where it's clear which phase each message was sent in +pub fn _format_messages_debug(messages: Vec) -> String{ + let mut string = "[\n".to_string(); + + for message in messages { + string += match message { + ChatMessageVariant::PhaseChange{..} => "\t", + _ => "\t\t", + }; + string += format!("{:?}", message).as_str(); + string += "\n"; + } + string += "]"; + return string; +} + /// Stuff that shouldn't be called directly - only in macro invocations. #[doc(hidden)] pub mod _init { @@ -93,4 +105,4 @@ pub mod _init { TestScenario { game, players } } -} +} \ No newline at end of file diff --git a/server/tests/kit/player.rs b/server/tests/kit/player.rs index d88610231..e9750a55a 100644 --- a/server/tests/kit/player.rs +++ b/server/tests/kit/player.rs @@ -170,4 +170,12 @@ impl From for Vec { fn from(value: TestPlayer) -> Self { vec![value] } -} \ No newline at end of file +} + +impl PartialEq for TestPlayer { + fn eq(&self, other: &TestPlayer) -> bool{ + return self.0 == other.0 + } +} + +impl Eq for TestPlayer {} \ No newline at end of file diff --git a/server/tests/role.rs b/server/tests/role.rs index d830fba8d..3233bd39e 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -1,9 +1,10 @@ mod kit; use std::{ops::Deref, vec}; +use kit::player::TestPlayer; pub(crate) use kit::{assert_contains, assert_not_contains}; -use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID}, game_conclusion::GameConclusion, role::engineer::Trap}; +use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID}, components::{confused::Confused, duration::Duration}, game_conclusion::GameConclusion, role::engineer::Trap}; pub use mafia_server::game::{ chat::{ChatMessageVariant, MessageSender, ChatGroup}, grave::*, @@ -105,6 +106,7 @@ pub use mafia_server::game::{ // Pub use so that submodules don't have to reimport everything. pub use mafia_server::packet::ToServerPacket; + #[test] fn no_unwanted_tags() { kit::scenario!(game in Dusk 1 where @@ -149,7 +151,7 @@ fn detective_basic() { game.next_phase(); assert_contains!( sher.get_messages_after_night(1), - ChatMessageVariant::SheriffResult { suspicious: true } + ChatMessageVariant::DetectiveResult { suspicious: true } ); game.skip_to(Night, 2); @@ -157,7 +159,7 @@ fn detective_basic() { game.next_phase(); assert_contains!( sher.get_messages_after_night(2), - ChatMessageVariant::SheriffResult { suspicious: false } + ChatMessageVariant::DetectiveResult { suspicious: false } ); } @@ -175,7 +177,7 @@ fn detective_neutrals(){ game.next_phase(); assert_contains!( sher.get_messages_after_night(1), - ChatMessageVariant::SheriffResult { suspicious: true } + ChatMessageVariant::DetectiveResult { suspicious: true } ); game.skip_to(Night, 2); @@ -183,7 +185,7 @@ fn detective_neutrals(){ game.next_phase(); assert_contains!( sher.get_messages_after_night(2), - ChatMessageVariant::SheriffResult { suspicious: false } + ChatMessageVariant::DetectiveResult { suspicious: false } ); game.skip_to(Night, 3); @@ -191,7 +193,7 @@ fn detective_neutrals(){ game.next_phase(); assert_contains!( sher.get_messages_after_night(3), - ChatMessageVariant::SheriffResult { suspicious: true } + ChatMessageVariant::DetectiveResult { suspicious: true } ); } @@ -293,7 +295,7 @@ fn detective_godfather() { ); sher.send_ability_input_player_list_typical(mafia); game.next_phase(); - assert_contains!(sher.get_messages(), ChatMessageVariant::SheriffResult { suspicious: false }); + assert_contains!(sher.get_messages(), ChatMessageVariant::DetectiveResult { suspicious: false }); } #[test] @@ -311,7 +313,7 @@ fn philosopher_basic() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(1), - ChatMessageVariant::SeerResult { enemies: true } + ChatMessageVariant::PhilosopherResult { enemies: true } ); game.skip_to(Night, 2); @@ -320,7 +322,7 @@ fn philosopher_basic() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(2), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); game.skip_to(Night, 3); @@ -329,7 +331,7 @@ fn philosopher_basic() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(3), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); } @@ -349,7 +351,7 @@ fn philosopher_neutrals() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(3), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); game.skip_to(Night, 4); @@ -358,7 +360,7 @@ fn philosopher_neutrals() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(4), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); game.skip_to(Night, 6); @@ -367,7 +369,7 @@ fn philosopher_neutrals() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(6), - ChatMessageVariant::SeerResult { enemies: true } + ChatMessageVariant::PhilosopherResult { enemies: true } ); game.skip_to(Night, 7); @@ -376,7 +378,7 @@ fn philosopher_neutrals() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(7), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); game.skip_to(Night, 8); @@ -385,7 +387,7 @@ fn philosopher_neutrals() { game.next_phase(); assert_contains!( philosopher.get_messages_after_night(8), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); } @@ -625,15 +627,15 @@ fn transporter_basic_seer_sheriff_framer() { game.skip_to(Obituary, 2); assert_contains!( philosopher.get_messages_after_night(1), - ChatMessageVariant::SeerResult { enemies: true } + ChatMessageVariant::PhilosopherResult { enemies: true } ); assert_contains!( town1.get_messages_after_night(1), - ChatMessageVariant::SheriffResult { suspicious: false } + ChatMessageVariant::DetectiveResult { suspicious: false } ); assert_contains!( town2.get_messages_after_night(1), - ChatMessageVariant::SheriffResult { suspicious: true } + ChatMessageVariant::DetectiveResult { suspicious: true } ); } @@ -700,7 +702,7 @@ fn retributionist_basic(){ assert_contains!( ret.get_messages_after_night(4), ChatMessageVariant::TargetsMessage{message: Box::new( - ChatMessageVariant::SheriffResult{ suspicious: true } + ChatMessageVariant::DetectiveResult{ suspicious: true } )} ); @@ -710,7 +712,7 @@ fn retributionist_basic(){ assert_contains!( ret.get_messages_after_night(5), ChatMessageVariant::TargetsMessage{message: Box::new( - ChatMessageVariant::SheriffResult{ suspicious: true } + ChatMessageVariant::DetectiveResult{ suspicious: true } )} ); @@ -720,7 +722,7 @@ fn retributionist_basic(){ assert_not_contains!( ret.get_messages_after_night(6), ChatMessageVariant::TargetsMessage{message: Box::new( - ChatMessageVariant::SheriffResult{ suspicious: true } + ChatMessageVariant::DetectiveResult{ suspicious: true } )} ); } @@ -765,7 +767,7 @@ fn witch_basic(){ assert!(witch.send_ability_input_two_player_typical(sher, mafioso)); game.next_phase(); assert_contains!(witch.get_messages(), ChatMessageVariant::TargetsMessage{message: Box::new( - ChatMessageVariant::SheriffResult{ suspicious: true } + ChatMessageVariant::DetectiveResult{ suspicious: true } )}); game.skip_to(Night, 2); @@ -775,7 +777,7 @@ fn witch_basic(){ assert_contains!( witch.get_messages_after_night(2), ChatMessageVariant::TargetsMessage{message: Box::new( - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } )} ); } @@ -1173,10 +1175,26 @@ fn drunk_suspicious_aura() { assert_contains!( detective.get_messages(), - ChatMessageVariant::SheriffResult { suspicious: true } + ChatMessageVariant::DetectiveResult { suspicious: true } ); } +#[test] +fn drunk_confused_and_drunk_aura() { + kit::scenario!(game in Night 1 where + drunk: Drunk, + _mafioso: Mafioso + ); + match game.confused.players_durations.get(&drunk.player_ref()) { + Some(duration) => assert!(*duration == Duration::Permanent), + None => panic!() + } + match game.drunk_aura.players_durations.get(&drunk.player_ref()) { + Some(duration) => assert!(*duration == Duration::Permanent), + None => panic!() + } +} + #[test] fn drunk_framer() { kit::scenario!(game in Night 2 where @@ -1239,6 +1257,220 @@ fn drunk_role_change() { ); } +#[test] +fn red_herrings() { + for _ in 0..20 { + kit::scenario!(game in Dusk 1 where + detective: Detective, + gossip: Gossip, + philosopher: Philosopher, + tester: Doctor, + mafia: Informant + ); + + Confused::add_player_permanent(&mut game, detective.player_ref()); + Confused::add_player_permanent(&mut game, gossip.player_ref()); + Confused::add_player_permanent(&mut game, philosopher.player_ref()); + + assert!(Confused::is_confused(&*game, detective.player_ref())); + assert!(Confused::is_confused(&*game, gossip.player_ref())); + assert!(Confused::is_confused(&*game, philosopher.player_ref())); + + let mut found_detective_red_herring = false; + let mut found_gossip_red_herring = false; + let mut found_philosopher_red_herring = false; + let mut philosopher_red_herring_is_tester = false; + + for index in 0u8..5 { + let target; + //The unsafe part is the fact that the player index is next ensured to be a player. + //this is fine as only indices 0 to 4 would be allowed which is what is going on here. + unsafe { + target = TestPlayer::new(PlayerReference::new_unchecked(index), &*game); + } + + detective.send_ability_input_player_list_typical(target); + gossip.send_ability_input_player_list_typical(tester); + philosopher.send_ability_input_two_player_typical(target, tester); + tester.send_ability_input_player_list_typical(target); + mafia.send_ability_input_player_list_typical(target); + + game.skip_to(Obituary, index+2); + + assert!(detective.alive()); + assert!(gossip.alive()); + assert!(philosopher.alive()); + assert!(tester.alive()); + assert!(mafia.alive()); + + let message_before_ability_message = ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: index+1 }; + + /* Test Detective */ + let detective_messages = detective.get_messages_after_last_message(message_before_ability_message.clone()); + + if detective_messages.contains(&ChatMessageVariant::DetectiveResult { suspicious: true }) { + if found_detective_red_herring { + panic!("detective_messages: {:?}", detective_messages); + } else { + found_detective_red_herring = true; + } + } else { + assert_eq!( + detective_messages.contains(&ChatMessageVariant::DetectiveResult { suspicious: false }), + target != detective + ) + } + + /* Test Gossip */ + let gossip_messages = gossip.get_messages_after_last_message(message_before_ability_message.clone()); + + if gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: true }){ + if found_gossip_red_herring { + panic!("gossip_messages: {:?}", gossip_messages); + } else { + found_gossip_red_herring = true; + } + } else { + assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: false })); + } + + + /* Test Philosopher */ + let philosopher_messages = philosopher.get_messages_after_last_message(message_before_ability_message.clone()); + + if philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: true }){ + if found_philosopher_red_herring { + if !philosopher_red_herring_is_tester { + if game.day_number() > 3 { + panic!("philosopher_messages: {:?}", philosopher_messages); + } else { + philosopher_red_herring_is_tester = true; + } + } + } else { + found_philosopher_red_herring = true; + } + } else { + assert_eq!( + philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: false }), + target != philosopher && target != tester + ) + } + } + + assert!(found_detective_red_herring); + + if !found_gossip_red_herring { + gossip.send_ability_input_player_list_typical(detective); + detective.send_ability_input_player_list_typical(tester); + + game.skip_to(Obituary, 7); + + let message_before_ability_message = ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: 6 }; + + let gossip_messages = gossip.get_messages_after_last_message(message_before_ability_message.clone()); + + assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: true })); + } + + assert!(found_philosopher_red_herring); + } +} + +#[test] +fn red_herrings_framer() { + for _ in 0..20 { + kit::scenario!(game in Dusk 1 where + detective: Detective, + gossip: Gossip, + philosopher: Philosopher, + tester: Doctor, + mafia: Framer + ); + + Confused::add_player_permanent(&mut game, detective.player_ref()); + Confused::add_player_permanent(&mut game, gossip.player_ref()); + Confused::add_player_permanent(&mut game, philosopher.player_ref()); + + assert!(Confused::is_confused(&*game, detective.player_ref())); + assert!(Confused::is_confused(&*game, gossip.player_ref())); + assert!(Confused::is_confused(&*game, philosopher.player_ref())); + + let mut found_philosopher_red_herring = false; + + let philosopher_red_herring_is_tester = + if let RoleState::Philosopher(Philosopher {red_herring}) = philosopher.player_ref().role_state(&*game) { + if let Some(red_herring) = red_herring { + *red_herring == tester.player_ref() + } else { + panic!() + } + } else { + panic!(); + }; + + for index in 0u8..4 { + let target; + //The unsafe part is the fact that the player index is next ensured to be a player. + //this is fine as only indices 0 to 4 would be allowed which is what is going on here. + unsafe { + target = TestPlayer::new(PlayerReference::new_unchecked(index), &*game); + } + + detective.send_ability_input_player_list_typical(target); + gossip.send_ability_input_player_list_typical(tester); + philosopher.send_ability_input_two_player_typical(target, tester); + tester.send_ability_input_player_list_typical(target); + mafia.send_ability_input_player_list_typical(target); + + game.skip_to(Obituary, index+2); + + assert!(detective.alive()); + assert!(gossip.alive()); + assert!(philosopher.alive()); + assert!(tester.alive()); + assert!(mafia.alive()); + + let message_before_ability_message = ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: index+1 }; + + /* Test Detective */ + let detective_messages = detective.get_messages_after_last_message(message_before_ability_message.clone()); + assert_eq!( + detective_messages.contains(&ChatMessageVariant::DetectiveResult { suspicious: true }), + target != detective + ); + + /* Test Gossip */ + let gossip_messages = gossip.get_messages_after_last_message(message_before_ability_message.clone()); + assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: true })); + + /* Test Philosopher */ + let philosopher_messages = philosopher.get_messages_after_last_message(message_before_ability_message.clone()); + if philosopher_red_herring_is_tester{ + assert_eq!( + philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: false }), + target != philosopher && target != tester + ); + assert!(!philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: true })); + } else if philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: false }) { + if found_philosopher_red_herring { + panic!("philosopher_messages: {}", kit::_format_messages_debug(philosopher_messages)); + } else { + assert!(target != tester); + assert!(target != tester); + found_philosopher_red_herring = true; + } + } else { + assert_eq!( + philosopher_messages.contains(&ChatMessageVariant::PhilosopherResult { enemies: true }), + target != philosopher && target != tester + ) + } + + } + } +} + #[test] fn vigilante_cant_select_night_one() { kit::scenario!(game in Night 1 where @@ -1538,7 +1770,7 @@ fn seer_cant_see_godfather() { philosopher.get_messages_after_last_message( ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: 1 } ), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); game.skip_to(Night, 2); @@ -1548,7 +1780,7 @@ fn seer_cant_see_godfather() { philosopher.get_messages_after_last_message( ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: 2 } ), - ChatMessageVariant::SeerResult { enemies: false } + ChatMessageVariant::PhilosopherResult { enemies: false } ); } @@ -1772,7 +2004,7 @@ fn puppeteer_marionettes_philosopher(){ game.next_phase(); assert_contains!( philo.get_messages_after_night(1), - ChatMessageVariant::SeerResult{ enemies: true } + ChatMessageVariant::PhilosopherResult{ enemies: true } ); game.skip_to(Night, 3); @@ -1782,7 +2014,7 @@ fn puppeteer_marionettes_philosopher(){ game.next_phase(); assert_contains!( philo.get_messages_after_night(2), - ChatMessageVariant::SeerResult{ enemies: false } + ChatMessageVariant::PhilosopherResult{ enemies: false } ); } @@ -1982,7 +2214,7 @@ fn arsonist_ignites_and_aura(){ assert_contains!(sher.get_messages_after_last_message( ChatMessageVariant::PhaseChange{phase: PhaseState::Night, day_number: 1} - ), ChatMessageVariant::SheriffResult{ suspicious: true }); + ), ChatMessageVariant::DetectiveResult{ suspicious: true }); game.skip_to(Night, 2); @@ -1993,7 +2225,7 @@ fn arsonist_ignites_and_aura(){ assert_contains!(sher.get_messages_after_last_message( ChatMessageVariant::PhaseChange{phase: PhaseState::Night, day_number: 2} - ), ChatMessageVariant::SheriffResult{ suspicious: true }); + ), ChatMessageVariant::DetectiveResult{ suspicious: true }); game.skip_to(Nomination, 3); @@ -2013,7 +2245,7 @@ fn arsonist_ignites_and_aura(){ assert_contains!(sher.get_messages_after_last_message( ChatMessageVariant::PhaseChange{phase: PhaseState::Night, day_number: 3} - ), ChatMessageVariant::SheriffResult{ suspicious: false }); + ), ChatMessageVariant::DetectiveResult{ suspicious: false }); } From e9c7d2f2dcfa0071e12c2c5a1d2961ce1115bcc6 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 23:06:38 -0500 Subject: [PATCH 07/23] Forgot to remove equals signs when merging --- server/src/game/components/duration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/game/components/duration.rs b/server/src/game/components/duration.rs index bd70873b2..3fa38901b 100644 --- a/server/src/game/components/duration.rs +++ b/server/src/game/components/duration.rs @@ -112,7 +112,7 @@ impl SubAssign for Duration { } } } -======= + use std::ops::{Add, AddAssign, Sub, SubAssign}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] @@ -227,4 +227,4 @@ impl SubAssign for Duration { } } } -} \ No newline at end of file +} From dde37b2c14d2e3c58184c006253beeac5e4eb13c Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 24 Feb 2025 23:12:31 -0500 Subject: [PATCH 08/23] Updated duration.rs The merge fucked it up --- server/src/game/components/duration.rs | 116 +------------------------ 1 file changed, 1 insertion(+), 115 deletions(-) diff --git a/server/src/game/components/duration.rs b/server/src/game/components/duration.rs index 3fa38901b..280cf2443 100644 --- a/server/src/game/components/duration.rs +++ b/server/src/game/components/duration.rs @@ -28,122 +28,8 @@ impl Duration { Self::Temporary(duration) => *duration += 1, } } - - pub fn is_over(&self) -> bool { - match self { - Self::Permanent => false, - Self::Temporary(duration) => { - return *duration == 0; - } - } - } -} - -impl Add for Duration { - type Output = Self; - fn add(self, rhs: Duration) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary(duration) => { - let rhs_duration = duration; - match rhs { - Self::Permanent => Self::Permanent, - Self::Temporary(duration) => Self::Temporary(rhs_duration.saturating_add(duration)), - } - } - } - } -} - -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: Self) { - match rhs { - Self::Permanent => *self = Self::Permanent, - Self::Temporary(duration) => { - let rhs_duration = duration; - match self { - Self::Permanent => return, - Self::Temporary (duration) => { - *duration = rhs_duration.saturating_add(*duration); - } - } - } - } - } -} - -impl Add for Duration { - type Output = Duration; - fn add(self, rhs: u8) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary (duration) => Self::Temporary(rhs.saturating_add(duration)) - } - } -} - -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: u8) { - match self { - Self::Permanent => return, - Self::Temporary(duration) => { - *duration = rhs.saturating_add(*duration); - } - } - } -} - -impl Sub for Duration { - type Output = Duration; - fn sub(self, rhs: u8) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary (duration) => Self::Temporary(duration.saturating_sub(rhs)) - } - } -} - -impl SubAssign for Duration { - fn sub_assign(&mut self, rhs: u8) { - match self { - Self::Permanent => return, - Self::Temporary(duration) => { - *duration = duration.saturating_sub(rhs); - } - } - } - -use std::ops::{Add, AddAssign, Sub, SubAssign}; - -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -/// All operators are saturating -pub enum Duration { - Temporary(u8), - Permanent, -} - -impl Duration { + ///Returns true if the duration is not 0 - pub fn decrement(&mut self) -> bool { - match self { - Self::Permanent => true, - Self::Temporary(duration) => { - if *duration == 0 { - return false; - } - *duration -= 1; - return *duration > 0; - } - } - } - - pub fn increment(&mut self) { - match self { - Self::Permanent => (), - Self::Temporary(duration) => *duration += 1, - } - } - pub fn is_over(&self) -> bool { match self { Self::Permanent => false, From b37908f73cc242b19c7dfa59a4093421f09b9b64 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 27 Feb 2025 12:01:03 -0500 Subject: [PATCH 09/23] Added changes to confusion & drunk aura from Clippy Cleanup II to here to prevent conflicts. --- server/src/game/components/confused.rs | 25 +++++++++++------------- server/src/game/components/drunk_aura.rs | 24 +++++++++++------------ 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index 184552126..39f0286ed 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -9,33 +9,30 @@ pub struct Confused { pub players_durations: VecMap, } - -impl Confused{ - fn confused<'a>(game: &'a Game)->&'a Self{ - &game.confused +impl Game { + fn confused(&self)->&Confused{ + &self.confused } - fn confused_mut<'a>(game: &'a mut Game)->&'a mut Self{ - &mut game.confused + fn confused_mut(&mut self)->&mut Confused{ + &mut self.confused } +} +impl Confused { pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - let confused = Self::confused_mut(game); - confused.players_durations.insert(player, Duration::Permanent); + game.confused_mut().players_durations.insert(player, Duration::Permanent); } pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - let confused = Self::confused_mut(game); - confused.players_durations.keep_greater(player, Duration::Temporary(duration)); + game.confused_mut().players_durations.keep_greater(player, Duration::Temporary(duration)); } pub fn remove_player(game: &mut Game, player: PlayerReference){ - let confused = Self::confused_mut(game); - confused.players_durations.remove(&player); + game.confused_mut().players_durations.remove(&player); } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ - let confused = Self::confused(game); - confused.players_durations.contains(&player) + game.confused().players_durations.contains(&player) } /// Decrements confusion durations and removes players whose durations are up diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index 91fb70053..ca42d6670 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -7,32 +7,30 @@ pub struct DrunkAura { pub players_durations: VecMap, } -impl DrunkAura { - fn drunk_aura(game: &Game) -> &Self { - &game.drunk_aura +impl Game { + fn drunk_aura(&self) -> &DrunkAura { + &self.drunk_aura } - fn drunk_aura_mut(game: &mut Game) -> &mut Self { - &mut game.drunk_aura + fn drunk_aura_mut(&mut self) -> &mut DrunkAura { + &mut self.drunk_aura } +} +impl DrunkAura { pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - let drunk_aura = Self::drunk_aura_mut(game); - drunk_aura.players_durations.insert(player, Duration::Permanent); + game.drunk_aura_mut().players_durations.insert(player, Duration::Permanent); } pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - let drunk_aura = Self::drunk_aura_mut(game); - drunk_aura.players_durations.keep_greater(player, Duration::Temporary(duration)); + game.drunk_aura_mut().players_durations.keep_greater(player, Duration::Temporary(duration)); } pub fn remove_player(game: &mut Game, player: PlayerReference){ - let drunk_aura = Self::drunk_aura_mut(game); - drunk_aura.players_durations.remove(&player); + game.drunk_aura_mut().players_durations.remove(&player); } pub fn has_drunk_aura(game: &Game, player: PlayerReference) -> bool { - let drunk_aura = Self::drunk_aura(game); - drunk_aura.players_durations.contains(&player) + game.drunk_aura().players_durations.contains(&player) } pub fn on_role_switch(game: &mut Game, player: PlayerReference) { From 8e134348f8ffb4f2d817ad641a59adb59c0ca9ba Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:21:21 -0500 Subject: [PATCH 10/23] renamed duration added red herrings to confused --- server/src/game/components/confused.rs | 22 ++++++++++++++-------- server/src/vec_map.rs | 8 +++++++- server/tests/role.rs | 6 +++++- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index 39f0286ed..b3d543820 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use rand::seq::IteratorRandom; use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; @@ -5,9 +7,13 @@ use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::V use super::duration::Duration; #[derive(Default, Clone)] -pub struct Confused { - pub players_durations: VecMap, + + +pub struct ConfusionData{ + duration: Duration, + red_herrings: VecSet, } +pub struct Confused(VecMap); impl Game { fn confused(&self)->&Confused{ @@ -20,19 +26,19 @@ impl Game { impl Confused { pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - game.confused_mut().players_durations.insert(player, Duration::Permanent); + game.confused_mut().0.insert_unsized(player, Duration::Permanent); } pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - game.confused_mut().players_durations.keep_greater(player, Duration::Temporary(duration)); + game.confused_mut().0.keep_greater(player, Duration::Temporary(duration)); } pub fn remove_player(game: &mut Game, player: PlayerReference){ - game.confused_mut().players_durations.remove(&player); + game.confused_mut().0.remove(&player); } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ - game.confused().players_durations.contains(&player) + game.confused().0.contains(&player) } /// Decrements confusion durations and removes players whose durations are up @@ -40,8 +46,8 @@ impl Confused { match phase { //feel free to change the phase, right now there aren't any ways to temporarily confuse a player so I chose Night mostly arbitrarily PhaseState::Night => { - game.confused.players_durations.retain_mut( - |_, duration| duration.decrement() + game.confused.0.retain_mut( + |_, (data)| data.duration.decrement() ); }, _=>{} diff --git a/server/src/vec_map.rs b/server/src/vec_map.rs index abd9f12dd..1c971290b 100644 --- a/server/src/vec_map.rs +++ b/server/src/vec_map.rs @@ -29,7 +29,6 @@ impl VecMap where K: Eq { /// returns the old value if the key already exists pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)>{ - if let Some((old_key, old_val)) = self.vec.iter_mut().find(|(k, _)| *k == key) { Some(( std::mem::replace(old_key, key), @@ -41,6 +40,13 @@ impl VecMap where K: Eq { } } + pub fn insert_unsized(&mut self, key: K, value: V) { + if let Some(i) = self.vec.iter().position(|(k, _)| *k == key) { + self.vec.swap_remove(i); + } + self.vec.push((key, value)); + } + pub fn get(&self, key: &K) -> Option<&V> { self.get_kvp(key).map(|(_, v)| v) } diff --git a/server/tests/role.rs b/server/tests/role.rs index 30edad53d..4e890618d 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -105,8 +105,12 @@ pub use mafia_server::game::{ }; // Pub use so that submodules don't have to reimport everything. pub use mafia_server::packet::ToServerPacket; +#[test] +fn rust_test_if_this_made_it_to_the_final_ver_remove_this_was_me_testing_how_rust_works(){ + let mut x: VecMap> = Vec::new(); + x.insert(1, vec![1]); - +} #[test] fn no_unwanted_tags() { kit::scenario!(game in Dusk 1 where From b2e988cda2fa6a6c7e5c41d3e5328f405d128761 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:21:24 -0500 Subject: [PATCH 11/23] prev cont. --- server/src/game/components/confused.rs | 53 ++++++++++++++----- server/src/game/components/drunk_aura.rs | 8 +-- server/src/game/components/mod.rs | 2 +- .../{duration.rs => status_duration.rs} | 26 ++++----- server/src/game/role_list.rs | 20 ++++++- server/src/vec_map.rs | 25 ++++++++- server/tests/role.rs | 19 ++++--- 7 files changed, 111 insertions(+), 42 deletions(-) rename server/src/game/components/{duration.rs => status_duration.rs} (85%) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index b3d543820..d7dd8229c 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -1,19 +1,46 @@ -use std::collections::HashMap; - use rand::seq::IteratorRandom; use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; -use super::duration::Duration; - -#[derive(Default, Clone)] - +use super::status_duration::StatusDuration; +#[derive(Default, Clone, PartialEq, Eq, PartialOrd)] pub struct ConfusionData{ - duration: Duration, - red_herrings: VecSet, + pub duration: StatusDuration, + pub red_herrings: Vec, } -pub struct Confused(VecMap); +impl ConfusionData { + pub fn new_perm(game: &Game, player: PlayerReference) -> ConfusionData { + ConfusionData { + duration: StatusDuration::Permanent, + red_herrings: Self::generate_red_herrings(game,player), + } + } + pub fn new_temp(game: &Game, player: PlayerReference, duration: u8) -> ConfusionData { + ConfusionData { + duration: StatusDuration::Temporary(duration), + red_herrings: Self::generate_red_herrings(game,player), + } + } + pub fn generate_red_herrings(game: &Game, player: PlayerReference) -> Vec { + let count = game.assignments.iter() + .filter(|a|a.2.is_evil()) + .count(); + PlayerReference::all_players(game) + .filter(|p|*p != player) + .choose_multiple(&mut rand::rng(), count) + + } +} +impl Ord for ConfusionData{ + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + return self.duration.cmp(&other.duration) + } +} + + +#[derive(Default, Clone)] +pub struct Confused(pub VecMap); impl Game { fn confused(&self)->&Confused{ @@ -26,11 +53,13 @@ impl Game { impl Confused { pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - game.confused_mut().0.insert_unsized(player, Duration::Permanent); + let data = ConfusionData::new_perm(game, player); + game.confused_mut().0.insert_unsized(player, data); } pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - game.confused_mut().0.keep_greater(player, Duration::Temporary(duration)); + let data = ConfusionData::new_temp(game, player, duration); + game.confused_mut().0.keep_greater_unsized(player, data); } pub fn remove_player(game: &mut Game, player: PlayerReference){ @@ -47,7 +76,7 @@ impl Confused { //feel free to change the phase, right now there aren't any ways to temporarily confuse a player so I chose Night mostly arbitrarily PhaseState::Night => { game.confused.0.retain_mut( - |_, (data)| data.duration.decrement() + |_, data| data.duration.decrement() ); }, _=>{} diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index ca42d6670..60a480680 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -1,10 +1,10 @@ use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; -use super::{duration::Duration, confused::Confused}; +use super::{status_duration::StatusDuration, confused::Confused}; #[derive(Default, Clone)] pub struct DrunkAura { - pub players_durations: VecMap, + pub players_durations: VecMap, } impl Game { @@ -18,11 +18,11 @@ impl Game { impl DrunkAura { pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - game.drunk_aura_mut().players_durations.insert(player, Duration::Permanent); + game.drunk_aura_mut().players_durations.insert(player, StatusDuration::Permanent); } pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - game.drunk_aura_mut().players_durations.keep_greater(player, Duration::Temporary(duration)); + game.drunk_aura_mut().players_durations.keep_greater(player, StatusDuration::Temporary(duration)); } pub fn remove_player(game: &mut Game, player: PlayerReference){ diff --git a/server/src/game/components/mod.rs b/server/src/game/components/mod.rs index d44778b31..8a1164c19 100644 --- a/server/src/game/components/mod.rs +++ b/server/src/game/components/mod.rs @@ -11,7 +11,7 @@ pub mod insider_group; pub mod detained; pub mod confused; pub mod drunk_aura; -pub mod duration; +pub mod status_duration; pub mod forfeit_vote; pub mod night_visits; pub mod syndicate_gun_item; diff --git a/server/src/game/components/duration.rs b/server/src/game/components/status_duration.rs similarity index 85% rename from server/src/game/components/duration.rs rename to server/src/game/components/status_duration.rs index 280cf2443..2f366fe40 100644 --- a/server/src/game/components/duration.rs +++ b/server/src/game/components/status_duration.rs @@ -1,13 +1,13 @@ use std::ops::{Add, AddAssign, Sub, SubAssign}; -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] /// All operators are saturating -pub enum Duration { +pub enum StatusDuration { Temporary(u8), - Permanent, + #[default] Permanent, } -impl Duration { +impl StatusDuration { ///Returns true if the duration is not 0 pub fn decrement(&mut self) -> bool { match self { @@ -40,9 +40,9 @@ impl Duration { } } -impl Add for Duration { +impl Add for StatusDuration { type Output = Self; - fn add(self, rhs: Duration) -> Self::Output { + fn add(self, rhs: StatusDuration) -> Self::Output { match self { Self::Permanent => Self::Permanent, Self::Temporary(duration) => { @@ -56,7 +56,7 @@ impl Add for Duration { } } -impl AddAssign for Duration { +impl AddAssign for StatusDuration { fn add_assign(&mut self, rhs: Self) { match rhs { Self::Permanent => *self = Self::Permanent, @@ -73,8 +73,8 @@ impl AddAssign for Duration { } } -impl Add for Duration { - type Output = Duration; +impl Add for StatusDuration { + type Output = StatusDuration; fn add(self, rhs: u8) -> Self::Output { match self { Self::Permanent => Self::Permanent, @@ -83,7 +83,7 @@ impl Add for Duration { } } -impl AddAssign for Duration { +impl AddAssign for StatusDuration { fn add_assign(&mut self, rhs: u8) { match self { Self::Permanent => return, @@ -94,8 +94,8 @@ impl AddAssign for Duration { } } -impl Sub for Duration { - type Output = Duration; +impl Sub for StatusDuration { + type Output = StatusDuration; fn sub(self, rhs: u8) -> Self::Output { match self { Self::Permanent => Self::Permanent, @@ -104,7 +104,7 @@ impl Sub for Duration { } } -impl SubAssign for Duration { +impl SubAssign for StatusDuration { fn sub_assign(&mut self, rhs: u8) { match self { Self::Permanent => return, diff --git a/server/src/game/role_list.rs b/server/src/game/role_list.rs index 7edcef004..a8aa34637 100644 --- a/server/src/game/role_list.rs +++ b/server/src/game/role_list.rs @@ -43,7 +43,25 @@ pub struct RoleAssignment { pub win_condition: RoleOutlineOptionWinCondition } - +impl RoleAssignment { + pub fn is_evil(&self)-> bool{ + match self.win_condition.clone() { + RoleOutlineOptionWinCondition::GameConclusionReached { win_if_any } => + win_if_any.into_iter().any(|a| + a == GameConclusion::Politician || a == GameConclusion::Cult || + a == GameConclusion::Fiends || a == GameConclusion::Mafia || + a == GameConclusion::NaughtyList + ), + RoleOutlineOptionWinCondition::RoleDefault => { + self.role == Role::Politician || + RoleSet::Mafia.get_roles().contains(&self.role) || + RoleSet::Minions.get_roles().contains(&self.role) || + RoleSet::Fiends.get_roles().contains(&self.role) || + RoleSet::Cult.get_roles().contains(&self.role) + }, + } + } +} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct RoleOutline { diff --git a/server/src/vec_map.rs b/server/src/vec_map.rs index 1c971290b..66310eee7 100644 --- a/server/src/vec_map.rs +++ b/server/src/vec_map.rs @@ -133,7 +133,6 @@ impl VecMap where K: Eq, V: PartialOrd { /// Returns the old value if the key already exists, the last value of the tuple is the true if the old value was replaced /// If the old value is greater than value, then it is not replaced pub fn keep_greater(&mut self, key: K, value: V) -> Option<(K, V, bool)>{ - if let Some((old_key, old_val)) = self.vec.iter_mut().find(|(k, _)| *k == key) { if *old_val > value { Some(( @@ -154,6 +153,18 @@ impl VecMap where K: Eq, V: PartialOrd { None } } + + /// If the old value is greater than value, then it is not replaced + pub fn keep_greater_unsized(&mut self, key: K, value: V) { + if let Some(i) = self.vec.iter().position(|(k, _)| *k == key) { + if self.vec[i].1 < value { + self.vec.swap_remove(i); + self.vec.push((key, value)); + } + return; + } + self.vec.push((key, value)); + } /// Returns the old value if the key already exists, the last value of the tuple is the true if the old value was replaced /// If the old value is lesser than value, then it is not replaced @@ -178,6 +189,18 @@ impl VecMap where K: Eq, V: PartialOrd { None } } + + /// If the old value is lesser than value, then it is not replaced + pub fn keep_lesser_unsized(&mut self, key: K, value: V) { + if let Some(i) = self.vec.iter().position(|(k, _)| *k == key) { + if self.vec[i].1 > value { + self.vec.swap_remove(i); + self.vec.push((key, value)); + } + return; + } + self.vec.push((key, value)); + } } impl PartialEq for VecMap where K: Eq, V: Eq { diff --git a/server/tests/role.rs b/server/tests/role.rs index 4e890618d..5cd9d5366 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -4,7 +4,7 @@ use std::{ops::Deref, vec}; use kit::player::TestPlayer; pub(crate) use kit::{assert_contains, assert_not_contains}; -use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID}, components::{confused::Confused, duration::Duration}, game_conclusion::GameConclusion, role::engineer::Trap}; +use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID}, components::{confused::Confused, status_duration::StatusDuration}, game_conclusion::GameConclusion, role::engineer::Trap}; pub use mafia_server::game::{ chat::{ChatMessageVariant, MessageSender, ChatGroup}, grave::*, @@ -105,12 +105,7 @@ pub use mafia_server::game::{ }; // Pub use so that submodules don't have to reimport everything. pub use mafia_server::packet::ToServerPacket; -#[test] -fn rust_test_if_this_made_it_to_the_final_ver_remove_this_was_me_testing_how_rust_works(){ - let mut x: VecMap> = Vec::new(); - x.insert(1, vec![1]); -} #[test] fn no_unwanted_tags() { kit::scenario!(game in Dusk 1 where @@ -1273,14 +1268,18 @@ fn drunk_suspicious_aura() { fn drunk_confused_and_drunk_aura() { kit::scenario!(game in Night 1 where drunk: Drunk, - _mafioso: Mafioso + mafioso: Mafioso ); - match game.confused.players_durations.get(&drunk.player_ref()) { - Some(duration) => assert!(*duration == Duration::Permanent), + match game.confused.0.get(&drunk.player_ref()) { + Some(value) => { + assert!(value.duration == StatusDuration::Permanent); + assert!(value.red_herrings.len() == 1); + assert!(value.red_herrings.first().is_some_and(|rh|*rh == mafioso.player_ref())); + }, None => panic!() } match game.drunk_aura.players_durations.get(&drunk.player_ref()) { - Some(duration) => assert!(*duration == Duration::Permanent), + Some(duration) => assert!(*duration == StatusDuration::Permanent), None => panic!() } } From ed7a42a9b6fb67643da14c656a3cad9af60a8a77 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:41:25 -0500 Subject: [PATCH 12/23] changed roles to used the red herrings stored in confusion --- server/src/game/components/confused.rs | 9 ++++++- server/src/game/role/detective.rs | 37 ++++++++++---------------- server/src/game/role/gossip.rs | 19 +++++-------- server/src/game/role/philosopher.rs | 18 +++---------- 4 files changed, 32 insertions(+), 51 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index d7dd8229c..e6529d868 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -56,7 +56,7 @@ impl Confused { let data = ConfusionData::new_perm(game, player); game.confused_mut().0.insert_unsized(player, data); } - + pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ let data = ConfusionData::new_temp(game, player, duration); game.confused_mut().0.keep_greater_unsized(player, data); @@ -82,6 +82,13 @@ impl Confused { _=>{} } } + + pub fn is_red_herring(game: &Game, confused_player: PlayerReference, target: PlayerReference) -> bool{ + return match game.confused().0.get(&confused_player) { + None => false, + Some(data) => data.red_herrings.contains(&target), + } + } } impl PlayerReference { diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index d2d075d59..83fc948dd 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -2,7 +2,6 @@ use serde::Serialize; use crate::game::ability_input::ControllerID; use crate::game::components::confused::Confused; -use crate::game::role::RoleState; use crate::game::{attack_power::DefensePower, chat::ChatMessageVariant}; use crate::game::game_conclusion::GameConclusion; use crate::game::player::PlayerReference; @@ -16,9 +15,7 @@ pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Detective { - red_herring: Option, -} +pub struct Detective; impl RoleStateImpl for Detective { type ClientRoleState = Detective; @@ -26,20 +23,20 @@ impl RoleStateImpl for Detective { if priority != Priority::Investigative {return;} let actor_visits = actor_ref.untagged_night_visits_cloned(game); - if let Some(visit) = actor_visits.first(){ - let suspicious = if Confused::is_confused(game, actor_ref) { - visit.target.night_framed(game) || - self.red_herring.is_some_and(|red_herring| red_herring == visit.target) - }else{ - Detective::player_is_suspicious(game, visit.target) - }; + let Some(visit) = actor_visits.first() else {return}; + let target_ref = visit.target; + let suspicious = if Confused::is_confused(game, actor_ref) { + target_ref.night_framed(game) || + Confused::is_red_herring(game, actor_ref, target_ref) + }else{ + Detective::player_is_suspicious(game, target_ref) + }; - let message = ChatMessageVariant::DetectiveResult { - suspicious - }; - - actor_ref.push_night_message(game, message); - } + let message = ChatMessageVariant::DetectiveResult { + suspicious + }; + + actor_ref.push_night_message(game, message); } fn controller_parameters_map(self, game: &Game, actor_ref: PlayerReference) -> ControllerParametersMap { crate::game::role::common_role::controller_parameters_map_player_list_night_typical( @@ -59,12 +56,6 @@ impl RoleStateImpl for Detective { false ) } - - fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { - actor_ref.set_role_state(game, RoleState::Detective(Detective{ - red_herring: PlayerReference::generate_red_herring(actor_ref, game) - })); - } } impl Detective { diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index c239f8c1b..1a3f8738f 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -8,16 +8,14 @@ use crate::game::visit::Visit; use crate::game::Game; use super::detective::Detective; -use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; +use super::{ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; pub(super) const MAXIMUM_COUNT: Option = None; pub(super) const DEFENSE: DefensePower = DefensePower::None; #[derive(Clone, Debug, Serialize, Default)] -pub struct Gossip { - red_herring: Option, -} +pub struct Gossip; impl RoleStateImpl for Gossip { type ClientRoleState = Gossip; @@ -54,12 +52,6 @@ impl RoleStateImpl for Gossip { false ) } - - fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { - actor_ref.set_role_state(game, RoleState::Gossip(Gossip{ - red_herring: PlayerReference::generate_red_herring(actor_ref, game) - })); - } } impl Gossip { @@ -71,11 +63,12 @@ impl Gossip { .iter() .map(|v|v.target.clone()) .any( - |targets_target: PlayerReference| + |target_of_target: PlayerReference| if Confused::is_confused(game, actor_ref) { - targets_target.night_framed(game) || self.red_herring.is_some_and(|red_herring| red_herring == targets_target) + target_of_target.night_framed(game) || + Confused::is_red_herring(game, actor_ref, target_of_target) } else { - Detective::player_is_suspicious(game, targets_target) + Detective::player_is_suspicious(game, target_of_target) } ) } diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index 02eb7137a..a1596bdbd 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -10,12 +10,10 @@ use crate::game::visit::Visit; use crate::game::Game; use crate::vec_set; -use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleState, RoleStateImpl}; +use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; #[derive(Clone, Debug, Serialize, Default)] -pub struct Philosopher{ - pub red_herring: Option, -} +pub struct Philosopher; pub(super) const MAXIMUM_COUNT: Option = None; @@ -36,10 +34,8 @@ impl RoleStateImpl for Philosopher { if first_visit.target == second_visit.target { false } else if Confused::is_confused(game, actor_ref) { - self.red_herring.is_some_and(|red_herring| - (red_herring == first_visit.target || first_visit.target.night_framed(game)) ^ - (red_herring == second_visit.target || second_visit.target.night_framed(game)) - ) + (first_visit.target.night_framed(game) || Confused::is_red_herring(game, actor_ref, first_visit.target)) ^ + (second_visit.target.night_framed(game) || Confused::is_red_herring(game, actor_ref, second_visit.target)) } else { Philosopher::players_are_enemies(game, first_visit.target, second_visit.target) }; @@ -82,12 +78,6 @@ impl RoleStateImpl for Philosopher { false ) } - - fn on_role_creation(self, game: &mut Game, actor_ref: PlayerReference) { - actor_ref.set_role_state(game, RoleState::Philosopher(Philosopher{ - red_herring: PlayerReference::generate_red_herring(actor_ref, game) - })); - } } impl Philosopher{ pub fn players_are_enemies(game: &Game, a: PlayerReference, b: PlayerReference) -> bool { From 55aaf9b5027c26ed77f7929ee6c3470da6432018 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:06:04 -0500 Subject: [PATCH 13/23] fixed error --- server/tests/role.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/server/tests/role.rs b/server/tests/role.rs index 5cd9d5366..1e496e70e 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -1487,16 +1487,7 @@ fn red_herrings_framer() { let mut found_philosopher_red_herring = false; - let philosopher_red_herring_is_tester = - if let RoleState::Philosopher(Philosopher {red_herring}) = philosopher.player_ref().role_state(&*game) { - if let Some(red_herring) = red_herring { - *red_herring == tester.player_ref() - } else { - panic!() - } - } else { - panic!(); - }; + let philosopher_red_herring_is_tester = Confused::is_red_herring(&*game, philosopher.player_ref(), tester.player_ref()); for index in 0u8..4 { let target; From fa409f42af458a2047601ba60f0a88939877dce9 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Wed, 5 Mar 2025 09:25:07 -0500 Subject: [PATCH 14/23] Fixed import issue? --- server/tests/kit/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/tests/kit/mod.rs b/server/tests/kit/mod.rs index b1c3c57bf..cf79619fe 100644 --- a/server/tests/kit/mod.rs +++ b/server/tests/kit/mod.rs @@ -4,6 +4,7 @@ use mafia_server::game::{ role::RoleState, settings::Settings, test::mock_game, + Game }; pub mod player; @@ -109,4 +110,4 @@ pub mod _init { TestScenario { game, players } } -} \ No newline at end of file +} From 2b43b3630dec3d0ed7282e6377bada80b2899764 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:05:44 -0500 Subject: [PATCH 15/23] removed duration. made it so red herrings stay the same when stuff about the player changes. added get_confusion_data func. updated tests --- server/src/game/components/confused.rs | 75 +++++------ server/src/game/components/drunk_aura.rs | 36 ++---- server/src/game/components/mod.rs | 1 - server/src/game/components/status_duration.rs | 116 ------------------ server/src/game/event/on_phase_start.rs | 4 +- server/src/game/role/drunk.rs | 4 +- server/tests/role.rs | 81 ++++++++---- 7 files changed, 102 insertions(+), 215 deletions(-) delete mode 100644 server/src/game/components/status_duration.rs diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index e6529d868..7eccb7e87 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -1,25 +1,20 @@ use rand::seq::IteratorRandom; -use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; - -use super::status_duration::StatusDuration; - -#[derive(Default, Clone, PartialEq, Eq, PartialOrd)] +use crate::{game::{player::PlayerReference, Game}, vec_map::VecMap}; +/// Its not an enum that is different for every role because if you swap roles but stay confused, +/// the information you get should be consistent with your previous role's info if possible. +/// the reason why it stores whether your confused instead of removing you if your not, +/// is so if you become confused after stop being confused, you have the same confusion data. +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ConfusionData{ - pub duration: StatusDuration, pub red_herrings: Vec, + pub confused: bool, } impl ConfusionData { - pub fn new_perm(game: &Game, player: PlayerReference) -> ConfusionData { - ConfusionData { - duration: StatusDuration::Permanent, - red_herrings: Self::generate_red_herrings(game,player), - } - } - pub fn new_temp(game: &Game, player: PlayerReference, duration: u8) -> ConfusionData { + pub fn new(game: &Game, player: PlayerReference) -> ConfusionData { ConfusionData { - duration: StatusDuration::Temporary(duration), red_herrings: Self::generate_red_herrings(game,player), + confused: true, } } pub fn generate_red_herrings(game: &Game, player: PlayerReference) -> Vec { @@ -29,12 +24,6 @@ impl ConfusionData { PlayerReference::all_players(game) .filter(|p|*p != player) .choose_multiple(&mut rand::rng(), count) - - } -} -impl Ord for ConfusionData{ - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - return self.duration.cmp(&other.duration) } } @@ -52,43 +41,41 @@ impl Game { } impl Confused { - pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - let data = ConfusionData::new_perm(game, player); - game.confused_mut().0.insert_unsized(player, data); + pub fn add_player(game: &mut Game, player: PlayerReference) { + match game.confused_mut().0.get_mut(&player) { + Some(data) => data.confused = true, + _=> { + let data = ConfusionData::new(game, player); + game.confused_mut().0.insert_unsized(player, data); + } + } } - pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - let data = ConfusionData::new_temp(game, player, duration); - game.confused_mut().0.keep_greater_unsized(player, data); - } - - pub fn remove_player(game: &mut Game, player: PlayerReference){ - game.confused_mut().0.remove(&player); + pub fn remove_player(game: &mut Game, player: PlayerReference) -> bool { + match game.confused_mut().0.get_mut(&player) { + Some(data) => { + let old = data.confused.clone(); + data.confused = false; + old + }, + None=>false, + } } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ game.confused().0.contains(&player) } - - /// Decrements confusion durations and removes players whose durations are up - pub fn on_phase_start(game: &mut Game, phase: PhaseState){ - match phase { - //feel free to change the phase, right now there aren't any ways to temporarily confuse a player so I chose Night mostly arbitrarily - PhaseState::Night => { - game.confused.0.retain_mut( - |_, data| data.duration.decrement() - ); - }, - _=>{} - } - } pub fn is_red_herring(game: &Game, confused_player: PlayerReference, target: PlayerReference) -> bool{ - return match game.confused().0.get(&confused_player) { + match game.confused().0.get(&confused_player) { None => false, Some(data) => data.red_herrings.contains(&target), } } + + pub fn get_confusion_data(game: &Game, player: PlayerReference) -> Option<&ConfusionData>{ + game.confused().0.get(&player) + } } impl PlayerReference { diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index 60a480680..a44b9486a 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -1,10 +1,10 @@ -use crate::{game::{phase::PhaseState, player::PlayerReference, Game}, vec_map::VecMap}; +use crate::{game::{player::PlayerReference, Game}, vec_set::VecSet}; -use super::{status_duration::StatusDuration, confused::Confused}; +use super::confused::Confused; #[derive(Default, Clone)] pub struct DrunkAura { - pub players_durations: VecMap, + pub players: VecSet, } impl Game { @@ -17,37 +17,21 @@ impl Game { } impl DrunkAura { - pub fn add_player_permanent(game: &mut Game, player: PlayerReference){ - game.drunk_aura_mut().players_durations.insert(player, StatusDuration::Permanent); + pub fn add_player(game: &mut Game, player: PlayerReference){ + game.drunk_aura_mut().players.insert(player); } - pub fn add_player_temporary(game: &mut Game, player: PlayerReference, duration: u8){ - game.drunk_aura_mut().players_durations.keep_greater(player, StatusDuration::Temporary(duration)); - } - - pub fn remove_player(game: &mut Game, player: PlayerReference){ - game.drunk_aura_mut().players_durations.remove(&player); + pub fn remove_player(game: &mut Game, player: PlayerReference) -> bool{ + game.drunk_aura_mut().players.remove(&player).is_some() } pub fn has_drunk_aura(game: &Game, player: PlayerReference) -> bool { - game.drunk_aura().players_durations.contains(&player) + game.drunk_aura().players.contains(&player) } pub fn on_role_switch(game: &mut Game, player: PlayerReference) { - Self::remove_player(game, player); - Confused::remove_player(game, player); - } - - ///Decrements drunk aura durations and removes players whose durations are up - pub fn on_phase_start(game: &mut Game, phase: PhaseState){ - match phase { - //feel free to change the phase, right now there aren't any ways to temporarily give a player drunk aura so I chose Night mostly arbitrarily - PhaseState::Night => { - game.drunk_aura.players_durations.retain_mut( - |_, duration| duration.decrement() - ); - }, - _=>{} + if Self::remove_player(game, player) { + Confused::remove_player(game, player); } } } diff --git a/server/src/game/components/mod.rs b/server/src/game/components/mod.rs index 8a1164c19..e459b1328 100644 --- a/server/src/game/components/mod.rs +++ b/server/src/game/components/mod.rs @@ -11,7 +11,6 @@ pub mod insider_group; pub mod detained; pub mod confused; pub mod drunk_aura; -pub mod status_duration; pub mod forfeit_vote; pub mod night_visits; pub mod syndicate_gun_item; diff --git a/server/src/game/components/status_duration.rs b/server/src/game/components/status_duration.rs deleted file mode 100644 index 2f366fe40..000000000 --- a/server/src/game/components/status_duration.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::ops::{Add, AddAssign, Sub, SubAssign}; - -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] -/// All operators are saturating -pub enum StatusDuration { - Temporary(u8), - #[default] Permanent, -} - -impl StatusDuration { - ///Returns true if the duration is not 0 - pub fn decrement(&mut self) -> bool { - match self { - Self::Permanent => true, - Self::Temporary(duration) => { - if *duration == 0 { - return false; - } - *duration -= 1; - return *duration > 0; - } - } - } - - pub fn increment(&mut self) { - match self { - Self::Permanent => (), - Self::Temporary(duration) => *duration += 1, - } - } - - ///Returns true if the duration is not 0 - pub fn is_over(&self) -> bool { - match self { - Self::Permanent => false, - Self::Temporary(duration) => { - return *duration == 0; - } - } - } -} - -impl Add for StatusDuration { - type Output = Self; - fn add(self, rhs: StatusDuration) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary(duration) => { - let rhs_duration = duration; - match rhs { - Self::Permanent => Self::Permanent, - Self::Temporary(duration) => Self::Temporary(rhs_duration.saturating_add(duration)), - } - } - } - } -} - -impl AddAssign for StatusDuration { - fn add_assign(&mut self, rhs: Self) { - match rhs { - Self::Permanent => *self = Self::Permanent, - Self::Temporary(duration) => { - let rhs_duration = duration; - match self { - Self::Permanent => return, - Self::Temporary (duration) => { - *duration = rhs_duration.saturating_add(*duration); - } - } - } - } - } -} - -impl Add for StatusDuration { - type Output = StatusDuration; - fn add(self, rhs: u8) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary (duration) => Self::Temporary(rhs.saturating_add(duration)) - } - } -} - -impl AddAssign for StatusDuration { - fn add_assign(&mut self, rhs: u8) { - match self { - Self::Permanent => return, - Self::Temporary(duration) => { - *duration = rhs.saturating_add(*duration); - } - } - } -} - -impl Sub for StatusDuration { - type Output = StatusDuration; - fn sub(self, rhs: u8) -> Self::Output { - match self { - Self::Permanent => Self::Permanent, - Self::Temporary (duration) => Self::Temporary(duration.saturating_sub(rhs)) - } - } -} - -impl SubAssign for StatusDuration { - fn sub_assign(&mut self, rhs: u8) { - match self { - Self::Permanent => return, - Self::Temporary(duration) => { - *duration = duration.saturating_sub(rhs); - } - } - } -} diff --git a/server/src/game/event/on_phase_start.rs b/server/src/game/event/on_phase_start.rs index f3c90b582..424369a8f 100644 --- a/server/src/game/event/on_phase_start.rs +++ b/server/src/game/event/on_phase_start.rs @@ -1,6 +1,6 @@ use crate::game::{ ability_input::saved_controllers_map::SavedControllersMap, components::{ - confused::Confused, cult::Cult, detained::Detained, drunk_aura::DrunkAura, mafia::Mafia, night_visits::NightVisits, verdicts_today::VerdictsToday + cult::Cult, detained::Detained, mafia::Mafia, night_visits::NightVisits, verdicts_today::VerdictsToday }, modifiers::Modifiers, phase::PhaseState, player::PlayerReference, Game }; @@ -24,8 +24,6 @@ impl OnPhaseStart{ Cult::on_phase_start(game, self.phase.phase()); SavedControllersMap::on_phase_start(game, self.phase.phase()); Modifiers::on_phase_start(game, self.phase.clone()); - Confused::on_phase_start(game, self.phase.clone()); - DrunkAura::on_phase_start(game, self.phase.clone()); game.on_phase_start(self.phase.phase()); } diff --git a/server/src/game/role/drunk.rs b/server/src/game/role/drunk.rs index 1fa832187..331efa0c4 100644 --- a/server/src/game/role/drunk.rs +++ b/server/src/game/role/drunk.rs @@ -30,8 +30,8 @@ impl RoleStateImpl for Drunk { actor_ref.set_role_state(game, random_town_role.new_state(game)); } - Confused::add_player_permanent(game, actor_ref); - DrunkAura::add_player_permanent(game, actor_ref); + Confused::add_player(game, actor_ref); + DrunkAura::add_player(game, actor_ref); } } impl Drunk{ diff --git a/server/tests/role.rs b/server/tests/role.rs index 6d67a3cc3..e0e93fbfd 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -4,7 +4,7 @@ use std::{ops::Deref, vec}; use kit::player::TestPlayer; pub(crate) use kit::{assert_contains, assert_not_contains}; -use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID, RoleOptionSelection}, components::{confused::Confused, status_duration::StatusDuration}, game_conclusion::GameConclusion, role::engineer::Trap}; +use mafia_server::game::{ability_input::{ability_selection::AbilitySelection, ControllerID, RoleOptionSelection}, components::{confused::Confused, drunk_aura::DrunkAura}, game_conclusion::GameConclusion, role::{engineer::Trap, reeducator::Reeducator}}; pub use mafia_server::game::{ chat::{ChatMessageVariant, MessageSender, ChatGroup}, @@ -1269,21 +1269,56 @@ fn drunk_suspicious_aura() { #[test] fn drunk_confused_and_drunk_aura() { - kit::scenario!(game in Night 1 where - drunk: Drunk, - mafioso: Mafioso - ); - match game.confused.0.get(&drunk.player_ref()) { - Some(value) => { - assert!(value.duration == StatusDuration::Permanent); - assert!(value.red_herrings.len() == 1); - assert!(value.red_herrings.first().is_some_and(|rh|*rh == mafioso.player_ref())); - }, - None => panic!() - } - match game.drunk_aura.players_durations.get(&drunk.player_ref()) { - Some(duration) => assert!(*duration == StatusDuration::Permanent), - None => panic!() + for _ in 0..20 { + kit::scenario!(game in Night 1 where + drunk: Drunk, + reed: Reeducator, + townie: Deputy + ); + + let red_herring; + match Confused::get_confusion_data(&*game, drunk.player_ref()) { + Some(value) => { + assert!(value.red_herrings.len() == 1); + assert!(value.red_herrings.first().is_some_and(|rh| + (*rh == reed.player_ref()) ^ (*rh == townie.player_ref()) + )); + assert!(value.confused); + red_herring = value.red_herrings[0]; + }, + None => unreachable!() + } + + assert!(Confused::is_confused(&*game, drunk.player_ref())); + assert!(!Confused::is_confused(&*game, reed.player_ref())); + assert!(!Confused::is_confused(&*game, townie.player_ref())); + + assert!(DrunkAura::has_drunk_aura(&*game, drunk.player_ref())); + assert!(!DrunkAura::has_drunk_aura(&*game, reed.player_ref())); + assert!(!DrunkAura::has_drunk_aura(&*game, townie.player_ref())); + + reed.send_ability_input_player_list_typical(drunk); + game.next_phase(); + + match game.confused.0.get(&drunk.player_ref()) { + Some(value) => { + assert!(value.red_herrings.len() == 1); + assert!(value.red_herrings.first().is_some_and(|rh| + (*rh == reed.player_ref()) ^ (*rh == townie.player_ref()) + )); + assert!(!value.confused); + assert!(red_herring == value.red_herrings[0]); + }, + None => unreachable!() + } + + assert!(!Confused::is_confused(&*game, drunk.player_ref())); + assert!(!Confused::is_confused(&*game, reed.player_ref())); + assert!(!Confused::is_confused(&*game, townie.player_ref())); + + assert!(!DrunkAura::has_drunk_aura(&*game, drunk.player_ref())); + assert!(!DrunkAura::has_drunk_aura(&*game, reed.player_ref())); + assert!(!DrunkAura::has_drunk_aura(&*game, townie.player_ref())); } } @@ -1360,9 +1395,9 @@ fn red_herrings() { mafia: Informant ); - Confused::add_player_permanent(&mut game, detective.player_ref()); - Confused::add_player_permanent(&mut game, gossip.player_ref()); - Confused::add_player_permanent(&mut game, philosopher.player_ref()); + Confused::add_player(&mut game, detective.player_ref()); + Confused::add_player(&mut game, gossip.player_ref()); + Confused::add_player(&mut game, philosopher.player_ref()); assert!(Confused::is_confused(&*game, detective.player_ref())); assert!(Confused::is_confused(&*game, gossip.player_ref())); @@ -1480,9 +1515,9 @@ fn red_herrings_framer() { mafia: Framer ); - Confused::add_player_permanent(&mut game, detective.player_ref()); - Confused::add_player_permanent(&mut game, gossip.player_ref()); - Confused::add_player_permanent(&mut game, philosopher.player_ref()); + Confused::add_player(&mut game, detective.player_ref()); + Confused::add_player(&mut game, gossip.player_ref()); + Confused::add_player(&mut game, philosopher.player_ref()); assert!(Confused::is_confused(&*game, detective.player_ref())); assert!(Confused::is_confused(&*game, gossip.player_ref())); @@ -1492,7 +1527,7 @@ fn red_herrings_framer() { let philosopher_red_herring_is_tester = Confused::is_red_herring(&*game, philosopher.player_ref(), tester.player_ref()); - for index in 0u8..4 { + for index in 0..5u8 { let target; //The unsafe part is the fact that the player index is next ensured to be a player. //this is fine as only indices 0 to 4 would be allowed which is what is going on here. From c846af90959d6f6f5c512b1a62a185f7893f246e Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:01:06 -0500 Subject: [PATCH 16/23] move the sus checking for confused players to a separate function & made tally clerk use red herrings --- .vscode/settings.json | 2 ++ server/src/game/components/confused.rs | 18 +++++------------- server/src/game/components/drunk_aura.rs | 2 +- server/src/game/role/detective.rs | 7 +++++-- server/src/game/role/gossip.rs | 24 ++++++++++++------------ server/src/game/role/philosopher.rs | 4 ++++ server/src/game/role/tally_clerk.rs | 22 +++++++++------------- 7 files changed, 38 insertions(+), 41 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 307da5237..ca454612d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,8 @@ "Doesnt", "dompurify", "entriy", + "guiltied", + "guilties", "Informant", "Informants", "inno", diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index 7eccb7e87..ffa0accce 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -63,28 +63,20 @@ impl Confused { } pub fn is_confused(game: &Game, player: PlayerReference)->bool{ - game.confused().0.contains(&player) + Self::get_confusion_data(game, player).is_some_and(|data|data.confused) } pub fn is_red_herring(game: &Game, confused_player: PlayerReference, target: PlayerReference) -> bool{ match game.confused().0.get(&confused_player) { + Some(data) => data.confused && data.red_herrings.contains(&target), None => false, - Some(data) => data.red_herrings.contains(&target), } } + /// WARNING: JUST BECAUSE A PLAYER HAS CONFUSION DATA, IT DOESN'T MEAN THEY ARE CONFUSED
+ /// to check if player is confused, either check whether the data's confused flag is true + /// or use is_confused function pub fn get_confusion_data(game: &Game, player: PlayerReference) -> Option<&ConfusionData>{ game.confused().0.get(&player) } -} - -impl PlayerReference { - pub fn generate_red_herring(self, game: &Game) -> Option{ - return PlayerReference::all_players(game) - .filter(|player| - player.alive(game) && - *player != self - ) - .choose(&mut rand::rng()) - } } \ No newline at end of file diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index a44b9486a..f4469beb3 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -20,7 +20,7 @@ impl DrunkAura { pub fn add_player(game: &mut Game, player: PlayerReference){ game.drunk_aura_mut().players.insert(player); } - + /// Returns true if the player was drunk pub fn remove_player(game: &mut Game, player: PlayerReference) -> bool{ game.drunk_aura_mut().players.remove(&player).is_some() } diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index 83fc948dd..c666cbcfd 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -26,8 +26,7 @@ impl RoleStateImpl for Detective { let Some(visit) = actor_visits.first() else {return}; let target_ref = visit.target; let suspicious = if Confused::is_confused(game, actor_ref) { - target_ref.night_framed(game) || - Confused::is_red_herring(game, actor_ref, target_ref) + Self::player_is_suspicious_confused(game, actor_ref, target_ref) }else{ Detective::player_is_suspicious(game, target_ref) }; @@ -69,4 +68,8 @@ impl Detective { !player_ref.win_condition(game).friends_with_resolution_state(GameConclusion::Town) } } + pub fn player_is_suspicious_confused(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { + actor_ref.night_framed(game) || + Confused::is_red_herring(game, actor_ref, player_ref) + } } \ No newline at end of file diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index 1a3f8738f..eec6ac0a4 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -27,8 +27,8 @@ impl RoleStateImpl for Gossip { let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ - let enemies = self.enemies(game, visit.target, actor_ref); - let message = ChatMessageVariant::GossipResult{ enemies }; + let enemies = Self::visited_enemies(game, visit.target, actor_ref); + let message: ChatMessageVariant = ChatMessageVariant::GossipResult{ enemies }; actor_ref.push_night_message(game, message); } @@ -55,21 +55,21 @@ impl RoleStateImpl for Gossip { } impl Gossip { - pub fn enemies(self, game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { + pub fn visited_enemies(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { match player_ref.night_appeared_visits(game) { Some(x) => x.clone(), None => player_ref.all_night_visits_cloned(game), } .iter() .map(|v|v.target.clone()) - .any( - |target_of_target: PlayerReference| - if Confused::is_confused(game, actor_ref) { - target_of_target.night_framed(game) || - Confused::is_red_herring(game, actor_ref, target_of_target) - } else { - Detective::player_is_suspicious(game, target_of_target) - } - ) + .any(|target_of_target: PlayerReference|Self::enemy(game, target_of_target, actor_ref)) + } + pub fn enemy(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool{ + if Confused::is_confused(game, actor_ref) { + player_ref.night_framed(game) || + Confused::is_red_herring(game, actor_ref, player_ref) + } else { + Detective::player_is_suspicious(game, player_ref) + } } } \ No newline at end of file diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index a1596bdbd..97582aef8 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -89,4 +89,8 @@ impl Philosopher{ !WinCondition::are_friends(a.win_condition(game), b.win_condition(game)) } } + pub fn players_are_enemies_confused(game: &Game, a: PlayerReference, b: PlayerReference, actor_ref: PlayerReference) -> bool { + (a.night_framed(game) || Confused::is_red_herring(game, actor_ref, a)) ^ + (b.night_framed(game) || Confused::is_red_herring(game, actor_ref, b)) + } } \ No newline at end of file diff --git a/server/src/game/role/tally_clerk.rs b/server/src/game/role/tally_clerk.rs index ea7d0967e..138dad1a6 100644 --- a/server/src/game/role/tally_clerk.rs +++ b/server/src/game/role/tally_clerk.rs @@ -30,21 +30,14 @@ impl RoleStateImpl for TallyClerk { .filter(|player|player.alive(game)) .filter(|player|VerdictsToday::player_guiltied_today(game, player)) { - if TallyClerk::player_is_suspicious(game, player){ + if Confused::is_confused(game, actor_ref) { + if Self::player_is_suspicious_confused(game, player, actor_ref) { + evil_count += 1; + } + } else if Self::player_is_suspicious(game, player) { evil_count += 1; } } - - if Confused::is_confused(game, actor_ref){ - let total_guilties = VerdictsToday::guilties(game).len(); - //add or subtract 1 randomly from the count - if rand::random::(){ - evil_count = (evil_count.saturating_add(1u8)).min(total_guilties as u8); - }else{ - evil_count = (evil_count.saturating_sub(1u8)).max(0); - } - } - let message = ChatMessageVariant::TallyClerkResult{ evil_count }; actor_ref.push_night_message(game, message); @@ -53,7 +46,6 @@ impl RoleStateImpl for TallyClerk { impl TallyClerk { pub fn player_is_suspicious(game: &Game, player_ref: PlayerReference) -> bool { - if player_ref.has_suspicious_aura(game){ true }else if player_ref.has_innocent_aura(game){ @@ -62,4 +54,8 @@ impl TallyClerk { !player_ref.win_condition(game).is_loyalist_for(GameConclusion::Town) } } + pub fn player_is_suspicious_confused(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { + actor_ref.night_framed(game) || + Confused::is_red_herring(game, actor_ref, player_ref) + } } \ No newline at end of file From 4c3f3efef06939354022e3e56e10e1a67408ff11 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:03:42 -0500 Subject: [PATCH 17/23] changed gossip to use to the detective funcs --- server/src/game/role/gossip.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index eec6ac0a4..49bdc5234 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -62,14 +62,12 @@ impl Gossip { } .iter() .map(|v|v.target.clone()) - .any(|target_of_target: PlayerReference|Self::enemy(game, target_of_target, actor_ref)) - } - pub fn enemy(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool{ - if Confused::is_confused(game, actor_ref) { - player_ref.night_framed(game) || - Confused::is_red_herring(game, actor_ref, player_ref) - } else { - Detective::player_is_suspicious(game, player_ref) - } + .any(|target_of_target: PlayerReference| + if Confused::is_confused(game, actor_ref) { + Detective::player_is_suspicious_confused(game, player_ref, actor_ref) + } else { + Detective::player_is_suspicious(game, player_ref) + } + ) } } \ No newline at end of file From b2b7f8c5247038f0cb177736dde1d963f3f727e1 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:04:19 -0500 Subject: [PATCH 18/23] fixed gossip so that its no longer detective --- server/src/game/role/gossip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index 49bdc5234..ca0c01c1a 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -64,9 +64,9 @@ impl Gossip { .map(|v|v.target.clone()) .any(|target_of_target: PlayerReference| if Confused::is_confused(game, actor_ref) { - Detective::player_is_suspicious_confused(game, player_ref, actor_ref) + Detective::player_is_suspicious_confused(game, target_of_target, actor_ref) } else { - Detective::player_is_suspicious(game, player_ref) + Detective::player_is_suspicious(game, target_of_target) } ) } From 4b74128b83919f1e308ca33fe5b2ff47e0fb66e4 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:28:59 -0500 Subject: [PATCH 19/23] fixed bugs --- server/src/game/components/confused.rs | 4 +-- server/src/game/components/drunk_aura.rs | 2 +- server/src/game/role/detective.rs | 4 +-- server/src/game/role/gossip.rs | 10 +++---- server/src/game/role/philosopher.rs | 5 ++-- server/src/vec_map.rs | 2 +- server/tests/role.rs | 34 +++++++++--------------- 7 files changed, 25 insertions(+), 36 deletions(-) diff --git a/server/src/game/components/confused.rs b/server/src/game/components/confused.rs index ffa0accce..1afd077ff 100644 --- a/server/src/game/components/confused.rs +++ b/server/src/game/components/confused.rs @@ -5,7 +5,7 @@ use crate::{game::{player::PlayerReference, Game}, vec_map::VecMap}; /// the information you get should be consistent with your previous role's info if possible. /// the reason why it stores whether your confused instead of removing you if your not, /// is so if you become confused after stop being confused, you have the same confusion data. -#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct ConfusionData{ pub red_herrings: Vec, pub confused: bool, @@ -28,7 +28,7 @@ impl ConfusionData { } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct Confused(pub VecMap); impl Game { diff --git a/server/src/game/components/drunk_aura.rs b/server/src/game/components/drunk_aura.rs index f4469beb3..8ec365d75 100644 --- a/server/src/game/components/drunk_aura.rs +++ b/server/src/game/components/drunk_aura.rs @@ -34,4 +34,4 @@ impl DrunkAura { Confused::remove_player(game, player); } } -} +} \ No newline at end of file diff --git a/server/src/game/role/detective.rs b/server/src/game/role/detective.rs index c666cbcfd..4d5637c6c 100644 --- a/server/src/game/role/detective.rs +++ b/server/src/game/role/detective.rs @@ -26,7 +26,7 @@ impl RoleStateImpl for Detective { let Some(visit) = actor_visits.first() else {return}; let target_ref = visit.target; let suspicious = if Confused::is_confused(game, actor_ref) { - Self::player_is_suspicious_confused(game, actor_ref, target_ref) + Self::player_is_suspicious_confused(game, target_ref, actor_ref) }else{ Detective::player_is_suspicious(game, target_ref) }; @@ -69,7 +69,7 @@ impl Detective { } } pub fn player_is_suspicious_confused(game: &Game, player_ref: PlayerReference, actor_ref: PlayerReference) -> bool { - actor_ref.night_framed(game) || + player_ref.night_framed(game) || Confused::is_red_herring(game, actor_ref, player_ref) } } \ No newline at end of file diff --git a/server/src/game/role/gossip.rs b/server/src/game/role/gossip.rs index ca0c01c1a..7fdb6a0a5 100644 --- a/server/src/game/role/gossip.rs +++ b/server/src/game/role/gossip.rs @@ -22,14 +22,11 @@ impl RoleStateImpl for Gossip { fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) { if priority != Priority::Investigative {return;} - - let actor_visits = actor_ref.untagged_night_visits_cloned(game); if let Some(visit) = actor_visits.first(){ let enemies = Self::visited_enemies(game, visit.target, actor_ref); let message: ChatMessageVariant = ChatMessageVariant::GossipResult{ enemies }; - actor_ref.push_night_message(game, message); } @@ -61,12 +58,11 @@ impl Gossip { None => player_ref.all_night_visits_cloned(game), } .iter() - .map(|v|v.target.clone()) - .any(|target_of_target: PlayerReference| + .any(|visit: &Visit| if Confused::is_confused(game, actor_ref) { - Detective::player_is_suspicious_confused(game, target_of_target, actor_ref) + Detective::player_is_suspicious_confused(game, visit.target, actor_ref) } else { - Detective::player_is_suspicious(game, target_of_target) + Detective::player_is_suspicious(game, visit.target) } ) } diff --git a/server/src/game/role/philosopher.rs b/server/src/game/role/philosopher.rs index 97582aef8..8d6125f20 100644 --- a/server/src/game/role/philosopher.rs +++ b/server/src/game/role/philosopher.rs @@ -10,6 +10,7 @@ use crate::game::visit::Visit; use crate::game::Game; use crate::vec_set; +use super::detective::Detective; use super::{common_role, AvailableAbilitySelection, ControllerID, ControllerParametersMap, Priority, Role, RoleStateImpl}; #[derive(Clone, Debug, Serialize, Default)] @@ -90,7 +91,7 @@ impl Philosopher{ } } pub fn players_are_enemies_confused(game: &Game, a: PlayerReference, b: PlayerReference, actor_ref: PlayerReference) -> bool { - (a.night_framed(game) || Confused::is_red_herring(game, actor_ref, a)) ^ - (b.night_framed(game) || Confused::is_red_herring(game, actor_ref, b)) + Detective::player_is_suspicious_confused(game, a, actor_ref) ^ + Detective::player_is_suspicious_confused(game, b, actor_ref) } } \ No newline at end of file diff --git a/server/src/vec_map.rs b/server/src/vec_map.rs index 66310eee7..9a732ff38 100644 --- a/server/src/vec_map.rs +++ b/server/src/vec_map.rs @@ -109,7 +109,7 @@ impl VecMap where K: Eq { self.vec.retain(|(k, v)| f(k, v)); } - pub fn retain_mut(&mut self, mut f: F) where F: FnMut(&mut K, &mut V) -> bool { + pub fn retain_mut(&mut self, mut f: F) where F: FnMut(&K, &mut V) -> bool { self.vec.retain_mut(|(k, v)| f(k, v)); } diff --git a/server/tests/role.rs b/server/tests/role.rs index e0e93fbfd..8a1be7af1 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -1391,6 +1391,7 @@ fn red_herrings() { detective: Detective, gossip: Gossip, philosopher: Philosopher, + //Has to be a doctor so they can visit themself tester: Doctor, mafia: Informant ); @@ -1415,7 +1416,7 @@ fn red_herrings() { unsafe { target = TestPlayer::new(PlayerReference::new_unchecked(index), &*game); } - + detective.send_ability_input_player_list_typical(target); gossip.send_ability_input_player_list_typical(tester); philosopher.send_ability_input_two_player_typical(target, tester); @@ -1440,12 +1441,14 @@ fn red_herrings() { panic!("detective_messages: {:?}", detective_messages); } else { found_detective_red_herring = true; + assert!(Confused::is_red_herring(&*game, detective.player_ref(), target.player_ref())); } } else { assert_eq!( detective_messages.contains(&ChatMessageVariant::DetectiveResult { suspicious: false }), target != detective - ) + ); + assert!(!Confused::is_red_herring(&*game, detective.player_ref(), target.player_ref())); } /* Test Gossip */ @@ -1456,9 +1459,11 @@ fn red_herrings() { panic!("gossip_messages: {:?}", gossip_messages); } else { found_gossip_red_herring = true; + assert!(Confused::is_red_herring(&*game, gossip.player_ref(), target.player_ref())) } } else { assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: false })); + assert!(!Confused::is_red_herring(&*game, gossip.player_ref(), target.player_ref())); } @@ -1486,20 +1491,7 @@ fn red_herrings() { } assert!(found_detective_red_herring); - - if !found_gossip_red_herring { - gossip.send_ability_input_player_list_typical(detective); - detective.send_ability_input_player_list_typical(tester); - - game.skip_to(Obituary, 7); - - let message_before_ability_message = ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: 6 }; - - let gossip_messages = gossip.get_messages_after_last_message(message_before_ability_message.clone()); - - assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: true })); - } - + assert!(found_gossip_red_herring); assert!(found_philosopher_red_herring); } } @@ -1527,20 +1519,20 @@ fn red_herrings_framer() { let philosopher_red_herring_is_tester = Confused::is_red_herring(&*game, philosopher.player_ref(), tester.player_ref()); - for index in 0..5u8 { + for index in 0..4u8 { let target; //The unsafe part is the fact that the player index is next ensured to be a player. //this is fine as only indices 0 to 4 would be allowed which is what is going on here. unsafe { target = TestPlayer::new(PlayerReference::new_unchecked(index), &*game); } - + detective.send_ability_input_player_list_typical(target); gossip.send_ability_input_player_list_typical(tester); philosopher.send_ability_input_two_player_typical(target, tester); tester.send_ability_input_player_list_typical(target); mafia.send_ability_input_player_list_typical(target); - + game.skip_to(Obituary, index+2); assert!(detective.alive()); @@ -1548,7 +1540,7 @@ fn red_herrings_framer() { assert!(philosopher.alive()); assert!(tester.alive()); assert!(mafia.alive()); - + let message_before_ability_message = ChatMessageVariant::PhaseChange { phase: PhaseState::Night, day_number: index+1 }; /* Test Detective */ @@ -1557,7 +1549,7 @@ fn red_herrings_framer() { detective_messages.contains(&ChatMessageVariant::DetectiveResult { suspicious: true }), target != detective ); - + /* Test Gossip */ let gossip_messages = gossip.get_messages_after_last_message(message_before_ability_message.clone()); assert!(gossip_messages.contains(&ChatMessageVariant::GossipResult { enemies: true })); From 6b47605a3669a1d87521040d8c0e1fc834fb7fee Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:53:05 -0500 Subject: [PATCH 20/23] forgot that reeducator doesn't work night 1 --- server/tests/role.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/tests/role.rs b/server/tests/role.rs index 8a1be7af1..7a2a2ee60 100644 --- a/server/tests/role.rs +++ b/server/tests/role.rs @@ -1270,7 +1270,7 @@ fn drunk_suspicious_aura() { #[test] fn drunk_confused_and_drunk_aura() { for _ in 0..20 { - kit::scenario!(game in Night 1 where + kit::scenario!(game in Night 2 where drunk: Drunk, reed: Reeducator, townie: Deputy From fb309fc8a6f1dc50168083685e735eb2a1ecfb92 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:32:06 -0500 Subject: [PATCH 21/23] added wiki entry. renamed messages on client side --- client/src/components/ChatMessage.tsx | 12 ++++++------ client/src/resources/lang/en_us.json | 12 +++++++----- client/src/resources/roles.json | 18 +++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/client/src/components/ChatMessage.tsx b/client/src/components/ChatMessage.tsx index ab970d4c2..852f99aaa 100644 --- a/client/src/components/ChatMessage.tsx +++ b/client/src/components/ChatMessage.tsx @@ -604,8 +604,8 @@ export function translateChatMessage( return translate("chatMessage.godfatherBackupKilled", playerNames[message.backup]); case "roleBlocked": return translate("chatMessage.roleBlocked" + (message.immune ? ".immune" : "")); - case "sheriffResult": - return translate("chatMessage.sheriffResult." + (message.suspicious ? "suspicious" : "innocent")); + case "detectiveResult": + return translate("chatMessage.detectiveResult." + (message.suspicious ? "suspicious" : "innocent")); case "snoopResult": return translate("chatMessage.snoopResult." + (message.townie ? "townie" : "inconclusive")); case "gossipResult": @@ -620,8 +620,8 @@ export function translateChatMessage( return translate("chatMessage.spyBug."+message.bug); case "trackerResult": return translate("chatMessage.trackerResult", playerListToString(message.players, playerNames)); - case "seerResult": - return translate("chatMessage.seerResult." + (message.enemies ? "enemies" : "friends")); + case "philosopherResult": + return translate("chatMessage.philosopherResult." + (message.enemies ? "enemies" : "friends")); case "psychicEvil": return translate( "chatMessage.psychicEvil", @@ -938,7 +938,7 @@ export type ChatMessageVariant = { { type: "wardblocked" } | { - type: "sheriffResult", + type: "detectiveResult", suspicious: boolean } | { type: "snoopResult", @@ -962,7 +962,7 @@ export type ChatMessageVariant = { type: "trackerResult", players: PlayerIndex[] } | { - type: "seerResult", + type: "philosopherResult", enemies: boolean } | { type: "psychicGood", diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index 99eec4d3b..1b69ef1dc 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -942,12 +942,12 @@ "chatMessage.youArePoisoned": "You are poisoned, you will be attacked tomorrow night.", - "chatMessage.sheriffResult.suspicious":"Your target is enemies with the town.", - "chatMessage.sheriffResult.innocent":"Your target is friends with the town.", + "chatMessage.detectiveResult.suspicious":"Your target is enemies with the town.", + "chatMessage.detectiveResult.innocent":"Your target is friends with the town.", "chatMessage.lookoutResult":"Your target was visited by \\0.", "chatMessage.trackerResult":"Your target visited \\0.", - "chatMessage.seerResult.friends":"Your targets are friends.", - "chatMessage.seerResult.enemies":"Your targets are enemies.", + "chatMessage.philosopherResultResult.friends":"Your targets are friends.", + "chatMessage.philosopherResultResult.enemies":"Your targets are enemies.", "chatMessage.spyMafiaVisit":"The syndicate visited \\0.", "chatMessage.spyBug.silenced":"Your target was silenced.", "chatMessage.spyBug.roleblocked":"Someone attempted to roleblock your target.", @@ -1251,7 +1251,9 @@ "wiki.article.standard.confused.title:var.0": "Confuses", "wiki.article.standard.confused.title:var.1": "Confusing", "wiki.article.standard.confused.title:var.2": "Confuse", - "wiki.article.standard.confused.text": "The role of a player who is confused might not function correctly. Confused affects each role differently. You might not be told that you are confused.\n How this affects every role:\n - Snoop: You are always told that you can't tell\n - Detective: You are always told innocent\n - Philosopher: You are always told friends\n - Gossip: You are always told that your target didn't visit an enemy\n - Psychic: You are told a random list of 3 players for your evil vision, You are told a random list of 2 players for your good vision.\n - Tally Clerk: You are told either 1 more or 1 less than the correct number you should be told. If it would exceed the number of players who voted guilty, it is that number instead. If it were to go under 0, it is 0 instead.\n - Auditor: You get random roles, they might be correct or incorrect. You are only told roles that could have come from that slot.", + "wiki.article.standard.confused.title:var.3": "red herring", + "wiki.article.standard.confused.title:var.4": "red herrings", + "wiki.article.standard.confused.text": "The role of a player who is confused might not function correctly. Confused affects each role differently. You might not be told that you are confused. All confused players are assigned a set of players they believe are evil, called red herrings. Not all confused roles are affected by red herrings. The number of red herrings for a confused player is the number of non-townie roles assigned at the start of the game before factoring in recruiter and reeducator.\n How this affects every role:\n - Snoop: You are always told that you can't tell\n - Detective: You are always told unless your target is framed or is one of your red herrings\n - Philosopher: your told enemies iff 1 one of the targets is framed or one of your red herrings, but the other isn't \n - Gossip: You are told your target visited an enemy if they visited a player that is framed or is one of your red herrings\n - Psychic: You are told a random list of 3 players for your evil vision, You are told a random list of 2 players for your good vision\n - Tally Clerk: You are told the number of players that are framed or are one of your red herrings that voted guilty\n - Auditor: You get random roles, they might be correct or incorrect. You are only told roles that could have come from that slot.", "wiki.article.standard.marionette.title": "String Up", "wiki.article.standard.marionette.title:var.0": "String", "wiki.article.standard.marionette.title:var.1": "Marionette", diff --git a/client/src/resources/roles.json b/client/src/resources/roles.json index 5e1295c3a..f53e3b965 100644 --- a/client/src/resources/roles.json +++ b/client/src/resources/roles.json @@ -62,8 +62,8 @@ "canWriteDeathNote": false, "canBeConvertedTo": [], "chatMessages":[ - {"type":"sheriffResult","suspicious":true}, - {"type":"sheriffResult","suspicious":false} + {"type": "detectiveResult","suspicious":true}, + {"type":"detectiveResult","suspicious":false} ] }, "lookout": { @@ -88,8 +88,8 @@ "canWriteDeathNote": false, "canBeConvertedTo": [], "chatMessages":[ - {"type": "seerResult", "enemies": true}, - {"type": "seerResult", "enemies": false} + {"type": "philosopherResult", "enemies": true}, + {"type": "philosopherResult", "enemies": false} ] }, "tracker": { @@ -499,7 +499,7 @@ "canBeConvertedTo": [], "chatMessages":[ {"type": "targetsMessage", "message": - {"type": "sheriffResult","suspicious":true} + {"type": "detectiveResult","suspicious":true} }, {"type": "youWerePossessed", "immune": false}, {"type": "youWerePossessed", "immune": true}, @@ -689,7 +689,7 @@ {"type": "framerResult", "mafiaMember": 1, "visitors": ["gossip", "detective"]}, {"type": "framerResult", "mafiaMember": 4, "visitors": ["doctor"]}, {"type": "framerResult", "mafiaMember": 20, "visitors": []}, - {"type": "sheriffResult","suspicious":true}, + {"type": "detectiveResult","suspicious":true}, {"type": "snoopResult", "townie":false}, {"type": "snoopResult", "townie":false}, {"type": "seerResult", "enemies": true} @@ -750,7 +750,7 @@ "canBeConvertedTo": ["godfather"], "chatMessages":[ {"type": "targetsMessage", "message": - {"type": "sheriffResult","suspicious":true} + {"type": "detectiveResult","suspicious":true} }, {"type": "targetHasRole", "role":"detective"}, {"type": "targetIsPossessionImmune"}, @@ -768,7 +768,7 @@ "canBeConvertedTo": ["godfather"], "chatMessages":[ {"type": "targetsMessage", "message": - {"type": "sheriffResult","suspicious":true} + {"type": "detectiveResult","suspicious":true} }, {"type": "targetIsPossessionImmune"}, {"type": "youWerePossessed", "immune": false}, @@ -969,7 +969,7 @@ "canBeConvertedTo": [], "chatMessages":[ {"type": "targetsMessage", "message": - {"type": "sheriffResult","suspicious":true} + {"type": "detectiveResult","suspicious":true} }, {"type": "targetHasRole", "role":"detective"}, {"type": "targetIsPossessionImmune"}, From f9e587c64e7152bde3245a2b4bfb2f02e29968ea Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:41:37 -0500 Subject: [PATCH 22/23] Made is_evil better, added into for VecSet to HashSet --- server/src/game/role_list.rs | 8 ++------ server/src/vec_set.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/server/src/game/role_list.rs b/server/src/game/role_list.rs index a8aa34637..73e1200b3 100644 --- a/server/src/game/role_list.rs +++ b/server/src/game/role_list.rs @@ -7,7 +7,7 @@ use vec1::{ use crate::vec_set::VecSet; -use super::{components::insider_group::InsiderGroupID, game_conclusion::GameConclusion, role::Role}; +use super::{components::insider_group::InsiderGroupID, game_conclusion::GameConclusion, role::Role, win_condition::WinCondition}; #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RoleList(pub Vec); @@ -47,11 +47,7 @@ impl RoleAssignment { pub fn is_evil(&self)-> bool{ match self.win_condition.clone() { RoleOutlineOptionWinCondition::GameConclusionReached { win_if_any } => - win_if_any.into_iter().any(|a| - a == GameConclusion::Politician || a == GameConclusion::Cult || - a == GameConclusion::Fiends || a == GameConclusion::Mafia || - a == GameConclusion::NaughtyList - ), + !WinCondition::GameConclusionReached{win_if_any: win_if_any.into()}.friends_with_resolution_state(GameConclusion::Town), RoleOutlineOptionWinCondition::RoleDefault => { self.role == Role::Politician || RoleSet::Mafia.get_roles().contains(&self.role) || diff --git a/server/src/vec_set.rs b/server/src/vec_set.rs index 85f4adb69..307a30182 100644 --- a/server/src/vec_set.rs +++ b/server/src/vec_set.rs @@ -1,3 +1,5 @@ +use std::{collections::HashSet, hash::Hash}; + use serde::{Deserialize, Serialize}; use crate::vec_map::VecMap; @@ -123,6 +125,16 @@ impl Ord for VecSet { } } +impl Into> for VecSet where K: Eq+Hash{ + fn into(self) -> HashSet { + let mut hash_set = HashSet::with_capacity(self.len()); + for key in self.vec { + hash_set.insert(key.0); + } + hash_set + } +} + pub use macros::vec_set; mod macros { #[macro_export] From e80db1473de922214745cb3d326a2e5596f0070c Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:45:42 -0500 Subject: [PATCH 23/23] made is_evil clearer --- server/src/game/role_list.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/game/role_list.rs b/server/src/game/role_list.rs index 73e1200b3..6758906fc 100644 --- a/server/src/game/role_list.rs +++ b/server/src/game/role_list.rs @@ -49,11 +49,7 @@ impl RoleAssignment { RoleOutlineOptionWinCondition::GameConclusionReached { win_if_any } => !WinCondition::GameConclusionReached{win_if_any: win_if_any.into()}.friends_with_resolution_state(GameConclusion::Town), RoleOutlineOptionWinCondition::RoleDefault => { - self.role == Role::Politician || - RoleSet::Mafia.get_roles().contains(&self.role) || - RoleSet::Minions.get_roles().contains(&self.role) || - RoleSet::Fiends.get_roles().contains(&self.role) || - RoleSet::Cult.get_roles().contains(&self.role) + !RoleSet::Town.get_roles().contains(&self.role) }, } }