refactored night actions a la day markings
This commit is contained in:
parent
cbeee94113
commit
5ad2831688
|
|
@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::night::Night,
|
game::night::{Night, ServerAction},
|
||||||
message::{
|
message::{
|
||||||
CharacterState, Identification,
|
CharacterState, Identification,
|
||||||
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||||
|
|
@ -133,7 +133,8 @@ impl Game {
|
||||||
GameState::Night { night },
|
GameState::Night { night },
|
||||||
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(ServerAction::Prompt(prompt)) => Ok(ServerToHostMessage::ActionPrompt(prompt)),
|
||||||
|
Ok(ServerAction::Result(res)) => Ok(ServerToHostMessage::ActionResult(
|
||||||
night.current_character().map(|c| c.identity()),
|
night.current_character().map(|c| c.identity()),
|
||||||
res,
|
res,
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -68,6 +68,28 @@ impl Village {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn killing_wolf(&self) -> Option<&Character> {
|
||||||
|
let wolves = self.characters.iter().filter(|c| c.is_wolf());
|
||||||
|
|
||||||
|
{
|
||||||
|
let ww = wolves
|
||||||
|
.clone()
|
||||||
|
.filter(|w| matches!(w.role().title(), RoleTitle::Werewolf))
|
||||||
|
.collect::<Box<[_]>>();
|
||||||
|
if !ww.is_empty() {
|
||||||
|
return Some(ww[rand::random_range(0..ww.len())]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let wolves = wolves.collect::<Box<[_]>>();
|
||||||
|
if wolves.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(wolves[rand::random_range(0..wolves.len())])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn date_time(&self) -> DateTime {
|
pub const fn date_time(&self) -> DateTime {
|
||||||
self.date_time
|
self.date_time
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,7 @@ use crate::{
|
||||||
game::{Game, GameSettings},
|
game::{Game, GameSettings},
|
||||||
message::{
|
message::{
|
||||||
CharacterState, Identification, PublicIdentity,
|
CharacterState, Identification, PublicIdentity,
|
||||||
host::{
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||||
HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage,
|
|
||||||
ServerToHostMessageTitle,
|
|
||||||
},
|
|
||||||
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult},
|
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult},
|
||||||
},
|
},
|
||||||
player::{CharacterId, PlayerId},
|
player::{CharacterId, PlayerId},
|
||||||
|
|
@ -20,12 +17,39 @@ use core::{num::NonZeroU8, ops::Range};
|
||||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
trait ActionResultExt {
|
||||||
|
fn sleep(&self);
|
||||||
|
fn r#continue(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionResultExt for ActionResult {
|
||||||
|
fn sleep(&self) {
|
||||||
|
assert_eq!(*self, ActionResult::GoBackToSleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn r#continue(&self) {
|
||||||
|
assert_eq!(*self, ActionResult::Continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait ServerToHostMessageExt {
|
trait ServerToHostMessageExt {
|
||||||
fn prompt(self) -> ActionPrompt;
|
fn prompt(self) -> ActionPrompt;
|
||||||
fn result(self) -> ActionResult;
|
fn result(self) -> ActionResult;
|
||||||
|
fn daytime(self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerToHostMessageExt for ServerToHostMessage {
|
impl ServerToHostMessageExt for ServerToHostMessage {
|
||||||
|
fn daytime(self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
|
||||||
|
match self {
|
||||||
|
Self::Daytime {
|
||||||
|
characters,
|
||||||
|
marked,
|
||||||
|
day,
|
||||||
|
} => (characters, marked, day),
|
||||||
|
resp => panic!("expected daytime, got {resp:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn prompt(self) -> ActionPrompt {
|
fn prompt(self) -> ActionPrompt {
|
||||||
match self {
|
match self {
|
||||||
Self::ActionPrompt(prompt) => prompt,
|
Self::ActionPrompt(prompt) => prompt,
|
||||||
|
|
@ -48,7 +72,10 @@ impl ServerToHostMessageExt for ServerToHostMessage {
|
||||||
|
|
||||||
trait GameExt {
|
trait GameExt {
|
||||||
fn next(&mut self) -> ActionPrompt;
|
fn next(&mut self) -> ActionPrompt;
|
||||||
|
fn r#continue(&mut self) -> ActionResult;
|
||||||
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||||
|
fn mark(&mut self, mark: &CharacterId) -> ActionPrompt;
|
||||||
|
fn mark_and_check(&mut self, mark: &CharacterId, check: impl FnOnce(&ActionPrompt) -> bool);
|
||||||
fn response(&mut self, resp: ActionResponse) -> ActionResult;
|
fn response(&mut self, resp: ActionResponse) -> ActionResult;
|
||||||
fn execute(&mut self) -> ActionPrompt;
|
fn execute(&mut self) -> ActionPrompt;
|
||||||
fn mark_for_execution(
|
fn mark_for_execution(
|
||||||
|
|
@ -58,6 +85,29 @@ trait GameExt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameExt for Game {
|
impl GameExt for Game {
|
||||||
|
fn r#continue(&mut self) -> ActionResult {
|
||||||
|
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
|
ActionResponse::Continue,
|
||||||
|
)))
|
||||||
|
.unwrap()
|
||||||
|
.result()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark(&mut self, mark: &CharacterId) -> ActionPrompt {
|
||||||
|
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
|
ActionResponse::MarkTarget(mark.clone()),
|
||||||
|
)))
|
||||||
|
.unwrap()
|
||||||
|
.prompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_and_check(&mut self, mark: &CharacterId, check: impl FnOnce(&ActionPrompt) -> bool) {
|
||||||
|
let prompt = self.mark(mark);
|
||||||
|
if !check(&prompt) {
|
||||||
|
panic!("unexpected prompt: {prompt:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn next(&mut self) -> ActionPrompt {
|
fn next(&mut self) -> ActionPrompt {
|
||||||
self.process(HostGameMessage::Night(HostNightMessage::Next))
|
self.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -179,7 +229,7 @@ fn no_wolf_kill_n1() {
|
||||||
let mut game = Game::new(&players, settings).unwrap();
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::ClearCoverOfDarkness
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
||||||
|
|
@ -191,7 +241,7 @@ fn no_wolf_kill_n1() {
|
||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::WolvesIntroAck
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
||||||
|
|
@ -214,7 +264,7 @@ fn yes_wolf_kill_n2() {
|
||||||
let mut game = Game::new(&players, settings).unwrap();
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::ClearCoverOfDarkness
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
||||||
|
|
@ -226,7 +276,7 @@ fn yes_wolf_kill_n2() {
|
||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::WolvesIntroAck
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.result(),
|
.result(),
|
||||||
|
|
@ -271,7 +321,7 @@ fn yes_wolf_kill_n2() {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::ClearCoverOfDarkness
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
||||||
|
|
@ -281,7 +331,8 @@ fn yes_wolf_kill_n2() {
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolfPackKill {
|
ServerToHostMessage::ActionPrompt(ActionPrompt::WolfPackKill {
|
||||||
living_villagers: _
|
living_villagers: _,
|
||||||
|
marked: _,
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +348,7 @@ fn protect_stops_shapeshift() {
|
||||||
let mut game = Game::new(&players, settings).unwrap();
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::ClearCoverOfDarkness
|
ActionResponse::Continue,
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
||||||
|
|
@ -309,7 +360,7 @@ fn protect_stops_shapeshift() {
|
||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::WolvesIntroAck
|
ActionResponse::Continue
|
||||||
)))
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
||||||
|
|
@ -353,14 +404,7 @@ fn protect_stops_shapeshift() {
|
||||||
.title(),
|
.title(),
|
||||||
ActionPromptTitle::CoverOfDarkness
|
ActionPromptTitle::CoverOfDarkness
|
||||||
);
|
);
|
||||||
assert_eq!(
|
game.r#continue().r#continue();
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
|
||||||
ActionResponse::ClearCoverOfDarkness
|
|
||||||
)))
|
|
||||||
.unwrap()
|
|
||||||
.result(),
|
|
||||||
ActionResult::Continue
|
|
||||||
);
|
|
||||||
|
|
||||||
let (prot_and_wolf_target, prot_char_id) = match game
|
let (prot_and_wolf_target, prot_char_id) = match game
|
||||||
.process(HostGameMessage::Night(HostNightMessage::Next))
|
.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||||
|
|
@ -369,6 +413,7 @@ fn protect_stops_shapeshift() {
|
||||||
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
|
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
|
||||||
character_id: prot_char_id,
|
character_id: prot_char_id,
|
||||||
targets,
|
targets,
|
||||||
|
marked: None,
|
||||||
}) => (
|
}) => (
|
||||||
targets
|
targets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -388,55 +433,35 @@ fn protect_stops_shapeshift() {
|
||||||
.clone();
|
.clone();
|
||||||
log::info!("target: {target:#?}");
|
log::info!("target: {target:#?}");
|
||||||
|
|
||||||
assert_eq!(
|
match game
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::Protector(prot_and_wolf_target.clone())
|
ActionResponse::MarkTarget(prot_and_wolf_target.clone()),
|
||||||
)))
|
)))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.result(),
|
{
|
||||||
ActionResult::GoBackToSleep,
|
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
|
||||||
);
|
marked: Some(mark), ..
|
||||||
|
}) => assert_eq!(mark, prot_and_wolf_target, "marked target"),
|
||||||
|
resp => panic!("unexpected response: {resp:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
game.r#continue().sleep();
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
|
||||||
.unwrap()
|
|
||||||
.prompt()
|
|
||||||
.title(),
|
|
||||||
ActionPromptTitle::WolfPackKill
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill);
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
|
||||||
ActionResponse::WolfPackKillVote(prot_and_wolf_target.clone())
|
|
||||||
)))
|
|
||||||
.unwrap()
|
|
||||||
.result(),
|
|
||||||
ActionResult::Continue,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
game.mark_and_check(&prot_and_wolf_target, |c| match c {
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
ActionPrompt::WolfPackKill {
|
||||||
.unwrap()
|
marked: Some(mark), ..
|
||||||
.prompt()
|
} => prot_and_wolf_target == *mark,
|
||||||
.title(),
|
_ => false,
|
||||||
ActionPromptTitle::Shapeshifter,
|
});
|
||||||
);
|
game.r#continue().r#continue();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(game.next().title(), ActionPromptTitle::Shapeshifter,);
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
|
||||||
ActionResponse::Shapeshifter(true)
|
|
||||||
)))
|
|
||||||
.unwrap()
|
|
||||||
.result(),
|
|
||||||
ActionResult::GoBackToSleep,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
game.response(ActionResponse::Shapeshift);
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
|
||||||
.unwrap()
|
game.next_expect_day();
|
||||||
.title(),
|
|
||||||
ServerToHostMessageTitle::Daytime,
|
|
||||||
);
|
|
||||||
|
|
||||||
let target = game
|
let target = game
|
||||||
.village()
|
.village()
|
||||||
|
|
@ -462,34 +487,11 @@ fn wolfpack_kill_all_targets_valid() {
|
||||||
settings.add(RoleTitle::Shapeshifter).unwrap();
|
settings.add(RoleTitle::Shapeshifter).unwrap();
|
||||||
settings.sub(RoleTitle::Werewolf);
|
settings.sub(RoleTitle::Werewolf);
|
||||||
let mut game = Game::new(&players, settings).unwrap();
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
assert_eq!(
|
game.r#continue().r#continue();
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
|
||||||
ActionResponse::ClearCoverOfDarkness
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
)))
|
game.r#continue().sleep();
|
||||||
.unwrap(),
|
game.next_expect_day();
|
||||||
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
|
let execution_target = game
|
||||||
.village()
|
.village()
|
||||||
|
|
@ -513,28 +515,18 @@ fn wolfpack_kill_all_targets_valid() {
|
||||||
resp => panic!("unexpected server message: {resp:#?}"),
|
resp => panic!("unexpected server message: {resp:#?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness);
|
||||||
game.process(HostGameMessage::Day(HostDayMessage::Execute))
|
game.r#continue().r#continue();
|
||||||
.unwrap()
|
|
||||||
.prompt()
|
|
||||||
.title(),
|
|
||||||
ActionPromptTitle::CoverOfDarkness
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
|
||||||
ActionResponse::ClearCoverOfDarkness
|
|
||||||
)))
|
|
||||||
.unwrap()
|
|
||||||
.result(),
|
|
||||||
ActionResult::Continue
|
|
||||||
);
|
|
||||||
|
|
||||||
let living_villagers = match game
|
let living_villagers = match game
|
||||||
.process(HostGameMessage::Night(HostNightMessage::Next))
|
.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.prompt()
|
.prompt()
|
||||||
{
|
{
|
||||||
ActionPrompt::WolfPackKill { living_villagers } => living_villagers,
|
ActionPrompt::WolfPackKill {
|
||||||
|
living_villagers,
|
||||||
|
marked: _,
|
||||||
|
} => living_villagers,
|
||||||
_ => panic!("not wolf pack kill"),
|
_ => panic!("not wolf pack kill"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -542,12 +534,14 @@ fn wolfpack_kill_all_targets_valid() {
|
||||||
let mut attempt = game.clone();
|
let mut attempt = game.clone();
|
||||||
if let ServerToHostMessage::Error(GameError::InvalidTarget) = attempt
|
if let ServerToHostMessage::Error(GameError::InvalidTarget) = attempt
|
||||||
.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||||
ActionResponse::WolfPackKillVote(target.character_id.clone()),
|
ActionResponse::MarkTarget(target.character_id.clone()),
|
||||||
)))
|
)))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
{
|
{
|
||||||
panic!("invalid target {target:?} at index [{idx}]");
|
panic!("invalid target {target:?} at index [{idx}]");
|
||||||
}
|
}
|
||||||
|
attempt.r#continue().r#continue();
|
||||||
|
assert_eq!(attempt.next().title(), ActionPromptTitle::Shapeshifter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,15 +553,9 @@ fn only_1_shapeshift_prompt_if_first_shifts() {
|
||||||
settings.add(RoleTitle::Shapeshifter).unwrap();
|
settings.add(RoleTitle::Shapeshifter).unwrap();
|
||||||
settings.sub(RoleTitle::Werewolf);
|
settings.sub(RoleTitle::Werewolf);
|
||||||
let mut game = Game::new(&players, settings).unwrap();
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
assert_eq!(
|
game.r#continue().r#continue();
|
||||||
game.response(ActionResponse::ClearCoverOfDarkness),
|
|
||||||
ActionResult::Continue
|
|
||||||
);
|
|
||||||
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
assert_eq!(
|
game.r#continue().sleep();
|
||||||
game.response(ActionResponse::WolvesIntroAck),
|
|
||||||
ActionResult::GoBackToSleep
|
|
||||||
);
|
|
||||||
game.next_expect_day();
|
game.next_expect_day();
|
||||||
let target = game
|
let target = game
|
||||||
.village()
|
.village()
|
||||||
|
|
@ -579,10 +567,7 @@ fn only_1_shapeshift_prompt_if_first_shifts() {
|
||||||
let (marked, target_list): (&[CharacterId], &[CharacterId]) = (&marked, &[target]);
|
let (marked, target_list): (&[CharacterId], &[CharacterId]) = (&marked, &[target]);
|
||||||
assert_eq!(target_list, marked);
|
assert_eq!(target_list, marked);
|
||||||
assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness);
|
assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness);
|
||||||
assert_eq!(
|
game.r#continue().r#continue();
|
||||||
game.response(ActionResponse::ClearCoverOfDarkness),
|
|
||||||
ActionResult::Continue
|
|
||||||
);
|
|
||||||
assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill);
|
assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill);
|
||||||
let target = game
|
let target = game
|
||||||
.village()
|
.village()
|
||||||
|
|
@ -590,20 +575,18 @@ fn only_1_shapeshift_prompt_if_first_shifts() {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find_map(|c| (c.is_village() && c.alive()).then_some(c.character_id().clone()))
|
.find_map(|c| (c.is_village() && c.alive()).then_some(c.character_id().clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
|
||||||
game.response(ActionResponse::WolfPackKillVote(target)),
|
game.mark_and_check(&target, |p| match p {
|
||||||
ActionResult::Continue,
|
ActionPrompt::WolfPackKill {
|
||||||
);
|
marked: Some(t), ..
|
||||||
|
} => *t == target,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
game.r#continue().r#continue();
|
||||||
assert_eq!(game.next().title(), ActionPromptTitle::Shapeshifter);
|
assert_eq!(game.next().title(), ActionPromptTitle::Shapeshifter);
|
||||||
assert_eq!(
|
game.response(ActionResponse::Shapeshift).r#continue();
|
||||||
game.response(ActionResponse::Shapeshifter(true)),
|
|
||||||
ActionResult::Continue,
|
|
||||||
);
|
|
||||||
assert_eq!(game.next().title(), ActionPromptTitle::RoleChange);
|
assert_eq!(game.next().title(), ActionPromptTitle::RoleChange);
|
||||||
assert_eq!(
|
game.r#continue().sleep();
|
||||||
game.response(ActionResponse::RoleChangeAck),
|
|
||||||
ActionResult::GoBackToSleep
|
|
||||||
);
|
|
||||||
|
|
||||||
game.next_expect_day();
|
game.next_expect_day();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,11 @@ fn night_order() {
|
||||||
character_id: character_identity(),
|
character_id: character_identity(),
|
||||||
},
|
},
|
||||||
ActionPrompt::WolfPackKill {
|
ActionPrompt::WolfPackKill {
|
||||||
|
marked: None,
|
||||||
living_villagers: Box::new([]),
|
living_villagers: Box::new([]),
|
||||||
},
|
},
|
||||||
ActionPrompt::Protector {
|
ActionPrompt::Protector {
|
||||||
|
marked: None,
|
||||||
character_id: character_identity(),
|
character_id: character_identity(),
|
||||||
targets: Box::new([]),
|
targets: Box::new([]),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ use serde::{Deserialize, Serialize};
|
||||||
use werewolves_macros::{ChecksAs, Titles};
|
use werewolves_macros::{ChecksAs, Titles};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::GameError,
|
||||||
message::CharacterIdentity,
|
message::CharacterIdentity,
|
||||||
player::CharacterId,
|
player::CharacterId,
|
||||||
role::{Alignment, PreviousGuardianAction, RoleTitle},
|
role::{Alignment, PreviousGuardianAction, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, GameError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||||
pub enum ActionType {
|
pub enum ActionType {
|
||||||
Cover,
|
Cover,
|
||||||
|
|
@ -37,7 +40,6 @@ pub enum ActionPrompt {
|
||||||
#[checks(ActionType::Cover)]
|
#[checks(ActionType::Cover)]
|
||||||
CoverOfDarkness,
|
CoverOfDarkness,
|
||||||
#[checks(ActionType::WolfPackKill)]
|
#[checks(ActionType::WolfPackKill)]
|
||||||
#[checks]
|
|
||||||
WolvesIntro {
|
WolvesIntro {
|
||||||
wolves: Box<[(CharacterIdentity, RoleTitle)]>,
|
wolves: Box<[(CharacterIdentity, RoleTitle)]>,
|
||||||
},
|
},
|
||||||
|
|
@ -50,48 +52,57 @@ pub enum ActionPrompt {
|
||||||
Seer {
|
Seer {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Protect)]
|
#[checks(ActionType::Protect)]
|
||||||
Protector {
|
Protector {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
targets: Box<[CharacterIdentity]>,
|
targets: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Other)]
|
#[checks(ActionType::Other)]
|
||||||
Arcanist {
|
Arcanist {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: (Option<CharacterId>, Option<CharacterId>),
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Other)]
|
#[checks(ActionType::Other)]
|
||||||
Gravedigger {
|
Gravedigger {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
dead_players: Box<[CharacterIdentity]>,
|
dead_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Other)]
|
#[checks(ActionType::Other)]
|
||||||
Hunter {
|
Hunter {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
current_target: Option<CharacterIdentity>,
|
current_target: Option<CharacterIdentity>,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Other)]
|
#[checks(ActionType::Other)]
|
||||||
Militia {
|
Militia {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Other)]
|
#[checks(ActionType::Other)]
|
||||||
MapleWolf {
|
MapleWolf {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
kill_or_die: bool,
|
kill_or_die: bool,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Protect)]
|
#[checks(ActionType::Protect)]
|
||||||
Guardian {
|
Guardian {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
previous: Option<PreviousGuardianAction>,
|
previous: Option<PreviousGuardianAction>,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::WolfPackKill)]
|
#[checks(ActionType::WolfPackKill)]
|
||||||
WolfPackKill {
|
WolfPackKill {
|
||||||
living_villagers: Box<[CharacterIdentity]>,
|
living_villagers: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::OtherWolf)]
|
#[checks(ActionType::OtherWolf)]
|
||||||
Shapeshifter { character_id: CharacterIdentity },
|
Shapeshifter { character_id: CharacterIdentity },
|
||||||
|
|
@ -99,15 +110,147 @@ pub enum ActionPrompt {
|
||||||
AlphaWolf {
|
AlphaWolf {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
living_villagers: Box<[CharacterIdentity]>,
|
living_villagers: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Direwolf)]
|
#[checks(ActionType::Direwolf)]
|
||||||
DireWolf {
|
DireWolf {
|
||||||
character_id: CharacterIdentity,
|
character_id: CharacterIdentity,
|
||||||
living_players: Box<[CharacterIdentity]>,
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionPrompt {
|
impl ActionPrompt {
|
||||||
|
pub fn with_mark(&self, mark: CharacterId) -> Result<ActionPrompt> {
|
||||||
|
let mut prompt = self.clone();
|
||||||
|
match &mut prompt {
|
||||||
|
ActionPrompt::WolvesIntro { .. }
|
||||||
|
| ActionPrompt::RoleChange { .. }
|
||||||
|
| ActionPrompt::Shapeshifter { .. }
|
||||||
|
| ActionPrompt::CoverOfDarkness => Err(GameError::InvalidMessageForGameState),
|
||||||
|
|
||||||
|
ActionPrompt::Guardian {
|
||||||
|
previous,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if !living_players.iter().any(|c| c.character_id == mark)
|
||||||
|
|| previous
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| match p {
|
||||||
|
PreviousGuardianAction::Protect(_) => None,
|
||||||
|
PreviousGuardianAction::Guard(c) => Some(c.character_id == mark),
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
// not in target list OR guarded target previous night
|
||||||
|
return Err(GameError::InvalidTarget);
|
||||||
|
}
|
||||||
|
match marked.as_mut() {
|
||||||
|
Some(marked_cid) => {
|
||||||
|
if marked_cid == &mark {
|
||||||
|
marked.take();
|
||||||
|
} else {
|
||||||
|
marked.replace(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
marked.replace(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(prompt)
|
||||||
|
}
|
||||||
|
ActionPrompt::Arcanist {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if !targets.iter().any(|t| t.character_id == mark) {
|
||||||
|
return Err(GameError::InvalidTarget);
|
||||||
|
}
|
||||||
|
match marked {
|
||||||
|
(None, Some(m)) | (Some(m), None) => {
|
||||||
|
if *m == mark {
|
||||||
|
*marked = (None, None);
|
||||||
|
} else {
|
||||||
|
*marked = (Some(m.clone()), Some(mark));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None) => *marked = (Some(mark), None),
|
||||||
|
(Some(m1), Some(m2)) => {
|
||||||
|
if *m1 == mark {
|
||||||
|
*marked = (Some(m2.clone()), None);
|
||||||
|
} else if *m2 == mark {
|
||||||
|
*marked = (Some(m1.clone()), None);
|
||||||
|
} else {
|
||||||
|
*marked = (Some(m2.clone()), Some(mark));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPrompt::Protector {
|
||||||
|
targets, marked, ..
|
||||||
|
}
|
||||||
|
| ActionPrompt::Seer {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::Gravedigger {
|
||||||
|
dead_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::Hunter {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::Militia {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::MapleWolf {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::WolfPackKill {
|
||||||
|
living_villagers: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::AlphaWolf {
|
||||||
|
living_villagers: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::DireWolf {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if !targets.iter().any(|t| t.character_id == mark) {
|
||||||
|
return Err(GameError::InvalidTarget);
|
||||||
|
}
|
||||||
|
if let Some(marked_char) = marked.as_ref()
|
||||||
|
&& *marked_char == mark
|
||||||
|
{
|
||||||
|
marked.take();
|
||||||
|
} else {
|
||||||
|
marked.replace(mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(prompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_wolfy(&self) -> bool {
|
pub const fn is_wolfy(&self) -> bool {
|
||||||
self.action_type().is_wolfy()
|
self.action_type().is_wolfy()
|
||||||
|| match self {
|
|| match self {
|
||||||
|
|
@ -126,25 +269,22 @@ impl PartialOrd for ActionPrompt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq, Deserialize, ChecksAs)]
|
#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)]
|
||||||
pub enum ActionResponse {
|
pub enum ActionResponse {
|
||||||
Seer(CharacterId),
|
// Seer(CharacterId),
|
||||||
Arcanist(CharacterId, CharacterId),
|
// Arcanist(Option<CharacterId>, Option<CharacterId>),
|
||||||
Gravedigger(CharacterId),
|
// Gravedigger(CharacterId),
|
||||||
Hunter(CharacterId),
|
// Hunter(CharacterId),
|
||||||
Militia(Option<CharacterId>),
|
// Militia(Option<CharacterId>),
|
||||||
MapleWolf(Option<CharacterId>),
|
// MapleWolf(Option<CharacterId>),
|
||||||
Guardian(CharacterId),
|
// Guardian(CharacterId),
|
||||||
WolfPackKillVote(CharacterId),
|
// WolfPackKillVote(CharacterId),
|
||||||
#[checks]
|
// AlphaWolf(Option<CharacterId>),
|
||||||
Shapeshifter(bool),
|
// Direwolf(CharacterId),
|
||||||
AlphaWolf(Option<CharacterId>),
|
// Protector(CharacterId),
|
||||||
Direwolf(CharacterId),
|
MarkTarget(CharacterId),
|
||||||
Protector(CharacterId),
|
Shapeshift,
|
||||||
#[checks]
|
Continue,
|
||||||
RoleChangeAck,
|
|
||||||
WolvesIntroAck,
|
|
||||||
ClearCoverOfDarkness,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
game::{DateTime, Village},
|
game::{DateTime, Village},
|
||||||
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
||||||
modifier::Modifier,
|
modifier::Modifier,
|
||||||
role::{MAPLE_WOLF_ABSTAIN_LIMIT, PreviousGuardianAction, Role, RoleTitle},
|
role::{Alignment, MAPLE_WOLF_ABSTAIN_LIMIT, PreviousGuardianAction, Role, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -173,6 +173,19 @@ impl Character {
|
||||||
&self.role
|
&self.role
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn gravedigger_dig(&self) -> Option<RoleTitle> {
|
||||||
|
match &self.role {
|
||||||
|
Role::Shapeshifter {
|
||||||
|
shifted_into: Some(_),
|
||||||
|
} => None,
|
||||||
|
_ => Some(self.role.title()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn alignment(&self) -> Alignment {
|
||||||
|
self.role.alignment()
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn role_mut(&mut self) -> &mut Role {
|
pub const fn role_mut(&mut self) -> &mut Role {
|
||||||
&mut self.role
|
&mut self.role
|
||||||
}
|
}
|
||||||
|
|
@ -222,22 +235,26 @@ impl Character {
|
||||||
Role::Seer => ActionPrompt::Seer {
|
Role::Seer => ActionPrompt::Seer {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_players: village.living_players_excluding(self.character_id()),
|
living_players: village.living_players_excluding(self.character_id()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Arcanist => ActionPrompt::Arcanist {
|
Role::Arcanist => ActionPrompt::Arcanist {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_players: village.living_players_excluding(self.character_id()),
|
living_players: village.living_players_excluding(self.character_id()),
|
||||||
|
marked: (None, None),
|
||||||
},
|
},
|
||||||
Role::Protector {
|
Role::Protector {
|
||||||
last_protected: Some(last_protected),
|
last_protected: Some(last_protected),
|
||||||
} => ActionPrompt::Protector {
|
} => ActionPrompt::Protector {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
targets: village.living_players_excluding(last_protected),
|
targets: village.living_players_excluding(last_protected),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Protector {
|
Role::Protector {
|
||||||
last_protected: None,
|
last_protected: None,
|
||||||
} => ActionPrompt::Protector {
|
} => ActionPrompt::Protector {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
targets: village.living_players_excluding(self.character_id()),
|
targets: village.living_players_excluding(self.character_id()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Apprentice(role) => {
|
Role::Apprentice(role) => {
|
||||||
let current_night = match village.date_time() {
|
let current_night = match village.date_time() {
|
||||||
|
|
@ -273,17 +290,21 @@ impl Character {
|
||||||
Role::Militia { targeted: None } => ActionPrompt::Militia {
|
Role::Militia { targeted: None } => ActionPrompt::Militia {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_players: village.living_players_excluding(self.character_id()),
|
living_players: village.living_players_excluding(self.character_id()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Werewolf => ActionPrompt::WolfPackKill {
|
Role::Werewolf => ActionPrompt::WolfPackKill {
|
||||||
living_villagers: village.living_players(),
|
living_villagers: village.living_players(),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::AlphaWolf { killed: None } => ActionPrompt::AlphaWolf {
|
Role::AlphaWolf { killed: None } => ActionPrompt::AlphaWolf {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_villagers: village.living_players_excluding(self.character_id()),
|
living_villagers: village.living_players_excluding(self.character_id()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::DireWolf => ActionPrompt::DireWolf {
|
Role::DireWolf => ActionPrompt::DireWolf {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_players: village.living_players(),
|
living_players: village.living_players(),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter {
|
Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
|
|
@ -291,16 +312,19 @@ impl Character {
|
||||||
Role::Gravedigger => ActionPrompt::Gravedigger {
|
Role::Gravedigger => ActionPrompt::Gravedigger {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
dead_players: village.dead_targets(),
|
dead_players: village.dead_targets(),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Hunter { target } => ActionPrompt::Hunter {
|
Role::Hunter { target } => ActionPrompt::Hunter {
|
||||||
character_id: self.identity(),
|
character_id: self.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()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::MapleWolf { last_kill_on_night } => ActionPrompt::MapleWolf {
|
Role::MapleWolf { last_kill_on_night } => ActionPrompt::MapleWolf {
|
||||||
character_id: self.identity(),
|
character_id: self.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()),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Guardian {
|
Role::Guardian {
|
||||||
last_protected: Some(PreviousGuardianAction::Guard(prev_target)),
|
last_protected: Some(PreviousGuardianAction::Guard(prev_target)),
|
||||||
|
|
@ -308,6 +332,7 @@ impl Character {
|
||||||
character_id: self.identity(),
|
character_id: self.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),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Guardian {
|
Role::Guardian {
|
||||||
last_protected: Some(PreviousGuardianAction::Protect(prev_target)),
|
last_protected: Some(PreviousGuardianAction::Protect(prev_target)),
|
||||||
|
|
@ -315,6 +340,7 @@ impl Character {
|
||||||
character_id: self.identity(),
|
character_id: self.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(),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
Role::Guardian {
|
Role::Guardian {
|
||||||
last_protected: None,
|
last_protected: None,
|
||||||
|
|
@ -322,6 +348,7 @@ impl Character {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
previous: None,
|
previous: None,
|
||||||
living_players: village.living_players(),
|
living_players: village.living_players(),
|
||||||
|
marked: None,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ struct Client {
|
||||||
who: String,
|
who: String,
|
||||||
sender: Sender<IdentifiedClientMessage>,
|
sender: Sender<IdentifiedClientMessage>,
|
||||||
receiver: Receiver<ServerMessage>,
|
receiver: Receiver<ServerMessage>,
|
||||||
// message_history: Vec<ServerMessage>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
|
@ -140,7 +139,6 @@ impl Client {
|
||||||
who,
|
who,
|
||||||
sender,
|
sender,
|
||||||
receiver,
|
receiver,
|
||||||
// message_history: Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "cbor")]
|
#[cfg(feature = "cbor")]
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ impl Lobby {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.next_message()
|
.next_message()
|
||||||
.await
|
.await
|
||||||
.expect("get next message");
|
.expect("get next message"); // TODO: keeps happening
|
||||||
|
|
||||||
match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) {
|
match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) {
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
|
|
|
||||||
|
|
@ -863,7 +863,8 @@ input {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-picker {
|
.character-picker,
|
||||||
|
.target-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -891,6 +892,16 @@ input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.dead {
|
||||||
|
$bg: rgba(128, 128, 128, 0.5);
|
||||||
|
background-color: $bg;
|
||||||
|
border: 1px solid color.change($bg, $alpha: 1.0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: color.change($bg, $alpha: 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
background-color: $village_bg;
|
background-color: $village_bg;
|
||||||
border: 1px solid $village_border;
|
border: 1px solid $village_border;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
use werewolves_proto::{
|
||||||
|
message::{CharacterIdentity, PublicIdentity},
|
||||||
|
player::CharacterId,
|
||||||
|
};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::{Button, Identity};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
|
pub struct TargetPickerProps {
|
||||||
|
pub targets: Box<[CharacterIdentity]>,
|
||||||
|
pub marked: Box<[CharacterId]>,
|
||||||
|
pub mark_callback: Option<Callback<CharacterId>>,
|
||||||
|
pub continue_callback: Option<Callback<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn TargetPicker(
|
||||||
|
TargetPickerProps {
|
||||||
|
targets,
|
||||||
|
marked,
|
||||||
|
mark_callback,
|
||||||
|
continue_callback,
|
||||||
|
}: &TargetPickerProps,
|
||||||
|
) -> Html {
|
||||||
|
let targets = targets
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let cb = mark_callback.clone();
|
||||||
|
let marked = marked.contains(&t.character_id);
|
||||||
|
html! {
|
||||||
|
<TargetCard target={t.clone()} marked={marked} mark_callback={cb}/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="target-picker">
|
||||||
|
<div class="targets">
|
||||||
|
{targets}
|
||||||
|
</div>
|
||||||
|
<Button on_click={continue_callback.clone().unwrap_or_default()}>
|
||||||
|
{"continue"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
|
pub struct TargetCardProps {
|
||||||
|
pub target: CharacterIdentity,
|
||||||
|
pub marked: bool,
|
||||||
|
pub mark_callback: Option<Callback<CharacterId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn TargetCard(
|
||||||
|
TargetCardProps {
|
||||||
|
target,
|
||||||
|
marked,
|
||||||
|
mark_callback,
|
||||||
|
}: &TargetCardProps,
|
||||||
|
) -> Html {
|
||||||
|
let click_target = target.character_id.clone();
|
||||||
|
let on_click = mark_callback
|
||||||
|
.clone()
|
||||||
|
.map(|cb| Callback::from(move |_| cb.emit(click_target.clone())))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let marked = marked.then_some("marked");
|
||||||
|
let ident: PublicIdentity = target.into();
|
||||||
|
html! {
|
||||||
|
<Button on_click={on_click} classes={classes!(marked, "character")}>
|
||||||
|
<Identity ident={ident}/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ use yew::prelude::*;
|
||||||
|
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
Button, CoverOfDarkness, Identity,
|
Button, CoverOfDarkness, Identity,
|
||||||
action::{BinaryChoice, OptionalSingleTarget, SingleTarget, TwoTarget, WolvesIntro},
|
action::{BinaryChoice, TargetPicker, WolvesIntro},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
|
|
@ -41,241 +41,62 @@ fn identity_html(props: &ActionPromptProps, ident: Option<&CharacterIdentity>) -
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn Prompt(props: &ActionPromptProps) -> Html {
|
pub fn Prompt(props: &ActionPromptProps) -> Html {
|
||||||
match &props.prompt {
|
|
||||||
ActionPrompt::CoverOfDarkness => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
let on_complete = props.on_complete.clone();
|
||||||
let next = props.big_screen.not().then(|| {
|
let continue_callback = props.big_screen.not().then(|| {
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
HostNightMessage::ActionResponse(ActionResponse::ClearCoverOfDarkness),
|
HostNightMessage::ActionResponse(ActionResponse::Continue),
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
return html! {
|
|
||||||
<CoverOfDarkness next={next} />
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ActionPrompt::WolvesIntro { wolves } => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
let on_complete = props.on_complete.clone();
|
||||||
let on_complete = Callback::from(move |_| {
|
let mark_callback = props.big_screen.not().then(|| {
|
||||||
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 {
|
|
||||||
character_id,
|
|
||||||
living_players,
|
|
||||||
} => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
|
||||||
let on_select = props.big_screen.not().then(|| {
|
|
||||||
Callback::from(move |target: CharacterId| {
|
Callback::from(move |target: CharacterId| {
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Seer(target)),
|
HostNightMessage::ActionResponse(ActionResponse::MarkTarget(target)),
|
||||||
)));
|
)));
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
html! {
|
let (character_id, targets, marked, role_info) = match &props.prompt {
|
||||||
<div>
|
ActionPrompt::CoverOfDarkness => {
|
||||||
{identity_html(props, Some(&character_id))}
|
return html! {
|
||||||
<SingleTarget
|
<CoverOfDarkness next={continue_callback}/>
|
||||||
targets={living_players.clone()}
|
};
|
||||||
target_selection={on_select}
|
|
||||||
headline={"check alignment"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
ActionPrompt::WolvesIntro { wolves } => {
|
||||||
|
return html! {
|
||||||
|
<WolvesIntro
|
||||||
|
on_complete={continue_callback}
|
||||||
|
wolves={wolves.clone()}
|
||||||
|
/>
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ActionPrompt::RoleChange {
|
ActionPrompt::RoleChange {
|
||||||
character_id,
|
character_id,
|
||||||
new_role,
|
new_role,
|
||||||
} => {
|
} => {
|
||||||
let on_complete = props.on_complete.clone();
|
let cont = continue_callback.map(|continue_callback| {
|
||||||
let on_click = Callback::from(move |_| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::RoleChangeAck),
|
|
||||||
)))
|
|
||||||
});
|
|
||||||
let cont = props.big_screen.not().then(|| {
|
|
||||||
html! {
|
html! {
|
||||||
<Button on_click={on_click}>{"continue"}</Button>
|
<Button on_click={continue_callback}>
|
||||||
|
{"continue"}
|
||||||
|
</Button>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
html! {
|
return html! {
|
||||||
<div>
|
<div class="role-change">
|
||||||
{identity_html(props, Some(&character_id))}
|
{identity_html(props, Some(character_id))}
|
||||||
<h2>{"your role has changed"}</h2>
|
<h2>{"your role has changed"}</h2>
|
||||||
<p>{new_role.to_string()}</p>
|
<h1>{new_role.to_string()}</h1>
|
||||||
{cont}
|
{cont}
|
||||||
</div>
|
</div>
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Protector(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<SingleTarget
|
|
||||||
targets={targets.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"protector"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Arcanist(t1, t2)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<TwoTarget
|
|
||||||
targets={living_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"arcanist"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Gravedigger(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<SingleTarget
|
|
||||||
targets={dead_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"gravedigger"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionPrompt::Hunter {
|
|
||||||
character_id,
|
|
||||||
current_target,
|
|
||||||
living_players,
|
|
||||||
} => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
|
||||||
let on_select = props.big_screen.not().then(|| {
|
|
||||||
Callback::from(move |target: CharacterId| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Hunter(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<SingleTarget
|
|
||||||
targets={living_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"hunter"}
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
<b>{"current target: "}</b>{current_target.clone().map(|t| html!{
|
|
||||||
<Identity ident={Into::<PublicIdentity>::into(t)} />
|
|
||||||
}).unwrap_or_else(|| html!{<i>{"none"}</i>})}
|
|
||||||
</h3>
|
|
||||||
</SingleTarget>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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>| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Militia(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<OptionalSingleTarget
|
|
||||||
targets={living_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"pew pew?"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionPrompt::MapleWolf {
|
|
||||||
character_id,
|
|
||||||
kill_or_die,
|
|
||||||
living_players,
|
|
||||||
} => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
|
||||||
let on_select = props.big_screen.not().then(|| {
|
|
||||||
Callback::from(move |target: Option<CharacterId>| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::MapleWolf(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let kill_or_die = kill_or_die.then(|| {
|
|
||||||
html! {
|
|
||||||
<em>{"if you fail to eat tonight, you will starve"}</em>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<OptionalSingleTarget
|
|
||||||
targets={living_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"nom nom?"}
|
|
||||||
>
|
|
||||||
{kill_or_die}
|
|
||||||
</OptionalSingleTarget>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionPrompt::Guardian {
|
ActionPrompt::Guardian {
|
||||||
character_id,
|
character_id,
|
||||||
previous,
|
previous,
|
||||||
living_players,
|
living_players,
|
||||||
|
marked,
|
||||||
} => {
|
} => {
|
||||||
let last_protect = previous.as_ref().map(|prev| match prev {
|
let last_protect = previous.as_ref().map(|prev| match prev {
|
||||||
PreviousGuardianAction::Protect(target) => {
|
PreviousGuardianAction::Protect(target) => {
|
||||||
|
|
@ -293,108 +114,515 @@ pub fn Prompt(props: &ActionPromptProps) -> Html {
|
||||||
</>
|
</>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let on_complete = props.on_complete.clone();
|
let marked = marked.iter().cloned().collect::<Box<[CharacterId]>>();
|
||||||
let on_select = props.big_screen.not().then_some({
|
|
||||||
move |prot| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Guardian(prot)),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
return html! {
|
||||||
<div>
|
<div>
|
||||||
{identity_html(props, Some(&character_id))}
|
{identity_html(props, Some(character_id))}
|
||||||
<SingleTarget
|
<h2>{"guardian"}</h2>
|
||||||
targets={living_players.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"pick someone to protect"}
|
|
||||||
>
|
|
||||||
{last_protect}
|
{last_protect}
|
||||||
</SingleTarget>
|
<TargetPicker
|
||||||
|
targets={living_players.clone()}
|
||||||
|
marked={marked}
|
||||||
|
mark_callback={mark_callback}
|
||||||
|
continue_callback={continue_callback}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActionPrompt::Seer {
|
||||||
|
character_id,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
living_players,
|
||||||
|
marked.iter().cloned().collect::<Box<[CharacterId]>>(),
|
||||||
|
html! {{"seer"}},
|
||||||
|
),
|
||||||
|
ActionPrompt::Protector {
|
||||||
|
character_id,
|
||||||
|
targets,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
targets,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
html! {{"protector"}},
|
||||||
|
),
|
||||||
|
ActionPrompt::Arcanist {
|
||||||
|
character_id,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
living_players,
|
||||||
|
[&marked.0, &marked.1]
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| (*c).clone())
|
||||||
|
.collect(),
|
||||||
|
html! {{"arcanist"}},
|
||||||
|
),
|
||||||
|
ActionPrompt::Gravedigger {
|
||||||
|
character_id,
|
||||||
|
dead_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
dead_players,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
html! {{"gravedigger"}},
|
||||||
|
),
|
||||||
|
ActionPrompt::Hunter {
|
||||||
|
character_id,
|
||||||
|
current_target,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
living_players,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
{
|
||||||
|
let current_target = current_target.as_ref().cloned().map(|t| {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<h3>{"current target:"}</h3>
|
||||||
|
<Identity ident={Into::<PublicIdentity>::into(t)} />
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
ActionPrompt::WolfPackKill { living_villagers } => {
|
|
||||||
let on_complete = props.on_complete.clone();
|
|
||||||
let on_select = props.big_screen.not().then(|| {
|
|
||||||
Callback::from(move |target: CharacterId| {
|
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
|
||||||
HostNightMessage::ActionResponse(ActionResponse::WolfPackKillVote(target)),
|
|
||||||
)));
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
html! {
|
html! {
|
||||||
<SingleTarget
|
<>
|
||||||
targets={living_villagers.clone()}
|
<h2>{"hunter"}</h2>
|
||||||
target_selection={on_select}
|
{current_target}
|
||||||
headline={"wolf pack kill"}
|
</>
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionPrompt::Militia {
|
||||||
|
character_id,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
living_players,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
html! {{"militia"}},
|
||||||
|
),
|
||||||
|
ActionPrompt::MapleWolf {
|
||||||
|
character_id,
|
||||||
|
kill_or_die,
|
||||||
|
living_players,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
Some(character_id),
|
||||||
|
living_players,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
html! {<>{"maple wolf"} {kill_or_die.then_some(" — starving")}</>},
|
||||||
|
),
|
||||||
|
ActionPrompt::WolfPackKill {
|
||||||
|
living_villagers,
|
||||||
|
marked,
|
||||||
|
} => (
|
||||||
|
None,
|
||||||
|
living_villagers,
|
||||||
|
marked.iter().cloned().collect(),
|
||||||
|
html! {{"wolfpack kill"}},
|
||||||
|
),
|
||||||
ActionPrompt::Shapeshifter { character_id } => {
|
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| {
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Shapeshifter(shift)),
|
HostNightMessage::ActionResponse(if shift {
|
||||||
|
ActionResponse::Shapeshift
|
||||||
|
} else {
|
||||||
|
ActionResponse::Continue
|
||||||
|
}),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
html! {
|
return html! {
|
||||||
<div>
|
<div>
|
||||||
{identity_html(props, Some(&character_id))}
|
{identity_html(props, Some(character_id))}
|
||||||
<BinaryChoice on_chosen={on_select}>
|
<BinaryChoice on_chosen={on_select}>
|
||||||
<h2>{"shapeshift?"}</h2>
|
<h2>{"shapeshift?"}</h2>
|
||||||
</BinaryChoice>
|
</BinaryChoice>
|
||||||
</div>
|
</div>
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
ActionPrompt::AlphaWolf {
|
ActionPrompt::AlphaWolf {
|
||||||
character_id,
|
character_id,
|
||||||
living_villagers,
|
living_villagers,
|
||||||
} => {
|
marked,
|
||||||
let on_complete = props.on_complete.clone();
|
} => (
|
||||||
let on_select = props.big_screen.not().then(|| {
|
Some(character_id),
|
||||||
Callback::from(move |target: Option<CharacterId>| {
|
living_villagers,
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
marked.iter().cloned().collect(),
|
||||||
HostNightMessage::ActionResponse(ActionResponse::AlphaWolf(target)),
|
html! {{"alpha wolf"}},
|
||||||
)));
|
),
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
{identity_html(props, Some(&character_id))}
|
|
||||||
<OptionalSingleTarget
|
|
||||||
targets={living_villagers.clone()}
|
|
||||||
target_selection={on_select}
|
|
||||||
headline={"alpha wolf target"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionPrompt::DireWolf {
|
ActionPrompt::DireWolf {
|
||||||
character_id,
|
character_id,
|
||||||
living_players,
|
living_players,
|
||||||
} => {
|
marked,
|
||||||
let on_complete = props.on_complete.clone();
|
} => (
|
||||||
let on_select = props.big_screen.not().then(|| {
|
Some(character_id),
|
||||||
Callback::from(move |target: CharacterId| {
|
living_players,
|
||||||
on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
marked.iter().cloned().collect(),
|
||||||
HostNightMessage::ActionResponse(ActionResponse::Direwolf(target)),
|
html! {{"dire wolf"}},
|
||||||
)));
|
),
|
||||||
})
|
};
|
||||||
});
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div class="prompt">
|
||||||
{identity_html(props, Some(&character_id))}
|
{identity_html(props, character_id)}
|
||||||
<SingleTarget
|
<h2>{role_info}</h2>
|
||||||
targets={living_players.clone()}
|
<TargetPicker
|
||||||
target_selection={on_select}
|
targets={targets.clone()}
|
||||||
headline={"direwolf block target"}
|
marked={marked}
|
||||||
|
mark_callback={mark_callback}
|
||||||
|
continue_callback={continue_callback}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// 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::ActionResponse(ActionResponse::ClearCoverOfDarkness),
|
||||||
|
// )))
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// 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(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 {
|
||||||
|
// character_id,
|
||||||
|
// living_players,
|
||||||
|
// } => {
|
||||||
|
// let on_complete = props.on_complete.clone();
|
||||||
|
// let on_select = props.big_screen.not().then(|| {
|
||||||
|
// Callback::from(move |target: CharacterId| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Seer(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"check alignment"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::RoleChangeAck),
|
||||||
|
// )))
|
||||||
|
// });
|
||||||
|
// let cont = props.big_screen.not().then(|| {
|
||||||
|
// html! {
|
||||||
|
// <Button on_click={on_click}>{"continue"}</Button>
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <h2>{"your role has changed"}</h2>
|
||||||
|
// <p>{new_role.to_string()}</p>
|
||||||
|
// {cont}
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Protector(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={targets.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"protector"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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)| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Arcanist(t1, t2)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <TwoTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"arcanist"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Gravedigger(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={dead_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"gravedigger"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ActionPrompt::Hunter {
|
||||||
|
// character_id,
|
||||||
|
// current_target,
|
||||||
|
// living_players,
|
||||||
|
// } => {
|
||||||
|
// let on_complete = props.on_complete.clone();
|
||||||
|
// let on_select = props.big_screen.not().then(|| {
|
||||||
|
// Callback::from(move |target: CharacterId| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Hunter(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"hunter"}
|
||||||
|
// >
|
||||||
|
// <h3>
|
||||||
|
// <b>{"current target: "}</b>{current_target.clone().map(|t| html!{
|
||||||
|
// <Identity ident={Into::<PublicIdentity>::into(t)} />
|
||||||
|
// }).unwrap_or_else(|| html!{<i>{"none"}</i>})}
|
||||||
|
// </h3>
|
||||||
|
// </SingleTarget>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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>| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Militia(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <OptionalSingleTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"pew pew?"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ActionPrompt::MapleWolf {
|
||||||
|
// character_id,
|
||||||
|
// kill_or_die,
|
||||||
|
// living_players,
|
||||||
|
// marked,
|
||||||
|
// } => {
|
||||||
|
// let kill_or_die = kill_or_die.then(|| {
|
||||||
|
// html! {
|
||||||
|
// <em>{"if you fail to eat tonight, you will starve"}</em>
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <OptionalSingleTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"nom nom?"}
|
||||||
|
// >
|
||||||
|
// {kill_or_die}
|
||||||
|
// </OptionalSingleTarget>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ActionPrompt::Guardian {
|
||||||
|
// character_id,
|
||||||
|
// previous,
|
||||||
|
// living_players,
|
||||||
|
// marked,
|
||||||
|
// } => {
|
||||||
|
// let last_protect = previous.as_ref().map(|prev| match prev {
|
||||||
|
// PreviousGuardianAction::Protect(target) => {
|
||||||
|
// html! {
|
||||||
|
// <>
|
||||||
|
// <b>{"last night you protected: "}</b>
|
||||||
|
// <Identity ident={Into::<PublicIdentity>::into(target)}/>
|
||||||
|
// </>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// PreviousGuardianAction::Guard(target) => html! {
|
||||||
|
// <>
|
||||||
|
// <b>{"last night you guarded: "}</b>
|
||||||
|
// <Identity ident={Into::<PublicIdentity>::into(target)}/>
|
||||||
|
// </>
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// let marked = marked.iter().cloned().collect();
|
||||||
|
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <h2>{"guardian"}</h2>
|
||||||
|
// {last_protect}
|
||||||
|
// <TargetPicker
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// marked={marked}
|
||||||
|
// mark_callback={mark_callback}
|
||||||
|
// continue_callback={continue_callback}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ActionPrompt::WolfPackKill { living_villagers } => {
|
||||||
|
// let on_complete = props.on_complete.clone();
|
||||||
|
|
||||||
|
// html! {
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={living_villagers.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"wolf pack kill"}
|
||||||
|
// />
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ActionPrompt::Shapeshifter { character_id } => {
|
||||||
|
// let on_complete = props.on_complete.clone();
|
||||||
|
// let on_select = props.big_screen.not().then_some({
|
||||||
|
// move |shift| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Shapeshifter(shift)),
|
||||||
|
// )));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <BinaryChoice on_chosen={on_select}>
|
||||||
|
// <h2>{"shapeshift?"}</h2>
|
||||||
|
// </BinaryChoice>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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>| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::AlphaWolf(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <OptionalSingleTarget
|
||||||
|
// targets={living_villagers.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"alpha wolf target"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 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| {
|
||||||
|
// on_complete.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
// HostNightMessage::ActionResponse(ActionResponse::Direwolf(target)),
|
||||||
|
// )));
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// {identity_html(props, Some(&character_id))}
|
||||||
|
// <SingleTarget
|
||||||
|
// targets={living_players.clone()}
|
||||||
|
// target_selection={on_select}
|
||||||
|
// headline={"direwolf block target"}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,13 @@ use werewolves_proto::{
|
||||||
};
|
};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::components::{CoverOfDarkness, Identity};
|
use crate::components::{Button, CoverOfDarkness, Identity};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
pub struct ActionResultProps {
|
pub struct ActionResultProps {
|
||||||
pub result: ActionResult,
|
pub result: ActionResult,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub ident: Option<PublicIdentity>,
|
pub ident: Option<PublicIdentity>,
|
||||||
#[prop_or_default]
|
|
||||||
pub big_screen: bool,
|
pub big_screen: bool,
|
||||||
pub on_complete: Callback<HostMessage>,
|
pub on_complete: Callback<HostMessage>,
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +39,7 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html {
|
||||||
let cont = props
|
let cont = props
|
||||||
.big_screen
|
.big_screen
|
||||||
.not()
|
.not()
|
||||||
.then(|| html! {<button onclick={on_complete}>{"continue"}</button>});
|
.then(|| html! {<Button on_click={on_complete}>{"continue"}</Button>});
|
||||||
match &props.result {
|
match &props.result {
|
||||||
ActionResult::RoleBlocked => {
|
ActionResult::RoleBlocked => {
|
||||||
html! {
|
html! {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use core::ops::Not;
|
|
||||||
|
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
message::{CharacterIdentity, PublicIdentity},
|
message::{CharacterIdentity, PublicIdentity},
|
||||||
role::RoleTitle,
|
role::RoleTitle,
|
||||||
|
|
@ -11,14 +9,18 @@ use crate::components::{Button, Identity};
|
||||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
pub struct WolvesIntroProps {
|
pub struct WolvesIntroProps {
|
||||||
pub wolves: Box<[(CharacterIdentity, RoleTitle)]>,
|
pub wolves: Box<[(CharacterIdentity, RoleTitle)]>,
|
||||||
pub big_screen: bool,
|
pub on_complete: Option<Callback<()>>,
|
||||||
pub on_complete: Callback<()>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn WolvesIntro(props: &WolvesIntroProps) -> Html {
|
pub fn WolvesIntro(props: &WolvesIntroProps) -> Html {
|
||||||
let on_complete = props.on_complete.clone();
|
let on_complete = props.on_complete.clone().map(|on_complete| {
|
||||||
let on_complete = Callback::from(move |_| on_complete.emit(()));
|
html! {
|
||||||
|
<Button on_click={Callback::from(move |_| on_complete.emit(()))}>
|
||||||
|
{"continue"}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
});
|
||||||
html! {
|
html! {
|
||||||
<div class="wolves-intro">
|
<div class="wolves-intro">
|
||||||
<h2>{"these are the wolves:"}</h2>
|
<h2>{"these are the wolves:"}</h2>
|
||||||
|
|
@ -32,11 +34,7 @@ pub fn WolvesIntro(props: &WolvesIntroProps) -> Html {
|
||||||
}).collect::<Html>()
|
}).collect::<Html>()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{on_complete}
|
||||||
props.big_screen.not().then_some(html!{
|
|
||||||
<Button on_click={on_complete}>{"continue"}</Button>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,14 @@ pub fn DaytimePlayer(
|
||||||
let dead = died_to.is_some().then_some("dead");
|
let dead = died_to.is_some().then_some("dead");
|
||||||
let marked = on_the_block.then_some("marked");
|
let marked = on_the_block.then_some("marked");
|
||||||
let character_id = identity.character_id.clone();
|
let character_id = identity.character_id.clone();
|
||||||
let on_click: Callback<_> = on_select
|
let on_click: Callback<_> = died_to
|
||||||
|
.is_none()
|
||||||
|
.then_some(())
|
||||||
|
.and(
|
||||||
|
on_select
|
||||||
.clone()
|
.clone()
|
||||||
.map(|on_select| Callback::from(move |_| on_select.emit(character_id.clone())))
|
.map(|on_select| Callback::from(move |_| on_select.emit(character_id.clone()))),
|
||||||
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let identity: PublicIdentity = identity.into();
|
let identity: PublicIdentity = identity.into();
|
||||||
html! {
|
html! {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue