diff --git a/style/main.scss b/style/main.scss index 4c5b6e7..3872b25 100644 --- a/style/main.scss +++ b/style/main.scss @@ -592,6 +592,7 @@ dialog .tab-content { } .player-lobby { + user-select: none; margin: 5vh 15vw 5vh 15vw; padding: 3ch; font-size: 1.5em; @@ -604,6 +605,74 @@ dialog .tab-content { } } +.player-additional { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.number-info { + display: flex; + flex-direction: column; + border: 1px solid rgb(0, 64, 0); + background-color: rgba(0, 64, 0, 0.3); + gap: 0.25ch; + width: fit-content; + padding: 3ch; + min-width: 40vw; + align-items: center; + + &.unset { + border: 1px solid rgb(128, 0, 0); + background-color: rgba(64, 0, 0, 0.3); + } + + #player-number { + text-align: center; + } + + .number-text { + font-size: 1.5em; + user-select: none; + + .number { + font-size: 1.25em; + } + + &.unset { + font-size: 1.25em; + color: rgb(192, 0, 0); + font-weight: bold; + } + } +} + +.number-update:not([hidden]) { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + gap: 0.25ch; + margin-top: 3ch; + font-size: 1.5em; + + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; + } + + input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + } + + input[type=submit] { + @extend button; + } +} + .tutorial-box { display: flex; flex-direction: column; diff --git a/werewolves-proto/src/diedto.rs b/werewolves-proto/src/diedto.rs index cf9e650..aae9702 100644 --- a/werewolves-proto/src/diedto.rs +++ b/werewolves-proto/src/diedto.rs @@ -19,7 +19,7 @@ use werewolves_macros::Titles; use crate::{character::CharacterId, game::GameTime}; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Titles)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Titles)] pub enum DiedTo { Execution { day: NonZeroU8, diff --git a/werewolves-proto/src/message.rs b/werewolves-proto/src/message.rs index 79a96b0..b3cf3a0 100644 --- a/werewolves-proto/src/message.rs +++ b/werewolves-proto/src/message.rs @@ -69,6 +69,7 @@ pub enum ServerToClientMessage { LobbyInfo { joined: bool, players: Box<[PublicIdentity]>, + current_number: Option, }, GameInProgress, GameStart { diff --git a/werewolves-proto/src/message/dead.rs b/werewolves-proto/src/message/dead.rs index 073279c..8c3870a 100644 --- a/werewolves-proto/src/message/dead.rs +++ b/werewolves-proto/src/message/dead.rs @@ -197,14 +197,14 @@ impl DeadChat { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeadChatMessage { pub id: Uuid, pub timestamp: DateTime, pub message: DeadChatContent, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum DeadChatContent { PlayerMessage { from: CharacterIdentity, diff --git a/werewolves-proto/src/message/ident.rs b/werewolves-proto/src/message/ident.rs index 8d87fec..a246edd 100644 --- a/werewolves-proto/src/message/ident.rs +++ b/werewolves-proto/src/message/ident.rs @@ -31,7 +31,7 @@ pub struct PublicIdentity { pub number: Option, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CharacterIdentity { pub character_id: CharacterId, pub name: String, diff --git a/werewolves/src/app/components/nav.rs b/werewolves/src/app/components/nav.rs index dfa1c85..bc4dd40 100644 --- a/werewolves/src/app/components/nav.rs +++ b/werewolves/src/app/components/nav.rs @@ -6,24 +6,20 @@ use leptos_router::hooks::use_url; use reactive_stores::Store; use crate::app::{ - components::LinkButton, + components::{DialogModal, LinkButton}, storage::user::{AuthContext, AuthContextStoreFields}, }; #[server] pub async fn get_active_game(token: TokenString) -> Result, ServerError> { - let db = use_context::() - .expect("no app state") - .db; + let db = expect_context::().db; let user = db.user().check_token(&token).await?; Ok(db.game().get_joined_active_game(user.id).await?) } #[server] pub async fn new_game(token: TokenString) -> Result { - let db = use_context::() - .expect("no app state") - .db; + let db = expect_context::().db; let user = db.user().check_token(&token).await?; Ok(db.game().new_game(user.id).await?.id) } @@ -100,6 +96,7 @@ pub fn Nav() -> impl IntoView { } }) }; + view! { {home_button} {session.name().to_string()} diff --git a/werewolves/src/app/pages/game/player.rs b/werewolves/src/app/pages/game/player.rs index 92582ce..bb05911 100644 --- a/werewolves/src/app/pages/game/player.rs +++ b/werewolves/src/app/pages/game/player.rs @@ -1,10 +1,21 @@ werewolves_macros::include_path!("werewolves/src/app/pages/game/player"); +use core::{ + hash::Hash, + num::{self, NonZeroU8}, + ops::{Deref, Not}, +}; +use std::collections::HashSet; + use convert_case::{Case, Casing}; -use leptos::{ev::MouseEvent, prelude::*}; +use leptos::{ + ev::{Event, MouseEvent, SubmitEvent, Targeted}, + prelude::*, +}; use werewolves_proto::{ message::{ - ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, dead::DeadChatMessage, + ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, UpdateSelf, + dead::DeadChatMessage, }, role::RoleTitle, }; @@ -13,8 +24,13 @@ use crate::{ConsoleLogError, app::components::ErrorBox}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Page { - Lobby { joined: bool }, - RoleReveal { role: RoleTitle }, + Lobby { + joined: bool, + current_number: Option, + }, + RoleReveal { + role: RoleTitle, + }, GameInProgress, Sleep, DeadChat, @@ -27,7 +43,7 @@ pub fn PlayerGamePage( disconnect: RwSignal, ) -> impl IntoView { let error = RwSignal::new(None); - let dead_chat: RwSignal>> = RwSignal::new(None); + let dead_chat: RwSignal>> = RwSignal::new(None); let page: RwSignal> = RwSignal::new(None); Effect::new(move || { let Some(message) = message.get() else { @@ -35,18 +51,30 @@ pub fn PlayerGamePage( }; match message { Srv2Client::Disconnect => disconnect.set(true), - Srv2Client::LobbyInfo { joined, .. } => page.set(Some(Page::Lobby { joined })), + Srv2Client::LobbyInfo { + joined, + current_number, + .. + } => page.set(Some(Page::Lobby { + joined, + current_number, + })), Srv2Client::GameInProgress => page.set(Some(Page::GameInProgress)), Srv2Client::GameStart { role } => page.set(Some(Page::RoleReveal { role })), Srv2Client::Story(game_story) => todo!("{game_story:#?}"), Srv2Client::Update(_) => {} Srv2Client::DeadChat(dead_chat_messages) => { - dead_chat.set(Some(dead_chat_messages.to_vec())); + dead_chat.set(Some( + dead_chat_messages + .into_iter() + .map(Into::::into) + .collect(), + )); page.set(Some(Page::DeadChat)); } Srv2Client::DeadChatMessage(dead_chat_message) => { if let Some(chat) = dead_chat.write().as_mut() { - chat.push(dead_chat_message); + chat.insert(dead_chat_message.into()); page.set(Some(Page::DeadChat)); } else { reply.set(Some(ClientMessage::DeadChat(ClientDeadChat::GetHistory))); @@ -61,6 +89,8 @@ pub fn PlayerGamePage( } // match message {} }); + let joined = RwSignal::new(false); + let current_number = RwSignal::new(None); let content = move || { let Some(page) = page.get() else { return ().into_any(); @@ -68,32 +98,19 @@ pub fn PlayerGamePage( match page { Page::DeadChat => todo!("dead chat"), Page::Sleep => view! {

"go to sleep"

}.into_any(), - Page::Lobby { joined } => { - let click = move |ev: MouseEvent| { - ev.prevent_default(); - reply.set(Some(if joined { - ClientMessage::Goodbye - } else { - ClientMessage::Hello - })); - }; - let text = match joined { - true => view! { -

"you are in the lobby"

-

"you're all good c:"

- } - .into_any(), - false => view! { -

"you are not in the lobby"

-

"join if you want to play"

- } - .into_any(), - }; + Page::Lobby { + joined: j, + current_number: c, + } => { + joined.set(j); + current_number.set(c); view! { -
- {text} - -
+ } .into_any() } @@ -123,3 +140,39 @@ pub fn PlayerGamePage( } .into_any() } + +#[repr(transparent)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HashedDeadChatMessage(DeadChatMessage); +impl Ord for HashedDeadChatMessage { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.timestamp.cmp(&other.timestamp) + } +} +impl PartialOrd for HashedDeadChatMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Hash for HashedDeadChatMessage { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} +impl From for HashedDeadChatMessage { + fn from(value: DeadChatMessage) -> Self { + Self(value) + } +} +impl From for DeadChatMessage { + fn from(value: HashedDeadChatMessage) -> Self { + value.0 + } +} +impl Deref for HashedDeadChatMessage { + type Target = DeadChatMessage; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/werewolves/src/app/pages/game/player/lobby.rs b/werewolves/src/app/pages/game/player/lobby.rs new file mode 100644 index 0000000..6a4e0f0 --- /dev/null +++ b/werewolves/src/app/pages/game/player/lobby.rs @@ -0,0 +1,128 @@ +use core::num::NonZeroU8; + +use leptos::{ + ev::{Event, MouseEvent, SubmitEvent, Targeted}, + prelude::*, +}; +use werewolves_proto::message::{ClientMessage, UpdateSelf}; + +#[component] +pub fn PlayerLobby( + reply: WriteSignal>, + joined: ReadSignal, + current_number: ReadSignal>, + error: RwSignal>, +) -> impl IntoView { + let click = move |ev: MouseEvent| { + ev.prevent_default(); + reply.set(Some(if joined.get() { + ClientMessage::Goodbye + } else { + ClientMessage::Hello + })); + }; + let text = move || match joined.get() { + true => view! { +

"you are in the lobby"

+

"you're all good c:"

+ } + .into_any(), + false => view! { +

"you are not in the lobby"

+

"join if you want to play"

+ } + .into_any(), + }; + let number: RwSignal> = RwSignal::new(None); + let number = move || { + let form_hidden = RwSignal::new(true); + let number_update = { + let update = move |e: Targeted| { + let value = e.target().value(); + if value.trim().is_empty() { + number.set(None); + return; + } + let current = number.get_untracked(); + let default = current.map(|c| c.get().to_string()).unwrap_or_default(); + let value_u8 = match e.target().value().trim().parse::() { + Ok(v) => v, + Err(_) => { + e.target().set_value(default.as_str()); + return; + } + }; + if let Some(nz) = NonZeroU8::new(value_u8) { + number.set(Some(nz)); + } else { + e.target().set_value(default.as_str()); + return; + } + }; + let submit = move |ev: SubmitEvent| { + ev.prevent_default(); + let Some(num) = number.get() else { + error.set(Some("please set a number".into())); + return; + }; + reply.set(Some(ClientMessage::UpdateSelf(UpdateSelf::Number(num)))); + form_hidden.set(true); + }; + view! { + + } + .into_any() + }; + match current_number.get() { + Some(num) => { + form_hidden.set(true); + view! { + + "you are in seat "{num.get()} + + + {number_update} + } + .into_any() + } + None => { + form_hidden.set(false); + view! { + "you don't have a seat number :c" + "please put your seat number in below" + {number_update} + } + .into_any() + } + } + }; + view! { +
+ {text} + +
+
+
+ {number} +
+
+ } +} diff --git a/werewolves/src/app/pages/user_settings.rs b/werewolves/src/app/pages/user_settings.rs index 0878196..bc518c7 100644 --- a/werewolves/src/app/pages/user_settings.rs +++ b/werewolves/src/app/pages/user_settings.rs @@ -24,9 +24,7 @@ pub fn UserSettings() -> impl IntoView { match tut_read.read().enabled { true => view! { } .into_any(), - false => view! { - - }.into_any(), + false => view! { }.into_any(), } }; view! { diff --git a/werewolves/src/server/lobby.rs b/werewolves/src/server/lobby.rs index 1c453d9..fbbc95b 100644 --- a/werewolves/src/server/lobby.rs +++ b/werewolves/src/server/lobby.rs @@ -69,19 +69,20 @@ impl<'a> Lobby<'a> { } pub async fn send_lobby_info_to_clients(&mut self) -> Result<(), ServerError> { - let (players, identities) = self - .db - .game() - .get_joined_players(self.game_id) - .await? - .into_iter() - .map(|p| (p.player_id, p.public)) + let joined = self.db.game().get_joined_players(self.game_id).await?; + let (players, identities) = joined + .iter() + .map(|p| (p.player_id, p.public.clone())) .collect::<(Vec<_>, Vec<_>)>(); let identities = identities.into_boxed_slice(); super::send_to_all_players_in_game_filtered(self.game_id, move |pid| { Some(ServerToClientMessage::LobbyInfo { joined: players.contains(&pid), players: identities.clone(), + current_number: joined + .iter() + .find_map(|j| (j.player_id == pid).then_some(j.public.number)) + .flatten(), }) }) .await; @@ -257,11 +258,16 @@ impl<'a> Lobby<'a> { self.send_lobby_info_to_clients().await.log_debug(loc!()); } HostOrClientMessage::Client(IdentifiedClientMessage { - identity: Identification { player_id, .. }, + identity: + Identification { + player_id, + public: PublicIdentity { number, .. }, + }, update: ClientUpdate::Message(ClientMessage::GetState), }) => { let joined = self.db.game().get_joined_players(self.game_id).await?; let msg = ServerToClientMessage::LobbyInfo { + current_number: number, joined: joined.iter().any(|p| p.player_id == player_id), players: joined.into_iter().map(|p| p.public).collect(), };