From a424211ab4aa3c54ffcb3162c278b39d5dc9b6a1 Mon Sep 17 00:00:00 2001 From: emilis Date: Mon, 13 Oct 2025 23:16:20 +0100 Subject: [PATCH] wolf reverting + fix killing wolf --- werewolves-proto/src/game/night.rs | 100 +++++++++++++++++- werewolves-proto/src/game/village.rs | 29 ++---- werewolves-proto/src/game_test/mod.rs | 15 +-- werewolves-proto/src/game_test/revert.rs | 123 +++++++++++++++++++++++ 4 files changed, 236 insertions(+), 31 deletions(-) create mode 100644 werewolves-proto/src/game_test/revert.rs diff --git a/werewolves-proto/src/game/night.rs b/werewolves-proto/src/game/night.rs index 56115e7..388b197 100644 --- a/werewolves-proto/src/game/night.rs +++ b/werewolves-proto/src/game/night.rs @@ -255,7 +255,27 @@ impl Night { .collect(), }); } - // let current_prompt = action_queue.pop_front().ok_or(GameError::NoNightActions)?; + if let Some(prompt) = village.wolf_revert_prompt() { + match &prompt { + ActionPrompt::RoleChange { + character_id, + new_role, + } => { + action_queue = Self::remove_reverted_prompts( + action_queue, + character_id.character_id, + *new_role, + ) + } + prompt => { + log::error!( + "wolf_revert_prompt should have returned a role change, got {prompt:?}" + ) + } + } + + action_queue.push_front(prompt); + } let night_state = NightState::Active { current_prompt: ActionPrompt::CoverOfDarkness, current_changes: Vec::new(), @@ -272,6 +292,84 @@ impl Night { }) } + fn remove_reverted_prompts( + mut action_queue: VecDeque, + reverting: CharacterId, + reverting_into: RoleTitle, + ) -> VecDeque { + let mut new_queue = VecDeque::new(); + if let Some(wolves) = action_queue.iter_mut().find_map(|q| match q { + ActionPrompt::WolvesIntro { wolves } => Some(wolves), + _ => None, + }) && let Some(w) = wolves.iter_mut().find(|w| w.0.character_id == reverting) + { + w.1 = reverting_into; + } + + // remove prompts by the reverting wolf that are in the queue + for prompt in action_queue { + let (wolf_id, prompt) = match prompt { + ActionPrompt::WolvesIntro { mut wolves } => { + if let Some(w) = wolves.iter_mut().find(|w| w.0.character_id == reverting) { + w.1 = reverting_into; + } + new_queue.push_front(ActionPrompt::WolvesIntro { wolves }); + continue; + } + + ActionPrompt::Shapeshifter { character_id } => ( + character_id.character_id, + ActionPrompt::Shapeshifter { character_id }, + ), + ActionPrompt::AlphaWolf { + character_id, + living_villagers, + marked, + } => ( + character_id.character_id, + ActionPrompt::AlphaWolf { + character_id, + living_villagers, + marked, + }, + ), + ActionPrompt::DireWolf { + character_id, + living_players, + marked, + } => ( + character_id.character_id, + ActionPrompt::DireWolf { + character_id, + living_players, + marked, + }, + ), + ActionPrompt::LoneWolfKill { + character_id, + living_players, + marked, + } => ( + character_id.character_id, + ActionPrompt::LoneWolfKill { + character_id, + living_players, + marked, + }, + ), + other => { + new_queue.push_front(other); + continue; + } + }; + if wolf_id != reverting { + new_queue.push_front(prompt); + } + } + + new_queue + } + /// changes that require no input (such as hunter firing) fn automatic_changes(&self) -> Vec { let mut changes = Vec::new(); diff --git a/werewolves-proto/src/game/village.rs b/werewolves-proto/src/game/village.rs index 2785147..7a12149 100644 --- a/werewolves-proto/src/game/village.rs +++ b/werewolves-proto/src/game/village.rs @@ -49,11 +49,21 @@ impl Village { let mut wolves = self .characters .iter() - .filter(|c| c.is_wolf()) + .filter(|c| c.alive() && c.is_wolf()) .collect::>(); wolves.sort_by_key(|w| w.killing_wolf_order()); wolves.first().copied() } + + pub fn wolf_revert_prompt(&self) -> Option { + self.killing_wolf() + .filter(|killing_wolf| RoleTitle::Werewolf != killing_wolf.role_title()) + .map(|killing_wolf| ActionPrompt::RoleChange { + character_id: killing_wolf.identity(), + new_role: RoleTitle::Werewolf, + }) + } + pub fn wolf_pack_kill(&self) -> Option { let night = match self.time { GameTime::Day { .. } => return None, @@ -162,23 +172,6 @@ impl Village { .collect() } - pub fn killing_wolf_id(&self) -> CharacterId { - let wolves = self.living_wolf_pack_players(); - if let Some(ww) = wolves - .iter() - .find(|w| matches!(w.role_title(), RoleTitle::Werewolf)) - { - ww.character_id() - } else if let Some(non_ss_wolf) = wolves - .iter() - .find(|w| w.is_wolf() && !matches!(w.role_title(), RoleTitle::Shapeshifter)) - { - non_ss_wolf.character_id() - } else { - wolves.into_iter().next().unwrap().character_id() - } - } - pub fn living_players(&self) -> Box<[CharacterIdentity]> { self.characters .iter() diff --git a/werewolves-proto/src/game_test/mod.rs b/werewolves-proto/src/game_test/mod.rs index dfa865d..d33d4d5 100644 --- a/werewolves-proto/src/game_test/mod.rs +++ b/werewolves-proto/src/game_test/mod.rs @@ -1,5 +1,6 @@ mod night_order; mod previous; +mod revert; mod role; use crate::{ @@ -788,19 +789,9 @@ fn wolfpack_kill_all_targets_valid() { } #[test] -fn varied_test() { +fn big_game_test_based_on_story_test() { init_log(); - let players = (1..32u8) - .filter_map(NonZeroU8::new) - .map(|n| Identification { - player_id: PlayerId::from_u128(n.get() as _), - public: PublicIdentity { - name: format!("Player {n}"), - pronouns: Some("he/him".into()), - number: Some(n), - }, - }) - .collect::>(); + let players = gen_players(1..32u8); let mut players_iter = players.iter().map(|p| p.player_id); let ( werewolf, diff --git a/werewolves-proto/src/game_test/revert.rs b/werewolves-proto/src/game_test/revert.rs new file mode 100644 index 0000000..5dd33c0 --- /dev/null +++ b/werewolves-proto/src/game_test/revert.rs @@ -0,0 +1,123 @@ +use crate::{ + game::{Game, GameSettings, SetupRole}, + game_test::{ + ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players, init_log, + }, + message::{CharacterIdentity, night::ActionPrompt}, + role::{Role, RoleTitle}, +}; + +#[test] +fn sole_non_werewolf_wolves_revert() { + const REVERTING_WOLVES: &[SetupRole] = &[ + SetupRole::DireWolf, + SetupRole::LoneWolf, + SetupRole::AlphaWolf, + SetupRole::Shapeshifter, + ]; + init_log(); + for wolf_role in REVERTING_WOLVES { + let role_title = Into::::into(wolf_role.clone()); + log::info!("testing initial reverting for [{role_title}]"); + + let players = gen_players(1..32u8); + let reverting_wolf = players[0].player_id; + let mut settings = GameSettings::empty(); + settings.add_and_assign(wolf_role.clone(), reverting_wolf); + settings.fill_remaining_slots_with_villagers(players.len()); + let mut game = Game::new(&players, settings).unwrap(); + game.r#continue().r#continue(); + + assert_eq!( + game.next(), + ActionPrompt::RoleChange { + character_id: game.character_by_player_id(reverting_wolf).identity(), + new_role: RoleTitle::Werewolf, + } + ); + game.r#continue().r#continue(); + + assert_eq!( + game.next(), + ActionPrompt::WolvesIntro { + wolves: Box::new([( + game.character_by_player_id(reverting_wolf).identity(), + RoleTitle::Werewolf + )]) + } + ); + game.r#continue().sleep(); + + game.next_expect_day(); + assert_eq!( + *game.character_by_player_id(reverting_wolf).role(), + Role::Werewolf + ); + } +} + +#[test] +fn wolves_revert_on_werewolf_death() { + const REVERTING_WOLVES: &[SetupRole] = &[ + SetupRole::DireWolf, + SetupRole::LoneWolf, + SetupRole::AlphaWolf, + SetupRole::Shapeshifter, + ]; + init_log(); + for wolf_role in REVERTING_WOLVES { + let role_title = Into::::into(wolf_role.clone()); + log::info!("testing initial reverting for [{role_title}]"); + + let players = gen_players(1..32u8); + let wolf = players[0].player_id; + let reverting_wolf = players[1].player_id; + let mut settings = GameSettings::empty(); + settings.add_and_assign(SetupRole::Werewolf, wolf); + settings.add_and_assign(wolf_role.clone(), reverting_wolf); + settings.fill_remaining_slots_with_villagers(players.len()); + let mut game = Game::new(&players, settings).unwrap(); + game.r#continue().r#continue(); + assert_eq!( + game.next(), + ActionPrompt::WolvesIntro { + wolves: Box::new([ + ( + game.character_by_player_id(wolf).identity(), + RoleTitle::Werewolf + ), + ( + game.character_by_player_id(reverting_wolf).identity(), + role_title + ) + ]) + } + ); + if RoleTitle::DireWolf == role_title { + game.r#continue().r#continue(); + game.next().title().direwolf(); + game.mark(game.living_villager().character_id()); + game.r#continue().sleep(); + } else { + game.r#continue().sleep(); + } + + game.next_expect_day(); + game.mark_for_execution(game.character_by_player_id(wolf).character_id()); + + assert_eq!( + game.execute(), + ActionPrompt::RoleChange { + character_id: game.character_by_player_id(reverting_wolf).identity(), + new_role: RoleTitle::Werewolf, + } + ); + game.r#continue().r#continue(); + + game.next().title().wolf_pack_kill(); + game.mark(game.living_villager().character_id()); + game.r#continue().sleep(); + + game.next_expect_day(); + } +}