fix wolves sleeping inbetween individual wolves
improved identity for prompts night order improvements started adding tests for game
This commit is contained in:
parent
d352cfb1ee
commit
d90f4ec6fe
|
|
@ -2427,8 +2427,10 @@ dependencies = [
|
|||
name = "werewolves-proto"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"pretty_env_logger",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@ werewolves-macros = { path = "../werewolves-macros" }
|
|||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { version = "1" }
|
||||
pretty_env_logger = { version = "0.5" }
|
||||
colored = { version = "3.0" }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use crate::{
|
|||
message::{
|
||||
CharacterState, Identification,
|
||||
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||
night::ActionResponse,
|
||||
},
|
||||
player::CharacterId,
|
||||
};
|
||||
|
|
@ -107,18 +108,15 @@ impl Game {
|
|||
}
|
||||
(GameState::Night { night }, HostGameMessage::GetState) => {
|
||||
if let Some(res) = night.current_result() {
|
||||
let char = night.current_character().unwrap();
|
||||
return Ok(ServerToHostMessage::ActionResult(
|
||||
char.public_identity().clone(),
|
||||
night
|
||||
.current_character()
|
||||
.map(|c| c.public_identity().clone()),
|
||||
res.clone(),
|
||||
));
|
||||
}
|
||||
if let Some(prompt) = night.current_prompt() {
|
||||
let char = night.current_character().unwrap();
|
||||
return Ok(ServerToHostMessage::ActionPrompt(
|
||||
char.public_identity().clone(),
|
||||
prompt.clone(),
|
||||
));
|
||||
return Ok(ServerToHostMessage::ActionPrompt(prompt.clone()));
|
||||
}
|
||||
match night.next() {
|
||||
Ok(_) => self.process(HostGameMessage::GetState),
|
||||
|
|
@ -140,7 +138,9 @@ impl Game {
|
|||
HostGameMessage::Night(HostNightMessage::ActionResponse(resp)),
|
||||
) => match night.received_response(resp.clone()) {
|
||||
Ok(res) => Ok(ServerToHostMessage::ActionResult(
|
||||
night.current_character().unwrap().public_identity().clone(),
|
||||
night
|
||||
.current_character()
|
||||
.map(|c| c.public_identity().clone()),
|
||||
res,
|
||||
)),
|
||||
Err(GameError::NightNeedsNext) => match night.next() {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -16,10 +16,10 @@ impl Default for GameSettings {
|
|||
Self {
|
||||
roles: [
|
||||
(RoleTitle::Werewolf, NonZeroU8::new(1).unwrap()),
|
||||
(RoleTitle::Seer, NonZeroU8::new(1).unwrap()),
|
||||
// (RoleTitle::Seer, NonZeroU8::new(1).unwrap()),
|
||||
// (RoleTitle::Militia, NonZeroU8::new(1).unwrap()),
|
||||
// (RoleTitle::Guardian, NonZeroU8::new(1).unwrap()),
|
||||
(RoleTitle::Apprentice, NonZeroU8::new(1).unwrap()),
|
||||
// (RoleTitle::Apprentice, NonZeroU8::new(1).unwrap()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,614 @@
|
|||
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 get_state(&mut self) -> ServerToHostMessage;
|
||||
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 get_state(&mut self) -> ServerToHostMessage {
|
||||
self.process(HostGameMessage::GetState).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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).unwrap(),
|
||||
},
|
||||
})
|
||||
.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();
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
use core::num::NonZeroU8;
|
||||
#[allow(unused)]
|
||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||
|
||||
use crate::{
|
||||
message::{
|
||||
CharacterIdentity, PublicIdentity,
|
||||
night::{ActionPrompt, ActionPromptTitle},
|
||||
},
|
||||
player::CharacterId,
|
||||
};
|
||||
|
||||
fn character_identity() -> CharacterIdentity {
|
||||
CharacterIdentity {
|
||||
character_id: CharacterId::new(),
|
||||
public: PublicIdentity {
|
||||
name: String::new(),
|
||||
pronouns: None,
|
||||
number: NonZeroU8::new(1).unwrap(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn night_order() {
|
||||
let test_cases: &[(&[ActionPrompt], &[ActionPromptTitle])] = &[(
|
||||
&[
|
||||
ActionPrompt::CoverOfDarkness,
|
||||
ActionPrompt::WolvesIntro {
|
||||
wolves: Box::new([]),
|
||||
},
|
||||
ActionPrompt::Shapeshifter {
|
||||
character_id: character_identity(),
|
||||
},
|
||||
ActionPrompt::WolfPackKill {
|
||||
living_villagers: Box::new([]),
|
||||
},
|
||||
ActionPrompt::Protector {
|
||||
character_id: character_identity(),
|
||||
targets: Box::new([]),
|
||||
},
|
||||
],
|
||||
&[
|
||||
ActionPromptTitle::CoverOfDarkness,
|
||||
ActionPromptTitle::Protector,
|
||||
ActionPromptTitle::WolvesIntro,
|
||||
ActionPromptTitle::WolfPackKill,
|
||||
ActionPromptTitle::Shapeshifter,
|
||||
],
|
||||
)];
|
||||
|
||||
for (input, expect) in test_cases {
|
||||
let mut prompts = input.to_vec();
|
||||
prompts.sort_by(|left_prompt, right_prompt| {
|
||||
left_prompt
|
||||
.partial_cmp(right_prompt)
|
||||
.unwrap_or(core::cmp::Ordering::Equal)
|
||||
});
|
||||
let actual = prompts.into_iter().map(|p| p.title()).collect::<Box<[_]>>();
|
||||
let actual: &[ActionPromptTitle] = &actual;
|
||||
assert_eq!(*expect, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ use thiserror::Error;
|
|||
pub mod diedto;
|
||||
pub mod error;
|
||||
pub mod game;
|
||||
#[cfg(test)]
|
||||
mod game_test;
|
||||
pub mod message;
|
||||
pub mod modifier;
|
||||
pub mod nonzero;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use core::num::NonZeroU8;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use werewolves_macros::Extract;
|
||||
|
||||
use crate::{
|
||||
error::GameError,
|
||||
|
|
@ -44,6 +45,12 @@ pub enum HostDayMessage {
|
|||
MarkForExecution(CharacterId),
|
||||
}
|
||||
|
||||
impl From<HostDayMessage> for HostGameMessage {
|
||||
fn from(value: HostDayMessage) -> Self {
|
||||
HostGameMessage::Day(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum HostLobbyMessage {
|
||||
GetState,
|
||||
|
|
@ -54,6 +61,7 @@ pub enum HostLobbyMessage {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(werewolves_macros::Titles))]
|
||||
pub enum ServerToHostMessage {
|
||||
Disconnect,
|
||||
Daytime {
|
||||
|
|
@ -61,8 +69,8 @@ pub enum ServerToHostMessage {
|
|||
marked: Box<[CharacterId]>,
|
||||
day: NonZeroU8,
|
||||
},
|
||||
ActionPrompt(PublicIdentity, ActionPrompt),
|
||||
ActionResult(PublicIdentity, ActionResult),
|
||||
ActionPrompt(ActionPrompt),
|
||||
ActionResult(Option<PublicIdentity>, ActionResult),
|
||||
Lobby(Box<[PlayerState]>),
|
||||
GameSettings(GameSettings),
|
||||
Error(GameError),
|
||||
|
|
@ -71,5 +79,4 @@ pub enum ServerToHostMessage {
|
|||
ackd: Box<[Target]>,
|
||||
waiting: Box<[Target]>,
|
||||
},
|
||||
CoverOfDarkness,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,21 @@ pub struct PublicIdentity {
|
|||
pub number: NonZeroU8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CharacterIdentity {
|
||||
pub character_id: CharacterId,
|
||||
pub public: PublicIdentity,
|
||||
}
|
||||
|
||||
impl CharacterIdentity {
|
||||
pub const fn new(character_id: CharacterId, public: PublicIdentity) -> Self {
|
||||
Self {
|
||||
character_id,
|
||||
public,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PublicIdentity {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -1,107 +1,125 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use werewolves_macros::ChecksAs;
|
||||
use werewolves_macros::{ChecksAs, Titles};
|
||||
|
||||
use crate::{
|
||||
diedto::DiedTo,
|
||||
message::PublicIdentity,
|
||||
message::CharacterIdentity,
|
||||
player::CharacterId,
|
||||
role::{Alignment, PreviousGuardianAction, Role, RoleTitle},
|
||||
role::{Alignment, PreviousGuardianAction, RoleTitle},
|
||||
};
|
||||
|
||||
use super::Target;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||
pub enum ActionType {
|
||||
Protect = 0,
|
||||
WolfPackKill = 1,
|
||||
Direwolf = 2,
|
||||
Wolf = 3,
|
||||
Block = 4,
|
||||
Other = 5,
|
||||
RoleChange = 6,
|
||||
Cover,
|
||||
WolvesIntro,
|
||||
Protect,
|
||||
WolfPackKill,
|
||||
Direwolf,
|
||||
OtherWolf,
|
||||
Block,
|
||||
Other,
|
||||
RoleChange,
|
||||
}
|
||||
|
||||
impl PartialOrd for ActionType {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
(*self as u8).partial_cmp(&(*other as u8))
|
||||
impl ActionType {
|
||||
const fn is_wolfy(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ActionType::Direwolf
|
||||
| ActionType::OtherWolf
|
||||
| ActionType::WolfPackKill
|
||||
| ActionType::WolvesIntro
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ChecksAs)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ChecksAs, Titles)]
|
||||
pub enum ActionPrompt {
|
||||
#[checks(ActionType::Cover)]
|
||||
CoverOfDarkness,
|
||||
#[checks(ActionType::WolfPackKill)]
|
||||
#[checks]
|
||||
WolvesIntro { wolves: Box<[(Target, RoleTitle)]> },
|
||||
#[checks(ActionType::RoleChange)]
|
||||
RoleChange { new_role: RoleTitle },
|
||||
RoleChange {
|
||||
character_id: CharacterIdentity,
|
||||
new_role: RoleTitle,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
Seer { living_players: Box<[Target]> },
|
||||
Seer {
|
||||
character_id: CharacterIdentity,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Protect)]
|
||||
Protector { targets: Box<[Target]> },
|
||||
Protector {
|
||||
character_id: CharacterIdentity,
|
||||
targets: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
Arcanist { living_players: Box<[Target]> },
|
||||
Arcanist {
|
||||
character_id: CharacterIdentity,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
Gravedigger { dead_players: Box<[Target]> },
|
||||
Gravedigger {
|
||||
character_id: CharacterIdentity,
|
||||
dead_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
Hunter {
|
||||
character_id: CharacterIdentity,
|
||||
current_target: Option<Target>,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
Militia { living_players: Box<[Target]> },
|
||||
Militia {
|
||||
character_id: CharacterIdentity,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Other)]
|
||||
MapleWolf {
|
||||
character_id: CharacterIdentity,
|
||||
kill_or_die: bool,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Protect)]
|
||||
Guardian {
|
||||
character_id: CharacterIdentity,
|
||||
previous: Option<PreviousGuardianAction>,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Wolf)]
|
||||
#[checks(ActionType::WolfPackKill)]
|
||||
WolfPackKill { living_villagers: Box<[Target]> },
|
||||
#[checks(ActionType::Wolf)]
|
||||
Shapeshifter,
|
||||
#[checks(ActionType::Wolf)]
|
||||
AlphaWolf { living_villagers: Box<[Target]> },
|
||||
#[checks(ActionType::OtherWolf)]
|
||||
Shapeshifter { character_id: CharacterIdentity },
|
||||
#[checks(ActionType::OtherWolf)]
|
||||
AlphaWolf {
|
||||
character_id: CharacterIdentity,
|
||||
living_villagers: Box<[Target]>,
|
||||
},
|
||||
#[checks(ActionType::Direwolf)]
|
||||
DireWolf { living_players: Box<[Target]> },
|
||||
DireWolf {
|
||||
character_id: CharacterIdentity,
|
||||
living_players: Box<[Target]>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ActionPrompt {
|
||||
pub const fn is_wolfy(&self) -> bool {
|
||||
self.action_type().is_wolfy()
|
||||
|| match self {
|
||||
ActionPrompt::RoleChange {
|
||||
character_id: _,
|
||||
new_role,
|
||||
} => new_role.wolf(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ActionPrompt {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
// fn ordering_num(prompt: &ActionPrompt) -> u8 {
|
||||
// match prompt {
|
||||
// ActionPrompt::WolvesIntro { wolves: _ } => 0,
|
||||
// ActionPrompt::Guardian {
|
||||
// living_players: _,
|
||||
// previous: _,
|
||||
// }
|
||||
// | ActionPrompt::Protector { targets: _ } => 1,
|
||||
// ActionPrompt::WolfPackKill {
|
||||
// living_villagers: _,
|
||||
// } => 2,
|
||||
// ActionPrompt::Shapeshifter => 3,
|
||||
// ActionPrompt::AlphaWolf {
|
||||
// living_villagers: _,
|
||||
// } => 4,
|
||||
// ActionPrompt::DireWolf { living_players: _ } => 5,
|
||||
// ActionPrompt::Seer { living_players: _ }
|
||||
// | ActionPrompt::Arcanist { living_players: _ }
|
||||
// | ActionPrompt::Gravedigger { dead_players: _ }
|
||||
// | ActionPrompt::Hunter {
|
||||
// current_target: _,
|
||||
// living_players: _,
|
||||
// }
|
||||
// | ActionPrompt::Militia { living_players: _ }
|
||||
// | ActionPrompt::MapleWolf {
|
||||
// kill_or_die: _,
|
||||
// living_players: _,
|
||||
// }
|
||||
// | ActionPrompt::RoleChange { new_role: _ } => 0xFF,
|
||||
// }
|
||||
// }
|
||||
// ordering_num(self).partial_cmp(&ordering_num(other))
|
||||
self.action_type().partial_cmp(&other.action_type())
|
||||
}
|
||||
}
|
||||
|
|
@ -124,6 +142,7 @@ pub enum ActionResponse {
|
|||
#[checks]
|
||||
RoleChangeAck,
|
||||
WolvesIntroAck,
|
||||
ClearCoverOfDarkness,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
@ -133,5 +152,5 @@ pub enum ActionResult {
|
|||
Arcanist { same: bool },
|
||||
GraveDigger(Option<RoleTitle>),
|
||||
GoBackToSleep,
|
||||
WolvesIntroDone,
|
||||
Continue,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
diedto::DiedTo,
|
||||
error::GameError,
|
||||
game::{DateTime, Village},
|
||||
message::{Identification, PublicIdentity, Target, night::ActionPrompt},
|
||||
message::{CharacterIdentity, Identification, PublicIdentity, Target, night::ActionPrompt},
|
||||
modifier::Modifier,
|
||||
role::{MAPLE_WOLF_ABSTAIN_LIMIT, PreviousGuardianAction, Role, RoleTitle},
|
||||
};
|
||||
|
|
@ -116,6 +116,13 @@ impl Character {
|
|||
&self.public
|
||||
}
|
||||
|
||||
pub fn character_identity(&self) -> CharacterIdentity {
|
||||
CharacterIdentity {
|
||||
character_id: self.character_id.clone(),
|
||||
public: self.public.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.public.name
|
||||
}
|
||||
|
|
@ -213,19 +220,23 @@ impl Character {
|
|||
| Role::Scapegoat
|
||||
| Role::Villager => return Ok(None),
|
||||
Role::Seer => ActionPrompt::Seer {
|
||||
character_id: self.character_identity(),
|
||||
living_players: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::Arcanist => ActionPrompt::Arcanist {
|
||||
character_id: self.character_identity(),
|
||||
living_players: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::Protector {
|
||||
last_protected: Some(last_protected),
|
||||
} => ActionPrompt::Protector {
|
||||
character_id: self.character_identity(),
|
||||
targets: village.living_players_excluding(last_protected),
|
||||
},
|
||||
Role::Protector {
|
||||
last_protected: None,
|
||||
} => ActionPrompt::Protector {
|
||||
character_id: self.character_identity(),
|
||||
targets: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::Apprentice(role) => {
|
||||
|
|
@ -243,6 +254,7 @@ impl Character {
|
|||
DateTime::Night { number } => number + 1 >= current_night,
|
||||
})
|
||||
.then(|| ActionPrompt::RoleChange {
|
||||
character_id: self.character_identity(),
|
||||
new_role: role.title(),
|
||||
}));
|
||||
}
|
||||
|
|
@ -253,49 +265,61 @@ impl Character {
|
|||
};
|
||||
return Ok((current_night == knows_on_night.get()).then_some({
|
||||
ActionPrompt::RoleChange {
|
||||
character_id: self.character_identity(),
|
||||
new_role: RoleTitle::Elder,
|
||||
}
|
||||
}));
|
||||
}
|
||||
Role::Militia { targeted: None } => ActionPrompt::Militia {
|
||||
character_id: self.character_identity(),
|
||||
living_players: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::Werewolf => ActionPrompt::WolfPackKill {
|
||||
living_villagers: village.living_players(),
|
||||
},
|
||||
Role::AlphaWolf { killed: None } => ActionPrompt::AlphaWolf {
|
||||
character_id: self.character_identity(),
|
||||
living_villagers: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::DireWolf => ActionPrompt::DireWolf {
|
||||
character_id: self.character_identity(),
|
||||
living_players: village.living_players(),
|
||||
},
|
||||
Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter,
|
||||
Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter {
|
||||
character_id: self.character_identity(),
|
||||
},
|
||||
Role::Gravedigger => ActionPrompt::Gravedigger {
|
||||
character_id: self.character_identity(),
|
||||
dead_players: village.dead_targets(),
|
||||
},
|
||||
Role::Hunter { target } => ActionPrompt::Hunter {
|
||||
character_id: self.character_identity(),
|
||||
current_target: target.as_ref().and_then(|t| village.target_by_id(t)),
|
||||
living_players: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::MapleWolf { last_kill_on_night } => ActionPrompt::MapleWolf {
|
||||
character_id: self.character_identity(),
|
||||
kill_or_die: last_kill_on_night + MAPLE_WOLF_ABSTAIN_LIMIT.get() == night,
|
||||
living_players: village.living_players_excluding(&self.character_id),
|
||||
},
|
||||
Role::Guardian {
|
||||
last_protected: Some(PreviousGuardianAction::Guard(prev_target)),
|
||||
} => ActionPrompt::Guardian {
|
||||
character_id: self.character_identity(),
|
||||
previous: Some(PreviousGuardianAction::Guard(prev_target.clone())),
|
||||
living_players: village.living_players_excluding(&prev_target.character_id),
|
||||
},
|
||||
Role::Guardian {
|
||||
last_protected: Some(PreviousGuardianAction::Protect(prev_target)),
|
||||
} => ActionPrompt::Guardian {
|
||||
character_id: self.character_identity(),
|
||||
previous: Some(PreviousGuardianAction::Protect(prev_target.clone())),
|
||||
living_players: village.living_players(),
|
||||
},
|
||||
Role::Guardian {
|
||||
last_protected: None,
|
||||
} => ActionPrompt::Guardian {
|
||||
character_id: self.character_identity(),
|
||||
previous: None,
|
||||
living_players: village.living_players(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
[Unit]
|
||||
Description=blog
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
User=blog
|
||||
Group=blog
|
||||
|
||||
WorkingDirectory=/home/blog
|
||||
Environment=RUST_LOG=info
|
||||
Environment=PORT=3024
|
||||
ExecStart=/home/blog/blog-server
|
||||
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=werewolves
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
User=werewolf
|
||||
Group=werewolf
|
||||
|
||||
WorkingDirectory=/home/werewolf
|
||||
Environment=RUST_LOG=info
|
||||
Environment=PORT=3028
|
||||
ExecStart=/home/werewolf/werewolves-server
|
||||
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -27,8 +27,6 @@ pub struct GameRunner {
|
|||
player_sender: LobbyPlayers,
|
||||
roles_revealed: bool,
|
||||
joined_players: JoinedPlayers,
|
||||
// _release_token: InGameToken,
|
||||
cover_of_darkness: bool,
|
||||
}
|
||||
|
||||
impl GameRunner {
|
||||
|
|
@ -38,7 +36,6 @@ impl GameRunner {
|
|||
player_sender: LobbyPlayers,
|
||||
connect_recv: Receiver<(PlayerId, bool)>,
|
||||
joined_players: JoinedPlayers,
|
||||
release_token: InGameToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
game,
|
||||
|
|
@ -47,8 +44,6 @@ impl GameRunner {
|
|||
player_sender,
|
||||
joined_players,
|
||||
roles_revealed: false,
|
||||
// _release_token: release_token,
|
||||
cover_of_darkness: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,18 +198,7 @@ impl GameRunner {
|
|||
if !self.roles_revealed {
|
||||
return Err(GameError::NeedRoleReveal);
|
||||
}
|
||||
if self.cover_of_darkness {
|
||||
match &message {
|
||||
HostMessage::GetState | HostMessage::InGame(HostGameMessage::GetState) => {
|
||||
return Ok(ServerToHostMessage::CoverOfDarkness);
|
||||
}
|
||||
HostMessage::InGame(HostGameMessage::Night(HostNightMessage::Next)) => {
|
||||
self.cover_of_darkness = false;
|
||||
return self.host_message(HostMessage::GetState);
|
||||
}
|
||||
_ => return Err(GameError::InvalidMessageForGameState),
|
||||
};
|
||||
}
|
||||
|
||||
match message {
|
||||
HostMessage::GetState => self.game.process(HostGameMessage::GetState),
|
||||
HostMessage::InGame(msg) => self.game.process(msg),
|
||||
|
|
|
|||
|
|
@ -190,15 +190,6 @@ impl Lobby {
|
|||
.iter()
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect::<Box<[_]>>();
|
||||
let release_token = self
|
||||
.joined_players
|
||||
.start_game_with(
|
||||
&playing_players
|
||||
.iter()
|
||||
.map(|id| id.player_id.clone())
|
||||
.collect::<Box<[_]>>(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let game = Game::new(&playing_players, self.settings.clone())?;
|
||||
assert_eq!(game.village().characters().len(), playing_players.len());
|
||||
|
|
@ -210,7 +201,6 @@ impl Lobby {
|
|||
self.players_in_lobby.clone(),
|
||||
recv,
|
||||
self.joined_players.clone(),
|
||||
release_token,
|
||||
)));
|
||||
}
|
||||
Message::Client(IdentifiedClientMessage {
|
||||
|
|
|
|||
|
|
@ -108,14 +108,14 @@ async fn main() {
|
|||
|
||||
let jp_clone = joined_players.clone();
|
||||
|
||||
let path = Path::new(option_env!("SAVE_PATH").unwrap_or(DEFAULT_SAVE_DIR))
|
||||
.canonicalize()
|
||||
.expect("canonicalizing path");
|
||||
let path = Path::new(option_env!("SAVE_PATH").unwrap_or(DEFAULT_SAVE_DIR));
|
||||
|
||||
if let Err(err) = std::fs::create_dir(&path)
|
||||
&& !matches!(err.kind(), std::io::ErrorKind::AlreadyExists)
|
||||
{
|
||||
panic!("creating save dir at [{path:?}]: {err}")
|
||||
}
|
||||
|
||||
// Check if we can write to the path
|
||||
{
|
||||
let test_file_path = path.join(".test");
|
||||
|
|
@ -125,7 +125,7 @@ async fn main() {
|
|||
std::fs::remove_file(&test_file_path).log_err();
|
||||
}
|
||||
|
||||
let saver = FileSaver::new(path);
|
||||
let saver = FileSaver::new(path.canonicalize().expect("canonicalizing path"));
|
||||
tokio::spawn(async move {
|
||||
crate::runner::run_game(jp_clone, lobby_comms, saver).await;
|
||||
panic!("game over");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use core::{num::NonZeroU8, ops::Not};
|
||||
use core::ops::Not;
|
||||
|
||||
use werewolves_proto::{
|
||||
message::{
|
||||
PublicIdentity, Target,
|
||||
PublicIdentity,
|
||||
host::{HostGameMessage, HostMessage, HostNightMessage},
|
||||
night::{ActionPrompt, ActionResponse},
|
||||
},
|
||||
|
|
@ -12,42 +12,59 @@ use werewolves_proto::{
|
|||
use yew::prelude::*;
|
||||
|
||||
use crate::components::{
|
||||
Identity,
|
||||
CoverOfDarkness, Identity,
|
||||
action::{BinaryChoice, OptionalSingleTarget, SingleTarget, TwoTarget, WolvesIntro},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct ActionPromptProps {
|
||||
pub prompt: ActionPrompt,
|
||||
pub ident: PublicIdentity,
|
||||
#[prop_or_default]
|
||||
pub big_screen: bool,
|
||||
pub on_complete: Callback<HostMessage>,
|
||||
}
|
||||
|
||||
fn identity_html(ident: Option<&PublicIdentity>) -> Option<Html> {
|
||||
ident.map(|ident| {
|
||||
html! {
|
||||
<Identity ident={ident.clone()}/>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Prompt(props: &ActionPromptProps) -> Html {
|
||||
let ident = props
|
||||
.big_screen
|
||||
.not()
|
||||
.then(|| html! {<Identity ident={props.ident.clone()}/>});
|
||||
match &props.prompt {
|
||||
ActionPrompt::CoverOfDarkness => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let next = props.big_screen.not().then(|| {
|
||||
Callback::from(move |_| {
|
||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
HostNightMessage::Next,
|
||||
)))
|
||||
})
|
||||
});
|
||||
return html! {
|
||||
<CoverOfDarkness next={next} />
|
||||
};
|
||||
}
|
||||
ActionPrompt::WolvesIntro { wolves } => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_complete = Callback::from(move |_| {
|
||||
on_complete.emit(HostMessage::InGame(
|
||||
werewolves_proto::message::host::HostGameMessage::Night(
|
||||
werewolves_proto::message::host::HostNightMessage::ActionResponse(
|
||||
werewolves_proto::message::night::ActionResponse::WolvesIntroAck,
|
||||
),
|
||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
HostNightMessage::ActionResponse(
|
||||
werewolves_proto::message::night::ActionResponse::WolvesIntroAck,
|
||||
),
|
||||
))
|
||||
)))
|
||||
});
|
||||
html! {
|
||||
<WolvesIntro big_screen={props.big_screen} on_complete={on_complete} wolves={wolves.clone()}/>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Seer { living_players } => {
|
||||
ActionPrompt::Seer {
|
||||
character_id,
|
||||
living_players,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: CharacterId| {
|
||||
|
|
@ -58,7 +75,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
});
|
||||
html! {
|
||||
<div>
|
||||
{ident}
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<SingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
|
|
@ -67,7 +84,10 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::RoleChange { new_role } => {
|
||||
ActionPrompt::RoleChange {
|
||||
character_id,
|
||||
new_role,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
|
|
@ -81,14 +101,17 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
});
|
||||
html! {
|
||||
<div>
|
||||
{ident}
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<h2>{"your role has changed"}</h2>
|
||||
<p>{new_role.to_string()}</p>
|
||||
{cont}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Protector { targets } => {
|
||||
ActionPrompt::Protector {
|
||||
character_id,
|
||||
targets,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: CharacterId| {
|
||||
|
|
@ -99,6 +122,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
});
|
||||
html! {
|
||||
<div>
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<SingleTarget
|
||||
targets={targets.clone()}
|
||||
target_selection={on_select}
|
||||
|
|
@ -107,7 +131,10 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Arcanist { living_players } => {
|
||||
ActionPrompt::Arcanist {
|
||||
character_id,
|
||||
living_players,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |(t1, t2): (CharacterId, CharacterId)| {
|
||||
|
|
@ -118,6 +145,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
});
|
||||
html! {
|
||||
<div>
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<TwoTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
|
|
@ -126,7 +154,10 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Gravedigger { dead_players } => {
|
||||
ActionPrompt::Gravedigger {
|
||||
character_id,
|
||||
dead_players,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: CharacterId| {
|
||||
|
|
@ -137,6 +168,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
});
|
||||
html! {
|
||||
<div>
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<SingleTarget
|
||||
targets={dead_players.clone()}
|
||||
target_selection={on_select}
|
||||
|
|
@ -146,6 +178,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
}
|
||||
}
|
||||
ActionPrompt::Hunter {
|
||||
character_id,
|
||||
current_target,
|
||||
living_players,
|
||||
} => {
|
||||
|
|
@ -158,20 +191,26 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
})
|
||||
});
|
||||
html! {
|
||||
<SingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"hunter"}
|
||||
>
|
||||
<h3>
|
||||
<b>{"current target: "}</b>{current_target.clone().map(|t| html!{
|
||||
<Identity ident={t.public} />
|
||||
}).unwrap_or_else(|| html!{<i>{"none"}</i>})}
|
||||
</h3>
|
||||
</SingleTarget>
|
||||
<div>
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<SingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"hunter"}
|
||||
>
|
||||
<h3>
|
||||
<b>{"current target: "}</b>{current_target.clone().map(|t| html!{
|
||||
<Identity ident={t.public} />
|
||||
}).unwrap_or_else(|| html!{<i>{"none"}</i>})}
|
||||
</h3>
|
||||
</SingleTarget>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Militia { living_players } => {
|
||||
ActionPrompt::Militia {
|
||||
character_id,
|
||||
living_players,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: Option<CharacterId>| {
|
||||
|
|
@ -181,14 +220,18 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
})
|
||||
});
|
||||
html! {
|
||||
<OptionalSingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"pew pew?"}
|
||||
/>
|
||||
<div>
|
||||
{identity_html(props.big_screen.then_some(&character_id.public))}
|
||||
<OptionalSingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"pew pew?"}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::MapleWolf {
|
||||
character_id: _,
|
||||
kill_or_die,
|
||||
living_players,
|
||||
} => {
|
||||
|
|
@ -206,16 +249,19 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
}
|
||||
});
|
||||
html! {
|
||||
<OptionalSingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"nom nom?"}
|
||||
>
|
||||
{kill_or_die}
|
||||
</OptionalSingleTarget>
|
||||
<div>
|
||||
<OptionalSingleTarget
|
||||
targets={living_players.clone()}
|
||||
target_selection={on_select}
|
||||
headline={"nom nom?"}
|
||||
>
|
||||
{kill_or_die}
|
||||
</OptionalSingleTarget>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Guardian {
|
||||
character_id: _,
|
||||
previous,
|
||||
living_players,
|
||||
} => {
|
||||
|
|
@ -271,7 +317,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
/>
|
||||
}
|
||||
}
|
||||
ActionPrompt::Shapeshifter => {
|
||||
ActionPrompt::Shapeshifter { character_id: _ } => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then_some({
|
||||
move |shift| {
|
||||
|
|
@ -286,7 +332,10 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
</BinaryChoice>
|
||||
}
|
||||
}
|
||||
ActionPrompt::AlphaWolf { living_villagers } => {
|
||||
ActionPrompt::AlphaWolf {
|
||||
character_id: _,
|
||||
living_villagers,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: Option<CharacterId>| {
|
||||
|
|
@ -303,7 +352,10 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
|||
/>
|
||||
}
|
||||
}
|
||||
ActionPrompt::DireWolf { living_players } => {
|
||||
ActionPrompt::DireWolf {
|
||||
character_id: _,
|
||||
living_players,
|
||||
} => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_select = props.big_screen.not().then(|| {
|
||||
Callback::from(move |target: CharacterId| {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ use crate::components::{CoverOfDarkness, Identity};
|
|||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct ActionResultProps {
|
||||
pub result: ActionResult,
|
||||
pub ident: PublicIdentity,
|
||||
#[prop_or_default]
|
||||
pub ident: Option<PublicIdentity>,
|
||||
#[prop_or_default]
|
||||
pub big_screen: bool,
|
||||
pub on_complete: Callback<HostMessage>,
|
||||
|
|
@ -24,10 +25,12 @@ pub struct ActionResultProps {
|
|||
|
||||
#[function_component]
|
||||
pub fn ActionResultView(props: &ActionResultProps) -> Html {
|
||||
let ident = props
|
||||
.big_screen
|
||||
.not()
|
||||
.then(|| html! {<Identity ident={props.ident.clone()}/>});
|
||||
let ident = props.ident.as_ref().and_then(|ident| {
|
||||
props
|
||||
.big_screen
|
||||
.not()
|
||||
.then(|| html! {<Identity ident={ident.clone()}/>})
|
||||
});
|
||||
let on_complete = props.on_complete.clone();
|
||||
let on_complete = Callback::from(move |_| {
|
||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
|
|
@ -98,18 +101,10 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html {
|
|||
</CoverOfDarkness>
|
||||
}
|
||||
}
|
||||
ActionResult::WolvesIntroDone => {
|
||||
let on_complete = props.on_complete.clone();
|
||||
let next = props.big_screen.not().then(|| {
|
||||
Callback::from(move |_| {
|
||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
HostNightMessage::Next,
|
||||
)))
|
||||
})
|
||||
});
|
||||
|
||||
ActionResult::Continue => {
|
||||
props.on_complete.emit(HostMessage::GetState);
|
||||
html! {
|
||||
<CoverOfDarkness message={"wolves go to sleep"} next={next}/>
|
||||
<CoverOfDarkness />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ pub struct Connection {
|
|||
|
||||
impl Connection {
|
||||
async fn connect_ws() -> WebSocket {
|
||||
let url = option_env!("DEBUG").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
let url = option_env!("LOCAL").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
loop {
|
||||
match WebSocket::open(url) {
|
||||
Ok(ws) => break ws,
|
||||
|
|
@ -78,7 +78,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
async fn run(mut self) {
|
||||
let url = option_env!("DEBUG").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
let url = option_env!("LOCAL").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
'outer: loop {
|
||||
log::info!("connecting to {url}");
|
||||
let mut ws = Self::connect_ws().await.fuse();
|
||||
|
|
@ -511,18 +511,18 @@ impl Component for Client {
|
|||
true
|
||||
}
|
||||
Message::Connect => {
|
||||
if let Some(player) = self.player.as_ref() {
|
||||
if let Some(recv) = self.recv.take() {
|
||||
yew::platform::spawn_local(
|
||||
Connection {
|
||||
scope: ctx.link().clone(),
|
||||
ident: player.clone(),
|
||||
recv,
|
||||
}
|
||||
.run(),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if let Some(player) = self.player.as_ref()
|
||||
&& let Some(recv) = self.recv.take()
|
||||
{
|
||||
yew::platform::spawn_local(
|
||||
Connection {
|
||||
scope: ctx.link().clone(),
|
||||
ident: player.clone(),
|
||||
recv,
|
||||
}
|
||||
.run(),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
while let Err(err) = self.send.try_send(ClientMessage::GetState) {
|
||||
log::error!("send IsThereALobby: {err}")
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const DEBUG_URL: &str = "ws://192.168.1.162:8080/connect/host";
|
|||
const LIVE_URL: &str = "wss://wolf.emilis.dev/connect/host";
|
||||
|
||||
async fn connect_ws() -> WebSocket {
|
||||
let url = option_env!("DEBUG").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
let url = option_env!("LOCAL").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
loop {
|
||||
match WebSocket::open(url) {
|
||||
Ok(ws) => break ws,
|
||||
|
|
@ -64,7 +64,7 @@ fn encode_message(msg: &impl Serialize) -> websocket::Message {
|
|||
}
|
||||
|
||||
async fn worker(mut recv: Receiver<HostMessage>, scope: Scope<Host>) {
|
||||
let url = option_env!("DEBUG").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
let url = option_env!("LOCAL").map(|_| DEBUG_URL).unwrap_or(LIVE_URL);
|
||||
'outer: loop {
|
||||
log::info!("connecting to {url}");
|
||||
let mut ws = connect_ws().await.fuse();
|
||||
|
|
@ -75,13 +75,11 @@ async fn worker(mut recv: Receiver<HostMessage>, scope: Scope<Host>) {
|
|||
continue 'outer;
|
||||
}
|
||||
|
||||
let mut last_msg = chrono::Local::now();
|
||||
loop {
|
||||
let msg = futures::select! {
|
||||
r = ws.next() => {
|
||||
match r {
|
||||
Some(Ok(msg)) => {
|
||||
last_msg = chrono::Local::now();
|
||||
msg
|
||||
},
|
||||
Some(Err(err)) => {
|
||||
|
|
@ -137,10 +135,6 @@ async fn worker(mut recv: Receiver<HostMessage>, scope: Scope<Host>) {
|
|||
}
|
||||
}
|
||||
};
|
||||
let took = chrono::Local::now() - last_msg;
|
||||
if took.num_milliseconds() >= 100 {
|
||||
log::warn!("took {took}")
|
||||
}
|
||||
match parse {
|
||||
Ok(msg) => scope.send_message::<HostEvent>(msg.into()),
|
||||
Err(err) => {
|
||||
|
|
@ -178,9 +172,8 @@ pub enum HostState {
|
|||
ackd: Box<[Target]>,
|
||||
waiting: Box<[Target]>,
|
||||
},
|
||||
Prompt(PublicIdentity, ActionPrompt),
|
||||
Result(PublicIdentity, ActionResult),
|
||||
CoverOfDarkness,
|
||||
Prompt(ActionPrompt),
|
||||
Result(Option<PublicIdentity>, ActionResult),
|
||||
}
|
||||
|
||||
impl From<ServerToHostMessage> for HostEvent {
|
||||
|
|
@ -202,8 +195,8 @@ impl From<ServerToHostMessage> for HostEvent {
|
|||
ServerToHostMessage::GameOver(game_over) => {
|
||||
HostEvent::SetState(HostState::GameOver { result: game_over })
|
||||
}
|
||||
ServerToHostMessage::ActionPrompt(ident, prompt) => {
|
||||
HostEvent::SetState(HostState::Prompt(ident, prompt))
|
||||
ServerToHostMessage::ActionPrompt(prompt) => {
|
||||
HostEvent::SetState(HostState::Prompt(prompt))
|
||||
}
|
||||
ServerToHostMessage::ActionResult(ident, result) => {
|
||||
HostEvent::SetState(HostState::Result(ident, result))
|
||||
|
|
@ -211,7 +204,6 @@ impl From<ServerToHostMessage> for HostEvent {
|
|||
ServerToHostMessage::WaitingForRoleRevealAcks { ackd, waiting } => {
|
||||
HostEvent::SetState(HostState::RoleReveal { ackd, waiting })
|
||||
}
|
||||
ServerToHostMessage::CoverOfDarkness => HostEvent::SetState(HostState::CoverOfDarkness),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -402,7 +394,7 @@ impl Component for Host {
|
|||
<RoleReveal ackd={ackd} waiting={waiting} on_force_ready={on_force_ready}/>
|
||||
}
|
||||
}
|
||||
HostState::Prompt(ident, prompt) => {
|
||||
HostState::Prompt(prompt) => {
|
||||
let send = self.send.clone();
|
||||
let on_complete = Callback::from(move |msg| {
|
||||
let mut send = send.clone();
|
||||
|
|
@ -418,7 +410,6 @@ impl Component for Host {
|
|||
prompt={prompt}
|
||||
big_screen={self.big_screen}
|
||||
on_complete={on_complete}
|
||||
ident={ident}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
|
@ -442,27 +433,6 @@ impl Component for Host {
|
|||
/>
|
||||
}
|
||||
}
|
||||
HostState::CoverOfDarkness => {
|
||||
let next = self.big_screen.not().then(|| {
|
||||
let send = self.send.clone();
|
||||
Callback::from(move |_| {
|
||||
let mut send = send.clone();
|
||||
yew::platform::spawn_local(async move {
|
||||
if let Err(err) = send
|
||||
.send(HostMessage::InGame(HostGameMessage::Night(
|
||||
HostNightMessage::Next,
|
||||
)))
|
||||
.await
|
||||
{
|
||||
log::error!("sending action result response: {err}")
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
return html! {
|
||||
<CoverOfDarkness next={next} />
|
||||
};
|
||||
}
|
||||
};
|
||||
let debug_nav = self.debug.then(|| {
|
||||
let on_error_click = callback::send_message(
|
||||
|
|
@ -506,8 +476,7 @@ impl Component for Host {
|
|||
settings: GameSettings::default(),
|
||||
}
|
||||
}
|
||||
HostState::CoverOfDarkness
|
||||
| HostState::Prompt(_, _)
|
||||
HostState::Prompt(_)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::Disconnected
|
||||
| HostState::RoleReveal {
|
||||
|
|
@ -540,8 +509,7 @@ impl Component for Host {
|
|||
*s = settings;
|
||||
true
|
||||
}
|
||||
HostState::CoverOfDarkness
|
||||
| HostState::Prompt(_, _)
|
||||
HostState::Prompt(_)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::Disconnected
|
||||
| HostState::RoleReveal {
|
||||
|
|
|
|||
Loading…
Reference in New Issue