From ec29c66e4b911f73bee032c33df43cd2f6713d3e Mon Sep 17 00:00:00 2001 From: emilis Date: Tue, 18 Nov 2025 10:47:38 +0000 Subject: [PATCH] view roles during nighttime --- werewolves-proto/src/game/mod.rs | 16 + werewolves-proto/src/message/host.rs | 2 + werewolves/src/clients/host/host.rs | 82 +- werewolves/src/clients/host/story_test.rs | 999 ---------------------- werewolves/src/clients/mod.rs | 16 - werewolves/src/components/host/chars.rs | 36 + werewolves/src/components/host/daytime.rs | 33 +- 7 files changed, 117 insertions(+), 1067 deletions(-) delete mode 100644 werewolves/src/clients/host/story_test.rs create mode 100644 werewolves/src/components/host/chars.rs diff --git a/werewolves-proto/src/game/mod.rs b/werewolves-proto/src/game/mod.rs index ea1f3c7..11afef6 100644 --- a/werewolves-proto/src/game/mod.rs +++ b/werewolves-proto/src/game/mod.rs @@ -83,6 +83,21 @@ impl Game { pub fn process(&mut self, message: HostGameMessage) -> Result { match (&mut self.state, message) { + (GameState::Night { night }, HostGameMessage::SeePlayersWithRoles) => { + Ok(ServerToHostMessage::PlayerStates( + night + .village() + .characters() + .into_iter() + .map(|c| CharacterState { + player_id: c.player_id(), + identity: c.identity(), + role: c.role_title(), + died_to: c.died_to().cloned(), + }) + .collect(), + )) + } (GameState::Night { night }, HostGameMessage::Night(HostNightMessage::NextPage)) => { night.next_page(); self.process(HostGameMessage::GetState) @@ -241,6 +256,7 @@ impl Game { night.previous_state()?; self.process(HostGameMessage::GetState) } + (_, HostGameMessage::SeePlayersWithRoles) => self.process(HostGameMessage::GetState), } } diff --git a/werewolves-proto/src/message/host.rs b/werewolves-proto/src/message/host.rs index 0776794..c3eee2c 100644 --- a/werewolves-proto/src/message/host.rs +++ b/werewolves-proto/src/message/host.rs @@ -51,6 +51,7 @@ pub enum HostGameMessage { Day(HostDayMessage), Night(HostNightMessage), PreviousState, + SeePlayersWithRoles, GetState, } @@ -94,6 +95,7 @@ pub enum ServerToHostMessage { day: NonZeroU8, settings: GameSettings, }, + PlayerStates(Box<[CharacterState]>), ActionPrompt(ActionPrompt, usize), ActionResult(Option, ActionResult), Lobby(Box<[PlayerState]>), diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index 995ea16..fdc42f2 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -43,7 +43,7 @@ use crate::{ components::{ Button, Lobby, LobbyPlayerAction, RoleReveal, Settings, Story, Victory, action::{ActionResultView, Prompt}, - host::{DaytimePlayerList, Setup}, + host::{CharacterStatesReadOnly, DaytimePlayerList, Setup}, }, pages::RolePage, storage::StorageKey, @@ -197,6 +197,7 @@ pub enum HostEvent { SetState(HostState), Continue, PlayerList(Box<[PlayerState]>), + CharacterList(Box<[CharacterState]>), Settings(GameSettings), Error(GameError), QrMode(bool), @@ -228,11 +229,13 @@ pub enum HostState { #[allow(unused)] page: usize, }, + CharacterStates(Box<[CharacterState]>), } impl From for HostEvent { fn from(msg: ServerToHostMessage) -> Self { match msg { + ServerToHostMessage::PlayerStates(states) => HostEvent::CharacterList(states), ServerToHostMessage::QrMode(mode) => HostEvent::QrMode(mode), ServerToHostMessage::Disconnect => HostEvent::SetState(HostState::Disconnected), ServerToHostMessage::Daytime { @@ -313,6 +316,11 @@ impl Component for Host { fn view(&self, _ctx: &Context) -> Html { log::trace!("state: {:?}", self.state); let content = match self.state.clone() { + HostState::CharacterStates(chars) => { + html! { + + } + } HostState::Story { story, .. } => { if let Some(outcome) = story .final_village() @@ -459,55 +467,38 @@ impl Component for Host { } }; let debug_nav = self.debug.then(|| { - let on_error_click = callback::send_message( - HostMessage::Echo(ServerToHostMessage::Error(GameError::NoApprenticeMentor)), - self.send.clone(), - ); - let client_click = Callback::from(|_| { - if let Some(loc) = gloo::utils::document().location() { - let _ = loc.replace("/"); - } - }); - let screen = self.big_screen.then_some({ - let to_normal = Callback::from(|_| { - if let Some(loc) = gloo::utils::document().location() { - let _ = loc.replace("/host"); - } - }); - html! { - - } - }); - let story_on_click = if let HostState::Story { .. } = &self.state { - crate::callback::send_message(HostMessage::GetState, self.send.clone()) - } else { - let s = _ctx.link().clone(); - Callback::from(move |_| { - s.send_message(HostEvent::SetState(HostState::Story { - story: crate::clients::host::story_test::test_story(), - page: 0, - })); - }) - }; - html! { - <> - - {screen} - - - - + } }); let on_prev_click = callback::send_message( HostMessage::InGame(HostGameMessage::PreviousState), self.send.clone(), ); + let view_roles_btn = match &self.state { + HostState::Prompt(_, _) | HostState::Result(_, _) => { + let on_view_click = crate::callback::send_message( + HostMessage::InGame(HostGameMessage::SeePlayersWithRoles), + self.send.clone(), + ); + + Some(html! { + + }) + } + HostState::CharacterStates(_) => { + let back = crate::callback::send_message(HostMessage::GetState, self.send.clone()); + Some(html! { + + }) + } + _ => None, + }; let nav = self.big_screen.not().then(|| { html! { } @@ -525,6 +516,13 @@ impl Component for Host { fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { log::debug!("update: {msg:?}, current: {:?}", self.state); match msg { + HostEvent::CharacterList(char) => { + if self.big_screen { + return false; + } + self.state = HostState::CharacterStates(char); + true + } HostEvent::QrMode(mode) => { self.qr_mode = mode; true @@ -563,7 +561,8 @@ impl Component for Host { }); } - HostState::Prompt(_, _) + HostState::CharacterStates(_) + | HostState::Prompt(_, _) | HostState::Result(_, _) | HostState::RoleReveal { .. } | HostState::Day { .. } => { @@ -588,7 +587,8 @@ impl Component for Host { *s = settings; true } - HostState::Story { .. } + HostState::CharacterStates(_) + | HostState::Story { .. } | HostState::Prompt(_, _) | HostState::Result(_, _) | HostState::Disconnected diff --git a/werewolves/src/clients/host/story_test.rs b/werewolves/src/clients/host/story_test.rs deleted file mode 100644 index d3ea1a0..0000000 --- a/werewolves/src/clients/host/story_test.rs +++ /dev/null @@ -1,999 +0,0 @@ -// Copyright (C) 2025 Emilis Bliūdžius -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -use core::num::NonZeroU8; - -use werewolves_proto::{ - character::{Character, CharacterId}, - diedto::DiedToTitle, - game::{self, Game, GameOver, GameSettings, OrRandom, SetupRole, story::GameStory}, - message::{ - CharacterState, Identification, PublicIdentity, - host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage}, - night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult, Visits}, - }, - player::PlayerId, - role::{Alignment, AlignmentEq, Killer, Powerful, RoleTitle}, -}; - -trait GameSettingsExt { - fn add_and_assign(&mut self, role: SetupRole, player_id: PlayerId); -} - -impl GameSettingsExt for GameSettings { - fn add_and_assign(&mut self, role: SetupRole, player_id: PlayerId) { - let slot_id = self.new_slot(role.clone().into()); - let mut slot = self.get_slot_by_id(slot_id).unwrap().clone(); - slot.role = role; - slot.assign_to.replace(player_id); - self.update_slot(slot); - } -} - -pub fn test_story() -> GameStory { - let players = (1..32u8) - .filter_map(NonZeroU8::new) - .map(|n| Identification { - player_id: PlayerId::from_u128(n.get() as _), - public: PublicIdentity { - name: format!("Player {n}"), - pronouns: Some("he/him".into()), - number: Some(n), - }, - }) - .collect::>(); - let mut players_iter = players.iter().map(|p| p.player_id); - let ( - werewolf, - dire_wolf, - shapeshifter, - alpha_wolf, - seer, - arcanist, - maple_wolf, - guardian, - vindicator, - adjudicator, - power_seer, - beholder, - gravedigger, - mortician, - insomniac, - empath, - scapegoat, - hunter, - diseased, - ) = ( - (SetupRole::Werewolf, players_iter.next().unwrap()), - (SetupRole::DireWolf, players_iter.next().unwrap()), - (SetupRole::Shapeshifter, players_iter.next().unwrap()), - (SetupRole::AlphaWolf, players_iter.next().unwrap()), - (SetupRole::Seer, players_iter.next().unwrap()), - (SetupRole::Arcanist, players_iter.next().unwrap()), - (SetupRole::MapleWolf, players_iter.next().unwrap()), - (SetupRole::Guardian, players_iter.next().unwrap()), - (SetupRole::Vindicator, players_iter.next().unwrap()), - (SetupRole::Adjudicator, players_iter.next().unwrap()), - (SetupRole::PowerSeer, players_iter.next().unwrap()), - (SetupRole::Beholder, players_iter.next().unwrap()), - (SetupRole::Gravedigger, players_iter.next().unwrap()), - (SetupRole::Mortician, players_iter.next().unwrap()), - (SetupRole::Insomniac, players_iter.next().unwrap()), - (SetupRole::Empath, players_iter.next().unwrap()), - ( - SetupRole::Scapegoat { - redeemed: OrRandom::Determined(false), - }, - players_iter.next().unwrap(), - ), - (SetupRole::Hunter, players_iter.next().unwrap()), - (SetupRole::Diseased, players_iter.next().unwrap()), - ); - let mut settings = GameSettings::empty(); - settings.add_and_assign(werewolf.0, werewolf.1); - settings.add_and_assign(dire_wolf.0, dire_wolf.1); - settings.add_and_assign(shapeshifter.0, shapeshifter.1); - settings.add_and_assign(alpha_wolf.0, alpha_wolf.1); - settings.add_and_assign(seer.0, seer.1); - settings.add_and_assign(arcanist.0, arcanist.1); - settings.add_and_assign(maple_wolf.0, maple_wolf.1); - settings.add_and_assign(guardian.0, guardian.1); - settings.add_and_assign(vindicator.0, vindicator.1); - settings.add_and_assign(adjudicator.0, adjudicator.1); - settings.add_and_assign(power_seer.0, power_seer.1); - settings.add_and_assign(beholder.0, beholder.1); - settings.add_and_assign(gravedigger.0, gravedigger.1); - settings.add_and_assign(mortician.0, mortician.1); - settings.add_and_assign(insomniac.0, insomniac.1); - settings.add_and_assign(empath.0, empath.1); - settings.add_and_assign(scapegoat.0, scapegoat.1); - settings.add_and_assign(hunter.0, hunter.1); - settings.add_and_assign(diseased.0, diseased.1); - settings.fill_remaining_slots_with_villagers(players.len()); - #[allow(unused)] - let ( - werewolf, - dire_wolf, - shapeshifter, - alpha_wolf, - seer, - arcanist, - maple_wolf, - guardian, - vindicator, - adjudicator, - power_seer, - beholder, - gravedigger, - mortician, - insomniac, - empath, - scapegoat, - hunter, - ) = ( - werewolf.1, - dire_wolf.1, - shapeshifter.1, - alpha_wolf.1, - seer.1, - arcanist.1, - maple_wolf.1, - guardian.1, - vindicator.1, - adjudicator.1, - power_seer.1, - beholder.1, - gravedigger.1, - mortician.1, - insomniac.1, - empath.1, - scapegoat.1, - hunter.1, - ); - // let village = Village::new(&players, settings).unwrap(); - let mut game = game::Game::new(&players, settings).unwrap(); - game.r#continue().r#continue(); - game.next().title().wolves_intro(); - game.r#continue().r#continue(); - - game.next().title().direwolf(); - game.mark(game.character_by_player_id(seer).character_id()); - game.r#continue().sleep(); - - game.next().title().seer(); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().seer(); - game.r#continue().sleep(); - - game.next().title().arcanist(); - game.mark(game.character_by_player_id(seer).character_id()); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().role_blocked(); - game.r#continue().sleep(); - - game.next().title().adjudicator(); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().adjudicator(); - game.r#continue().sleep(); - - game.next().title().power_seer(); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next_expect_day(); - game.mark_for_execution(game.character_by_player_id(dire_wolf).character_id()); - game.mark_for_execution(game.character_by_player_id(alpha_wolf).character_id()); - - game.execute().title().guardian(); - let protect = game.living_villager(); - game.mark(protect.character_id()); - game.r#continue().sleep(); - - game.next().title().wolf_pack_kill(); - game.mark(protect.character_id()); - game.r#continue().r#continue(); - - game.next().title().shapeshifter(); - game.response(ActionResponse::Shapeshift).sleep(); - - game.next().title().seer(); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().seer(); - game.r#continue().sleep(); - - game.next().title().arcanist(); - game.mark(game.character_by_player_id(seer).character_id()); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().arcanist(); - game.r#continue().sleep(); - - game.next().title().adjudicator(); - game.mark(game.character_by_player_id(seer).character_id()); - game.r#continue().adjudicator(); - game.r#continue().sleep(); - - game.next().title().power_seer(); - game.mark(game.living_villager().character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next().title().gravedigger(); - game.mark(game.character_by_player_id(dire_wolf).character_id()); - assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::DireWolf)); - game.r#continue().sleep(); - - game.next().title().mortician(); - game.mark(game.character_by_player_id(dire_wolf).character_id()); - assert_eq!(game.r#continue().mortician(), DiedToTitle::Execution); - game.r#continue().sleep(); - - game.next().title().empath(); - game.mark(game.living_villager().character_id()); - assert!(!game.r#continue().empath()); - game.r#continue().sleep(); - - game.next().title().maple_wolf(); - game.mark( - game.living_villager_excl(protect.player_id()) - .character_id(), - ); - game.r#continue().sleep(); - - game.next().title().hunter(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().sleep(); - - game.next().title().insomniac(); - game.r#continue().insomniac(); - game.r#continue().sleep(); - - game.next().title().beholder(); - game.mark(game.character_by_player_id(power_seer).character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next_expect_day(); - - assert_eq!( - game.character_by_player_id(protect.player_id()).died_to(), - None - ); - - game.mark_for_execution( - game.living_villager_excl(protect.player_id()) - .character_id(), - ); - game.execute().title().guardian(); - game.mark(protect.character_id()); - game.r#continue().sleep(); - - game.next().title().vindicator(); - game.mark( - game.living_villager_excl(protect.player_id()) - .character_id(), - ); - game.r#continue().sleep(); - - game.next().title().wolf_pack_kill(); - game.mark(protect.character_id()); - game.r#continue().r#continue(); - - game.next().title().shapeshifter(); - game.r#continue().sleep(); - - game.next().title().seer(); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().seer(); - game.r#continue().sleep(); - - game.next().title().arcanist(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.mark(game.character_by_player_id(werewolf).character_id()); - game.r#continue().arcanist(); - game.r#continue().sleep(); - - game.next().title().adjudicator(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().adjudicator(); - game.r#continue().sleep(); - - game.next().title().power_seer(); - game.mark(game.living_villager().character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next().title().gravedigger(); - game.mark(game.character_by_player_id(dire_wolf).character_id()); - assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::DireWolf)); - game.r#continue().sleep(); - - game.next().title().mortician(); - game.mark(game.character_by_player_id(dire_wolf).character_id()); - assert_eq!(game.r#continue().mortician(), DiedToTitle::Execution); - game.r#continue().sleep(); - - game.next().title().empath(); - game.mark(game.character_by_player_id(scapegoat).character_id()); - assert!(game.r#continue().empath()); - game.r#continue().sleep(); - - game.next().title().maple_wolf(); - game.r#continue().sleep(); - - game.next().title().hunter(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().sleep(); - - game.next().title().insomniac(); - game.r#continue().insomniac(); - game.r#continue().sleep(); - - game.next().title().beholder(); - game.mark(game.character_by_player_id(power_seer).character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next_expect_day(); - game.mark_for_execution( - game.living_villager_excl(protect.player_id()) - .character_id(), - ); - - game.execute().title().vindicator(); - game.mark(game.character_by_player_id(shapeshifter).character_id()); - game.r#continue().sleep(); - - game.next().title().wolf_pack_kill(); - game.mark(game.character_by_player_id(empath).character_id()); - game.r#continue().r#continue(); - - game.next().title().shapeshifter(); - game.process(HostGameMessage::Night(HostNightMessage::ActionResponse( - ActionResponse::Shapeshift, - ))) - .expect("shapeshift"); - // game.r#continue().r#continue(); - - assert_eq!( - game.next(), - ActionPrompt::RoleChange { - character_id: game.character_by_player_id(empath).identity(), - new_role: RoleTitle::Werewolf - } - ); - game.r#continue().sleep(); - - game.next().title().seer(); - game.mark(game.character_by_player_id(shapeshifter).character_id()); - game.r#continue().seer(); - game.r#continue().sleep(); - - game.next().title().arcanist(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.mark(game.character_by_player_id(shapeshifter).character_id()); - game.r#continue().arcanist(); - game.r#continue().sleep(); - - game.next().title().adjudicator(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().adjudicator(); - game.r#continue().sleep(); - - game.next().title().power_seer(); - game.mark(game.living_villager().character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next().title().gravedigger(); - game.mark(game.character_by_player_id(guardian).character_id()); - assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::Guardian)); - game.r#continue().sleep(); - - game.next().title().mortician(); - game.mark(game.character_by_player_id(guardian).character_id()); - assert_eq!(game.r#continue().mortician(), DiedToTitle::Wolfpack); - game.r#continue().sleep(); - - game.next().title().maple_wolf(); - game.r#continue().sleep(); - - game.next().title().hunter(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().sleep(); - - game.next().title().insomniac(); - game.r#continue().insomniac(); - game.r#continue().sleep(); - - game.next().title().beholder(); - game.mark(game.character_by_player_id(gravedigger).character_id()); - assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::Guardian)); - game.r#continue().sleep(); - - game.next_expect_day(); - game.mark_for_execution(game.character_by_player_id(vindicator).character_id()); - game.execute().title().wolf_pack_kill(); - game.mark(game.living_villager().character_id()); - game.r#continue().sleep(); - - game.next().title().seer(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.r#continue().seer(); - game.r#continue().sleep(); - - game.next().title().arcanist(); - game.mark(game.character_by_player_id(insomniac).character_id()); - game.mark(game.character_by_player_id(empath).character_id()); - game.r#continue().arcanist(); - game.r#continue().sleep(); - - game.next().title().adjudicator(); - game.mark(game.character_by_player_id(empath).character_id()); - game.r#continue().adjudicator(); - game.r#continue().sleep(); - - game.next().title().power_seer(); - game.mark(game.character_by_player_id(empath).character_id()); - game.r#continue().power_seer(); - game.r#continue().sleep(); - - game.next().title().gravedigger(); - game.mark(game.character_by_player_id(shapeshifter).character_id()); - assert_eq!(game.r#continue().gravedigger(), None); - game.r#continue().sleep(); - - game.next().title().mortician(); - game.mark(game.character_by_player_id(werewolf).character_id()); - assert_eq!( - game.r#continue().mortician(), - DiedToTitle::GuardianProtecting - ); - game.r#continue().sleep(); - - game.next().title().maple_wolf(); - game.mark(game.character_by_player_id(hunter).character_id()); - game.r#continue().sleep(); - - game.next().title().hunter(); - game.mark(game.character_by_player_id(empath).character_id()); - game.r#continue().sleep(); - - game.next().title().insomniac(); - game.r#continue().insomniac(); - game.r#continue().sleep(); - - game.next().title().beholder(); - game.mark(game.character_by_player_id(mortician).character_id()); - assert_eq!( - game.r#continue().mortician(), - DiedToTitle::GuardianProtecting - ); - game.r#continue().sleep(); - - game.story() -} - -#[allow(unused)] -pub trait ActionPromptTitleExt { - fn wolf_pack_kill(&self); - fn cover_of_darkness(&self); - fn wolves_intro(&self); - fn role_change(&self); - fn seer(&self); - fn protector(&self); - fn arcanist(&self); - fn gravedigger(&self); - fn hunter(&self); - fn militia(&self); - fn maple_wolf(&self); - fn guardian(&self); - fn shapeshifter(&self); - fn alphawolf(&self); - fn direwolf(&self); - fn masons_wake(&self); - fn masons_leader_recruit(&self); - fn beholder(&self); - fn vindicator(&self); - fn pyremaster(&self); - fn empath(&self); - fn adjudicator(&self); - fn lone_wolf(&self); - fn insomniac(&self); - fn power_seer(&self); - fn mortician(&self); -} - -impl ActionPromptTitleExt for ActionPromptTitle { - fn mortician(&self) { - assert_eq!(*self, ActionPromptTitle::Mortician); - } - fn cover_of_darkness(&self) { - assert_eq!(*self, ActionPromptTitle::CoverOfDarkness); - } - fn wolves_intro(&self) { - assert_eq!(*self, ActionPromptTitle::WolvesIntro); - } - fn role_change(&self) { - assert_eq!(*self, ActionPromptTitle::RoleChange); - } - fn seer(&self) { - assert_eq!(*self, ActionPromptTitle::Seer); - } - fn protector(&self) { - assert_eq!(*self, ActionPromptTitle::Protector); - } - fn arcanist(&self) { - assert_eq!(*self, ActionPromptTitle::Arcanist); - } - fn gravedigger(&self) { - assert_eq!(*self, ActionPromptTitle::Gravedigger); - } - fn hunter(&self) { - assert_eq!(*self, ActionPromptTitle::Hunter); - } - fn militia(&self) { - assert_eq!(*self, ActionPromptTitle::Militia); - } - fn maple_wolf(&self) { - assert_eq!(*self, ActionPromptTitle::MapleWolf); - } - fn guardian(&self) { - assert_eq!(*self, ActionPromptTitle::Guardian); - } - fn shapeshifter(&self) { - assert_eq!(*self, ActionPromptTitle::Shapeshifter); - } - fn alphawolf(&self) { - assert_eq!(*self, ActionPromptTitle::AlphaWolf); - } - fn direwolf(&self) { - assert_eq!(*self, ActionPromptTitle::DireWolf); - } - fn wolf_pack_kill(&self) { - assert_eq!(*self, ActionPromptTitle::WolfPackKill); - } - fn masons_wake(&self) { - assert_eq!(*self, ActionPromptTitle::MasonsWake) - } - fn masons_leader_recruit(&self) { - assert_eq!(*self, ActionPromptTitle::MasonLeaderRecruit) - } - fn beholder(&self) { - assert_eq!(*self, ActionPromptTitle::Beholder) - } - fn vindicator(&self) { - assert_eq!(*self, ActionPromptTitle::Vindicator) - } - fn pyremaster(&self) { - assert_eq!(*self, ActionPromptTitle::PyreMaster) - } - fn adjudicator(&self) { - assert_eq!(*self, ActionPromptTitle::Adjudicator) - } - fn empath(&self) { - assert_eq!(*self, ActionPromptTitle::Empath) - } - fn lone_wolf(&self) { - assert_eq!(*self, ActionPromptTitle::LoneWolfKill) - } - fn insomniac(&self) { - assert_eq!(*self, ActionPromptTitle::Insomniac) - } - fn power_seer(&self) { - assert_eq!(*self, ActionPromptTitle::PowerSeer) - } -} - -#[allow(unused)] -pub trait ActionResultExt { - fn sleep(&self); - fn r#continue(&self); - fn seer(&self) -> Alignment; - fn insomniac(&self) -> Visits; - fn arcanist(&self) -> AlignmentEq; - fn adjudicator(&self) -> Killer; - fn role_blocked(&self); - fn gravedigger(&self) -> Option; - fn power_seer(&self) -> Powerful; - fn mortician(&self) -> DiedToTitle; - fn empath(&self) -> bool; -} - -impl ActionResultExt for ActionResult { - fn empath(&self) -> bool { - match self { - Self::Empath { scapegoat } => *scapegoat, - resp => panic!("expected empath, got {resp:?}"), - } - } - fn mortician(&self) -> DiedToTitle { - match self { - Self::Mortician(role) => *role, - resp => panic!("expected mortician, got {resp:?}"), - } - } - fn gravedigger(&self) -> Option { - match self { - Self::GraveDigger(role) => *role, - resp => panic!("expected gravedigger, got {resp:?}"), - } - } - fn adjudicator(&self) -> Killer { - match self { - Self::Adjudicator { killer } => *killer, - resp => panic!("expected adjudicator, got {resp:?}"), - } - } - fn power_seer(&self) -> Powerful { - match self { - Self::PowerSeer { powerful } => *powerful, - resp => panic!("expected power seer, got {resp:?}"), - } - } - fn sleep(&self) { - assert_eq!(*self, ActionResult::GoBackToSleep) - } - - fn role_blocked(&self) { - assert_eq!(*self, ActionResult::RoleBlocked) - } - - fn r#continue(&self) { - assert_eq!(*self, ActionResult::Continue) - } - - fn seer(&self) -> Alignment { - match self { - ActionResult::Seer(a) => *a, - _ => panic!("expected a seer result"), - } - } - - fn arcanist(&self) -> AlignmentEq { - match self { - ActionResult::Arcanist(same) => *same, - _ => panic!("expected an arcanist result"), - } - } - - fn insomniac(&self) -> Visits { - match self { - ActionResult::Insomniac(v) => v.clone(), - _ => panic!("expected an insomniac result"), - } - } -} -#[allow(unused)] -pub trait AlignmentExt { - fn village(&self); - fn wolves(&self); -} - -impl AlignmentExt for Alignment { - fn village(&self) { - assert_eq!(*self, Alignment::Village) - } - - fn wolves(&self) { - assert_eq!(*self, Alignment::Wolves) - } -} -#[allow(unused)] -pub trait ServerToHostMessageExt { - fn prompt(self) -> ActionPrompt; - fn result(self) -> ActionResult; - fn daytime(self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8); -} - -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 { - match self { - Self::ActionPrompt(prompt, _) => prompt, - Self::Daytime { .. } => panic!("{}", "[got daytime]"), - msg => panic!("expected server message <<{msg:?}>> to be an ActionPrompt"), - } - } - - fn result(self) -> ActionResult { - match self { - Self::ActionResult(_, res) => res, - msg => panic!("expected server message <<{msg:?}>> to be an ActionResult"), - } - } -} - -#[allow(unused)] -pub trait GameExt { - fn villager_character_ids(&self) -> Box<[CharacterId]>; - fn character_by_player_id(&self, player_id: PlayerId) -> Character; - fn character_by_character_id(&self, character_id: CharacterId) -> Character; - fn next(&mut self) -> ActionPrompt; - fn r#continue(&mut self) -> ActionResult; - 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); - fn response(&mut self, resp: ActionResponse) -> ActionResult; - fn execute(&mut self) -> ActionPrompt; - fn mark_for_execution( - &mut self, - target: CharacterId, - ) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8); - fn living_villager_excl(&self, excl: PlayerId) -> Character; - fn living_villager(&self) -> Character; - #[allow(unused)] - fn get_state(&mut self) -> ServerToHostMessage; - fn next_expect_game_over(&mut self) -> GameOver; -} - -impl GameExt for Game { - fn next_expect_game_over(&mut self) -> GameOver { - match self - .process(HostGameMessage::Night(HostNightMessage::Next)) - .unwrap() - { - ServerToHostMessage::GameOver(outcome) => outcome, - resp => panic!("expected game to be over, got: {resp:?}"), - } - } - fn get_state(&mut self) -> ServerToHostMessage { - self.process(HostGameMessage::GetState).unwrap() - } - fn living_villager(&self) -> Character { - self.village() - .characters() - .into_iter() - .find(|c| c.alive() && matches!(c.role_title(), RoleTitle::Villager)) - .unwrap() - } - - fn living_villager_excl(&self, excl: PlayerId) -> Character { - self.village() - .characters() - .into_iter() - .find(|c| { - c.alive() && matches!(c.role_title(), RoleTitle::Villager) && c.player_id() != excl - }) - .unwrap() - } - - fn villager_character_ids(&self) -> Box<[CharacterId]> { - self.village() - .characters() - .into_iter() - .filter_map(|c| { - (c.alive() && matches!(c.role_title(), RoleTitle::Villager)) - .then_some(c.character_id()) - }) - .collect() - } - - fn character_by_player_id(&self, player_id: PlayerId) -> Character { - self.village() - .character_by_player_id(player_id) - .unwrap() - .clone() - } - - fn character_by_character_id(&self, character_id: CharacterId) -> Character { - self.village() - .character_by_id(character_id) - .unwrap() - .clone() - } - - 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), - ))) - .unwrap() - .prompt() - } - - fn mark_and_check(&mut self, mark: CharacterId) { - let prompt = self.mark(mark); - match prompt { - ActionPrompt::TraitorIntro { .. } - | ActionPrompt::Insomniac { .. } - | ActionPrompt::MasonsWake { .. } - | ActionPrompt::ElderReveal { .. } - | ActionPrompt::CoverOfDarkness - | ActionPrompt::WolvesIntro { .. } - | ActionPrompt::RoleChange { .. } - | ActionPrompt::Shapeshifter { .. } => panic!("expected a prompt with a mark"), - ActionPrompt::Arcanist { .. } => panic!("wrong call for arcanist"), - - ActionPrompt::Bloodletter { - marked: Some(marked), - .. - } - | ActionPrompt::LoneWolfKill { - marked: Some(marked), - .. - } - | ActionPrompt::Seer { - marked: Some(marked), - .. - } - | ActionPrompt::Adjudicator { - marked: Some(marked), - .. - } - | ActionPrompt::PowerSeer { - marked: Some(marked), - .. - } - | ActionPrompt::Mortician { - marked: Some(marked), - .. - } - | ActionPrompt::Beholder { - marked: Some(marked), - .. - } - | ActionPrompt::MasonLeaderRecruit { - marked: Some(marked), - .. - } - | ActionPrompt::Empath { - marked: Some(marked), - .. - } - | ActionPrompt::Vindicator { - marked: Some(marked), - .. - } - | ActionPrompt::PyreMaster { - marked: Some(marked), - .. - } - | ActionPrompt::Protector { - marked: Some(marked), - .. - } - | ActionPrompt::Gravedigger { - marked: Some(marked), - .. - } - | ActionPrompt::Hunter { - marked: Some(marked), - .. - } - | ActionPrompt::Militia { - marked: Some(marked), - .. - } - | ActionPrompt::MapleWolf { - marked: Some(marked), - .. - } - | ActionPrompt::Guardian { - marked: Some(marked), - .. - } - | ActionPrompt::WolfPackKill { - marked: Some(marked), - .. - } - | ActionPrompt::AlphaWolf { - marked: Some(marked), - .. - } - | ActionPrompt::DireWolf { - marked: Some(marked), - .. - } => assert_eq!(marked, mark, "marked character"), - ActionPrompt::Bloodletter { marked: None, .. } - | ActionPrompt::Seer { marked: None, .. } - | ActionPrompt::Adjudicator { marked: None, .. } - | ActionPrompt::PowerSeer { marked: None, .. } - | ActionPrompt::Mortician { marked: None, .. } - | ActionPrompt::Beholder { marked: None, .. } - | ActionPrompt::MasonLeaderRecruit { marked: None, .. } - | ActionPrompt::Empath { marked: None, .. } - | ActionPrompt::Vindicator { marked: None, .. } - | ActionPrompt::PyreMaster { marked: None, .. } - | ActionPrompt::Protector { marked: None, .. } - | ActionPrompt::Gravedigger { marked: None, .. } - | ActionPrompt::Hunter { marked: None, .. } - | ActionPrompt::Militia { marked: None, .. } - | ActionPrompt::MapleWolf { marked: None, .. } - | ActionPrompt::Guardian { marked: None, .. } - | ActionPrompt::WolfPackKill { marked: None, .. } - | ActionPrompt::AlphaWolf { marked: None, .. } - | ActionPrompt::DireWolf { marked: None, .. } - | ActionPrompt::LoneWolfKill { marked: None, .. } => panic!("no mark"), - } - } - - fn next(&mut self) -> ActionPrompt { - self.process(HostGameMessage::Night(HostNightMessage::Next)) - .unwrap() - .prompt() - } - - fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) { - match self - .process(HostGameMessage::Night(HostNightMessage::Next)) - .unwrap() - { - ServerToHostMessage::Daytime { - characters, - marked, - day, - .. - } => (characters, marked, day), - res => panic!("unexpected response to next_expect_day: {res:?}"), - } - } - - fn response(&mut self, resp: ActionResponse) -> ActionResult { - self.process(HostGameMessage::Night(HostNightMessage::ActionResponse( - resp, - ))) - .unwrap() - .result() - } - - fn mark_for_execution( - &mut self, - target: CharacterId, - ) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) { - match self - .process(HostGameMessage::Day(HostDayMessage::MarkForExecution( - target, - ))) - .unwrap() - { - ServerToHostMessage::Daytime { - characters, - marked, - day, - .. - } => (characters, marked, day), - res => panic!("unexpected response to mark_for_execution: {res:?}"), - } - } - - fn execute(&mut self) -> ActionPrompt { - assert_eq!( - self.process(HostGameMessage::Day(HostDayMessage::Execute)) - .unwrap() - .prompt(), - ActionPrompt::CoverOfDarkness - ); - self.r#continue().r#continue(); - self.next() - } -} diff --git a/werewolves/src/clients/mod.rs b/werewolves/src/clients/mod.rs index d355bd3..4a5223c 100644 --- a/werewolves/src/clients/mod.rs +++ b/werewolves/src/clients/mod.rs @@ -19,7 +19,6 @@ pub mod client { } pub mod host { mod host; - pub mod story_test; pub use host::*; } pub mod test_remote; @@ -27,18 +26,3 @@ pub mod test_remote; const DEBUG_URL: &str = "ws://192.168.1.162:8080/connect/"; const LIVE_URL: &str = "wss://wolf.emilis.dev/connect/"; - -pub mod story_test { - use yew::prelude::*; - - use crate::components::Story; - #[function_component] - pub fn StoryTest() -> Html { - let story = crate::clients::host::story_test::test_story(); - html! { -
- -
- } - } -} diff --git a/werewolves/src/components/host/chars.rs b/werewolves/src/components/host/chars.rs new file mode 100644 index 0000000..00598d6 --- /dev/null +++ b/werewolves/src/components/host/chars.rs @@ -0,0 +1,36 @@ +use werewolves_proto::message::CharacterState; +// Copyright (C) 2025 Emilis Bliūdžius +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +use yew::prelude::*; + +use crate::components::host::DaytimePlayerList; + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct CharacterStatesProps { + pub states: Box<[CharacterState]>, +} + +#[function_component] +pub fn CharacterStatesReadOnly(CharacterStatesProps { states }: &CharacterStatesProps) -> Html { + let marked: Box<[_]> = Box::new([]); + html! { + + } +} diff --git a/werewolves/src/components/host/daytime.rs b/werewolves/src/components/host/daytime.rs index 87b5776..879fef1 100644 --- a/werewolves/src/components/host/daytime.rs +++ b/werewolves/src/components/host/daytime.rs @@ -22,16 +22,16 @@ use werewolves_proto::{ }; use yew::prelude::*; -use crate::components::{ - AssociatedIcon, Button, Icon, IconType, Identity, PartialAssociatedIcon, -}; +use crate::components::{AssociatedIcon, Button, Icon, IconType, Identity, PartialAssociatedIcon}; #[derive(Debug, Clone, PartialEq, Properties)] pub struct DaytimePlayerListProps { - pub day: NonZeroU8, + #[prop_or_default] + pub day: Option, pub characters: Box<[CharacterState]>, pub marked: Box<[CharacterId]>, - pub on_execute: Callback<()>, + #[prop_or_default] + pub on_execute: Option>, pub on_mark: Callback, pub big_screen: bool, } @@ -60,7 +60,9 @@ pub fn DaytimePlayerList( Some(died_to) => match died_to.date_time() { GameTime::Day { .. } => Some(MarkState::Dead), GameTime::Night { number } => { - if number == day.get() - 1 { + if let Some(day) = day.as_ref() + && number == day.get() - 1 + { Some(MarkState::DiedLastNight) } else { Some(MarkState::Dead) @@ -82,16 +84,25 @@ pub fn DaytimePlayerList( } else { "execute" }; - let button = big_screen.not().then(|| { + let button = big_screen + .not() + .then_some(()) + .and_then(|_| on_execute.clone()) + .map(|on_execute| { + html! { + + } + }); + let day = day.as_ref().map(|day| { html! { - +

{"day "}{day.get()}

} }); html! {
-

{"day "}{day.to_string()}

+ {day}
{chars}