black knight role and tests
This commit is contained in:
parent
153d39f0bb
commit
9fa5843b5a
|
|
@ -72,11 +72,7 @@ impl Character {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn is_power_role(&self) -> bool {
|
pub const fn is_power_role(&self) -> bool {
|
||||||
match &self.role {
|
!matches!(&self.role, Role::Scapegoat { .. } | Role::Villager)
|
||||||
Role::Scapegoat { .. } | Role::Villager => false,
|
|
||||||
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identity(&self) -> CharacterIdentity {
|
pub fn identity(&self) -> CharacterIdentity {
|
||||||
|
|
@ -103,7 +99,14 @@ impl Character {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(&mut self, died_to: DiedTo) {
|
pub fn kill(&mut self, died_to: DiedTo) {
|
||||||
|
if self.died_to.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
match (&mut self.role, died_to.date_time()) {
|
match (&mut self.role, died_to.date_time()) {
|
||||||
|
(Role::BlackKnight { attacked }, DateTime::Night { .. }) => {
|
||||||
|
attacked.replace(died_to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
(
|
(
|
||||||
Role::Elder {
|
Role::Elder {
|
||||||
lost_protection_night: Some(_),
|
lost_protection_night: Some(_),
|
||||||
|
|
@ -606,6 +609,40 @@ impl Character {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn black_knight<'a>(&'a self) -> Result<BlackKnight<'a>> {
|
||||||
|
match &self.role {
|
||||||
|
Role::BlackKnight { attacked } => Ok(BlackKnight(attacked)),
|
||||||
|
_ => Err(GameError::InvalidRole {
|
||||||
|
expected: RoleTitle::BlackKnight,
|
||||||
|
got: self.role_title(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn black_knight_kill<'a>(&'a mut self) -> Result<BlackKnightKill<'a>> {
|
||||||
|
let title = self.role.title();
|
||||||
|
match &self.role {
|
||||||
|
Role::BlackKnight { attacked } => Ok(BlackKnightKill {
|
||||||
|
attacked,
|
||||||
|
died_to: &mut self.died_to,
|
||||||
|
}),
|
||||||
|
_ => Err(GameError::InvalidRole {
|
||||||
|
expected: RoleTitle::BlackKnight,
|
||||||
|
got: title,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn black_knight_mut<'a>(&'a mut self) -> Result<BlackKnightMut<'a>> {
|
||||||
|
let title = self.role.title();
|
||||||
|
match &mut self.role {
|
||||||
|
Role::BlackKnight { attacked } => Ok(BlackKnightMut(attacked)),
|
||||||
|
_ => Err(GameError::InvalidRole {
|
||||||
|
expected: RoleTitle::BlackKnight,
|
||||||
|
got: title,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn initial_shown_role(&self) -> RoleTitle {
|
pub const fn initial_shown_role(&self) -> RoleTitle {
|
||||||
self.role.initial_shown_role()
|
self.role.initial_shown_role()
|
||||||
}
|
}
|
||||||
|
|
@ -644,8 +681,23 @@ decl_ref_and_mut!(
|
||||||
Shapeshifter, ShapeshifterMut: Option<CharacterId>;
|
Shapeshifter, ShapeshifterMut: Option<CharacterId>;
|
||||||
Scapegoat, ScapegoatMut: bool;
|
Scapegoat, ScapegoatMut: bool;
|
||||||
Empath, EmpathMut: bool;
|
Empath, EmpathMut: bool;
|
||||||
|
BlackKnight, BlackKnightMut: Option<DiedTo>;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub struct BlackKnightKill<'a> {
|
||||||
|
attacked: &'a Option<DiedTo>,
|
||||||
|
died_to: &'a mut Option<DiedTo>,
|
||||||
|
}
|
||||||
|
impl BlackKnightKill<'_> {
|
||||||
|
pub fn kill(self) {
|
||||||
|
if let Some(attacked) = self.attacked.as_ref().and_then(|a| a.next_night())
|
||||||
|
&& self.died_to.is_none()
|
||||||
|
{
|
||||||
|
self.died_to.replace(attacked.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MasonLeader<'a>(&'a u8, &'a [CharacterId]);
|
pub struct MasonLeader<'a>(&'a u8, &'a [CharacterId]);
|
||||||
impl MasonLeader<'_> {
|
impl MasonLeader<'_> {
|
||||||
pub const fn remaining_recruits(&self) -> u8 {
|
pub const fn remaining_recruits(&self) -> u8 {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,24 @@ pub enum DiedTo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiedTo {
|
impl DiedTo {
|
||||||
|
pub fn next_night(&self) -> Option<DiedTo> {
|
||||||
|
let mut next = self.clone();
|
||||||
|
match &mut next {
|
||||||
|
DiedTo::Execution { .. } => return None,
|
||||||
|
DiedTo::MapleWolf { night, .. }
|
||||||
|
| DiedTo::MapleWolfStarved { night }
|
||||||
|
| DiedTo::Militia { night, .. }
|
||||||
|
| DiedTo::Wolfpack { night, .. }
|
||||||
|
| DiedTo::AlphaWolf { night, .. }
|
||||||
|
| DiedTo::Shapeshift { night, .. }
|
||||||
|
| DiedTo::Hunter { night, .. }
|
||||||
|
| DiedTo::GuardianProtecting { night, .. }
|
||||||
|
| DiedTo::PyreMaster { night, .. } => *night = NonZeroU8::new(night.get() + 1)?,
|
||||||
|
DiedTo::MasonLeaderRecruitFail { night, .. } => *night = *night + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
pub const fn killer(&self) -> Option<CharacterId> {
|
pub const fn killer(&self) -> Option<CharacterId> {
|
||||||
match self {
|
match self {
|
||||||
DiedTo::Execution { .. }
|
DiedTo::Execution { .. }
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl KillOutcome {
|
||||||
fn resolve_protection(
|
fn resolve_protection(
|
||||||
killer: CharacterId,
|
killer: CharacterId,
|
||||||
killed_with: &DiedTo,
|
killed_with: &DiedTo,
|
||||||
target: &CharacterId,
|
target: CharacterId,
|
||||||
protection: &Protection,
|
protection: &Protection,
|
||||||
night: NonZeroU8,
|
night: NonZeroU8,
|
||||||
) -> Option<KillOutcome> {
|
) -> Option<KillOutcome> {
|
||||||
|
|
@ -68,7 +68,7 @@ fn resolve_protection(
|
||||||
} => Some(KillOutcome::Guarding {
|
} => Some(KillOutcome::Guarding {
|
||||||
original_killer: killer,
|
original_killer: killer,
|
||||||
guardian: *source,
|
guardian: *source,
|
||||||
original_target: *target,
|
original_target: target,
|
||||||
original_kill: killed_with.clone(),
|
original_kill: killed_with.clone(),
|
||||||
night,
|
night,
|
||||||
}),
|
}),
|
||||||
|
|
@ -83,7 +83,7 @@ fn resolve_protection(
|
||||||
|
|
||||||
pub fn resolve_kill(
|
pub fn resolve_kill(
|
||||||
changes: &mut ChangesLookup<'_>,
|
changes: &mut ChangesLookup<'_>,
|
||||||
target: &CharacterId,
|
target: CharacterId,
|
||||||
died_to: &DiedTo,
|
died_to: &DiedTo,
|
||||||
night: u8,
|
night: u8,
|
||||||
village: &Village,
|
village: &Village,
|
||||||
|
|
@ -124,7 +124,7 @@ pub fn resolve_kill(
|
||||||
return Ok(Some(KillOutcome::Single(
|
return Ok(Some(KillOutcome::Single(
|
||||||
*ss_source,
|
*ss_source,
|
||||||
DiedTo::Shapeshift {
|
DiedTo::Shapeshift {
|
||||||
into: *target,
|
into: target,
|
||||||
night: *night,
|
night: *night,
|
||||||
},
|
},
|
||||||
)));
|
)));
|
||||||
|
|
@ -134,7 +134,7 @@ pub fn resolve_kill(
|
||||||
|
|
||||||
let protection = match changes.protected_take(target) {
|
let protection = match changes.protected_take(target) {
|
||||||
Some(prot) => prot,
|
Some(prot) => prot,
|
||||||
None => return Ok(Some(KillOutcome::Single(*target, died_to.clone()))),
|
None => return Ok(Some(KillOutcome::Single(target, died_to.clone()))),
|
||||||
};
|
};
|
||||||
|
|
||||||
match protection {
|
match protection {
|
||||||
|
|
@ -145,7 +145,7 @@ pub fn resolve_kill(
|
||||||
original_killer: died_to
|
original_killer: died_to
|
||||||
.killer()
|
.killer()
|
||||||
.ok_or(GameError::GuardianInvalidOriginalKill)?,
|
.ok_or(GameError::GuardianInvalidOriginalKill)?,
|
||||||
original_target: *target,
|
original_target: target,
|
||||||
original_kill: died_to.clone(),
|
original_kill: died_to.clone(),
|
||||||
guardian: source,
|
guardian: source,
|
||||||
night: NonZeroU8::new(night).unwrap(),
|
night: NonZeroU8::new(night).unwrap(),
|
||||||
|
|
@ -165,20 +165,20 @@ impl<'a> ChangesLookup<'a> {
|
||||||
Self(changes, Vec::new())
|
Self(changes, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn killed(&self, target: &CharacterId) -> Option<&'a DiedTo> {
|
pub fn killed(&self, target: CharacterId) -> Option<&'a DiedTo> {
|
||||||
self.0.iter().enumerate().find_map(|(idx, c)| {
|
self.0.iter().enumerate().find_map(|(idx, c)| {
|
||||||
self.1
|
self.1
|
||||||
.contains(&idx)
|
.contains(&idx)
|
||||||
.not()
|
.not()
|
||||||
.then(|| match c {
|
.then(|| match c {
|
||||||
NightChange::Kill { target: t, died_to } => (t == target).then_some(died_to),
|
NightChange::Kill { target: t, died_to } => (*t == target).then_some(died_to),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn protected_take(&mut self, target: &CharacterId) -> Option<Protection> {
|
pub fn protected_take(&mut self, target: CharacterId) -> Option<Protection> {
|
||||||
if let Some((idx, c)) = self.0.iter().enumerate().find_map(|(idx, c)| {
|
if let Some((idx, c)) = self.0.iter().enumerate().find_map(|(idx, c)| {
|
||||||
self.1
|
self.1
|
||||||
.contains(&idx)
|
.contains(&idx)
|
||||||
|
|
@ -187,7 +187,7 @@ impl<'a> ChangesLookup<'a> {
|
||||||
NightChange::Protection {
|
NightChange::Protection {
|
||||||
target: t,
|
target: t,
|
||||||
protection,
|
protection,
|
||||||
} => (t == target).then_some((idx, protection)),
|
} => (*t == target).then_some((idx, protection)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
|
||||||
|
|
@ -261,10 +261,7 @@ impl Night {
|
||||||
.collect::<Result<Box<[_]>>>()?
|
.collect::<Result<Box<[_]>>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.chain((night > 0).then(|| ActionPrompt::WolfPackKill {
|
.chain(village.wolf_pack_kill())
|
||||||
marked: None,
|
|
||||||
living_villagers: village.living_villagers(),
|
|
||||||
}))
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
action_queue.sort_by(|left_prompt, right_prompt| {
|
action_queue.sort_by(|left_prompt, right_prompt| {
|
||||||
left_prompt
|
left_prompt
|
||||||
|
|
@ -388,7 +385,7 @@ impl Night {
|
||||||
NightChange::HunterTarget { source, target } => {
|
NightChange::HunterTarget { source, target } => {
|
||||||
let hunter_character = new_village.character_by_id_mut(*source).unwrap();
|
let hunter_character = new_village.character_by_id_mut(*source).unwrap();
|
||||||
hunter_character.hunter_mut()?.replace(*target);
|
hunter_character.hunter_mut()?.replace(*target);
|
||||||
if changes.killed(source).is_some()
|
if changes.killed(*source).is_some()
|
||||||
&& changes.protected(source).is_none()
|
&& changes.protected(source).is_none()
|
||||||
&& changes.protected(target).is_none()
|
&& changes.protected(target).is_none()
|
||||||
{
|
{
|
||||||
|
|
@ -404,7 +401,7 @@ impl Night {
|
||||||
NightChange::Kill { target, died_to } => {
|
NightChange::Kill { target, died_to } => {
|
||||||
if let Some(kill) = kill::resolve_kill(
|
if let Some(kill) = kill::resolve_kill(
|
||||||
&mut changes,
|
&mut changes,
|
||||||
target,
|
*target,
|
||||||
died_to,
|
died_to,
|
||||||
self.night,
|
self.night,
|
||||||
&self.village,
|
&self.village,
|
||||||
|
|
@ -460,6 +457,15 @@ impl Night {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for knight in new_village
|
||||||
|
.characters_mut()
|
||||||
|
.into_iter()
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
if new_village.is_game_over().is_none() {
|
if new_village.is_game_over().is_none() {
|
||||||
new_village.to_day()?;
|
new_village.to_day()?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ impl SetupRoleTitle {
|
||||||
SetupRoleTitle::Empath => Role::Empath { cursed: false },
|
SetupRoleTitle::Empath => Role::Empath { cursed: false },
|
||||||
SetupRoleTitle::Vindicator => Role::Vindicator,
|
SetupRoleTitle::Vindicator => Role::Vindicator,
|
||||||
SetupRoleTitle::Diseased => Role::Diseased,
|
SetupRoleTitle::Diseased => Role::Diseased,
|
||||||
SetupRoleTitle::BlackKnight => Role::BlackKnight { attacked: false },
|
SetupRoleTitle::BlackKnight => Role::BlackKnight { attacked: None },
|
||||||
SetupRoleTitle::Weightlifter => Role::Weightlifter,
|
SetupRoleTitle::Weightlifter => Role::Weightlifter,
|
||||||
SetupRoleTitle::PyreMaster => Role::PyreMaster {
|
SetupRoleTitle::PyreMaster => Role::PyreMaster {
|
||||||
villagers_killed: 0,
|
villagers_killed: 0,
|
||||||
|
|
@ -272,7 +272,7 @@ impl SetupRole {
|
||||||
SetupRole::Empath => Role::Empath { cursed: false },
|
SetupRole::Empath => Role::Empath { cursed: false },
|
||||||
SetupRole::Vindicator => Role::Vindicator,
|
SetupRole::Vindicator => Role::Vindicator,
|
||||||
SetupRole::Diseased => Role::Diseased,
|
SetupRole::Diseased => Role::Diseased,
|
||||||
SetupRole::BlackKnight => Role::BlackKnight { attacked: false },
|
SetupRole::BlackKnight => Role::BlackKnight { attacked: None },
|
||||||
SetupRole::Weightlifter => Role::Weightlifter,
|
SetupRole::Weightlifter => Role::Weightlifter,
|
||||||
SetupRole::PyreMaster => Role::PyreMaster {
|
SetupRole::PyreMaster => Role::PyreMaster {
|
||||||
villagers_killed: 0,
|
villagers_killed: 0,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::{DateTime, GameOver, GameSettings},
|
game::{DateTime, GameOver, GameSettings},
|
||||||
message::{CharacterIdentity, Identification},
|
message::{CharacterIdentity, Identification, night::ActionPrompt},
|
||||||
player::PlayerId,
|
player::PlayerId,
|
||||||
role::{Role, RoleTitle},
|
role::{Role, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
@ -59,6 +59,27 @@ impl Village {
|
||||||
Some(wolves[rand::random_range(0..wolves.len())])
|
Some(wolves[rand::random_range(0..wolves.len())])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn wolf_pack_kill(&self) -> Option<ActionPrompt> {
|
||||||
|
let night = match self.date_time {
|
||||||
|
DateTime::Day { .. } => return None,
|
||||||
|
DateTime::Night { number } => number,
|
||||||
|
};
|
||||||
|
let no_kill_due_to_disease = self
|
||||||
|
.characters
|
||||||
|
.iter()
|
||||||
|
.filter(|d| matches!(d.role_title(), RoleTitle::Diseased))
|
||||||
|
.any(|d| match d.died_to() {
|
||||||
|
Some(DiedTo::Wolfpack {
|
||||||
|
night: diseased_death_night,
|
||||||
|
..
|
||||||
|
}) => (diseased_death_night.get() + 1) == night,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
(night > 0 && !no_kill_due_to_disease).then_some(ActionPrompt::WolfPackKill {
|
||||||
|
marked: None,
|
||||||
|
living_villagers: self.living_villagers(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn date_time(&self) -> DateTime {
|
pub const fn date_time(&self) -> DateTime {
|
||||||
self.date_time
|
self.date_time
|
||||||
|
|
@ -222,10 +243,21 @@ impl Village {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn living_characters_by_role_mut(&mut self, role: RoleTitle) -> Box<[&mut Character]> {
|
||||||
|
self.characters
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|c| c.role_title() == role)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn characters(&self) -> Box<[Character]> {
|
pub fn characters(&self) -> Box<[Character]> {
|
||||||
self.characters.iter().cloned().collect()
|
self.characters.iter().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn characters_mut(&mut self) -> Box<[&mut Character]> {
|
||||||
|
self.characters.iter_mut().collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn character_by_id_mut(&mut self, character_id: CharacterId) -> Result<&mut Character> {
|
pub fn character_by_id_mut(&mut self, character_id: CharacterId) -> Result<&mut Character> {
|
||||||
self.characters
|
self.characters
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
@ -287,7 +319,7 @@ impl RoleTitle {
|
||||||
RoleTitle::Empath => Role::Empath { cursed: false },
|
RoleTitle::Empath => Role::Empath { cursed: false },
|
||||||
RoleTitle::Vindicator => Role::Vindicator,
|
RoleTitle::Vindicator => Role::Vindicator,
|
||||||
RoleTitle::Diseased => Role::Diseased,
|
RoleTitle::Diseased => Role::Diseased,
|
||||||
RoleTitle::BlackKnight => Role::BlackKnight { attacked: false },
|
RoleTitle::BlackKnight => Role::BlackKnight { attacked: None },
|
||||||
RoleTitle::Weightlifter => Role::Weightlifter,
|
RoleTitle::Weightlifter => Role::Weightlifter,
|
||||||
RoleTitle::PyreMaster => Role::PyreMaster {
|
RoleTitle::PyreMaster => Role::PyreMaster {
|
||||||
villagers_killed: 0,
|
villagers_killed: 0,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
#[allow(unused)]
|
||||||
|
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diedto::DiedTo,
|
||||||
|
game::{Game, GameSettings, OrRandom, SetupRole},
|
||||||
|
game_test::{
|
||||||
|
ActionPromptTitleExt, ActionResultExt, AlignmentExt, GameExt, ServerToHostMessageExt,
|
||||||
|
SettingsExt, gen_players,
|
||||||
|
},
|
||||||
|
message::{
|
||||||
|
host::{HostDayMessage, HostGameMessage},
|
||||||
|
night::{ActionPrompt, ActionPromptTitle, ActionResult},
|
||||||
|
},
|
||||||
|
role::Role,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dies_day_after() {
|
||||||
|
let players = gen_players(1..10);
|
||||||
|
let black_knight = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::BlackKnight, black_knight);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
|
||||||
|
settings.fill_remaining_slots_with_villagers(9);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
game.mark(game.character_by_player_id(black_knight).character_id());
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
assert_eq!(game.character_by_player_id(black_knight).died_to(), None);
|
||||||
|
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
game.mark(game.villager_character_ids().into_iter().next().unwrap());
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
game.character_by_player_id(black_knight).died_to().cloned(),
|
||||||
|
Some(DiedTo::Wolfpack {
|
||||||
|
killing_wolf: game.character_by_player_id(wolf_player_id).character_id(),
|
||||||
|
night: NonZeroU8::new(2).unwrap()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn you_can_just_keep_whacking_it() {
|
||||||
|
let players = gen_players(1..10);
|
||||||
|
let black_knight = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::BlackKnight, black_knight);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
|
||||||
|
settings.fill_remaining_slots_with_villagers(9);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
game.mark(game.character_by_player_id(black_knight).character_id());
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
for _ in 0..100 {
|
||||||
|
assert_eq!(game.character_by_player_id(black_knight).died_to(), None);
|
||||||
|
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
game.mark(game.character_by_player_id(black_knight).character_id());
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
#[allow(unused)]
|
||||||
|
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diedto::DiedTo,
|
||||||
|
game::{Game, GameSettings, OrRandom, SetupRole},
|
||||||
|
game_test::{
|
||||||
|
ActionPromptTitleExt, ActionResultExt, AlignmentExt, GameExt, ServerToHostMessageExt,
|
||||||
|
SettingsExt, gen_players,
|
||||||
|
},
|
||||||
|
message::{
|
||||||
|
host::{HostDayMessage, HostGameMessage},
|
||||||
|
night::{ActionPrompt, ActionPromptTitle, ActionResult},
|
||||||
|
},
|
||||||
|
role::Role,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wolf_kill_prevented() {
|
||||||
|
let players = gen_players(1..10);
|
||||||
|
let diseased_player_id = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::Diseased, diseased_player_id);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
|
||||||
|
settings.fill_remaining_slots_with_villagers(9);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
game.mark(
|
||||||
|
game.character_by_player_id(diseased_player_id)
|
||||||
|
.character_id(),
|
||||||
|
);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
game.character_by_player_id(diseased_player_id)
|
||||||
|
.died_to()
|
||||||
|
.cloned(),
|
||||||
|
Some(DiedTo::Wolfpack {
|
||||||
|
killing_wolf: game.character_by_player_id(wolf_player_id).character_id(),
|
||||||
|
night: NonZeroU8::new(1).unwrap()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
game.process(HostGameMessage::Day(HostDayMessage::Execute))
|
||||||
|
.unwrap()
|
||||||
|
.prompt(),
|
||||||
|
ActionPrompt::CoverOfDarkness
|
||||||
|
);
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
game.next_expect_day();
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
let target = game.living_villager_excl(wolf_player_id);
|
||||||
|
game.mark(target.character_id());
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
assert_eq!(
|
||||||
|
game.character_by_player_id(target.player_id())
|
||||||
|
.died_to()
|
||||||
|
.cloned(),
|
||||||
|
Some(DiedTo::Wolfpack {
|
||||||
|
killing_wolf: game.character_by_player_id(wolf_player_id).character_id(),
|
||||||
|
night: NonZeroU8::new(3).unwrap()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn elder_doesnt_die_first_try_night_doesnt_know() {
|
fn doesnt_die_first_try_night_doesnt_know() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let elder_player_id = players[0].player_id;
|
let elder_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[2].player_id;
|
let wolf_player_id = players[2].player_id;
|
||||||
|
|
@ -62,7 +62,7 @@ fn elder_doesnt_die_first_try_night_doesnt_know() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn elder_doesnt_die_first_try_night_knows() {
|
fn doesnt_die_first_try_night_knows() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let elder_player_id = players[0].player_id;
|
let elder_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[2].player_id;
|
let wolf_player_id = players[2].player_id;
|
||||||
|
|
@ -121,7 +121,7 @@ fn elder_doesnt_die_first_try_night_knows() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn elder_executed_doesnt_know() {
|
fn executed_doesnt_know() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let elder_player_id = players[0].player_id;
|
let elder_player_id = players[0].player_id;
|
||||||
let seer_player_id = players[1].player_id;
|
let seer_player_id = players[1].player_id;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empath_nothing_on_wolf() {
|
fn nothing_on_wolf() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let empath_player_id = players[0].player_id;
|
let empath_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[1].player_id;
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
@ -44,7 +44,7 @@ fn empath_nothing_on_wolf() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empath_takes_on_scapegoats_curse() {
|
fn takes_on_scapegoats_curse() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let empath_player_id = players[0].player_id;
|
let empath_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[1].player_id;
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mason_recruits_decrement() {
|
fn recruits_decrement() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let mason_leader_player_id = players[0].player_id;
|
let mason_leader_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[1].player_id;
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
mod beholder;
|
mod beholder;
|
||||||
|
mod black_knight;
|
||||||
|
mod diseased;
|
||||||
mod elder;
|
mod elder;
|
||||||
mod empath;
|
mod empath;
|
||||||
mod mason;
|
mod mason;
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ fn redeemed_scapegoat_role_changes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn redeemed_scapegoat_cannot_redeem_into_wolf() {
|
fn cannot_redeem_into_wolf() {
|
||||||
let players = gen_players(1..10);
|
let players = gen_players(1..10);
|
||||||
let scapegoat_player_id = players[0].player_id;
|
let scapegoat_player_id = players[0].player_id;
|
||||||
let wolf_player_id = players[1].player_id;
|
let wolf_player_id = players[1].player_id;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use werewolves_macros::{ChecksAs, Titles};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
|
diedto::DiedTo,
|
||||||
game::{DateTime, Village},
|
game::{DateTime, Village},
|
||||||
message::CharacterIdentity,
|
message::CharacterIdentity,
|
||||||
};
|
};
|
||||||
|
|
@ -63,7 +64,7 @@ pub enum Role {
|
||||||
#[checks(Alignment::Village)]
|
#[checks(Alignment::Village)]
|
||||||
#[checks("powerful")]
|
#[checks("powerful")]
|
||||||
#[checks("is_mentor")]
|
#[checks("is_mentor")]
|
||||||
BlackKnight { attacked: bool },
|
BlackKnight { attacked: Option<DiedTo> },
|
||||||
#[checks(Alignment::Village)]
|
#[checks(Alignment::Village)]
|
||||||
#[checks("powerful")]
|
#[checks("powerful")]
|
||||||
#[checks("is_mentor")]
|
#[checks("is_mentor")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue