pyremaster role and tests

This commit is contained in:
emilis 2025-10-06 22:30:01 +01:00
parent 9fa5843b5a
commit da580a459e
No known key found for this signature in database
6 changed files with 110 additions and 2 deletions

View File

@ -49,6 +49,10 @@ pub enum DiedTo {
killer: CharacterId,
night: NonZeroU8,
},
PyreMasterLynchMob {
source: CharacterId,
night: NonZeroU8,
},
MasonLeaderRecruitFail {
tried_recruiting: CharacterId,
night: u8,
@ -68,6 +72,7 @@ impl DiedTo {
| DiedTo::Shapeshift { night, .. }
| DiedTo::Hunter { night, .. }
| DiedTo::GuardianProtecting { night, .. }
| DiedTo::PyreMasterLynchMob { night, .. }
| DiedTo::PyreMaster { night, .. } => *night = NonZeroU8::new(night.get() + 1)?,
DiedTo::MasonLeaderRecruitFail { night, .. } => *night = *night + 1,
}
@ -95,6 +100,7 @@ impl DiedTo {
tried_recruiting: killer,
..
}
| DiedTo::PyreMasterLynchMob { source: killer, .. }
| DiedTo::PyreMaster { killer, .. } => Some(*killer),
}
}
@ -121,6 +127,7 @@ impl DiedTo {
}
| DiedTo::AlphaWolf { killer: _, night }
| DiedTo::Shapeshift { into: _, night }
| DiedTo::PyreMasterLynchMob { night, .. }
| DiedTo::PyreMaster { night, .. }
| DiedTo::Hunter { killer: _, night } => DateTime::Night {
number: night.get(),

View File

@ -15,7 +15,7 @@ use crate::{
},
message::night::{ActionPrompt, ActionResponse, ActionResult},
player::Protection,
role::{PreviousGuardianAction, RoleBlock, RoleTitle},
role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleBlock, RoleTitle},
};
#[derive(Debug, Clone, Serialize, Deserialize, Extract)]
@ -457,15 +457,44 @@ impl Night {
}
}
}
// black knights death
for knight in new_village
.characters_mut()
.into_iter()
.filter(|k| k.alive())
.filter(|k| k.black_knight().ok().and_then(|t| (*t).clone()).is_some())
.filter(|k| changes.killed(k.character_id()).is_none())
{
knight.black_knight_kill()?.kill();
}
// pyre masters death
let village_dead = new_village
.characters()
.into_iter()
.filter(|c| c.is_village())
.filter_map(|c| c.died_to().cloned())
.filter_map(|c| c.killer().map(|k| (k, c)))
.collect::<Box<[_]>>();
for pyremaster in new_village
.living_characters_by_role_mut(RoleTitle::PyreMaster)
.into_iter()
.filter(|p| {
village_dead
.iter()
.filter(|(k, _)| *k == p.character_id())
.count()
>= PYREMASTER_VILLAGER_KILLS_TO_DIE.get() as usize
})
{
if let Some(night) = NonZeroU8::new(self.night) {
pyremaster.kill(DiedTo::PyreMasterLynchMob {
night,
source: pyremaster.character_id(),
});
}
}
if new_village.is_game_over().is_none() {
new_village.to_day()?;
}

View File

@ -224,6 +224,7 @@ pub trait GameExt {
target: CharacterId,
) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
fn living_villager_excl(&self, excl: PlayerId) -> Character;
fn living_villager(&self) -> Character;
#[allow(unused)]
fn get_state(&mut self) -> ServerToHostMessage;
}
@ -232,6 +233,13 @@ impl GameExt for Game {
fn get_state(&mut self) -> ServerToHostMessage {
self.process(HostGameMessage::GetState).unwrap()
}
fn living_villager(&self) -> Character {
self.village()
.characters()
.into_iter()
.find(|c| c.alive() && matches!(c.role_title(), RoleTitle::Villager))
.unwrap()
}
fn living_villager_excl(&self, excl: PlayerId) -> Character {
self.village()
@ -248,7 +256,8 @@ impl GameExt for Game {
.characters()
.into_iter()
.filter_map(|c| {
matches!(c.role_title(), RoleTitle::Villager).then_some(c.character_id())
(c.alive() && matches!(c.role_title(), RoleTitle::Villager))
.then_some(c.character_id())
})
.collect()
}

View File

@ -4,5 +4,6 @@ mod diseased;
mod elder;
mod empath;
mod mason;
mod pyremaster;
mod scapegoat;
mod shapeshifter;

View File

@ -0,0 +1,61 @@
use core::num::NonZeroU8;
#[allow(unused)]
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
use crate::{
diedto::DiedTo,
game::{Game, GameSettings, SetupRole},
game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players},
message::night::ActionPromptTitle,
};
#[test]
fn burnout() {
let players = gen_players(1..21);
let pyremaster_player_id = players[0].player_id;
let wolf_player_id = players[1].player_id;
let mut settings = GameSettings::empty();
settings.add_and_assign(SetupRole::PyreMaster, pyremaster_player_id);
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
settings.fill_remaining_slots_with_villagers(20);
let mut game = Game::new(&players, settings).unwrap();
game.r#continue().r#continue();
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
game.r#continue().sleep();
let mut villagers = game.villager_character_ids().into_iter();
game.next_expect_day();
game.execute().title().wolf_pack_kill();
game.mark(villagers.next().unwrap());
game.r#continue().sleep();
game.next().title().pyremaster();
game.mark(villagers.next().unwrap());
game.r#continue().sleep();
game.next_expect_day();
villagers = game.villager_character_ids().into_iter();
game.execute().title().wolf_pack_kill();
game.mark(villagers.next().unwrap());
game.r#continue().sleep();
game.next().title().pyremaster();
game.mark(villagers.next().unwrap());
game.r#continue().sleep();
game.next_expect_day();
assert_eq!(
game.character_by_player_id(pyremaster_player_id)
.died_to()
.cloned(),
Some(DiedTo::PyreMasterLynchMob {
source: game
.character_by_player_id(pyremaster_player_id)
.character_id(),
night: NonZeroU8::new(2).unwrap()
})
);
}

View File

@ -254,6 +254,7 @@ pub enum ArcanistCheck {
}
pub const MAPLE_WOLF_ABSTAIN_LIMIT: NonZeroU8 = NonZeroU8::new(3).unwrap();
pub const PYREMASTER_VILLAGER_KILLS_TO_DIE: NonZeroU8 = NonZeroU8::new(2).unwrap();
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RoleBlock {