role reveal for host
This commit is contained in:
parent
f280f35305
commit
d3d2819e12
|
|
@ -784,3 +784,80 @@ form {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.start-game-status {
|
||||||
|
min-height: 6ch;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:has(.error) {
|
||||||
|
border: 1px solid rgb(128, 0, 0);
|
||||||
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: 1.5em;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '⚠️';
|
||||||
|
margin-right: 1ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-game {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-game-dialog-info {
|
||||||
|
word-wrap: normal;
|
||||||
|
|
||||||
|
margin-bottom: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.host-role-reveal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25ch;
|
||||||
|
|
||||||
|
.force-all {
|
||||||
|
font-size: 1.25em;
|
||||||
|
margin-top: 1ch;
|
||||||
|
margin-bottom: 1ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-reveal-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25ch;
|
||||||
|
|
||||||
|
.player {
|
||||||
|
@media only screen and (min-width : 1199px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25ch;
|
||||||
|
padding: 0.5ch;
|
||||||
|
|
||||||
|
.identity {
|
||||||
|
align-self: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ready {
|
||||||
|
background-color: $village_color_faint;
|
||||||
|
border: 1px solid $village_color;
|
||||||
|
|
||||||
|
button {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ pub enum GameError {
|
||||||
HostChannelClosed,
|
HostChannelClosed,
|
||||||
#[error("too many players: there's {got} players but only {need} roles")]
|
#[error("too many players: there's {got} players but only {need} roles")]
|
||||||
TooManyPlayers { got: u8, need: u8 },
|
TooManyPlayers { got: u8, need: u8 },
|
||||||
|
#[error(
|
||||||
|
"too few players: there's {got} players and this setup would require {need} roles to not be at instant parity"
|
||||||
|
)]
|
||||||
|
TooFewPlayers { got: u32, need: u32 },
|
||||||
#[error("it's already daytime")]
|
#[error("it's already daytime")]
|
||||||
AlreadyDaytime,
|
AlreadyDaytime,
|
||||||
#[error("it's not the end of the night yet")]
|
#[error("it's not the end of the night yet")]
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,12 @@ impl GameSettings {
|
||||||
|
|
||||||
pub fn check_with_player_list(&self, players: &[Identification]) -> Result<()> {
|
pub fn check_with_player_list(&self, players: &[Identification]) -> Result<()> {
|
||||||
self.check()?;
|
self.check()?;
|
||||||
|
if self.min_players_needed() > players.len() {
|
||||||
|
return Err(GameError::TooFewPlayers {
|
||||||
|
got: players.len() as _,
|
||||||
|
need: self.min_players_needed() as _,
|
||||||
|
});
|
||||||
|
}
|
||||||
let (p_len, r_len) = (players.len(), self.roles.len());
|
let (p_len, r_len) = (players.len(), self.roles.len());
|
||||||
if p_len > r_len {
|
if p_len > r_len {
|
||||||
return Err(GameError::TooManyPlayers {
|
return Err(GameError::TooManyPlayers {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ pub enum HostMessage {
|
||||||
Lobby(HostLobbyMessage),
|
Lobby(HostLobbyMessage),
|
||||||
InGame(HostGameMessage),
|
InGame(HostGameMessage),
|
||||||
ForceRoleAckFor(CharacterId),
|
ForceRoleAckFor(CharacterId),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
ForceAllRoleAcks,
|
||||||
PostGame(PostGameMessage),
|
PostGame(PostGameMessage),
|
||||||
Echo(ServerToHostMessage),
|
Echo(ServerToHostMessage),
|
||||||
CancelGame,
|
CancelGame,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ pub fn ChangePlayerNumber(
|
||||||
on:input:target=update
|
on:input:target=update
|
||||||
value=move || { number.get().map(|n| n.get().to_string()).unwrap_or_default() }
|
value=move || { number.get().map(|n| n.get().to_string()).unwrap_or_default() }
|
||||||
/>
|
/>
|
||||||
<input value="submit" type="submit" on:click=submit_click/>
|
<input value="submit" type="submit" on:click=submit_click />
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ enum HostPage {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Settings,
|
Settings,
|
||||||
|
RoleRevealAcks,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
@ -33,6 +34,8 @@ pub fn HostGamePage(
|
||||||
let qr_mode = RwSignal::new(false);
|
let qr_mode = RwSignal::new(false);
|
||||||
let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([]));
|
let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([]));
|
||||||
let dialog_open = RwSignal::new(HashMap::new());
|
let dialog_open = RwSignal::new(HashMap::new());
|
||||||
|
let acks: RwSignal<Box<[RoleRevealCharacter]>> = RwSignal::new(Box::new([]));
|
||||||
|
|
||||||
let open_categories = RwSignal::new(
|
let open_categories = RwSignal::new(
|
||||||
Category::ALL
|
Category::ALL
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -74,6 +77,22 @@ pub fn HostGamePage(
|
||||||
Srv2Host::Error(err) => {
|
Srv2Host::Error(err) => {
|
||||||
error.set(Some(err.to_string()));
|
error.set(Some(err.to_string()));
|
||||||
}
|
}
|
||||||
|
Srv2Host::WaitingForRoleRevealAcks { ackd, waiting } => {
|
||||||
|
let mut reveals = ackd
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| RoleRevealCharacter {
|
||||||
|
char: a,
|
||||||
|
acknowledged: true,
|
||||||
|
})
|
||||||
|
.chain(waiting.into_iter().map(|w| RoleRevealCharacter {
|
||||||
|
char: w,
|
||||||
|
acknowledged: false,
|
||||||
|
}))
|
||||||
|
.collect::<Box<_>>();
|
||||||
|
reveals.sort_by_key(|r| r.char.number);
|
||||||
|
acks.set(reveals);
|
||||||
|
page.set(HostPage::RoleRevealAcks);
|
||||||
|
}
|
||||||
_ => log::error!("{message:#?}"),
|
_ => log::error!("{message:#?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +117,9 @@ pub fn HostGamePage(
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
.into_any(),
|
.into_any(),
|
||||||
|
HostPage::RoleRevealAcks => {
|
||||||
|
view! { <RoleRevealAcks reply=reply error=error acks=acks.read_only() /> }.into_any()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
{cancel}
|
{cancel}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ pub fn HostPlayerList(
|
||||||
<IdentificationInline ident=sample_no_number />
|
<IdentificationInline ident=sample_no_number />
|
||||||
</button>
|
</button>
|
||||||
<Equals />
|
<Equals />
|
||||||
<span class="ok">"no number assigned"</span>
|
<span class="ok">"no seat assigned"</span>
|
||||||
</Sample>
|
</Sample>
|
||||||
</TutorialBox>
|
</TutorialBox>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
use core::ops::Not;
|
||||||
|
|
||||||
|
use leptos::{ev::MouseEvent, prelude::*};
|
||||||
|
use werewolves_proto::message::{CharacterIdentity, host::HostMessage};
|
||||||
|
|
||||||
|
use crate::app::components::IdentityInline;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RoleRevealCharacter {
|
||||||
|
pub char: CharacterIdentity,
|
||||||
|
pub acknowledged: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn RoleRevealAcks(
|
||||||
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
acks: ReadSignal<Box<[RoleRevealCharacter]>>,
|
||||||
|
error: WriteSignal<Option<String>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let acks = move || {
|
||||||
|
acks.get()
|
||||||
|
.into_iter()
|
||||||
|
.map(|ackd| {
|
||||||
|
let RoleRevealCharacter { char, acknowledged } = ackd;
|
||||||
|
let char_id = char.character_id;
|
||||||
|
let ident = RwSignal::new(char.into_public()).read_only();
|
||||||
|
let force_ready = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
reply.set(Some(HostMessage::ForceRoleAckFor(char_id)));
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="player" class:ready=acknowledged>
|
||||||
|
<IdentityInline ident=ident />
|
||||||
|
<button disabled=acknowledged on:click=force_ready>
|
||||||
|
"force ready"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view()
|
||||||
|
};
|
||||||
|
|
||||||
|
let force_all = move || {
|
||||||
|
(option_env!("LEPTOS_WATCH") == Some("true")).then_some({
|
||||||
|
let force = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
reply.set(Some(HostMessage::ForceAllRoleAcks));
|
||||||
|
};
|
||||||
|
view! { <button on:click=force class="force-all">"force all"</button> }
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
move || {
|
||||||
|
view! { <div class="host-role-reveal">{force_all} <div class="role-reveal-list">{acks}</div></div> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,13 +37,32 @@ pub fn Settings(
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(move |s| {
|
.map(move |s| {
|
||||||
let signal = RwSignal::new(s);
|
let signal = RwSignal::new(s);
|
||||||
Effect::new(move || {
|
let remove = RwSignal::new(false);
|
||||||
let mut s = settings.get();
|
Effect::new(move || {
|
||||||
s.update_slot(signal.get());
|
let mut s = settings.get();
|
||||||
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s))));
|
s.update_slot(signal.get());
|
||||||
});
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(
|
||||||
|
s,
|
||||||
|
))));
|
||||||
|
});
|
||||||
|
Effect::new(move || {
|
||||||
|
if remove.get() {
|
||||||
|
let mut s = settings.get();
|
||||||
|
s.remove_slot(signal.read().slot_id);
|
||||||
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(
|
||||||
|
s,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
view! { <SettingsSetupSlot setup_slot=signal players=players dialog_open=dialog_open /> }
|
view! {
|
||||||
|
<SettingsSetupSlot
|
||||||
|
remove=remove.write_only()
|
||||||
|
setup_slot=signal
|
||||||
|
players=players
|
||||||
|
dialog_open=dialog_open
|
||||||
|
/>
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect_view()
|
.collect_view()
|
||||||
};
|
};
|
||||||
|
|
@ -124,9 +143,67 @@ pub fn Settings(
|
||||||
})
|
})
|
||||||
.collect_view();
|
.collect_view();
|
||||||
|
|
||||||
|
let fill_villagers = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
let mut s = settings.get();
|
||||||
|
s.fill_remaining_slots_with_villagers(players.read().len());
|
||||||
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(
|
||||||
|
s,
|
||||||
|
))));
|
||||||
|
};
|
||||||
|
let clear_setup = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(
|
||||||
|
GameSettings::empty(),
|
||||||
|
))));
|
||||||
|
};
|
||||||
|
let fill_disabled = move || settings.read().slots().len() >= players.read().len();
|
||||||
|
let clear_disabled = move || settings.read().slots().is_empty();
|
||||||
|
|
||||||
|
let start_content = move || {
|
||||||
|
if let Err(err) = settings.read().check_with_player_list(
|
||||||
|
&players
|
||||||
|
.read()
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.identification.clone())
|
||||||
|
.collect::<Box<_>>(),
|
||||||
|
) {
|
||||||
|
return view! { <span class="error">{err.to_string()}</span> }.into_any();
|
||||||
|
}
|
||||||
|
let start = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::Start)));
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<DialogModal
|
||||||
|
button_class="start-game".into()
|
||||||
|
button_content="start game"
|
||||||
|
mode=DialogMode::Box
|
||||||
|
>
|
||||||
|
<h3>"start game"</h3>
|
||||||
|
<span class="start-game-dialog-info">
|
||||||
|
"once the game starts, all players will recieve their role on their phones"
|
||||||
|
</span>
|
||||||
|
<span class="start-game-dialog-info">
|
||||||
|
"instruct them to ensure no other player can see their screen at this time"
|
||||||
|
</span>
|
||||||
|
<button on:click=start>"start game"</button>
|
||||||
|
</DialogModal>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
// }.into_any()
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="game-settings">
|
<div class="game-settings">
|
||||||
<div class="top-bar">{qr_mode_btn}</div>
|
<div class="top-bar">
|
||||||
|
{qr_mode_btn} <button on:click=fill_villagers disabled=fill_disabled>
|
||||||
|
"fill slots with villagers"
|
||||||
|
</button> <button on:click=clear_setup disabled=clear_disabled>
|
||||||
|
"clear setup"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="start-game-status">{start_content}</div>
|
||||||
<div class="role-add-list">{categories}</div>
|
<div class="role-add-list">{categories}</div>
|
||||||
<div class="setup-slots">{slots}</div>
|
<div class="setup-slots">{slots}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -138,6 +215,7 @@ fn SettingsSetupSlot(
|
||||||
setup_slot: RwSignal<SetupSlot>,
|
setup_slot: RwSignal<SetupSlot>,
|
||||||
players: ReadSignal<Box<[PlayerState]>>,
|
players: ReadSignal<Box<[PlayerState]>>,
|
||||||
dialog_open: RwSignal<HashMap<SlotId, bool>>,
|
dialog_open: RwSignal<HashMap<SlotId, bool>>,
|
||||||
|
remove: WriteSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let auras = move || {
|
let auras = move || {
|
||||||
let slot = setup_slot.read();
|
let slot = setup_slot.read();
|
||||||
|
|
@ -215,7 +293,7 @@ fn SettingsSetupSlot(
|
||||||
button_content=setup_slot.read().role.title().to_string().to_case(Case::Title)
|
button_content=setup_slot.read().role.title().to_string().to_case(Case::Title)
|
||||||
close_backdrop=true
|
close_backdrop=true
|
||||||
>
|
>
|
||||||
<SlotSettingsDialogBody setup_slot=setup_slot players=players />
|
<SlotSettingsDialogBody remove=remove setup_slot=setup_slot players=players />
|
||||||
</DialogModal>
|
</DialogModal>
|
||||||
{assigned_to}
|
{assigned_to}
|
||||||
{auras}
|
{auras}
|
||||||
|
|
@ -228,6 +306,7 @@ fn SettingsSetupSlot(
|
||||||
fn SlotSettingsDialogBody(
|
fn SlotSettingsDialogBody(
|
||||||
setup_slot: RwSignal<SetupSlot>,
|
setup_slot: RwSignal<SetupSlot>,
|
||||||
players: ReadSignal<Box<[PlayerState]>>,
|
players: ReadSignal<Box<[PlayerState]>>,
|
||||||
|
remove: WriteSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
enum OpenTab {
|
enum OpenTab {
|
||||||
|
|
@ -257,6 +336,10 @@ fn SlotSettingsDialogBody(
|
||||||
.map(|p| p.identification.public)
|
.map(|p| p.identification.public)
|
||||||
.map(|id| view! { <IdentityInline ident=RwSignal::new(id).read_only() /> }.into_any())
|
.map(|id| view! { <IdentityInline ident=RwSignal::new(id).read_only() /> }.into_any())
|
||||||
.unwrap_or_else(|| view! { "none" }.into_any());
|
.unwrap_or_else(|| view! { "none" }.into_any());
|
||||||
|
let remove = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
remove.set(true);
|
||||||
|
};
|
||||||
view! {
|
view! {
|
||||||
<span class=[
|
<span class=[
|
||||||
"role-title",
|
"role-title",
|
||||||
|
|
@ -288,6 +371,7 @@ fn SlotSettingsDialogBody(
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">{tab_view}</div>
|
<div class="tab-content">{tab_view}</div>
|
||||||
|
<button on:click=remove>"remove role"</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,11 @@ pub fn PlayerLobby(
|
||||||
form_hidden.set(true);
|
form_hidden.set(true);
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<form class="number-update bigger" on:submit=submit hidden=move || form_hidden.get()>
|
<form
|
||||||
|
class="number-update bigger"
|
||||||
|
on:submit=submit
|
||||||
|
hidden=move || form_hidden.get()
|
||||||
|
>
|
||||||
<label for="player-number">"change seat number"</label>
|
<label for="player-number">"change seat number"</label>
|
||||||
<input
|
<input
|
||||||
id="player-number"
|
id="player-number"
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl GameDatabase {
|
||||||
let new_game =
|
let new_game =
|
||||||
Game::new_with_assigned_character_ids(&with_char_ids, settings.clone())?;
|
Game::new_with_assigned_character_ids(&with_char_ids, settings.clone())?;
|
||||||
|
|
||||||
record.game_state = GameRecordState::Started(new_game);
|
record.game_state = GameRecordState::RoleReveal(new_game);
|
||||||
}
|
}
|
||||||
GameRecordState::GameOver(_)
|
GameRecordState::GameOver(_)
|
||||||
| GameRecordState::RoleReveal(_)
|
| GameRecordState::RoleReveal(_)
|
||||||
|
|
@ -136,7 +136,10 @@ impl GameDatabase {
|
||||||
E: ::sqlx::Executor<'a, Database = Postgres>,
|
E: ::sqlx::Executor<'a, Database = Postgres>,
|
||||||
{
|
{
|
||||||
let game_state_json = leptos::serde_json::to_value(&record.game_state)
|
let game_state_json = leptos::serde_json::to_value(&record.game_state)
|
||||||
.map_err(|err| DatabaseError::Serialization(err.to_string()))?;
|
.map_err(|err| DatabaseError::Serialization(err.to_string()))
|
||||||
|
.inspect_err(|err| {
|
||||||
|
log::error!("serializing game state: {:?}: {err}", record.game_state)
|
||||||
|
})?;
|
||||||
let game_status = match &record.game_state {
|
let game_status = match &record.game_state {
|
||||||
GameRecordState::Lobby(_) => "Lobby",
|
GameRecordState::Lobby(_) => "Lobby",
|
||||||
GameRecordState::RoleReveal(_) => "RoleReveal",
|
GameRecordState::RoleReveal(_) => "RoleReveal",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ mod ssr {
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use leptos::prelude::RenderHtml;
|
use leptos::prelude::RenderHtml;
|
||||||
|
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
use werewolves::db::AppState;
|
use werewolves::db::AppState;
|
||||||
|
|
||||||
pub async fn server_fn_handler(
|
pub async fn server_fn_handler(
|
||||||
|
|
@ -50,7 +51,24 @@ mod ssr {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
file
|
if option_env!("LEPTOS_WATCH") == Some("true") {
|
||||||
|
let file_path = std::path::PathBuf::from(".")
|
||||||
|
.join("target")
|
||||||
|
.join("site")
|
||||||
|
.join(path);
|
||||||
|
if let Ok(mut f) = tokio::fs::File::open(file_path).await {
|
||||||
|
let mut content = Vec::new();
|
||||||
|
if f.read_to_end(&mut content).await.is_ok() {
|
||||||
|
content
|
||||||
|
} else {
|
||||||
|
file.to_vec()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.to_vec()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.to_vec()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
leptos_meta::provide_meta_context();
|
leptos_meta::provide_meta_context();
|
||||||
let opts = state.leptos_options.clone();
|
let opts = state.leptos_options.clone();
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,12 @@ type Result<T> = core::result::Result<T, GameError>;
|
||||||
pub struct GameRunner<'a> {
|
pub struct GameRunner<'a> {
|
||||||
game_id: GameId,
|
game_id: GameId,
|
||||||
game: &'a mut Game,
|
game: &'a mut Game,
|
||||||
roles_revealed: bool,
|
|
||||||
db: Database,
|
db: Database,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GameRunner<'a> {
|
impl<'a> GameRunner<'a> {
|
||||||
pub fn new(game_id: GameId, game: &'a mut Game, db: Database) -> Self {
|
pub fn new(game_id: GameId, game: &'a mut Game, db: Database) -> Self {
|
||||||
Self {
|
Self { db, game, game_id }
|
||||||
db,
|
|
||||||
game,
|
|
||||||
game_id,
|
|
||||||
roles_revealed: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn game_over(&self) -> Option<GameOver> {
|
pub fn game_over(&self) -> Option<GameOver> {
|
||||||
|
|
@ -232,13 +226,11 @@ impl<'a> GameRunner<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn host_message(&mut self, message: HostMessage) -> Result<ServerToHostMessage> {
|
pub fn host_message(&mut self, message: HostMessage) -> Result<ServerToHostMessage> {
|
||||||
if !self.roles_revealed {
|
|
||||||
return Err(GameError::NeedRoleReveal);
|
|
||||||
}
|
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
HostMessage::GetState => self.game.process(HostGameMessage::GetState),
|
HostMessage::GetState => self.game.process(HostGameMessage::GetState),
|
||||||
HostMessage::InGame(msg) => self.game.process(msg),
|
HostMessage::InGame(msg) => self.game.process(msg),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
HostMessage::ForceAllRoleAcks => Err(GameError::GameOngoing),
|
||||||
HostMessage::Lobby(_) | HostMessage::PostGame(_) | HostMessage::ForceRoleAckFor(_) => {
|
HostMessage::Lobby(_) | HostMessage::PostGame(_) | HostMessage::ForceRoleAckFor(_) => {
|
||||||
Err(GameError::GameOngoing)
|
Err(GameError::GameOngoing)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,10 @@ impl<'a> Lobby<'a> {
|
||||||
self.qr_mode = mode;
|
self.qr_mode = mode;
|
||||||
self.send_lobby_info_to_host().await.log_debug(loc!());
|
self.send_lobby_info_to_host().await.log_debug(loc!());
|
||||||
}
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
HostOrClientMessage::Host(HostMessage::ForceAllRoleAcks) => {
|
||||||
|
return Err(GameError::InvalidMessageForGameState.into());
|
||||||
|
}
|
||||||
HostOrClientMessage::Host(HostMessage::InGame(_))
|
HostOrClientMessage::Host(HostMessage::InGame(_))
|
||||||
| HostOrClientMessage::Host(HostMessage::ForceRoleAckFor(_)) => {
|
| HostOrClientMessage::Host(HostMessage::ForceRoleAckFor(_)) => {
|
||||||
return Err(GameError::InvalidMessageForGameState.into());
|
return Err(GameError::InvalidMessageForGameState.into());
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,17 @@ impl<'a> RoleReveal<'a> {
|
||||||
/// returns `true` once every player has ack'd
|
/// returns `true` once every player has ack'd
|
||||||
pub async fn process(&mut self, message: HostOrClientMessage) -> bool {
|
pub async fn process(&mut self, message: HostOrClientMessage) -> bool {
|
||||||
match message {
|
match message {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
HostOrClientMessage::Host(HostMessage::ForceAllRoleAcks) => {
|
||||||
|
let mut to_notify = Vec::new();
|
||||||
|
for (c, ackd) in self.acks.iter_mut().filter(|(_, ackd)| !*ackd) {
|
||||||
|
*ackd = true;
|
||||||
|
to_notify.push(c.player_id());
|
||||||
|
}
|
||||||
|
for pid in to_notify {
|
||||||
|
self.notify_of_role(pid).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
HostOrClientMessage::Host(HostMessage::ForceRoleAckFor(char_id)) => {
|
HostOrClientMessage::Host(HostMessage::ForceRoleAckFor(char_id)) => {
|
||||||
if let Some((c, ackd)) = self
|
if let Some((c, ackd)) = self
|
||||||
.acks
|
.acks
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue