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