Compare commits

...

2 Commits

Author SHA1 Message Date
emilis a424211ab4
wolf reverting + fix killing wolf 2025-10-14 09:16:20 +01:00
emilis 5c8e33dab1
make insomniac (and beholder) sleep n0 2025-10-14 09:15:40 +01:00
5 changed files with 241 additions and 40 deletions

View File

@ -255,7 +255,27 @@ impl Night {
.collect(), .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 { let night_state = NightState::Active {
current_prompt: ActionPrompt::CoverOfDarkness, current_prompt: ActionPrompt::CoverOfDarkness,
current_changes: Vec::new(), current_changes: Vec::new(),
@ -272,6 +292,84 @@ impl Night {
}) })
} }
fn remove_reverted_prompts(
mut action_queue: VecDeque<ActionPrompt>,
reverting: CharacterId,
reverting_into: RoleTitle,
) -> VecDeque<ActionPrompt> {
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) /// changes that require no input (such as hunter firing)
fn automatic_changes(&self) -> Vec<NightChange> { fn automatic_changes(&self) -> Vec<NightChange> {
let mut changes = Vec::new(); let mut changes = Vec::new();

View File

@ -49,11 +49,21 @@ impl Village {
let mut wolves = self let mut wolves = self
.characters .characters
.iter() .iter()
.filter(|c| c.is_wolf()) .filter(|c| c.alive() && c.is_wolf())
.collect::<Box<[_]>>(); .collect::<Box<[_]>>();
wolves.sort_by_key(|w| w.killing_wolf_order()); wolves.sort_by_key(|w| w.killing_wolf_order());
wolves.first().copied() wolves.first().copied()
} }
pub fn wolf_revert_prompt(&self) -> Option<ActionPrompt> {
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<ActionPrompt> { pub fn wolf_pack_kill(&self) -> Option<ActionPrompt> {
let night = match self.time { let night = match self.time {
GameTime::Day { .. } => return None, GameTime::Day { .. } => return None,
@ -162,23 +172,6 @@ impl Village {
.collect() .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]> { pub fn living_players(&self) -> Box<[CharacterIdentity]> {
self.characters self.characters
.iter() .iter()

View File

@ -1,5 +1,6 @@
mod night_order; mod night_order;
mod previous; mod previous;
mod revert;
mod role; mod role;
use crate::{ use crate::{
@ -788,19 +789,9 @@ fn wolfpack_kill_all_targets_valid() {
} }
#[test] #[test]
fn varied_test() { fn big_game_test_based_on_story_test() {
init_log(); init_log();
let players = (1..32u8) let players = gen_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::<Box<[_]>>();
let mut players_iter = players.iter().map(|p| p.player_id); let mut players_iter = players.iter().map(|p| p.player_id);
let ( let (
werewolf, werewolf,

View File

@ -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::<RoleTitle>::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::<RoleTitle>::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();
}
}

View File

@ -1,8 +1,4 @@
use core::{ use core::{fmt::Display, num::NonZeroU8, ops::Not};
fmt::Display,
num::NonZeroU8,
ops::{Deref, Not},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use werewolves_macros::{ChecksAs, Titles}; use werewolves_macros::{ChecksAs, Titles};
@ -307,15 +303,15 @@ impl Role {
pub const fn wakes_night_zero(&self) -> bool { pub const fn wakes_night_zero(&self) -> bool {
match self { match self {
Role::Insomniac Role::PowerSeer
| Role::PowerSeer
| Role::Beholder
| Role::Adjudicator | Role::Adjudicator
| Role::DireWolf { .. } | Role::DireWolf { .. }
| Role::Arcanist | Role::Arcanist
| Role::Seer => true, | Role::Seer => true,
Role::LoneWolf Role::Insomniac // has to at least get one good night of sleep, right?
| Role::Beholder
| Role::LoneWolf
| Role::Shapeshifter { .. } | Role::Shapeshifter { .. }
| Role::Werewolf | Role::Werewolf
| Role::AlphaWolf { .. } | Role::AlphaWolf { .. }