Compare commits
2 Commits
563a3c5c40
...
08db7f9bfc
| Author | SHA1 | Date |
|---|---|---|
|
|
08db7f9bfc | |
|
|
42a240b0ea |
|
|
@ -56,6 +56,10 @@ impl Game {
|
|||
|
||||
pub fn process(&mut self, message: HostGameMessage) -> Result<ServerToHostMessage> {
|
||||
match (&mut self.state, message) {
|
||||
(GameState::Night { night }, HostGameMessage::Night(HostNightMessage::NextPage)) => {
|
||||
night.next_page();
|
||||
self.process(HostGameMessage::GetState)
|
||||
}
|
||||
(GameState::Night { night }, HostGameMessage::Night(HostNightMessage::Next)) => {
|
||||
night.next()?;
|
||||
self.process(HostGameMessage::GetState)
|
||||
|
|
@ -106,6 +110,7 @@ impl Game {
|
|||
DateTime::Day { number } => number,
|
||||
DateTime::Night { number: _ } => unreachable!(),
|
||||
},
|
||||
settings: village.settings(),
|
||||
})
|
||||
}
|
||||
(GameState::Night { night }, HostGameMessage::GetState) => {
|
||||
|
|
@ -115,8 +120,8 @@ impl Game {
|
|||
res.clone(),
|
||||
));
|
||||
}
|
||||
if let Some(prompt) = night.current_prompt() {
|
||||
return Ok(ServerToHostMessage::ActionPrompt(prompt.clone()));
|
||||
if let Some((prompt, page)) = night.current_prompt() {
|
||||
return Ok(ServerToHostMessage::ActionPrompt(prompt.clone(), page));
|
||||
}
|
||||
match night.next() {
|
||||
Ok(_) => self.process(HostGameMessage::GetState),
|
||||
|
|
@ -137,7 +142,10 @@ impl Game {
|
|||
GameState::Night { night },
|
||||
HostGameMessage::Night(HostNightMessage::ActionResponse(resp)),
|
||||
) => match night.received_response(resp.clone()) {
|
||||
Ok(ServerAction::Prompt(prompt)) => Ok(ServerToHostMessage::ActionPrompt(prompt)),
|
||||
Ok(ServerAction::Prompt(prompt)) => Ok(ServerToHostMessage::ActionPrompt(
|
||||
prompt,
|
||||
night.page().unwrap_or_default(),
|
||||
)),
|
||||
Ok(ServerAction::Result(res)) => Ok(ServerToHostMessage::ActionResult(
|
||||
night.current_character().map(|c| c.identity()),
|
||||
res,
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ enum NightState {
|
|||
current_prompt: ActionPrompt,
|
||||
current_result: Option<ActionResult>,
|
||||
current_changes: Vec<NightChange>,
|
||||
current_page: usize,
|
||||
},
|
||||
Complete,
|
||||
}
|
||||
|
|
@ -294,6 +295,7 @@ impl Night {
|
|||
current_prompt: ActionPrompt::CoverOfDarkness,
|
||||
current_changes: Vec::new(),
|
||||
current_result: None,
|
||||
current_page: 0,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -341,7 +343,15 @@ impl Night {
|
|||
current_prompt,
|
||||
current_result,
|
||||
current_changes,
|
||||
} => (current_prompt, current_result, current_changes),
|
||||
current_page,
|
||||
} => {
|
||||
if let Some(page_back) = current_page.checked_sub(1) {
|
||||
*current_page = page_back;
|
||||
return Ok(());
|
||||
} else {
|
||||
(current_prompt, current_result, current_changes)
|
||||
}
|
||||
}
|
||||
NightState::Complete => return Err(GameError::NightOver),
|
||||
};
|
||||
if let Some((prompt, _, changes)) = self.used_actions.pop() {
|
||||
|
|
@ -621,6 +631,13 @@ impl Night {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub const fn page(&self) -> Option<usize> {
|
||||
match &self.night_state {
|
||||
NightState::Active { current_page, .. } => Some(*current_page),
|
||||
NightState::Complete => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn received_response(&mut self, resp: ActionResponse) -> Result<ServerAction> {
|
||||
match self.received_response_with_role_blocks(resp)? {
|
||||
BlockResolvedOutcome::PromptUpdate(prompt) => match &mut self.night_state {
|
||||
|
|
@ -643,7 +660,7 @@ impl Night {
|
|||
} => current_result.replace(result.clone()),
|
||||
NightState::Complete => return Err(GameError::NightOver),
|
||||
};
|
||||
if let NightChange::Shapeshift { source, into } = &change {
|
||||
if let NightChange::Shapeshift { source, .. } = &change {
|
||||
// needs to be resolved _now_ so that the target can be woken
|
||||
// for the role change with the wolves
|
||||
self.apply_shapeshift(source)?;
|
||||
|
|
@ -692,7 +709,7 @@ impl Night {
|
|||
) -> Result<ResponseOutcome> {
|
||||
let (current_cover, current_wolfy) = self
|
||||
.current_prompt()
|
||||
.map(|current_prompt| {
|
||||
.map(|(current_prompt, _)| {
|
||||
(
|
||||
*current_prompt == ActionPrompt::CoverOfDarkness,
|
||||
current_prompt.is_wolfy(),
|
||||
|
|
@ -752,7 +769,12 @@ impl Night {
|
|||
match self.received_response_consecutive_wolves_dont_sleep(resp)? {
|
||||
ResponseOutcome::PromptUpdate(update) => Ok(BlockResolvedOutcome::PromptUpdate(update)),
|
||||
ResponseOutcome::ActionComplete(ActionComplete { result, change }) => {
|
||||
match self.current_prompt().ok_or(GameError::NightOver)?.unless() {
|
||||
match self
|
||||
.current_prompt()
|
||||
.ok_or(GameError::NightOver)?
|
||||
.0
|
||||
.unless()
|
||||
{
|
||||
Some(Unless::TargetBlocked(unless_blocked)) => {
|
||||
if self.changes_from_actions().into_iter().any(|c| match c {
|
||||
NightChange::RoleBlock {
|
||||
|
|
@ -1285,13 +1307,14 @@ impl Night {
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn current_prompt(&self) -> Option<&ActionPrompt> {
|
||||
pub const fn current_prompt(&self) -> Option<(&ActionPrompt, usize)> {
|
||||
match &self.night_state {
|
||||
NightState::Active {
|
||||
current_prompt,
|
||||
current_result: _,
|
||||
current_page,
|
||||
..
|
||||
} => Some(current_prompt),
|
||||
} => Some((current_prompt, *current_page)),
|
||||
NightState::Complete => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1351,6 +1374,7 @@ impl Night {
|
|||
current_prompt,
|
||||
current_result: Some(result),
|
||||
current_changes,
|
||||
..
|
||||
} => {
|
||||
self.used_actions.push((
|
||||
current_prompt.clone(),
|
||||
|
|
@ -1377,6 +1401,7 @@ impl Night {
|
|||
current_prompt: prompt,
|
||||
current_result: None,
|
||||
current_changes: Vec::new(),
|
||||
current_page: 0,
|
||||
};
|
||||
} else {
|
||||
self.night_state = NightState::Complete;
|
||||
|
|
@ -1534,6 +1559,13 @@ impl Night {
|
|||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn next_page(&mut self) {
|
||||
match &mut self.night_state {
|
||||
NightState::Active { current_page, .. } => *current_page += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ServerAction {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use core::num::NonZeroU8;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -18,6 +19,7 @@ use crate::{
|
|||
pub struct Village {
|
||||
characters: Box<[Character]>,
|
||||
date_time: DateTime,
|
||||
settings: GameSettings,
|
||||
}
|
||||
|
||||
impl Village {
|
||||
|
|
@ -33,11 +35,16 @@ impl Village {
|
|||
characters.sort_by_key(|l| l.number());
|
||||
|
||||
Ok(Self {
|
||||
settings,
|
||||
characters,
|
||||
date_time: DateTime::Night { number: 0 },
|
||||
})
|
||||
}
|
||||
|
||||
pub fn settings(&self) -> GameSettings {
|
||||
self.settings.clone()
|
||||
}
|
||||
|
||||
pub fn killing_wolf(&self) -> Option<&Character> {
|
||||
let wolves = self.characters.iter().filter(|c| c.is_wolf());
|
||||
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ impl ServerToHostMessageExt for ServerToHostMessage {
|
|||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
resp => panic!("expected daytime, got {resp:?}"),
|
||||
}
|
||||
|
|
@ -216,12 +217,8 @@ impl ServerToHostMessageExt for ServerToHostMessage {
|
|||
|
||||
fn prompt(self) -> ActionPrompt {
|
||||
match self {
|
||||
Self::ActionPrompt(prompt) => prompt,
|
||||
Self::Daytime {
|
||||
characters: _,
|
||||
marked: _,
|
||||
day: _,
|
||||
} => panic!("{}", "[got daytime]".bold().red()),
|
||||
Self::ActionPrompt(prompt, _) => prompt,
|
||||
Self::Daytime { .. } => panic!("{}", "[got daytime]".bold().red()),
|
||||
msg => panic!("expected server message <<{msg:?}>> to be an ActionPrompt"),
|
||||
}
|
||||
}
|
||||
|
|
@ -447,6 +444,7 @@ impl GameExt for Game {
|
|||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
res => panic!("unexpected response to next_expect_day: {res:?}"),
|
||||
}
|
||||
|
|
@ -474,6 +472,7 @@ impl GameExt for Game {
|
|||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
res => panic!("unexpected response to mark_for_execution: {res:?}"),
|
||||
}
|
||||
|
|
@ -547,7 +546,7 @@ fn starts_with_wolf_intro() {
|
|||
let resp = game.process(HostGameMessage::GetState).unwrap();
|
||||
assert_eq!(
|
||||
resp,
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness)
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness, 0)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -578,7 +577,7 @@ fn no_wolf_kill_n1() {
|
|||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ }, 0)
|
||||
));
|
||||
assert_eq!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
|
|
@ -587,15 +586,7 @@ fn no_wolf_kill_n1() {
|
|||
.unwrap(),
|
||||
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
||||
);
|
||||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::Daytime {
|
||||
characters: _,
|
||||
marked: _,
|
||||
day: _,
|
||||
}
|
||||
));
|
||||
game.next_expect_day();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -616,7 +607,7 @@ fn yes_wolf_kill_n2() {
|
|||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ }, 0)
|
||||
));
|
||||
assert_eq!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
|
|
@ -626,15 +617,7 @@ fn yes_wolf_kill_n2() {
|
|||
.result(),
|
||||
ActionResult::GoBackToSleep,
|
||||
);
|
||||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::Daytime {
|
||||
characters: _,
|
||||
marked: _,
|
||||
day: _,
|
||||
}
|
||||
));
|
||||
game.next_expect_day();
|
||||
|
||||
let execution_target = game
|
||||
.village()
|
||||
|
|
@ -653,6 +636,7 @@ fn yes_wolf_kill_n2() {
|
|||
characters: _,
|
||||
marked,
|
||||
day: _,
|
||||
..
|
||||
} => assert_eq!(marked.to_vec(), vec![execution_target]),
|
||||
resp => panic!("unexpected server message: {resp:#?}"),
|
||||
}
|
||||
|
|
@ -660,7 +644,7 @@ fn yes_wolf_kill_n2() {
|
|||
assert_eq!(
|
||||
game.process(HostGameMessage::Day(HostDayMessage::Execute))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness)
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::CoverOfDarkness, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
|
|
@ -673,10 +657,13 @@ fn yes_wolf_kill_n2() {
|
|||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolfPackKill {
|
||||
living_villagers: _,
|
||||
marked: _,
|
||||
})
|
||||
ServerToHostMessage::ActionPrompt(
|
||||
ActionPrompt::WolfPackKill {
|
||||
living_villagers: _,
|
||||
marked: _,
|
||||
},
|
||||
0
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -720,6 +707,7 @@ fn wolfpack_kill_all_targets_valid() {
|
|||
characters: _,
|
||||
marked,
|
||||
day: _,
|
||||
..
|
||||
} => assert_eq!(marked.to_vec(), vec![execution_target]),
|
||||
resp => panic!("unexpected server message: {resp:#?}"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,14 @@ fn previous_shapeshifter_undone_redone() {
|
|||
}
|
||||
assert_eq!(
|
||||
game.get_state(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::Shapeshifter {
|
||||
character_id: game
|
||||
.character_by_player_id(shapeshifter_player_id)
|
||||
.identity()
|
||||
})
|
||||
ServerToHostMessage::ActionPrompt(
|
||||
ActionPrompt::Shapeshifter {
|
||||
character_id: game
|
||||
.character_by_player_id(shapeshifter_player_id)
|
||||
.identity()
|
||||
},
|
||||
0
|
||||
)
|
||||
);
|
||||
game.response(ActionResponse::Shapeshift).r#continue();
|
||||
assert_eq!(
|
||||
|
|
@ -104,11 +107,14 @@ fn previous_shapeshifter_undone_and_changed_to_no() {
|
|||
}
|
||||
assert_eq!(
|
||||
game.get_state(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::Shapeshifter {
|
||||
character_id: game
|
||||
.character_by_player_id(shapeshifter_player_id)
|
||||
.identity()
|
||||
})
|
||||
ServerToHostMessage::ActionPrompt(
|
||||
ActionPrompt::Shapeshifter {
|
||||
character_id: game
|
||||
.character_by_player_id(shapeshifter_player_id)
|
||||
.identity()
|
||||
},
|
||||
0
|
||||
)
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
|
|
|
|||
|
|
@ -40,11 +40,7 @@ fn protect_stops_shapeshift() {
|
|||
.unwrap(),
|
||||
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
|
||||
);
|
||||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::WolvesIntro { wolves: _ })
|
||||
));
|
||||
game.next().title().wolves_intro();
|
||||
assert_eq!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
ActionResponse::Continue
|
||||
|
|
@ -52,15 +48,7 @@ fn protect_stops_shapeshift() {
|
|||
.unwrap(),
|
||||
ServerToHostMessage::ActionResult(None, ActionResult::GoBackToSleep),
|
||||
);
|
||||
assert!(matches!(
|
||||
game.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap(),
|
||||
ServerToHostMessage::Daytime {
|
||||
characters: _,
|
||||
marked: _,
|
||||
day: _,
|
||||
}
|
||||
));
|
||||
game.next_expect_day();
|
||||
|
||||
let execution_target = game
|
||||
.village()
|
||||
|
|
@ -75,11 +63,9 @@ fn protect_stops_shapeshift() {
|
|||
)))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::Daytime {
|
||||
characters: _,
|
||||
marked,
|
||||
day: _,
|
||||
} => assert_eq!(marked.to_vec(), vec![execution_target]),
|
||||
ServerToHostMessage::Daytime { marked, .. } => {
|
||||
assert_eq!(marked.to_vec(), vec![execution_target])
|
||||
}
|
||||
resp => panic!("unexpected server message: {resp:#?}"),
|
||||
}
|
||||
|
||||
|
|
@ -96,11 +82,14 @@ fn protect_stops_shapeshift() {
|
|||
.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
|
||||
character_id: prot_char_id,
|
||||
targets,
|
||||
marked: None,
|
||||
}) => (
|
||||
ServerToHostMessage::ActionPrompt(
|
||||
ActionPrompt::Protector {
|
||||
character_id: prot_char_id,
|
||||
targets,
|
||||
marked: None,
|
||||
},
|
||||
0,
|
||||
) => (
|
||||
targets
|
||||
.into_iter()
|
||||
.map(|c| game.village().character_by_id(c.character_id).unwrap())
|
||||
|
|
@ -124,9 +113,12 @@ fn protect_stops_shapeshift() {
|
|||
)))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::ActionPrompt(ActionPrompt::Protector {
|
||||
marked: Some(mark), ..
|
||||
}) => assert_eq!(mark, prot_and_wolf_target, "marked target"),
|
||||
ServerToHostMessage::ActionPrompt(
|
||||
ActionPrompt::Protector {
|
||||
marked: Some(mark), ..
|
||||
},
|
||||
0,
|
||||
) => assert_eq!(mark, prot_and_wolf_target, "marked target"),
|
||||
resp => panic!("unexpected response: {resp:?}"),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ pub enum HostGameMessage {
|
|||
pub enum HostNightMessage {
|
||||
ActionResponse(ActionResponse),
|
||||
Next,
|
||||
NextPage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
@ -69,8 +70,9 @@ pub enum ServerToHostMessage {
|
|||
characters: Box<[CharacterState]>,
|
||||
marked: Box<[CharacterId]>,
|
||||
day: NonZeroU8,
|
||||
settings: GameSettings,
|
||||
},
|
||||
ActionPrompt(ActionPrompt),
|
||||
ActionPrompt(ActionPrompt, usize),
|
||||
ActionResult(Option<CharacterIdentity>, ActionResult),
|
||||
Lobby(Box<[PlayerState]>),
|
||||
QrMode(bool),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use core::ops::{Deref, DerefMut};
|
||||
use core::{
|
||||
num::NonZeroU8,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tokio::sync::broadcast::{self, Sender};
|
||||
use werewolves_proto::{
|
||||
error::GameError,
|
||||
game::{Game, GameSettings},
|
||||
message::{
|
||||
ClientMessage, Identification, PlayerState, ServerMessage,
|
||||
ClientMessage, Identification, PlayerState, PublicIdentity, ServerMessage,
|
||||
host::{HostLobbyMessage, HostMessage, ServerToHostMessage},
|
||||
},
|
||||
player::PlayerId,
|
||||
|
|
@ -335,6 +338,27 @@ impl DerefMut for LobbyPlayers {
|
|||
}
|
||||
|
||||
impl LobbyPlayers {
|
||||
pub fn with_dummies(dummy_count: NonZeroU8) -> Self {
|
||||
let (send, mut recv) = broadcast::channel(100);
|
||||
tokio::spawn(async move { while recv.recv().await.is_ok() {} });
|
||||
Self(
|
||||
(0..dummy_count.get())
|
||||
.map(|p| {
|
||||
(
|
||||
Identification {
|
||||
player_id: PlayerId::from_u128(p as u128 + 1),
|
||||
public: PublicIdentity {
|
||||
name: format!("Player {}", p as u16 + 1),
|
||||
pronouns: Some(String::from("he/him")),
|
||||
number: NonZeroU8::new(p + 1),
|
||||
},
|
||||
},
|
||||
send.clone(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
pub fn find(&self, player_id: PlayerId) -> Option<&Sender<ServerMessage>> {
|
||||
self.iter()
|
||||
.find_map(|(id, s)| (id.player_id == player_id).then_some(s))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use core::time::Duration;
|
||||
use core::{num::NonZeroU8, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
use werewolves_proto::{
|
||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
communication::lobby::LobbyComms,
|
||||
connection::JoinedPlayers,
|
||||
game::{GameEnd, GameRunner},
|
||||
lobby::Lobby,
|
||||
lobby::{Lobby, LobbyPlayers},
|
||||
saver::Saver,
|
||||
};
|
||||
|
||||
|
|
@ -22,7 +22,13 @@ pub struct IdentifiedClientMessage {
|
|||
}
|
||||
|
||||
pub async fn run_game(joined_players: JoinedPlayers, comms: LobbyComms, mut saver: impl Saver) {
|
||||
let mut state = RunningState::Lobby(Lobby::new(joined_players, comms));
|
||||
let mut lobby = Lobby::new(joined_players, comms);
|
||||
if let Some(dummies) = option_env!("DUMMY_PLAYERS").and_then(|p| p.parse::<NonZeroU8>().ok()) {
|
||||
log::info!("creating {dummies} dummy players");
|
||||
lobby.set_players_in_lobby(LobbyPlayers::with_dummies(dummies));
|
||||
lobby.send_lobby_info_to_host().await.log_debug();
|
||||
}
|
||||
let mut state = RunningState::Lobby(lobby);
|
||||
loop {
|
||||
match &mut state {
|
||||
RunningState::Lobby(lobby) => {
|
||||
|
|
|
|||
|
|
@ -24,15 +24,15 @@
|
|||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="71.5"
|
||||
inkscape:cy="601.5"
|
||||
inkscape:zoom="2.8284271"
|
||||
inkscape:cx="190.03495"
|
||||
inkscape:cy="871.68588"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1042"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="17"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer3"><inkscape:page
|
||||
inkscape:current-layer="layer4"><inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="52.348"
|
||||
|
|
@ -40,7 +40,22 @@
|
|||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" /></sodipodi:namedview><defs
|
||||
id="defs1" /><g
|
||||
id="defs1"><inkscape:path-effect
|
||||
effect="mirror_symmetry"
|
||||
start_point="41.438814,242.41673"
|
||||
end_point="41.438814,281.85261"
|
||||
center_point="41.438814,262.13467"
|
||||
id="path-effect74"
|
||||
is_visible="true"
|
||||
lpeversion="1.2"
|
||||
lpesatellites=""
|
||||
mode="free"
|
||||
discard_orig_path="false"
|
||||
fuse_paths="false"
|
||||
oposite_fuse="false"
|
||||
split_items="false"
|
||||
split_open="false"
|
||||
link_styles="false" /></defs><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Layer 4"
|
||||
|
|
@ -52,13 +67,18 @@
|
|||
r="25"
|
||||
inkscape:export-filename="../../src/werewolves/werewolves/img/wolf.svg"
|
||||
inkscape:export-xdpi="900.08"
|
||||
inkscape:export-ydpi="900.08" /><path
|
||||
id="path52"
|
||||
style="fill:#0f07ff;fill-opacity:1;stroke:#0f07ff;stroke-width:0.646547;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:transform-center-x="0.38117126"
|
||||
inkscape:transform-center-y="-3.2844551"
|
||||
d="m 57.107952,201.21922 c -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477016,6.95297 0.467671,8.69922 0.577742,0.51878 1.869738,0.91451 2.822455,1.08642 0.645263,0.48645 0.441435,1.16866 0.235128,1.54824 -2.113413,0.40992 -4.423609,1.57646 -5.591796,3.31071 -3.059556,4.54209 -0.05893,15.93547 0,21.41161 0.01685,1.56554 14.736774,1.63168 14.555185,-0.0661 -0.582408,-5.44541 2.927265,-16.80337 -0.132292,-21.34547 -1.319465,-1.95883 -3.263349,-3.04974 -5.692251,-3.31904 -0.233696,-0.43409 -0.260906,-1.13472 0.150896,-1.67225 1.115403,-0.2586 1.751648,-0.75274 2.352402,-1.42176 1.746251,-1.94469 1.477016,-6.95348 -0.467672,-8.69973 -0.972343,-0.87312 -2.710997,-1.24193 -4.424535,-1.1498 z"
|
||||
sodipodi:nodetypes="sssccssssccssss" /><path
|
||||
inkscape:export-ydpi="900.08" /><g
|
||||
id="g74"
|
||||
inkscape:path-effect="#path-effect74"
|
||||
transform="translate(16.485186,-41.489963)"><path
|
||||
d="m 41.438814,253.85737 v -11.44064 c -0.10314,0.002 -0.206411,0.006 -0.309542,0.0114 -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477015,6.95296 0.467672,8.69921 0.577742,0.51878 1.869333,0.91432 2.82205,1.08623 0.05301,0.04 0.6259,0.0432 1.295011,0.0258 z m 0,0 v -11.44064 c 0.10314,0.002 0.206411,0.006 0.309542,0.0114 1.713539,0.0921 3.402067,0.64565 4.275191,1.61799 1.746251,1.94469 1.477015,6.95296 -0.467672,8.69921 -0.577742,0.51878 -1.869333,0.91432 -2.82205,1.08623 -0.05301,0.04 -0.6259,0.0432 -1.295011,0.0258 z"
|
||||
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||
id="path73"
|
||||
inkscape:original-d="m 41.438814,253.85737 v -11.44064 c -0.10314,0.002 -0.206411,0.006 -0.309542,0.0114 -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477015,6.95296 0.467672,8.69921 0.577742,0.51878 1.869333,0.91432 2.82205,1.08623 0.05301,0.04 0.6259,0.0432 1.295011,0.0258 z" /><path
|
||||
d="m 41.437781,281.85256 v -26.00719 c -0.537817,0.003 -1.074156,0.0344 -1.599386,0.0879 -2.633998,0.53128 -5.422716,2.05847 -6.299357,4.75888 -1.013707,3.1868 -0.65843,6.59772 -0.366903,9.8702 0.3233,3.36773 0.971622,6.70664 1.07487,10.09344 0.63992,0.8794 1.985837,0.77429 2.985348,0.97565 1.39519,0.12946 2.800457,0.21075 4.205428,0.22117 z m 0.0021,0 v -26.00719 c 0.537817,0.003 1.074156,0.0344 1.599386,0.0879 2.633998,0.53128 5.422716,2.05847 6.299357,4.75888 1.013707,3.1868 0.65843,6.59772 0.366903,9.8702 -0.3233,3.36773 -0.971622,6.70664 -1.07487,10.09344 -0.63992,0.8794 -1.985837,0.77429 -2.985348,0.97565 -1.39519,0.12946 -2.800457,0.21075 -4.205428,0.22117 z"
|
||||
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||
id="path74"
|
||||
inkscape:original-d="m 41.437781,281.85256 v -26.00719 c -0.537817,0.003 -1.074156,0.0344 -1.599386,0.0879 -2.633998,0.53128 -5.422716,2.05847 -6.299357,4.75888 -1.013707,3.1868 -0.65843,6.59772 -0.366903,9.8702 0.3233,3.36773 0.971622,6.70664 1.07487,10.09344 0.63992,0.8794 1.985837,0.77429 2.985348,0.97565 1.39519,0.12946 2.800457,0.21075 4.205428,0.22117 z" /></g><path
|
||||
id="rect55"
|
||||
style="display:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.7;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 54.680079,205.91192 0.0075,3.95139 h -0.02186 v 1.21435 h 2.712393 v 25.2552 l 0.5302,0.68461 0.58136,-0.68461 v -25.2552 h 2.712392 v -1.21435 h -0.02232 l -0.007,-3.95139 -0.404006,-1.44994 -0.408967,1.44994 0.007,3.95139 h -1.877095 -0.149294 l -0.0075,-3.95139 -0.440632,-1.43824 -0.372342,1.43824 0.007,3.95139 H 57.37808 55.50052 l -0.007,-3.95139 -0.442679,-1.43824 z"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.0 KiB |
|
|
@ -931,6 +931,13 @@ input {
|
|||
$village_bg: color.change($village_color, $alpha: 0.3);
|
||||
$village_border: color.change($village_color, $alpha: 1.0);
|
||||
|
||||
.targets {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.character {
|
||||
padding: 0.5cm;
|
||||
|
||||
|
|
@ -1305,3 +1312,15 @@ input {
|
|||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// width: ;
|
||||
|
||||
& .next {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use crate::{
|
|||
action::{ActionResultView, Prompt},
|
||||
host::{DaytimePlayerList, Setup},
|
||||
},
|
||||
pages::RolePage,
|
||||
storage::StorageKey,
|
||||
};
|
||||
|
||||
|
|
@ -196,6 +197,7 @@ pub enum HostState {
|
|||
characters: Box<[CharacterState]>,
|
||||
marked_for_execution: Box<[CharacterId]>,
|
||||
day: NonZeroU8,
|
||||
settings: GameSettings,
|
||||
},
|
||||
GameOver {
|
||||
result: GameOver,
|
||||
|
|
@ -204,7 +206,7 @@ pub enum HostState {
|
|||
ackd: Box<[CharacterIdentity]>,
|
||||
waiting: Box<[CharacterIdentity]>,
|
||||
},
|
||||
Prompt(ActionPrompt),
|
||||
Prompt(ActionPrompt, usize),
|
||||
Result(Option<CharacterIdentity>, ActionResult),
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +219,12 @@ impl From<ServerToHostMessage> for HostEvent {
|
|||
characters,
|
||||
day,
|
||||
marked: marked_for_execution,
|
||||
settings,
|
||||
} => HostEvent::SetState(HostState::Day {
|
||||
characters,
|
||||
day,
|
||||
marked_for_execution,
|
||||
settings,
|
||||
}),
|
||||
ServerToHostMessage::Lobby(players) => HostEvent::PlayerList(players),
|
||||
ServerToHostMessage::GameSettings(settings) => HostEvent::Settings(settings),
|
||||
|
|
@ -228,8 +232,8 @@ impl From<ServerToHostMessage> for HostEvent {
|
|||
ServerToHostMessage::GameOver(game_over) => {
|
||||
HostEvent::SetState(HostState::GameOver { result: game_over })
|
||||
}
|
||||
ServerToHostMessage::ActionPrompt(prompt) => {
|
||||
HostEvent::SetState(HostState::Prompt(prompt))
|
||||
ServerToHostMessage::ActionPrompt(prompt, page) => {
|
||||
HostEvent::SetState(HostState::Prompt(prompt, page))
|
||||
}
|
||||
ServerToHostMessage::ActionResult(_, ActionResult::Continue) => HostEvent::Continue,
|
||||
ServerToHostMessage::ActionResult(ident, result) => {
|
||||
|
|
@ -309,7 +313,7 @@ impl Component for Host {
|
|||
},
|
||||
HostState::Lobby { players, settings } => {
|
||||
if self.big_screen {
|
||||
self.lobby_big_screen_show_setup(players, settings)
|
||||
self.lobby_big_screen_show_setup(settings)
|
||||
} else {
|
||||
self.lobby_setup(players, settings)
|
||||
}
|
||||
|
|
@ -318,30 +322,35 @@ impl Component for Host {
|
|||
characters,
|
||||
day,
|
||||
marked_for_execution,
|
||||
settings,
|
||||
} => {
|
||||
let on_mark = crate::callback::send_fn(
|
||||
|target| {
|
||||
HostMessage::InGame(HostGameMessage::Day(HostDayMessage::MarkForExecution(
|
||||
target,
|
||||
)))
|
||||
},
|
||||
self.send.clone(),
|
||||
);
|
||||
let on_execute = crate::callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::Day(HostDayMessage::Execute)),
|
||||
self.send.clone(),
|
||||
);
|
||||
html! {
|
||||
<DaytimePlayerList
|
||||
day={day}
|
||||
marked={marked_for_execution}
|
||||
big_screen={self.big_screen}
|
||||
characters={
|
||||
characters.into_iter().map(|c| c.into()).collect::<Box<[_]>>()
|
||||
}
|
||||
on_execute={on_execute}
|
||||
on_mark={on_mark}
|
||||
/>
|
||||
if self.big_screen {
|
||||
self.lobby_big_screen_show_setup(settings)
|
||||
} else {
|
||||
let on_mark = crate::callback::send_fn(
|
||||
|target| {
|
||||
HostMessage::InGame(HostGameMessage::Day(
|
||||
HostDayMessage::MarkForExecution(target),
|
||||
))
|
||||
},
|
||||
self.send.clone(),
|
||||
);
|
||||
let on_execute = crate::callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::Day(HostDayMessage::Execute)),
|
||||
self.send.clone(),
|
||||
);
|
||||
html! {
|
||||
<DaytimePlayerList
|
||||
day={day}
|
||||
marked={marked_for_execution}
|
||||
big_screen={self.big_screen}
|
||||
characters={
|
||||
characters.into_iter().map(|c| c.into()).collect::<Box<[_]>>()
|
||||
}
|
||||
on_execute={on_execute}
|
||||
on_mark={on_mark}
|
||||
/>
|
||||
}
|
||||
}
|
||||
}
|
||||
HostState::RoleReveal { ackd, waiting } => {
|
||||
|
|
@ -364,7 +373,7 @@ impl Component for Host {
|
|||
<RoleReveal ackd={ackd} waiting={waiting} on_force_ready={on_force_ready}/>
|
||||
}
|
||||
}
|
||||
HostState::Prompt(prompt) => {
|
||||
HostState::Prompt(prompt, page) => {
|
||||
let send = self.send.clone();
|
||||
let on_complete = Callback::from(move |msg| {
|
||||
let mut send = send.clone();
|
||||
|
|
@ -374,9 +383,12 @@ impl Component for Host {
|
|||
}
|
||||
});
|
||||
});
|
||||
let pages = prompt.role_pages(self.big_screen);
|
||||
|
||||
html! {
|
||||
<Prompt
|
||||
pages={pages}
|
||||
page_idx={page}
|
||||
prompt={prompt}
|
||||
big_screen={self.big_screen}
|
||||
on_complete={on_complete}
|
||||
|
|
@ -494,17 +506,10 @@ impl Component for Host {
|
|||
});
|
||||
}
|
||||
|
||||
HostState::Prompt(_)
|
||||
HostState::Prompt(_, _)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::RoleReveal {
|
||||
ackd: _,
|
||||
waiting: _,
|
||||
}
|
||||
| HostState::Day {
|
||||
characters: _,
|
||||
day: _,
|
||||
marked_for_execution: _,
|
||||
} => {
|
||||
| HostState::RoleReveal { .. }
|
||||
| HostState::Day { .. } => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -526,7 +531,7 @@ impl Component for Host {
|
|||
*s = settings;
|
||||
true
|
||||
}
|
||||
HostState::Prompt(_)
|
||||
HostState::Prompt(_, _)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::Disconnected
|
||||
| HostState::RoleReveal {
|
||||
|
|
@ -534,11 +539,7 @@ impl Component for Host {
|
|||
waiting: _,
|
||||
}
|
||||
| HostState::GameOver { result: _ }
|
||||
| HostState::Day {
|
||||
characters: _,
|
||||
day: _,
|
||||
marked_for_execution: _,
|
||||
} => {
|
||||
| HostState::Day { .. } => {
|
||||
log::info!("ignoring settings get");
|
||||
false
|
||||
}
|
||||
|
|
@ -588,7 +589,7 @@ impl Component for Host {
|
|||
}
|
||||
|
||||
impl Host {
|
||||
fn lobby_big_screen_show_setup(&self, _: Rc<[PlayerState]>, settings: GameSettings) -> Html {
|
||||
fn lobby_big_screen_show_setup(&self, settings: GameSettings) -> Html {
|
||||
if !self.qr_mode {
|
||||
return html! {
|
||||
<Setup settings={settings}/>
|
||||
|
|
|
|||
|
|
@ -33,15 +33,20 @@ pub fn TargetPicker(
|
|||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
let continue_button = continue_callback.clone().map(|continue_callback| {
|
||||
html! {
|
||||
<Button on_click={continue_callback}>
|
||||
{"continue"}
|
||||
</Button>
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<div class="target-picker">
|
||||
<div class="targets">
|
||||
{targets}
|
||||
</div>
|
||||
<Button on_click={continue_callback.clone().unwrap_or_default()}>
|
||||
{"continue"}
|
||||
</Button>
|
||||
{continue_button}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use core::ops::Not;
|
||||
use std::rc::Rc;
|
||||
|
||||
use werewolves_proto::{
|
||||
character::CharacterId,
|
||||
|
|
@ -22,6 +23,10 @@ pub struct ActionPromptProps {
|
|||
#[prop_or_default]
|
||||
pub big_screen: bool,
|
||||
pub on_complete: Callback<HostMessage>,
|
||||
#[prop_or_default]
|
||||
pub page_idx: usize,
|
||||
#[prop_or_default]
|
||||
pub pages: Rc<[Html]>,
|
||||
}
|
||||
|
||||
fn identity_html(props: &ActionPromptProps, ident: Option<&CharacterIdentity>) -> Option<Html> {
|
||||
|
|
@ -41,6 +46,31 @@ fn identity_html(props: &ActionPromptProps, ident: Option<&CharacterIdentity>) -
|
|||
|
||||
#[function_component]
|
||||
pub fn Prompt(props: &ActionPromptProps) -> Html {
|
||||
if let Some(page) = props.pages.get(props.page_idx).map(|page| {
|
||||
let next = props.big_screen.not().then(|| {
|
||||
let send = props.on_complete.clone();
|
||||
let on_page_next = Callback::from(move |_| {
|
||||
send.emit(HostMessage::InGame(HostGameMessage::Night(
|
||||
HostNightMessage::NextPage,
|
||||
)))
|
||||
});
|
||||
html! {
|
||||
<Button on_click={on_page_next} classes={classes!("next")}>
|
||||
{"next"}
|
||||
</Button>
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<div class="page">
|
||||
{page.clone()}
|
||||
{next}
|
||||
</div>
|
||||
}
|
||||
}) {
|
||||
return page;
|
||||
}
|
||||
|
||||
let on_complete = props.on_complete.clone();
|
||||
let continue_callback = props.big_screen.not().then(|| {
|
||||
Callback::from(move |_| {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
use core::ops::Not;
|
||||
use std::rc::Rc;
|
||||
|
||||
use werewolves_proto::message::{PublicIdentity, night::ActionPrompt};
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components::Identity;
|
||||
|
||||
werewolves_macros::include_path!("werewolves/src/pages/role_page");
|
||||
pub trait RolePage {
|
||||
fn role_pages(&self, big_screen: bool) -> Rc<[yew::Html]>;
|
||||
}
|
||||
|
||||
impl RolePage for ActionPrompt {
|
||||
fn role_pages(&self, big_screen: bool) -> Rc<[yew::Html]> {
|
||||
match self {
|
||||
ActionPrompt::Beholder { character_id, .. } => {
|
||||
let ident = big_screen.not().then(|| {
|
||||
html! {
|
||||
<Identity ident={Into::<PublicIdentity>::into(character_id)} />
|
||||
}
|
||||
});
|
||||
Rc::new([
|
||||
html! {
|
||||
<>
|
||||
{ident.clone()}
|
||||
<BeholderPage1 />
|
||||
</>
|
||||
},
|
||||
html! {
|
||||
<>
|
||||
{ident}
|
||||
<BeholderPage2 />
|
||||
</>
|
||||
},
|
||||
])
|
||||
}
|
||||
_ => Rc::new([]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
#[function_component]
|
||||
pub fn BeholderPage1() -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<h1>{"this is a beholder"}</h1>
|
||||
<h2>{"there's information on this page"}</h2>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn BeholderPage2() -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<h1>{"more information on beholder"}</h1>
|
||||
<h2>{"idk, hi?"}</h2>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue