diff --git a/werewolves-macros/src/all.rs b/werewolves-macros/src/all.rs new file mode 100644 index 0000000..abcef64 --- /dev/null +++ b/werewolves-macros/src/all.rs @@ -0,0 +1,47 @@ +use quote::{ToTokens, quote}; +use syn::spanned::Spanned; + +pub struct All { + name: syn::Ident, + variants: Box<[syn::Ident]>, +} + +impl All { + pub fn parse(input: syn::DeriveInput) -> Result { + let data = match input.data { + syn::Data::Enum(enu) => enu, + _ => { + return Err(syn::Error::new( + input.span(), + "All can only be used on enums", + )); + } + }; + let variants = data + .variants + .into_iter() + .map(|v| match &v.fields { + syn::Fields::Named(_) | syn::Fields::Unnamed(_) => Err(syn::Error::new( + v.ident.span(), + "All can only be used on enums with only unit fields", + )), + syn::Fields::Unit => Ok(v.ident), + }) + .collect::, _>>()?; + let name = input.ident; + Ok(Self { name, variants }) + } +} + +impl ToTokens for All { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let name = &self.name; + let variants = &self.variants; + let count = self.variants.len(); + tokens.extend(quote! { + impl #name { + pub const ALL: [#name; #count] = [#(#name::#variants),*]; + } + }); + } +} diff --git a/werewolves-macros/src/lib.rs b/werewolves-macros/src/lib.rs index 028fdf7..174562a 100644 --- a/werewolves-macros/src/lib.rs +++ b/werewolves-macros/src/lib.rs @@ -9,6 +9,7 @@ use proc_macro2::Span; use quote::{ToTokens, quote}; use syn::{parse::Parse, parse_macro_input}; +mod all; mod checks; pub(crate) mod hashlist; mod targets; @@ -163,9 +164,10 @@ where continue; } if let Some(file_name) = item.file_name().to_str() - && include_in_rerun(file_name) { - out.push(FileWithPath::from_path(item.path(), origin_path)?); - } + && include_in_rerun(file_name) + { + out.push(FileWithPath::from_path(item.path(), origin_path)?); + } } Ok(()) @@ -542,3 +544,10 @@ pub fn extract(input: proc_macro::TokenStream) -> proc_macro::TokenStream { quote! {#checks_as}.into() } + +#[proc_macro_derive(All)] +pub fn all(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let all = all::All::parse(parse_macro_input!(input)).unwrap(); + + quote! {#all}.into() +} diff --git a/werewolves-proto/src/game/mod.rs b/werewolves-proto/src/game/mod.rs index e98260c..3e953b1 100644 --- a/werewolves-proto/src/game/mod.rs +++ b/werewolves-proto/src/game/mod.rs @@ -23,7 +23,7 @@ use crate::{ }; pub use { - settings::{Category, GameSettings, OrRandom, SetupRole, SetupSlot, SlotId}, + settings::{Category, GameSettings, OrRandom, SetupRole, SetupRoleTitle, SetupSlot, SlotId}, village::Village, }; diff --git a/werewolves-proto/src/game/settings.rs b/werewolves-proto/src/game/settings.rs index dc50961..417097e 100644 --- a/werewolves-proto/src/game/settings.rs +++ b/werewolves-proto/src/game/settings.rs @@ -209,11 +209,11 @@ impl GameSettings { .filter(|r| Into::::into(r.role.clone()).is_mentor()) .count(); self.roles.iter().try_for_each(|s| match &s.role { - SetupRole::Apprentice { specifically: None } => (mentor_count > 0) + SetupRole::Apprentice { to: None } => (mentor_count > 0) .then_some(()) .ok_or(GameError::NoApprenticeMentor), SetupRole::Apprentice { - specifically: Some(role), + to: Some(role), } => role .is_mentor() .then_some(()) diff --git a/werewolves-proto/src/game/settings/settings_role.rs b/werewolves-proto/src/game/settings/settings_role.rs index ba685b1..3af1b47 100644 --- a/werewolves-proto/src/game/settings/settings_role.rs +++ b/werewolves-proto/src/game/settings/settings_role.rs @@ -6,7 +6,7 @@ use core::{ use rand::distr::{Distribution, StandardUniform}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use werewolves_macros::ChecksAs; +use werewolves_macros::{All, ChecksAs, Titles}; use crate::{ error::GameError, @@ -40,7 +40,9 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ChecksAs)] +#[derive( + Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ChecksAs, All, +)] pub enum Category { #[checks] Wolves, @@ -50,8 +52,30 @@ pub enum Category { Offensive, StartsAsVillager, } +impl Category { + pub fn entire_category(&self) -> Box<[SetupRoleTitle]> { + SetupRoleTitle::ALL + .iter() + .filter(|r| r.category() == *self) + .cloned() + .collect() + } +} -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChecksAs)] +impl Display for Category { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Category::Wolves => "Wolves", + Category::Villager => "Villager", + Category::Intel => "Intel", + Category::Defensive => "Defensive", + Category::Offensive => "Offensive", + Category::StartsAsVillager => "Starts As Villager", + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChecksAs, Titles)] pub enum SetupRole { #[checks(Category::Villager)] Villager, @@ -74,7 +98,7 @@ pub enum SetupRole { #[checks(Category::Defensive)] Protector, #[checks(Category::StartsAsVillager)] - Apprentice { specifically: Option }, + Apprentice { to: Option }, #[checks(Category::StartsAsVillager)] Elder { knows_on_night: NonZeroU8 }, @@ -88,6 +112,39 @@ pub enum SetupRole { Shapeshifter, } +impl SetupRoleTitle { + pub const fn into_role(self) -> Role { + match self { + SetupRoleTitle::Villager => Role::Villager, + SetupRoleTitle::Scapegoat => Role::Scapegoat { redeemed: false }, + SetupRoleTitle::Seer => Role::Seer, + SetupRoleTitle::Arcanist => Role::Arcanist, + SetupRoleTitle::Gravedigger => Role::Gravedigger, + SetupRoleTitle::Hunter => Role::Hunter { target: None }, + SetupRoleTitle::Militia => Role::Militia { targeted: None }, + SetupRoleTitle::MapleWolf => Role::MapleWolf { + last_kill_on_night: 0, + }, + SetupRoleTitle::Guardian => Role::Guardian { + last_protected: None, + }, + SetupRoleTitle::Protector => Role::Protector { + last_protected: None, + }, + SetupRoleTitle::Apprentice => Role::Apprentice(RoleTitle::Arcanist), + SetupRoleTitle::Elder => Role::Elder { + woken_for_reveal: false, + lost_protection_night: None, + knows_on_night: NonZeroU8::new(1).unwrap(), + }, + SetupRoleTitle::Werewolf => Role::Werewolf, + SetupRoleTitle::AlphaWolf => Role::AlphaWolf { killed: None }, + SetupRoleTitle::DireWolf => Role::DireWolf, + SetupRoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None }, + } + } +} + impl Display for SetupRole { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { @@ -132,10 +189,8 @@ impl SetupRole { SetupRole::Protector => Role::Protector { last_protected: None, }, - SetupRole::Apprentice { - specifically: Some(role), - } => Role::Apprentice(role), - SetupRole::Apprentice { specifically: None } => { + SetupRole::Apprentice { to: Some(role) } => Role::Apprentice(role), + SetupRole::Apprentice { to: None } => { let mentors = roles_in_game .iter() .filter(|r| r.is_mentor()) @@ -197,7 +252,7 @@ impl From for SetupRole { RoleTitle::MapleWolf => SetupRole::MapleWolf, RoleTitle::Guardian => SetupRole::Guardian, RoleTitle::Protector => SetupRole::Protector, - RoleTitle::Apprentice => SetupRole::Apprentice { specifically: None }, + RoleTitle::Apprentice => SetupRole::Apprentice { to: None }, RoleTitle::Elder => SetupRole::Elder { knows_on_night: NonZeroU8::new(3).unwrap(), }, diff --git a/werewolves-proto/src/role.rs b/werewolves-proto/src/role.rs index 191ed62..d24cc94 100644 --- a/werewolves-proto/src/role.rs +++ b/werewolves-proto/src/role.rs @@ -99,29 +99,33 @@ impl Role { } } + pub const fn wakes_night_zero(&self) -> bool { + match self { + Role::DireWolf | Role::Arcanist | Role::Seer => true, + + Role::Shapeshifter { .. } + | Role::Werewolf + | Role::AlphaWolf { .. } + | Role::Elder { .. } + | Role::Gravedigger + | Role::Hunter { .. } + | Role::Militia { .. } + | Role::MapleWolf { .. } + | Role::Guardian { .. } + | Role::Apprentice(_) + | Role::Villager + | Role::Scapegoat { .. } + | Role::Protector { .. } => false, + } + } + pub fn wakes(&self, village: &Village) -> bool { let night_zero = match village.date_time() { DateTime::Day { number: _ } => return false, DateTime::Night { number } => number == 0, }; if night_zero { - return match self { - Role::DireWolf | Role::Arcanist | Role::Seer => true, - - Role::Shapeshifter { .. } - | Role::Werewolf - | Role::AlphaWolf { .. } - | Role::Elder { .. } - | Role::Gravedigger - | Role::Hunter { .. } - | Role::Militia { .. } - | Role::MapleWolf { .. } - | Role::Guardian { .. } - | Role::Apprentice(_) - | Role::Villager - | Role::Scapegoat { .. } - | Role::Protector { .. } => false, - }; + return self.wakes_night_zero(); } match self { Role::AlphaWolf { killed: Some(_) } diff --git a/werewolves-server/src/client.rs b/werewolves-server/src/client.rs index aa432cf..a965da9 100644 --- a/werewolves-server/src/client.rs +++ b/werewolves-server/src/client.rs @@ -237,7 +237,7 @@ impl Client { self.handle_message(msg).await } Err(err) => { - log::warn!("[{}] recv error: {err}", self.connection_id.player_id()); + log::debug!("[{}] recv error: {err}", self.connection_id.player_id()); return; } } diff --git a/werewolves-server/src/lobby.rs b/werewolves-server/src/lobby.rs index e891766..24c5a57 100644 --- a/werewolves-server/src/lobby.rs +++ b/werewolves-server/src/lobby.rs @@ -86,12 +86,13 @@ impl Lobby { } pub async fn next(&mut self) -> Option { - let msg = self - .comms() - .unwrap() - .next_message() - .await - .expect("get next message"); // TODO: keeps happening + let msg = match self.comms().unwrap().next_message().await { + Ok(msg) => msg, + Err(err) => { + log::error!("get next message: {err}"); + return None; + } + }; match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) { Ok(None) => {} @@ -162,7 +163,14 @@ impl Lobby { )) .log_warn(), Message::Host(HostMessage::Lobby(HostLobbyMessage::GetState)) - | Message::Host(HostMessage::GetState) => self.send_lobby_info_to_host().await?, + | Message::Host(HostMessage::GetState) => { + self.send_lobby_info_to_host().await?; + let settings = self.settings.clone(); + self.comms()? + .host() + .send(ServerToHostMessage::GameSettings(settings)) + .log_warn(); + } Message::Host(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) => { let msg = ServerToHostMessage::GameSettings(self.settings.clone()); let _ = self.comms().unwrap().host().send(msg); diff --git a/werewolves/img/killer.svg b/werewolves/img/killer.svg new file mode 100644 index 0000000..1839c88 --- /dev/null +++ b/werewolves/img/killer.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + diff --git a/werewolves/img/powerful.svg b/werewolves/img/powerful.svg new file mode 100644 index 0000000..8e8e1e9 --- /dev/null +++ b/werewolves/img/powerful.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + diff --git a/werewolves/img/village.svg b/werewolves/img/village.svg new file mode 100644 index 0000000..462766b --- /dev/null +++ b/werewolves/img/village.svg @@ -0,0 +1,65 @@ + + + + diff --git a/werewolves/img/wolf.svg b/werewolves/img/wolf.svg new file mode 100644 index 0000000..645761d --- /dev/null +++ b/werewolves/img/wolf.svg @@ -0,0 +1,220 @@ + + + + diff --git a/werewolves/index.scss b/werewolves/index.scss index 5724957..b4f7151 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -1078,3 +1078,94 @@ input { font-size: 2em; } + +.top-settings { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; +} + +.setup-screen { + width: 80%; + height: 80%; + position: fixed; + left: 10%; + top: 10%; + + .setup { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 5%; + width: 100%; + height: 80%; + } + + .category { + margin-bottom: 30px; + width: 30%; + text-align: center; + display: flex; + flex-direction: column; + + & .title { + margin-bottom: 10px; + } + + & .count { + left: -30px; + position: relative; + width: 0; + height: 0; + } + + .category-list { + text-align: left; + flex: 1, 1, 100%; + display: flex; + flex-wrap: nowrap; + flex-direction: column; + gap: 5px; + + .slot { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + + .attributes { + margin-left: 10px; + align-self: flex-end; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: 10px; + + .icon { + width: 32px; + height: 32px; + + &:hover { + filter: contrast(120%) brightness(120%); + } + } + + .inactive { + filter: grayscale(100%) brightness(30%); + } + } + + .role { + width: 100%; + filter: saturate(40%); + padding-left: 10px; + padding-right: 10px; + + &.wakes { + border: 2px solid yellow; + } + } + } + } + } +} diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index 5fd0376..f529b1e 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -28,7 +28,7 @@ use crate::{ components::{ Button, CoverOfDarkness, Lobby, LobbyPlayerAction, RoleReveal, Settings, action::{ActionResultView, Prompt}, - host::DaytimePlayerList, + host::{DaytimePlayerList, Setup}, }, }; @@ -302,76 +302,10 @@ impl Component for Host { }, HostState::Lobby { players, settings } => { - let on_error = self.error_callback.clone(); - - let settings = self.big_screen.not().then(|| { - let send = self.send.clone(); - let on_changed = Callback::from(move |s| { - let send = send.clone(); - yew::platform::spawn_local(async move { - let mut send = send.clone(); - if let Err(err) = send - .send(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s))) - .await - { - log::error!("sending game settings update: {err}"); - } - if let Err(err) = send - .send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) - .await - { - log::error!("sending game settings get: {err}"); - } - }); - }); - let send = self.send.clone(); - let on_start = Callback::from(move |_| { - let send = send.clone(); - let on_error = on_error.clone(); - yew::platform::spawn_local(async move { - let mut send = send.clone(); - if let Err(err) = - send.send(HostMessage::Lobby(HostLobbyMessage::Start)).await - { - on_error.emit(Some(err.into())) - } - }); - }); - html! { - - } - }); - let on_action = self.big_screen.not().then(|| { - let on_error = self.error_callback.clone(); - let send = self.send.clone(); - Callback::from(move |(player_id, act): (PlayerId, LobbyPlayerAction)| { - let msg = match act { - LobbyPlayerAction::Kick => { - HostMessage::Lobby(HostLobbyMessage::Kick(player_id)) - } - LobbyPlayerAction::SetNumber(num) => HostMessage::Lobby( - HostLobbyMessage::SetPlayerNumber(player_id, num), - ), - }; - let mut send = send.clone(); - let on_error = on_error.clone(); - yew::platform::spawn_local(async move { - if let Err(err) = send.send(msg).await { - on_error.emit(Some(err.into())) - } - }); - }) - }); - html! { -
- {settings} - -
+ if self.big_screen { + self.lobby_big_screen_show_setup(players, settings) + } else { + self.lobby_setup(players, settings) } } HostState::Day { @@ -644,3 +578,83 @@ impl Component for Host { } } } + +impl Host { + fn lobby_big_screen_show_setup(&self, _: Rc<[PlayerState]>, settings: GameSettings) -> Html { + html! { + + } + } + + fn lobby_setup(&self, players: Rc<[PlayerState]>, settings: GameSettings) -> Html { + let on_error = self.error_callback.clone(); + + let settings = self.big_screen.not().then(|| { + let send = self.send.clone(); + let on_changed = Callback::from(move |s| { + let send = send.clone(); + yew::platform::spawn_local(async move { + let mut send = send.clone(); + if let Err(err) = send + .send(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s))) + .await + { + log::error!("sending game settings update: {err}"); + } + if let Err(err) = send + .send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) + .await + { + log::error!("sending game settings get: {err}"); + } + }); + }); + let send = self.send.clone(); + let on_start = Callback::from(move |_| { + let send = send.clone(); + let on_error = on_error.clone(); + yew::platform::spawn_local(async move { + let mut send = send.clone(); + if let Err(err) = send.send(HostMessage::Lobby(HostLobbyMessage::Start)).await { + on_error.emit(Some(err.into())) + } + }); + }); + html! { + + } + }); + let on_action = self.big_screen.not().then(|| { + let on_error = self.error_callback.clone(); + let send = self.send.clone(); + Callback::from(move |(player_id, act): (PlayerId, LobbyPlayerAction)| { + let msg = match act { + LobbyPlayerAction::Kick => { + HostMessage::Lobby(HostLobbyMessage::Kick(player_id)) + } + LobbyPlayerAction::SetNumber(num) => { + HostMessage::Lobby(HostLobbyMessage::SetPlayerNumber(player_id, num)) + } + }; + let mut send = send.clone(); + let on_error = on_error.clone(); + yew::platform::spawn_local(async move { + if let Err(err) = send.send(msg).await { + on_error.emit(Some(err.into())) + } + }); + }) + }); + html! { +
+ {settings} + +
+ } + } +} diff --git a/werewolves/src/components/host/setup.rs b/werewolves/src/components/host/setup.rs new file mode 100644 index 0000000..df354f1 --- /dev/null +++ b/werewolves/src/components/host/setup.rs @@ -0,0 +1,162 @@ +use core::ops::Not; +use std::collections::HashMap; + +use rand::Rng; +use werewolves_proto::{ + game::{Category, GameSettings, SetupRole, SetupRoleTitle}, + role::Alignment, +}; +use yew::prelude::*; + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct SetupProps { + pub settings: GameSettings, +} + +#[function_component] +pub fn Setup(SetupProps { settings }: &SetupProps) -> Html { + let mut by_category: HashMap> = + Category::ALL.into_iter().map(|c| (c, Vec::new())).collect(); + for slot in settings.slots() { + by_category + .get_mut(&slot.role.category()) + .unwrap() + .push(slot.role.clone()); + } + + let mut categories = by_category.into_iter().collect::>(); + categories.sort_by_key(|(c, _)| match c { + Category::Wolves => 1u8, + Category::Intel => 2, + Category::Villager => 4, + Category::Defensive => 3, + Category::Offensive => 5, + Category::StartsAsVillager => 6, + }); + + let categories = categories + .into_iter() + .map(|(cat, members)| { + let hide = match cat { + Category::Wolves => CategoryMode::ShowExactRoleCount, + Category::Villager => CategoryMode::ShowExactRoleCount, + Category::Intel + | Category::Defensive + | Category::Offensive + | Category::StartsAsVillager => CategoryMode::HideAllInfo, + }; + html! { + + } + }) + .collect::(); + + let power_roles_count = settings + .slots() + .iter() + .filter(|r| !matches!(r.role.category(), Category::Villager | Category::Wolves)) + .count(); + + html! { +
+
+ {categories} +
+
+ {power_roles_count} + {"Power roles from..."} +
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[allow(unused)] +pub enum CategoryMode { + #[default] + HideAllInfo, + ShowTotalCount, + ShowExactRoleCount, +} + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct SetupCategoryProps { + pub category: Category, + pub roles: Box<[SetupRole]>, + #[prop_or_default] + pub mode: CategoryMode, +} + +#[function_component] +pub fn SetupCategory( + SetupCategoryProps { + category, + roles, + mode, + }: &SetupCategoryProps, +) -> Html { + let roles_count = match mode { + CategoryMode::HideAllInfo => None, + CategoryMode::ShowTotalCount | CategoryMode::ShowExactRoleCount => Some(html! { + {roles.len().to_string()} + }), + }; + let mut roles_in_category = SetupRoleTitle::ALL + .into_iter() + .filter(|r| r.category() == *category) + .collect::>(); + roles_in_category.sort_by_key(|l| l.into_role().wakes_night_zero()); + + let all_roles = roles_in_category + .into_iter() + .map(|r| (r, roles.iter().filter(|sr| sr.title() == r).count())) + .filter(|(_, count)| !(matches!(mode, CategoryMode::ShowExactRoleCount) && *count == 0)) + .map(|(r, count)| { + let as_role = r.into_role(); + let wakes = as_role.wakes_night_zero().then_some("wakes"); + let count = matches!(mode, CategoryMode::ShowExactRoleCount).then(|| { + html! { + {count} + } + }); + let killer_inactive = as_role.killer().not().then_some("inactive"); + let powerful_inactive = as_role.powerful().not().then_some("inactive"); + let alignment = match as_role.alignment() { + Alignment::Village => "/img/village.svg", + Alignment::Wolves => "/img/wolf.svg", + }; + html! { +
+
+ {count} + {r.to_string()} +
+
+
+ {"alignment"}/ +
+
+ killer icon +
+
+ powerful icon +
+
+
+ } + }) + .collect::(); + html! { +
+ {roles_count} +
{category.to_string()}
+
+ {all_roles} +
+
+ } +} diff --git a/werewolves/src/components/settings.rs b/werewolves/src/components/settings.rs index 39f7e0f..bc7a6a4 100644 --- a/werewolves/src/components/settings.rs +++ b/werewolves/src/components/settings.rs @@ -212,9 +212,42 @@ pub fn Settings( } }); + let clear_setup = { + let update = on_update.clone(); + let on_click = Callback::from(move |_| { + update.emit(GameSettings::empty()); + }); + let disabled_reason = settings.slots().is_empty().then_some("no setup to clear"); + html! { + + } + }; + + let fill_empty_with_villagers = { + let disabled_reason = + (settings.min_players_needed() >= players.len()).then_some("no empty slots"); + let update = on_update.clone(); + let settings = settings.clone(); + let player_count = players.len(); + let on_click = Callback::from(move |_| { + let mut settings = (*settings).clone(); + settings.fill_remaining_slots_with_villagers(player_count); + update.emit(settings); + }); + html! { + + } + }; + html! {
+ {fill_empty_with_villagers} + {clear_setup} {clear_all_assignments} {clear_bad_assigned}
@@ -376,7 +409,7 @@ fn setup_options_for_slot( }) } - SetupRole::Apprentice { specifically } => { + SetupRole::Apprentice { to: specifically } => { let options = roles_in_setup .iter() .filter(|r| r.is_mentor()) @@ -398,7 +431,7 @@ fn setup_options_for_slot( let role_name = option.to_string().to_case(Case::Title); let mut slot = slot.clone(); match &mut slot.role { - SetupRole::Apprentice { specifically } => { + SetupRole::Apprentice { to: specifically } => { specifically.replace(option); } _ => unreachable!(), @@ -421,7 +454,7 @@ fn setup_options_for_slot( let role_name = "random"; let mut slot = slot.clone(); match &mut slot.role { - SetupRole::Apprentice { specifically } => { + SetupRole::Apprentice { to: specifically } => { specifically.take(); } _ => unreachable!(), diff --git a/werewolves/src/main.rs b/werewolves/src/main.rs index d0aac83..633e29b 100644 --- a/werewolves/src/main.rs +++ b/werewolves/src/main.rs @@ -55,7 +55,7 @@ fn main() { } } else if path.starts_with("/many-client") { let clients = document.query_selector("clients").unwrap().unwrap(); - for (player_id, name, num, dupe) in (1..=16).map(|num| { + for (player_id, name, num, dupe) in (1..=7).map(|num| { ( PlayerId::from_u128(num as u128), format!("player {num}"),