qr mode + fix mortician
This commit is contained in:
parent
1c6321322f
commit
98493d34be
|
|
@ -464,6 +464,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fast_qr"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f5ea9788036fedaf55f43a2db0ba01eedf47d26fd6852f01e5cf51952571d57"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
|
|
@ -2447,6 +2453,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"ciborium",
|
||||
"colored",
|
||||
"fast_qr",
|
||||
"futures",
|
||||
"log",
|
||||
"mime-sniffer",
|
||||
|
|
|
|||
|
|
@ -400,7 +400,13 @@ impl Character {
|
|||
},
|
||||
Role::Mortician => ActionPrompt::Mortician {
|
||||
character_id: self.identity(),
|
||||
dead_players: village.dead_targets(),
|
||||
dead_players: {
|
||||
let dead = village.dead_targets();
|
||||
if dead.is_empty() {
|
||||
return Ok(Box::new([]));
|
||||
}
|
||||
dead
|
||||
},
|
||||
marked: None,
|
||||
},
|
||||
Role::Beholder => ActionPrompt::Beholder {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use core::num::NonZeroU8;
|
|||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||
|
||||
use crate::{
|
||||
diedto::DiedTo,
|
||||
game::{Game, GameSettings, SetupRole},
|
||||
game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players},
|
||||
message::night::{ActionPrompt, ActionPromptTitle},
|
||||
|
|
@ -67,3 +68,46 @@ fn recruits_decrement() {
|
|||
|
||||
game.next_expect_day();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dies_recruiting_wolf() {
|
||||
let players = gen_players(1..10);
|
||||
let mason_leader_player_id = players[0].player_id;
|
||||
let wolf_player_id = players[1].player_id;
|
||||
let mut settings = GameSettings::empty();
|
||||
settings.add_and_assign(
|
||||
SetupRole::MasonLeader {
|
||||
recruits_available: NonZeroU8::new(1).unwrap(),
|
||||
},
|
||||
mason_leader_player_id,
|
||||
);
|
||||
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||
settings.fill_remaining_slots_with_villagers(9);
|
||||
let mut game = Game::new(&players, settings).unwrap();
|
||||
game.r#continue().r#continue();
|
||||
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||
game.r#continue().sleep();
|
||||
game.next_expect_day();
|
||||
|
||||
assert_eq!(game.execute().title(), ActionPromptTitle::WolfPackKill);
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
assert_eq!(game.next().title(), ActionPromptTitle::MasonLeaderRecruit);
|
||||
game.mark(game.character_by_player_id(wolf_player_id).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
|
||||
assert_eq!(
|
||||
game.character_by_player_id(mason_leader_player_id)
|
||||
.died_to()
|
||||
.cloned(),
|
||||
Some(DiedTo::MasonLeaderRecruitFail {
|
||||
tried_recruiting: game.character_by_player_id(wolf_player_id).character_id(),
|
||||
night: 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// todo: masons wake even if leader dead
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod diseased;
|
|||
mod elder;
|
||||
mod empath;
|
||||
mod mason;
|
||||
mod mortician;
|
||||
mod pyremaster;
|
||||
mod scapegoat;
|
||||
mod shapeshifter;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
use core::num::NonZeroU8;
|
||||
#[allow(unused)]
|
||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||
|
||||
use crate::{
|
||||
diedto::DiedTo,
|
||||
game::{Game, GameSettings, SetupRole},
|
||||
game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players},
|
||||
message::night::{ActionPrompt, ActionPromptTitle},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn no_dead_doesnt_prompt() {
|
||||
let players = gen_players(1..10);
|
||||
let mortician_player_id = players[0].player_id;
|
||||
let wolf_player_id = players[1].player_id;
|
||||
let mut settings = GameSettings::empty();
|
||||
settings.add_and_assign(SetupRole::Mortician, mortician_player_id);
|
||||
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||
settings.fill_remaining_slots_with_villagers(9);
|
||||
let mut game = Game::new(&players, settings).unwrap();
|
||||
game.r#continue().r#continue();
|
||||
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||
game.r#continue().sleep();
|
||||
game.next_expect_day();
|
||||
|
||||
assert_eq!(game.execute().title(), ActionPromptTitle::WolfPackKill);
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ pub enum HostLobbyMessage {
|
|||
SetPlayerNumber(PlayerId, NonZeroU8),
|
||||
GetGameSettings,
|
||||
SetGameSettings(GameSettings),
|
||||
SetQrMode(bool),
|
||||
Start,
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +73,7 @@ pub enum ServerToHostMessage {
|
|||
ActionPrompt(ActionPrompt),
|
||||
ActionResult(Option<CharacterIdentity>, ActionResult),
|
||||
Lobby(Box<[PlayerState]>),
|
||||
QrMode(bool),
|
||||
GameSettings(GameSettings),
|
||||
Error(GameError),
|
||||
GameOver(GameOver),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
thiserror = { version = "2" }
|
||||
ciborium = { version = "0.2", optional = true }
|
||||
colored = { version = "3.0" }
|
||||
fast_qr = { version = "0.13", features = ["svg"] }
|
||||
|
||||
[features]
|
||||
default = ["cbor"]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub struct Lobby {
|
|||
settings: GameSettings,
|
||||
joined_players: JoinedPlayers,
|
||||
comms: Option<LobbyComms>,
|
||||
qr_mode: bool,
|
||||
}
|
||||
|
||||
impl Lobby {
|
||||
|
|
@ -33,6 +34,7 @@ impl Lobby {
|
|||
comms: Some(comms),
|
||||
settings: GameSettings::default(),
|
||||
players_in_lobby: LobbyPlayers(Vec::new()),
|
||||
qr_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +81,10 @@ impl Lobby {
|
|||
|
||||
pub async fn send_lobby_info_to_host(&mut self) -> Result<(), GameError> {
|
||||
let players = self.get_lobby_player_list().await;
|
||||
self.comms()?
|
||||
.host()
|
||||
.send(ServerToHostMessage::Lobby(players))
|
||||
.map_err(|err| GameError::GenericError(err.to_string()))
|
||||
let qr_mode = self.qr_mode;
|
||||
let host = self.comms()?.host();
|
||||
host.send(ServerToHostMessage::Lobby(players))?;
|
||||
host.send(ServerToHostMessage::QrMode(qr_mode))
|
||||
}
|
||||
|
||||
pub async fn next(&mut self) -> Option<GameRunner> {
|
||||
|
|
@ -150,6 +152,10 @@ impl Lobby {
|
|||
|
||||
async fn next_inner(&mut self, msg: Message) -> Result<Option<GameRunner>, GameError> {
|
||||
match msg {
|
||||
Message::Host(HostMessage::Lobby(HostLobbyMessage::SetQrMode(mode))) => {
|
||||
self.qr_mode = mode;
|
||||
self.send_lobby_info_to_host().await.log_debug();
|
||||
}
|
||||
Message::Host(HostMessage::InGame(_))
|
||||
| Message::Host(HostMessage::ForceRoleAckFor(_)) => {
|
||||
return Err(GameError::InvalidMessageForGameState);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ mod saver;
|
|||
|
||||
use axum::{
|
||||
Router,
|
||||
http::{Request, header},
|
||||
http::{Request, StatusCode, header},
|
||||
response::IntoResponse,
|
||||
routing::{any, get},
|
||||
};
|
||||
|
|
@ -17,6 +17,7 @@ use axum_extra::headers;
|
|||
use communication::lobby::LobbyComms;
|
||||
use connection::JoinedPlayers;
|
||||
use core::{fmt::Display, net::SocketAddr, str::FromStr};
|
||||
use fast_qr::convert::{Builder, Shape, svg::SvgBuilder};
|
||||
use runner::IdentifiedClientMessage;
|
||||
use std::{env, io::Write, path::Path};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
|
|
@ -29,6 +30,7 @@ use crate::{
|
|||
const DEFAULT_PORT: u16 = 8080;
|
||||
const DEFAULT_HOST: &str = "127.0.0.1";
|
||||
const DEFAULT_SAVE_DIR: &str = "werewolves-saves/";
|
||||
const DEFAULT_QRCODE_URL: &str = "https://wolf.emilis.dev/";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
|
@ -136,6 +138,7 @@ async fn main() {
|
|||
let app = Router::new()
|
||||
.route("/connect/client", any(client::handler))
|
||||
.route("/connect/host", any(host::handler))
|
||||
.route("/qrcode", get(handle_qr_code))
|
||||
.with_state(state)
|
||||
.fallback(get(handle_http_static));
|
||||
let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
|
||||
|
|
@ -165,6 +168,33 @@ impl Clone for AppState {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_qr_code() -> impl IntoResponse {
|
||||
const QRCODE: &str = const {
|
||||
match option_env!("QRCODE_URL") {
|
||||
Some(qrcode) => qrcode,
|
||||
None => DEFAULT_QRCODE_URL,
|
||||
}
|
||||
};
|
||||
const EMPTY: &[u8] = &[];
|
||||
let qr_str = match fast_qr::QRBuilder::new(QRCODE).build() {
|
||||
Ok(qr) => SvgBuilder::default().shape(Shape::Square).to_str(&qr),
|
||||
Err(err) => {
|
||||
log::error!("generating qr code from [{QRCODE}]: {err}");
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
EMPTY.to_vec(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "image/svg+xml")],
|
||||
qr_str.as_bytes().to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_http_static(req: Request<axum::body::Body>) -> impl IntoResponse {
|
||||
use mime_sniffer::MimeTypeSniffer;
|
||||
const INDEX_FILE: &[u8] = include_bytes!("../../werewolves/dist/index.html");
|
||||
|
|
|
|||
|
|
@ -355,6 +355,17 @@ button.confirm {
|
|||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.roles-in-setup {
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
padding: 10px;
|
||||
|
||||
&>h3 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.role-list {
|
||||
list-style: none;
|
||||
|
||||
|
|
@ -980,6 +991,7 @@ input {
|
|||
text-align: center;
|
||||
|
||||
& button label {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
@ -1058,10 +1070,11 @@ input {
|
|||
gap: 10px;
|
||||
|
||||
.assignment {
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border: 1px solid white;
|
||||
// border: 1px solid white;
|
||||
|
||||
&>* {
|
||||
cursor: pointer;
|
||||
|
|
@ -1169,3 +1182,28 @@ input {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 5vw;
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
gap: 1cm;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.details {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
border: 1px solid $village_border;
|
||||
background-color: color.change($village_color, $alpha: 0.3);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use crate::{
|
|||
action::{ActionResultView, Prompt},
|
||||
host::{DaytimePlayerList, Setup},
|
||||
},
|
||||
storage::StorageKey,
|
||||
};
|
||||
|
||||
use crate::WerewolfError;
|
||||
|
|
@ -182,6 +183,7 @@ pub enum HostEvent {
|
|||
PlayerList(Box<[PlayerState]>),
|
||||
Settings(GameSettings),
|
||||
Error(GameError),
|
||||
QrMode(bool),
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HostState {
|
||||
|
|
@ -209,6 +211,7 @@ pub enum HostState {
|
|||
impl From<ServerToHostMessage> for HostEvent {
|
||||
fn from(msg: ServerToHostMessage) -> Self {
|
||||
match msg {
|
||||
ServerToHostMessage::QrMode(mode) => HostEvent::QrMode(mode),
|
||||
ServerToHostMessage::Disconnect => HostEvent::SetState(HostState::Disconnected),
|
||||
ServerToHostMessage::Daytime {
|
||||
characters,
|
||||
|
|
@ -244,6 +247,7 @@ pub struct Host {
|
|||
state: HostState,
|
||||
error_callback: Callback<Option<WerewolfError>>,
|
||||
big_screen: bool,
|
||||
qr_mode: bool,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +273,7 @@ impl Component for Host {
|
|||
state: HostState::Disconnected,
|
||||
debug: option_env!("DEBUG").is_some(),
|
||||
big_screen: false,
|
||||
qr_mode: false,
|
||||
error_callback: Callback::from(|err| {
|
||||
if let Some(err) = err {
|
||||
log::error!("{err}")
|
||||
|
|
@ -453,6 +458,10 @@ impl Component for Host {
|
|||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
HostEvent::QrMode(mode) => {
|
||||
self.qr_mode = mode;
|
||||
true
|
||||
}
|
||||
HostEvent::PlayerList(mut players) => {
|
||||
const LAST: NonZeroU8 = NonZeroU8::new(0xFF).unwrap();
|
||||
players.sort_by(|l, r| {
|
||||
|
|
@ -582,18 +591,50 @@ impl Component for Host {
|
|||
|
||||
impl Host {
|
||||
fn lobby_big_screen_show_setup(&self, _: Rc<[PlayerState]>, settings: GameSettings) -> Html {
|
||||
html! {
|
||||
if !self.qr_mode {
|
||||
return html! {
|
||||
<Setup settings={settings}/>
|
||||
};
|
||||
}
|
||||
let qrcode_url = if option_env!("LOCAL").is_some() {
|
||||
String::from("http://192.168.1.162:8080/qrcode")
|
||||
} else {
|
||||
String::from("/qrcode")
|
||||
};
|
||||
html! {
|
||||
<div class="qrcode">
|
||||
<img src={qrcode_url} alt="qr code to join"/>
|
||||
<div class="details">
|
||||
<h3>{"scan the qrcode to join"}</h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn lobby_setup(&self, players: Rc<[PlayerState]>, settings: GameSettings) -> Html {
|
||||
let on_error = self.error_callback.clone();
|
||||
let qr_mode_toggle = {
|
||||
let on_click = crate::callback::send_message(
|
||||
HostMessage::Lobby(HostLobbyMessage::SetQrMode(!self.qr_mode)),
|
||||
self.send.clone(),
|
||||
);
|
||||
let text = if self.qr_mode {
|
||||
"disable qr mode"
|
||||
} else {
|
||||
"enable qr mode"
|
||||
};
|
||||
html! {
|
||||
<Button on_click={on_click}>{text}</Button>
|
||||
}
|
||||
};
|
||||
|
||||
let settings = self.big_screen.not().then(|| {
|
||||
let send = self.send.clone();
|
||||
let on_changed = Callback::from(move |s| {
|
||||
let on_changed = Callback::from(move |s: GameSettings| {
|
||||
let send = send.clone();
|
||||
if let Err(err) = s.save_to_storage() {
|
||||
log::error!("saving settings to local storage: {err}");
|
||||
}
|
||||
yew::platform::spawn_local(async move {
|
||||
let mut send = send.clone();
|
||||
if let Err(err) = send
|
||||
|
|
@ -614,6 +655,7 @@ impl Host {
|
|||
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 {
|
||||
|
|
@ -627,6 +669,7 @@ impl Host {
|
|||
on_start={on_start}
|
||||
on_update={on_changed}
|
||||
players_in_lobby={players.clone()}
|
||||
qr_mode_button={qr_mode_toggle}
|
||||
/>
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ pub fn SetupCategory(
|
|||
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))
|
||||
.filter(|(_, count)| !(matches!(category, Category::Wolves) && *count == 0))
|
||||
.map(|(r, count)| {
|
||||
let as_role = r.into_role();
|
||||
let wakes = as_role.wakes_night_zero().then_some("wakes");
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ pub struct SettingsProps {
|
|||
pub players_in_lobby: Rc<[PlayerState]>,
|
||||
pub on_update: Callback<GameSettings>,
|
||||
pub on_start: Callback<()>,
|
||||
#[prop_or_default]
|
||||
pub on_error: Option<Callback<GameError>>,
|
||||
pub qr_mode_button: Html,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
|
|
@ -29,7 +28,7 @@ pub fn Settings(
|
|||
players_in_lobby,
|
||||
on_update,
|
||||
on_start,
|
||||
on_error,
|
||||
qr_mode_button,
|
||||
}: &SettingsProps,
|
||||
) -> Html {
|
||||
let players = players_in_lobby
|
||||
|
|
@ -83,18 +82,22 @@ pub fn Settings(
|
|||
.collect::<Html>();
|
||||
|
||||
let add_roles_update = on_update.clone();
|
||||
let add_roles_buttons = RoleTitle::ALL
|
||||
.iter()
|
||||
let sorted_role_tiles = {
|
||||
let mut v = RoleTitle::ALL.to_vec();
|
||||
v.sort_by_key(|v| Into::<SetupRole>::into(*v).category());
|
||||
v
|
||||
};
|
||||
let add_roles_buttons = sorted_role_tiles
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let update = add_roles_update.clone();
|
||||
let settings = settings.clone();
|
||||
let role = *r;
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*settings).clone();
|
||||
settings.new_slot(role);
|
||||
settings.new_slot(r);
|
||||
update.emit(settings);
|
||||
});
|
||||
let class = Into::<SetupRole>::into(*r).category().class();
|
||||
let class = Into::<SetupRole>::into(r).category().class();
|
||||
let name = r.to_string().to_case(Case::Title);
|
||||
html! {
|
||||
<Button on_click={on_click} classes={classes!(class, "add-role")}>
|
||||
|
|
@ -159,7 +162,8 @@ pub fn Settings(
|
|||
.iter()
|
||||
.any(|s| s.assign_to.is_some())
|
||||
.then(|| {
|
||||
let assignments = settings
|
||||
let assignments =
|
||||
settings
|
||||
.slots()
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
@ -171,17 +175,18 @@ pub fn Settings(
|
|||
.iter()
|
||||
.find(|p| p.player_id == *a)
|
||||
.map(|assign| {
|
||||
html! {
|
||||
let class = s.role.category().class();
|
||||
(html! {
|
||||
<Identity ident={assign.public.clone()}/>
|
||||
}
|
||||
}, Some(class))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
html! {
|
||||
(html! {
|
||||
<span>{"[left the lobby]"}</span>
|
||||
}
|
||||
}, None)
|
||||
})
|
||||
})
|
||||
.map(|who| {
|
||||
.map(|(who, class)| {
|
||||
let assignments_update = on_update.clone();
|
||||
let assignments_settings = settings.clone();
|
||||
let click_slot = s.clone();
|
||||
|
|
@ -193,7 +198,7 @@ pub fn Settings(
|
|||
assignments_update.emit(settings);
|
||||
});
|
||||
html! {
|
||||
<Button classes={classes!("assignment")} on_click={on_click}>
|
||||
<Button classes={classes!("assignment", class)} on_click={on_click}>
|
||||
<label>{s.role.to_string().to_case(Case::Title)}</label>
|
||||
{who}
|
||||
</Button>
|
||||
|
|
@ -246,20 +251,24 @@ pub fn Settings(
|
|||
html! {
|
||||
<div class="settings">
|
||||
<div class="top-settings">
|
||||
{qr_mode_button.clone()}
|
||||
{fill_empty_with_villagers}
|
||||
{clear_setup}
|
||||
{clear_all_assignments}
|
||||
{clear_bad_assigned}
|
||||
</div>
|
||||
{assignments}
|
||||
<p>{format!("min roles for setup: {}", settings.min_players_needed())}</p>
|
||||
<p>{format!("current role count: {}", settings.slots().len())}</p>
|
||||
<div class="roles-add-list">
|
||||
{add_roles_buttons}
|
||||
</div>
|
||||
<div class="roles-in-setup">
|
||||
<h3>{"roles in the game"}</h3>
|
||||
<div class="role-list">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
{assignments}
|
||||
<Button
|
||||
disabled_reason={disabled_reason}
|
||||
classes={classes!("start-game")}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,11 @@ 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..=7).map(|num| {
|
||||
let client_count = option_env!("CLIENTS")
|
||||
.and_then(|c| c.parse::<NonZeroU8>().ok())
|
||||
.unwrap_or(NonZeroU8::new(7).unwrap())
|
||||
.get();
|
||||
for (player_id, name, num, dupe) in (1..=client_count).map(|num| {
|
||||
(
|
||||
PlayerId::from_u128(num as u128),
|
||||
format!("player {num}"),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use gloo::storage::{LocalStorage, Storage, errors::StorageError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use werewolves_proto::{message::PublicIdentity, player::PlayerId};
|
||||
use werewolves_proto::{game::GameSettings, message::PublicIdentity, player::PlayerId};
|
||||
|
||||
type Result<T> = core::result::Result<T, StorageError>;
|
||||
|
||||
|
|
@ -27,3 +27,7 @@ impl StorageKey for PlayerId {
|
|||
impl StorageKey for PublicIdentity {
|
||||
const KEY: &str = "ww_public_identity";
|
||||
}
|
||||
|
||||
impl StorageKey for GameSettings {
|
||||
const KEY: &str = "game_settings";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue