pages for prompts, daytime setup

This commit is contained in:
emilis 2025-10-09 22:27:21 +01:00
parent 42a240b0ea
commit 08db7f9bfc
No known key found for this signature in database
15 changed files with 315 additions and 133 deletions

View File

@ -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,

View File

@ -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 {

View File

@ -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());

View File

@ -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 {
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:#?}"),
}

View File

@ -48,11 +48,14 @@ fn previous_shapeshifter_undone_redone() {
}
assert_eq!(
game.get_state(),
ServerToHostMessage::ActionPrompt(ActionPrompt::Shapeshifter {
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 {
ServerToHostMessage::ActionPrompt(
ActionPrompt::Shapeshifter {
character_id: game
.character_by_player_id(shapeshifter_player_id)
.identity()
})
},
0
)
);
game.r#continue().sleep();

View File

@ -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 {
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 {
ServerToHostMessage::ActionPrompt(
ActionPrompt::Protector {
marked: Some(mark), ..
}) => assert_eq!(mark, prot_and_wolf_target, "marked target"),
},
0,
) => assert_eq!(mark, prot_and_wolf_target, "marked target"),
resp => panic!("unexpected response: {resp:?}"),
}

View File

@ -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),

View File

@ -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))

View File

@ -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) => {

View File

@ -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 {}
}

View File

@ -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,12 +322,16 @@ impl Component for Host {
characters,
day,
marked_for_execution,
settings,
} => {
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,
)))
HostMessage::InGame(HostGameMessage::Day(
HostDayMessage::MarkForExecution(target),
))
},
self.send.clone(),
);
@ -344,6 +352,7 @@ impl Component for Host {
/>
}
}
}
HostState::RoleReveal { ackd, waiting } => {
let send = self.send.clone();
let on_force_ready = self.big_screen.not().then(|| {
@ -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}/>

View File

@ -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>
}
}

View File

@ -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 |_| {

View File

@ -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([]),
}
}
}

View File

@ -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>
}
}