diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index fdc42f2..daf855b 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -22,6 +22,7 @@ use futures::{ use gloo::net::websocket::{self, futures::WebSocket}; use instant::Instant; use serde::Serialize; +use werewolves_macros::Titles; use werewolves_proto::{ character::CharacterId, error::GameError, @@ -47,6 +48,7 @@ use crate::{ }, pages::RolePage, storage::StorageKey, + test_util::TestScreens, }; use crate::WerewolfError; @@ -190,7 +192,7 @@ async fn worker(mut recv: Receiver, scope: Scope) { } } -#[derive(Debug)] +#[derive(Debug, Clone, Titles, PartialEq)] pub enum HostEvent { SetErrorCallback(Callback>), SetBigScreenState(bool), @@ -201,8 +203,11 @@ pub enum HostEvent { Settings(GameSettings), Error(GameError), QrMode(bool), + ToOverrideView, + ReturnFromOverride, + ExpectEcho(Box), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Titles)] pub enum HostState { Disconnected, Lobby { @@ -224,6 +229,9 @@ pub enum HostState { }, Prompt(ActionPrompt, usize), Result(Option, ActionResult), + ScreenOverrides { + return_to: Box, + }, Story { story: GameStory, #[allow(unused)] @@ -280,6 +288,7 @@ pub struct Host { big_screen: bool, qr_mode: bool, debug: bool, + expecting_echo: Option, } impl Component for Host { @@ -310,12 +319,33 @@ impl Component for Host { log::error!("{err}") } }), + expecting_echo: None, } } fn view(&self, _ctx: &Context) -> Html { log::trace!("state: {:?}", self.state); let content = match self.state.clone() { + HostState::ScreenOverrides { .. } => { + let send = { + let send = self.send.clone(); + let on_err = self.error_callback.clone(); + let scope = _ctx.link().clone(); + Callback::from(move |msg: ServerToHostMessage| { + scope.send_message(HostEvent::ExpectEcho(Box::new(msg.clone().into()))); + let mut send = send.clone(); + let on_err = on_err.clone(); + yew::platform::spawn_local(async move { + if let Err(err) = send.send(HostMessage::Echo(msg)).await { + on_err.emit(Some(err.into())) + } + }); + }) + }; + html! { + + } + } HostState::CharacterStates(chars) => { html! { @@ -466,15 +496,6 @@ impl Component for Host { } } }; - let debug_nav = self.debug.then(|| { - html! { - - } - }); - 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( @@ -494,12 +515,52 @@ impl Component for Host { } _ => None, }; + let override_screens_btn = match &self.state { + HostState::Prompt(_, _) | HostState::Result(_, _) => { + let overrides_click = { + let scope = _ctx.link().clone(); + Callback::from(move |_| { + scope.send_message(HostEvent::ToOverrideView); + }) + }; + + Some(html! { + + }) + } + HostState::ScreenOverrides { .. } => { + let return_click = { + let scope = _ctx.link().clone(); + Callback::from(move |_| { + scope.send_message(HostEvent::ReturnFromOverride); + }) + }; + + Some(html! { + + }) + } + _ => None, + }; + let previous_btn = match &self.state { + HostState::Prompt(_, _) | HostState::Result(_, _) => { + let on_prev_click = callback::send_message( + HostMessage::InGame(HostGameMessage::PreviousState), + self.send.clone(), + ); + + Some(html! { + + }) + } + _ => None, + }; let nav = self.big_screen.not().then(|| { html! { } }); @@ -514,116 +575,8 @@ 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 - } - HostEvent::PlayerList(mut players) => { - const LAST: NonZeroU8 = NonZeroU8::new(0xFF).unwrap(); - players.sort_by(|l, r| { - l.identification - .public - .number - .unwrap_or(LAST) - .cmp(&r.identification.public.number.unwrap_or(LAST)) - }); - match &mut self.state { - HostState::Lobby { - players: p, - settings: _, - } => *p = players.into_iter().collect(), - HostState::Story { .. } - | HostState::Disconnected - | HostState::GameOver { .. } => { - let mut send = self.send.clone(); - let on_err = self.error_callback.clone(); - - self.state = HostState::Lobby { - players: players.into_iter().collect(), - settings: Default::default(), - }; - yew::platform::spawn_local(async move { - if let Err(err) = send - .send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) - .await - { - on_err.emit(Some(err.into())) - } - }); - } - - HostState::CharacterStates(_) - | HostState::Prompt(_, _) - | HostState::Result(_, _) - | HostState::RoleReveal { .. } - | HostState::Day { .. } => { - return false; - } - } - true - } - HostEvent::SetErrorCallback(callback) => { - self.error_callback = callback; - false - } - HostEvent::SetState(state) => { - self.state = state; - true - } - HostEvent::Settings(settings) => match &mut self.state { - HostState::Lobby { - players: _, - settings: s, - } => { - *s = settings; - true - } - HostState::CharacterStates(_) - | HostState::Story { .. } - | HostState::Prompt(_, _) - | HostState::Result(_, _) - | HostState::Disconnected - | HostState::RoleReveal { - ackd: _, - waiting: _, - } - | HostState::GameOver { result: _ } - | HostState::Day { .. } => { - log::info!("ignoring settings get"); - false - } - }, - HostEvent::Error(err) => { - self.error_callback.emit(Some(WerewolfError::Game(err))); - false - } - HostEvent::SetBigScreenState(state) => { - self.big_screen = state; - if self.big_screen { - gloo::utils::document_element().set_class_name("big-screen") - } - - if state { - let (mut discard_send, mut discard_recv) = futures::channel::mpsc::channel(10); - core::mem::swap(&mut discard_send, &mut self.send); - Box::leak(Box::new(discard_send)); - yew::platform::spawn_local(async move { - while discard_recv.next().await.is_some() {} - }); - } - self.debug = false; - self.error_callback = Callback::noop(); - false - } + log::debug!("update: {}, current: {}", msg.title(), self.state.title()); + match &msg { HostEvent::Continue => { if self.big_screen { return false; @@ -639,13 +592,178 @@ impl Component for Host { log::error!("sending next: {err:?}") } }); - false } + HostEvent::SetErrorCallback(cb) => { + self.error_callback = cb.clone(); + } + HostEvent::SetBigScreenState(state) => { + self.big_screen = *state; + if self.big_screen { + gloo::utils::document_element().set_class_name("big-screen") + } + + if *state { + let (mut discard_send, mut discard_recv) = futures::channel::mpsc::channel(10); + core::mem::swap(&mut discard_send, &mut self.send); + Box::leak(Box::new(discard_send)); + yew::platform::spawn_local(async move { + while discard_recv.next().await.is_some() {} + }); + } + self.debug = false; + self.error_callback = Callback::noop(); + } + HostEvent::QrMode(mode) => { + self.qr_mode = *mode; + } + HostEvent::ExpectEcho(echo) => { + self.expecting_echo.replace((**echo).clone()); + } + HostEvent::ReturnFromOverride => { + if let HostState::ScreenOverrides { return_to } = &self.state { + log::debug!("returning from screen overrides to: {}", return_to.title()); + self.state = (**return_to).clone(); + return true; + } else { + return false; + } + } + _ => {} } + if let HostState::ScreenOverrides { .. } = &self.state { + let (state, update) = self.message_to_new_state(msg.clone()); + if let Some(mut state) = state + // re-borrow so we can borrow self immutably for [message_to_new_state] + && let HostState::ScreenOverrides { return_to } = &mut self.state + && (self.expecting_echo.is_none() + || self + .expecting_echo + .as_ref() + .map(|exp| *exp != msg) + .unwrap_or_default()) + { + log::debug!("screen override: new return_to: {}", return_to.title()); + core::mem::swap(&mut **return_to, &mut state); + self.expecting_echo.take(); + } + return update; + } + + let (state, update) = self.message_to_new_state(msg); + if let Some(state) = state { + self.state = state; + } + + update } } impl Host { + fn message_to_new_state(&self, msg: HostEvent) -> (Option, bool) { + match msg { + HostEvent::ExpectEcho(_) => (None, false), + HostEvent::ReturnFromOverride => { + if let HostState::ScreenOverrides { return_to } = &self.state { + (Some((**return_to).clone()), true) + } else { + (None, false) + } + } + HostEvent::ToOverrideView => { + if let HostState::ScreenOverrides { .. } = &self.state { + return (None, false); + } + ( + Some(HostState::ScreenOverrides { + return_to: Box::new(self.state.clone()), + }), + true, + ) + } + HostEvent::CharacterList(char) => { + if self.big_screen { + return (None, false); + } + (Some(HostState::CharacterStates(char)), true) + } + HostEvent::QrMode(mode) => (None, true), + HostEvent::PlayerList(mut players) => { + const LAST: NonZeroU8 = NonZeroU8::new(0xFF).unwrap(); + players.sort_by(|l, r| { + l.identification + .public + .number + .unwrap_or(LAST) + .cmp(&r.identification.public.number.unwrap_or(LAST)) + }); + match &self.state { + HostState::Lobby { settings, .. } => ( + Some(HostState::Lobby { + players: players.into_iter().collect(), + settings: settings.clone(), + }), + true, + ), + HostState::Story { .. } + | HostState::Disconnected + | HostState::GameOver { .. } => { + let mut send = self.send.clone(); + let on_err = self.error_callback.clone(); + + let state = Some(HostState::Lobby { + players: players.into_iter().collect(), + settings: Default::default(), + }); + yew::platform::spawn_local(async move { + if let Err(err) = send + .send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) + .await + { + on_err.emit(Some(err.into())) + } + }); + (state, true) + } + + HostState::ScreenOverrides { .. } + | HostState::CharacterStates(_) + | HostState::Prompt(_, _) + | HostState::Result(_, _) + | HostState::RoleReveal { .. } + | HostState::Day { .. } => (None, false), + } + } + HostEvent::SetErrorCallback(callback) => (None, false), + HostEvent::SetState(state) => (Some(state), true), + HostEvent::Settings(settings) => match &self.state { + HostState::Lobby { players, .. } => ( + Some(HostState::Lobby { + settings, + players: players.clone(), + }), + true, + ), + HostState::ScreenOverrides { .. } + | HostState::CharacterStates(_) + | HostState::Story { .. } + | HostState::Prompt(_, _) + | HostState::Result(_, _) + | HostState::Disconnected + | HostState::RoleReveal { + ackd: _, + waiting: _, + } + | HostState::GameOver { result: _ } + | HostState::Day { .. } => { + log::info!("ignoring settings get"); + (None, false) + } + }, + HostEvent::Error(err) => (None, false), + HostEvent::SetBigScreenState(state) => (None, true), + HostEvent::Continue => (None, false), + } + } fn lobby_big_screen_show_setup(&self, settings: GameSettings) -> Html { if !self.qr_mode { return html! {