werewolves/werewolves-proto/src/game_test/mod.rs

610 lines
18 KiB
Rust
Raw Normal View History

mod night_order;
use crate::{
error::GameError,
game::{Game, GameSettings},
message::{
CharacterState, Identification, PublicIdentity,
host::{
HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage,
ServerToHostMessageTitle,
},
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult},
},
player::{CharacterId, PlayerId},
role::RoleTitle,
};
use colored::Colorize;
use core::{num::NonZeroU8, ops::Range};
#[allow(unused)]
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
use std::io::Write;
trait ServerToHostMessageExt {
fn prompt(self) -> ActionPrompt;
fn result(self) -> ActionResult;
}
impl ServerToHostMessageExt for ServerToHostMessage {
fn prompt(self) -> ActionPrompt {
match self {
Self::ActionPrompt(prompt) => prompt,
Self::Daytime {
characters: _,
marked: _,
day: _,
} => panic!("{}", "[got daytime]".bold().red()),
msg => panic!("expected server message <<{msg:?}>> to be an ActionPrompt"),
}
}
fn result(self) -> ActionResult {
match self {
Self::ActionResult(_, res) => res,
msg => panic!("expected server message <<{msg:?}>> to be an ActionResult"),
}
}
}
trait GameExt {
fn next(&mut self) -> ActionPrompt;
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
fn response(&mut self, resp: ActionResponse) -> ActionResult;
fn execute(&mut self) -> ActionPrompt;
fn mark_for_execution(
&mut self,
target: CharacterId,
) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
}
impl GameExt for Game {
fn next(&mut self) -> ActionPrompt {
self.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
.prompt()
}
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
match self
.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
{
ServerToHostMessage::Daytime {
characters,
marked,
day,
} => (characters, marked, day),
res => panic!("unexpected response to next_expect_day: {res:?}"),
}
}
fn response(&mut self, resp: ActionResponse) -> ActionResult {
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
resp,
)))
.unwrap()
.result()
}
fn mark_for_execution(
&mut self,
target: CharacterId,
) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
match self
.process(HostGameMessage::Day(HostDayMessage::MarkForExecution(
target,
)))
.unwrap()
{
ServerToHostMessage::Daytime {
characters,
marked,
day,
} => (characters, marked, day),
res => panic!("unexpected response to mark_for_execution: {res:?}"),
}
}
fn execute(&mut self) -> ActionPrompt {
self.process(HostGameMessage::Day(HostDayMessage::Execute))
.unwrap()
.prompt()
}
}
fn init_log() {
let _ = pretty_env_logger::formatted_builder()
.filter_level(log::LevelFilter::Debug)
.format(|f, record| match record.file() {
Some(file) => {
let file = format!(
"[{file}{}]",
record
.line()
.map(|l| format!(":{l}"))
.unwrap_or_else(String::new),
)
.dimmed();
let level = match record.level() {
log::Level::Error => "[err]".red().bold(),
log::Level::Warn => "[warn]".yellow().bold(),
log::Level::Info => "[info]".white().bold(),
log::Level::Debug => "[debug]".dimmed().bold(),
log::Level::Trace => "[trace]".dimmed(),
};
let args = record.args();
let arrow = "".bold().magenta();
writeln!(f, "{file}\n{level} {arrow} {args}")
}
_ => writeln!(f, "[{}] {}", record.level(), record.args()),
})
.is_test(true)
.try_init();
}
fn gen_players(range: Range<u8>) -> Box<[Identification]> {
range
.into_iter()
.map(|num| Identification {
player_id: PlayerId::from_u128(num as _),
public: PublicIdentity {
name: format!("player {num}"),
pronouns: None,
number: NonZeroU8::new(num),
},
})
.collect()
}
#[test]
fn starts_with_wolf_intro() {
let players = gen_players(1..10);
let settings = GameSettings::default();
let mut game = Game::new(&players, settings).unwrap();
let resp = game.process(HostGameMessage::GetState).unwrap();
assert_eq!(
resp,
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness)
)
}
#[test]
fn no_wolf_kill_n1() {
let players = gen_players(1..10);
let mut settings = GameSettings::default();
settings.add(RoleTitle::Shapeshifter).unwrap();
settings.sub(RoleTitle::Werewolf);
settings.add(RoleTitle::Protector).unwrap();
let mut game = Game::new(&players, settings).unwrap();
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
));
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolvesIntroAck
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::Daytime {
characters: _,
marked: _,
day: _,
}
));
}
#[test]
fn yes_wolf_kill_n2() {
let players = gen_players(1..10);
let settings = GameSettings::default();
let mut game = Game::new(&players, settings).unwrap();
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
));
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolvesIntroAck
)))
.unwrap()
.result(),
ActionResult::GoBackToSleep,
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::Daytime {
characters: _,
marked: _,
day: _,
}
));
let execution_target = game
.village()
.characters()
.into_iter()
.find(|v| v.is_village())
.unwrap()
.character_id()
.clone();
match game
.process(HostGameMessage::Day(HostDayMessage::MarkForExecution(
execution_target.clone(),
)))
.unwrap()
{
ServerToHostMessage::Daytime {
characters: _,
marked,
day: _,
} => assert_eq!(marked.to_vec(), vec![execution_target]),
resp => panic!("unexpected server message: {resp:#?}"),
}
assert_eq!(
game.process(HostGameMessage::Day(HostDayMessage::Execute))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness)
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::WolfPackKill {
living_villagers: _
})
));
}
#[test]
fn protect_stops_shapeshift() {
init_log();
let players = gen_players(1..10);
let mut settings = GameSettings::default();
settings.add(RoleTitle::Shapeshifter).unwrap();
settings.sub(RoleTitle::Werewolf);
settings.add(RoleTitle::Protector).unwrap();
let mut game = Game::new(&players, settings).unwrap();
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
));
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolvesIntroAck
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::Daytime {
characters: _,
marked: _,
day: _,
}
));
let execution_target = game
.village()
.characters()
.into_iter()
.find(|v| v.is_village() && !matches!(v.role().title(), RoleTitle::Protector))
.unwrap()
.character_id()
.clone();
match game
.process(HostGameMessage::Day(HostDayMessage::MarkForExecution(
execution_target.clone(),
)))
.unwrap()
{
ServerToHostMessage::Daytime {
characters: _,
marked,
day: _,
} => assert_eq!(marked.to_vec(), vec![execution_target]),
resp => panic!("unexpected server message: {resp:#?}"),
}
assert_eq!(
game.process(HostGameMessage::Day(HostDayMessage::Execute))
.unwrap()
.prompt()
.title(),
ActionPromptTitle::CoverOfDarkness
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap()
.result(),
ActionResult::Continue
);
let (prot_and_wolf_target, prot_char_id) = match game
.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
{
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
character_id: prot_char_id,
targets,
}) => (
targets
.into_iter()
.map(|c| game.village().character_by_id(&c.character_id).unwrap())
.find(|c| c.is_village())
.unwrap()
.character_id()
.clone(),
prot_char_id,
),
_ => panic!("first n2 prompt isn't protector"),
};
let target = game
.village()
.character_by_id(&prot_and_wolf_target)
.unwrap()
.clone();
log::info!("target: {target:#?}");
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::Protector(prot_and_wolf_target.clone())
)))
.unwrap()
.result(),
ActionResult::GoBackToSleep,
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
.prompt()
.title(),
ActionPromptTitle::WolfPackKill
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolfPackKillVote(prot_and_wolf_target.clone())
)))
.unwrap()
.result(),
ActionResult::Continue,
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
.prompt()
.title(),
ActionPromptTitle::Shapeshifter,
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::Shapeshifter(true)
)))
.unwrap()
.result(),
ActionResult::GoBackToSleep,
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
.title(),
ServerToHostMessageTitle::Daytime,
);
let target = game
.village()
.character_by_id(target.character_id())
.unwrap();
assert!(target.is_village());
assert!(target.alive());
let prot = game
.village()
.character_by_id(&prot_char_id.character_id)
.unwrap();
assert!(prot.is_village());
assert!(prot.alive());
assert_eq!(prot.role().title(), RoleTitle::Protector);
}
#[test]
fn wolfpack_kill_all_targets_valid() {
init_log();
let players = gen_players(1..10);
let mut settings = GameSettings::default();
settings.add(RoleTitle::Shapeshifter).unwrap();
settings.sub(RoleTitle::Werewolf);
let mut game = Game::new(&players, settings).unwrap();
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
));
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolvesIntroAck
)))
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::Daytime {
characters: _,
marked: _,
day: _,
}
));
let execution_target = game
.village()
.characters()
.into_iter()
.find(|v| v.is_village() && !matches!(v.role().title(), RoleTitle::Protector))
.unwrap()
.character_id()
.clone();
match game
.process(HostGameMessage::Day(HostDayMessage::MarkForExecution(
execution_target.clone(),
)))
.unwrap()
{
ServerToHostMessage::Daytime {
characters: _,
marked,
day: _,
} => assert_eq!(marked.to_vec(), vec![execution_target]),
resp => panic!("unexpected server message: {resp:#?}"),
}
assert_eq!(
game.process(HostGameMessage::Day(HostDayMessage::Execute))
.unwrap()
.prompt()
.title(),
ActionPromptTitle::CoverOfDarkness
);
assert_eq!(
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::ClearCoverOfDarkness
)))
.unwrap()
.result(),
ActionResult::Continue
);
let living_villagers = match game
.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap()
.prompt()
{
ActionPrompt::WolfPackKill { living_villagers } => living_villagers,
_ => panic!("not wolf pack kill"),
};
for (idx, target) in living_villagers.into_iter().enumerate() {
let mut attempt = game.clone();
if let ServerToHostMessage::Error(GameError::InvalidTarget) = attempt
.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::WolfPackKillVote(target.character_id.clone()),
)))
.unwrap()
{
panic!("invalid target {target:?} at index [{idx}]");
}
}
}
#[test]
fn only_1_shapeshift_prompt_if_first_shifts() {
let players = gen_players(1..10);
let mut settings = GameSettings::default();
settings.add(RoleTitle::Shapeshifter).unwrap();
settings.add(RoleTitle::Shapeshifter).unwrap();
settings.sub(RoleTitle::Werewolf);
let mut game = Game::new(&players, settings).unwrap();
assert_eq!(
game.response(ActionResponse::ClearCoverOfDarkness),
ActionResult::Continue
);
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
assert_eq!(
game.response(ActionResponse::WolvesIntroAck),
ActionResult::GoBackToSleep
);
game.next_expect_day();
let target = game
.village()
.characters()
.into_iter()
.find_map(|c| c.is_village().then_some(c.character_id().clone()))
.unwrap();
let (_, marked, _) = game.mark_for_execution(target.clone());
let (marked, target_list): (&[CharacterId], &[CharacterId]) = (&marked, &[target]);
assert_eq!(target_list, marked);
assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness);
assert_eq!(
game.response(ActionResponse::ClearCoverOfDarkness),
ActionResult::Continue
);
assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill);
let target = game
.village()
.characters()
.into_iter()
.find_map(|c| (c.is_village() && c.alive()).then_some(c.character_id().clone()))
.unwrap();
assert_eq!(
game.response(ActionResponse::WolfPackKillVote(target)),
ActionResult::Continue,
);
assert_eq!(game.next().title(), ActionPromptTitle::Shapeshifter);
assert_eq!(
game.response(ActionResponse::Shapeshifter(true)),
ActionResult::Continue,
);
assert_eq!(game.next().title(), ActionPromptTitle::RoleChange);
assert_eq!(
game.response(ActionResponse::RoleChangeAck),
ActionResult::GoBackToSleep
);
game.next_expect_day();
}