view roles during nighttime
This commit is contained in:
parent
7a50c3320a
commit
ec29c66e4b
|
|
@ -83,6 +83,21 @@ impl Game {
|
|||
|
||||
pub fn process(&mut self, message: HostGameMessage) -> Result<ServerToHostMessage> {
|
||||
match (&mut self.state, message) {
|
||||
(GameState::Night { night }, HostGameMessage::SeePlayersWithRoles) => {
|
||||
Ok(ServerToHostMessage::PlayerStates(
|
||||
night
|
||||
.village()
|
||||
.characters()
|
||||
.into_iter()
|
||||
.map(|c| CharacterState {
|
||||
player_id: c.player_id(),
|
||||
identity: c.identity(),
|
||||
role: c.role_title(),
|
||||
died_to: c.died_to().cloned(),
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
(GameState::Night { night }, HostGameMessage::Night(HostNightMessage::NextPage)) => {
|
||||
night.next_page();
|
||||
self.process(HostGameMessage::GetState)
|
||||
|
|
@ -241,6 +256,7 @@ impl Game {
|
|||
night.previous_state()?;
|
||||
self.process(HostGameMessage::GetState)
|
||||
}
|
||||
(_, HostGameMessage::SeePlayersWithRoles) => self.process(HostGameMessage::GetState),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ pub enum HostGameMessage {
|
|||
Day(HostDayMessage),
|
||||
Night(HostNightMessage),
|
||||
PreviousState,
|
||||
SeePlayersWithRoles,
|
||||
GetState,
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +95,7 @@ pub enum ServerToHostMessage {
|
|||
day: NonZeroU8,
|
||||
settings: GameSettings,
|
||||
},
|
||||
PlayerStates(Box<[CharacterState]>),
|
||||
ActionPrompt(ActionPrompt, usize),
|
||||
ActionResult(Option<CharacterIdentity>, ActionResult),
|
||||
Lobby(Box<[PlayerState]>),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ use crate::{
|
|||
components::{
|
||||
Button, Lobby, LobbyPlayerAction, RoleReveal, Settings, Story, Victory,
|
||||
action::{ActionResultView, Prompt},
|
||||
host::{DaytimePlayerList, Setup},
|
||||
host::{CharacterStatesReadOnly, DaytimePlayerList, Setup},
|
||||
},
|
||||
pages::RolePage,
|
||||
storage::StorageKey,
|
||||
|
|
@ -197,6 +197,7 @@ pub enum HostEvent {
|
|||
SetState(HostState),
|
||||
Continue,
|
||||
PlayerList(Box<[PlayerState]>),
|
||||
CharacterList(Box<[CharacterState]>),
|
||||
Settings(GameSettings),
|
||||
Error(GameError),
|
||||
QrMode(bool),
|
||||
|
|
@ -228,11 +229,13 @@ pub enum HostState {
|
|||
#[allow(unused)]
|
||||
page: usize,
|
||||
},
|
||||
CharacterStates(Box<[CharacterState]>),
|
||||
}
|
||||
|
||||
impl From<ServerToHostMessage> for HostEvent {
|
||||
fn from(msg: ServerToHostMessage) -> Self {
|
||||
match msg {
|
||||
ServerToHostMessage::PlayerStates(states) => HostEvent::CharacterList(states),
|
||||
ServerToHostMessage::QrMode(mode) => HostEvent::QrMode(mode),
|
||||
ServerToHostMessage::Disconnect => HostEvent::SetState(HostState::Disconnected),
|
||||
ServerToHostMessage::Daytime {
|
||||
|
|
@ -313,6 +316,11 @@ impl Component for Host {
|
|||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
log::trace!("state: {:?}", self.state);
|
||||
let content = match self.state.clone() {
|
||||
HostState::CharacterStates(chars) => {
|
||||
html! {
|
||||
<CharacterStatesReadOnly states={chars}/>
|
||||
}
|
||||
}
|
||||
HostState::Story { story, .. } => {
|
||||
if let Some(outcome) = story
|
||||
.final_village()
|
||||
|
|
@ -459,55 +467,38 @@ impl Component for Host {
|
|||
}
|
||||
};
|
||||
let debug_nav = self.debug.then(|| {
|
||||
let on_error_click = callback::send_message(
|
||||
HostMessage::Echo(ServerToHostMessage::Error(GameError::NoApprenticeMentor)),
|
||||
self.send.clone(),
|
||||
);
|
||||
let client_click = Callback::from(|_| {
|
||||
if let Some(loc) = gloo::utils::document().location() {
|
||||
let _ = loc.replace("/");
|
||||
}
|
||||
});
|
||||
let screen = self.big_screen.then_some({
|
||||
let to_normal = Callback::from(|_| {
|
||||
if let Some(loc) = gloo::utils::document().location() {
|
||||
let _ = loc.replace("/host");
|
||||
}
|
||||
});
|
||||
html! {
|
||||
<Button on_click={to_normal}>{"small screen"}</Button>
|
||||
}
|
||||
});
|
||||
let story_on_click = if let HostState::Story { .. } = &self.state {
|
||||
crate::callback::send_message(HostMessage::GetState, self.send.clone())
|
||||
} else {
|
||||
let s = _ctx.link().clone();
|
||||
Callback::from(move |_| {
|
||||
s.send_message(HostEvent::SetState(HostState::Story {
|
||||
story: crate::clients::host::story_test::test_story(),
|
||||
page: 0,
|
||||
}));
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<Button on_click={on_error_click}>{"error"}</Button>
|
||||
{screen}
|
||||
<Button on_click={client_click}>{"client"}</Button>
|
||||
<a href="/host/test"><Button on_click={|_|()}>{"test screens"}</Button></a>
|
||||
<Button on_click={story_on_click}>{"story"}</Button>
|
||||
</>
|
||||
<a href="/host/test"><Button on_click={|_|()}>{"test screens"}</Button></a>
|
||||
}
|
||||
});
|
||||
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(
|
||||
HostMessage::InGame(HostGameMessage::SeePlayersWithRoles),
|
||||
self.send.clone(),
|
||||
);
|
||||
|
||||
Some(html! {
|
||||
<Button on_click={on_view_click}>{"view players"}</Button>
|
||||
})
|
||||
}
|
||||
HostState::CharacterStates(_) => {
|
||||
let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
|
||||
Some(html! {
|
||||
<Button on_click={back}>{"back"}</Button>
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let nav = self.big_screen.not().then(|| {
|
||||
html! {
|
||||
<nav class="host-nav" style="z-index: 3;">
|
||||
<Button on_click={on_prev_click}>{"previous"}</Button>
|
||||
{view_roles_btn}
|
||||
{debug_nav}
|
||||
</nav>
|
||||
}
|
||||
|
|
@ -525,6 +516,13 @@ impl Component for Host {
|
|||
fn update(&mut self, _ctx: &Context<Self>, 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
|
||||
|
|
@ -563,7 +561,8 @@ impl Component for Host {
|
|||
});
|
||||
}
|
||||
|
||||
HostState::Prompt(_, _)
|
||||
HostState::CharacterStates(_)
|
||||
| HostState::Prompt(_, _)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::RoleReveal { .. }
|
||||
| HostState::Day { .. } => {
|
||||
|
|
@ -588,7 +587,8 @@ impl Component for Host {
|
|||
*s = settings;
|
||||
true
|
||||
}
|
||||
HostState::Story { .. }
|
||||
HostState::CharacterStates(_)
|
||||
| HostState::Story { .. }
|
||||
| HostState::Prompt(_, _)
|
||||
| HostState::Result(_, _)
|
||||
| HostState::Disconnected
|
||||
|
|
|
|||
|
|
@ -1,999 +0,0 @@
|
|||
// Copyright (C) 2025 Emilis Bliūdžius
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
use werewolves_proto::{
|
||||
character::{Character, CharacterId},
|
||||
diedto::DiedToTitle,
|
||||
game::{self, Game, GameOver, GameSettings, OrRandom, SetupRole, story::GameStory},
|
||||
message::{
|
||||
CharacterState, Identification, PublicIdentity,
|
||||
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult, Visits},
|
||||
},
|
||||
player::PlayerId,
|
||||
role::{Alignment, AlignmentEq, Killer, Powerful, RoleTitle},
|
||||
};
|
||||
|
||||
trait GameSettingsExt {
|
||||
fn add_and_assign(&mut self, role: SetupRole, player_id: PlayerId);
|
||||
}
|
||||
|
||||
impl GameSettingsExt for GameSettings {
|
||||
fn add_and_assign(&mut self, role: SetupRole, player_id: PlayerId) {
|
||||
let slot_id = self.new_slot(role.clone().into());
|
||||
let mut slot = self.get_slot_by_id(slot_id).unwrap().clone();
|
||||
slot.role = role;
|
||||
slot.assign_to.replace(player_id);
|
||||
self.update_slot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_story() -> GameStory {
|
||||
let players = (1..32u8)
|
||||
.filter_map(NonZeroU8::new)
|
||||
.map(|n| Identification {
|
||||
player_id: PlayerId::from_u128(n.get() as _),
|
||||
public: PublicIdentity {
|
||||
name: format!("Player {n}"),
|
||||
pronouns: Some("he/him".into()),
|
||||
number: Some(n),
|
||||
},
|
||||
})
|
||||
.collect::<Box<[_]>>();
|
||||
let mut players_iter = players.iter().map(|p| p.player_id);
|
||||
let (
|
||||
werewolf,
|
||||
dire_wolf,
|
||||
shapeshifter,
|
||||
alpha_wolf,
|
||||
seer,
|
||||
arcanist,
|
||||
maple_wolf,
|
||||
guardian,
|
||||
vindicator,
|
||||
adjudicator,
|
||||
power_seer,
|
||||
beholder,
|
||||
gravedigger,
|
||||
mortician,
|
||||
insomniac,
|
||||
empath,
|
||||
scapegoat,
|
||||
hunter,
|
||||
diseased,
|
||||
) = (
|
||||
(SetupRole::Werewolf, players_iter.next().unwrap()),
|
||||
(SetupRole::DireWolf, players_iter.next().unwrap()),
|
||||
(SetupRole::Shapeshifter, players_iter.next().unwrap()),
|
||||
(SetupRole::AlphaWolf, players_iter.next().unwrap()),
|
||||
(SetupRole::Seer, players_iter.next().unwrap()),
|
||||
(SetupRole::Arcanist, players_iter.next().unwrap()),
|
||||
(SetupRole::MapleWolf, players_iter.next().unwrap()),
|
||||
(SetupRole::Guardian, players_iter.next().unwrap()),
|
||||
(SetupRole::Vindicator, players_iter.next().unwrap()),
|
||||
(SetupRole::Adjudicator, players_iter.next().unwrap()),
|
||||
(SetupRole::PowerSeer, players_iter.next().unwrap()),
|
||||
(SetupRole::Beholder, players_iter.next().unwrap()),
|
||||
(SetupRole::Gravedigger, players_iter.next().unwrap()),
|
||||
(SetupRole::Mortician, players_iter.next().unwrap()),
|
||||
(SetupRole::Insomniac, players_iter.next().unwrap()),
|
||||
(SetupRole::Empath, players_iter.next().unwrap()),
|
||||
(
|
||||
SetupRole::Scapegoat {
|
||||
redeemed: OrRandom::Determined(false),
|
||||
},
|
||||
players_iter.next().unwrap(),
|
||||
),
|
||||
(SetupRole::Hunter, players_iter.next().unwrap()),
|
||||
(SetupRole::Diseased, players_iter.next().unwrap()),
|
||||
);
|
||||
let mut settings = GameSettings::empty();
|
||||
settings.add_and_assign(werewolf.0, werewolf.1);
|
||||
settings.add_and_assign(dire_wolf.0, dire_wolf.1);
|
||||
settings.add_and_assign(shapeshifter.0, shapeshifter.1);
|
||||
settings.add_and_assign(alpha_wolf.0, alpha_wolf.1);
|
||||
settings.add_and_assign(seer.0, seer.1);
|
||||
settings.add_and_assign(arcanist.0, arcanist.1);
|
||||
settings.add_and_assign(maple_wolf.0, maple_wolf.1);
|
||||
settings.add_and_assign(guardian.0, guardian.1);
|
||||
settings.add_and_assign(vindicator.0, vindicator.1);
|
||||
settings.add_and_assign(adjudicator.0, adjudicator.1);
|
||||
settings.add_and_assign(power_seer.0, power_seer.1);
|
||||
settings.add_and_assign(beholder.0, beholder.1);
|
||||
settings.add_and_assign(gravedigger.0, gravedigger.1);
|
||||
settings.add_and_assign(mortician.0, mortician.1);
|
||||
settings.add_and_assign(insomniac.0, insomniac.1);
|
||||
settings.add_and_assign(empath.0, empath.1);
|
||||
settings.add_and_assign(scapegoat.0, scapegoat.1);
|
||||
settings.add_and_assign(hunter.0, hunter.1);
|
||||
settings.add_and_assign(diseased.0, diseased.1);
|
||||
settings.fill_remaining_slots_with_villagers(players.len());
|
||||
#[allow(unused)]
|
||||
let (
|
||||
werewolf,
|
||||
dire_wolf,
|
||||
shapeshifter,
|
||||
alpha_wolf,
|
||||
seer,
|
||||
arcanist,
|
||||
maple_wolf,
|
||||
guardian,
|
||||
vindicator,
|
||||
adjudicator,
|
||||
power_seer,
|
||||
beholder,
|
||||
gravedigger,
|
||||
mortician,
|
||||
insomniac,
|
||||
empath,
|
||||
scapegoat,
|
||||
hunter,
|
||||
) = (
|
||||
werewolf.1,
|
||||
dire_wolf.1,
|
||||
shapeshifter.1,
|
||||
alpha_wolf.1,
|
||||
seer.1,
|
||||
arcanist.1,
|
||||
maple_wolf.1,
|
||||
guardian.1,
|
||||
vindicator.1,
|
||||
adjudicator.1,
|
||||
power_seer.1,
|
||||
beholder.1,
|
||||
gravedigger.1,
|
||||
mortician.1,
|
||||
insomniac.1,
|
||||
empath.1,
|
||||
scapegoat.1,
|
||||
hunter.1,
|
||||
);
|
||||
// let village = Village::new(&players, settings).unwrap();
|
||||
let mut game = game::Game::new(&players, settings).unwrap();
|
||||
game.r#continue().r#continue();
|
||||
game.next().title().wolves_intro();
|
||||
game.r#continue().r#continue();
|
||||
|
||||
game.next().title().direwolf();
|
||||
game.mark(game.character_by_player_id(seer).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().seer();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().arcanist();
|
||||
game.mark(game.character_by_player_id(seer).character_id());
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().role_blocked();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().adjudicator();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().adjudicator();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().power_seer();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
game.mark_for_execution(game.character_by_player_id(dire_wolf).character_id());
|
||||
game.mark_for_execution(game.character_by_player_id(alpha_wolf).character_id());
|
||||
|
||||
game.execute().title().guardian();
|
||||
let protect = game.living_villager();
|
||||
game.mark(protect.character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().wolf_pack_kill();
|
||||
game.mark(protect.character_id());
|
||||
game.r#continue().r#continue();
|
||||
|
||||
game.next().title().shapeshifter();
|
||||
game.response(ActionResponse::Shapeshift).sleep();
|
||||
|
||||
game.next().title().seer();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().arcanist();
|
||||
game.mark(game.character_by_player_id(seer).character_id());
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().arcanist();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().adjudicator();
|
||||
game.mark(game.character_by_player_id(seer).character_id());
|
||||
game.r#continue().adjudicator();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().power_seer();
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().gravedigger();
|
||||
game.mark(game.character_by_player_id(dire_wolf).character_id());
|
||||
assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::DireWolf));
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().mortician();
|
||||
game.mark(game.character_by_player_id(dire_wolf).character_id());
|
||||
assert_eq!(game.r#continue().mortician(), DiedToTitle::Execution);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().empath();
|
||||
game.mark(game.living_villager().character_id());
|
||||
assert!(!game.r#continue().empath());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().maple_wolf();
|
||||
game.mark(
|
||||
game.living_villager_excl(protect.player_id())
|
||||
.character_id(),
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().hunter();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().insomniac();
|
||||
game.r#continue().insomniac();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().beholder();
|
||||
game.mark(game.character_by_player_id(power_seer).character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
|
||||
assert_eq!(
|
||||
game.character_by_player_id(protect.player_id()).died_to(),
|
||||
None
|
||||
);
|
||||
|
||||
game.mark_for_execution(
|
||||
game.living_villager_excl(protect.player_id())
|
||||
.character_id(),
|
||||
);
|
||||
game.execute().title().guardian();
|
||||
game.mark(protect.character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().vindicator();
|
||||
game.mark(
|
||||
game.living_villager_excl(protect.player_id())
|
||||
.character_id(),
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().wolf_pack_kill();
|
||||
game.mark(protect.character_id());
|
||||
game.r#continue().r#continue();
|
||||
|
||||
game.next().title().shapeshifter();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().seer();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().arcanist();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
game.r#continue().arcanist();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().adjudicator();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().adjudicator();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().power_seer();
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().gravedigger();
|
||||
game.mark(game.character_by_player_id(dire_wolf).character_id());
|
||||
assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::DireWolf));
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().mortician();
|
||||
game.mark(game.character_by_player_id(dire_wolf).character_id());
|
||||
assert_eq!(game.r#continue().mortician(), DiedToTitle::Execution);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().empath();
|
||||
game.mark(game.character_by_player_id(scapegoat).character_id());
|
||||
assert!(game.r#continue().empath());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().maple_wolf();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().hunter();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().insomniac();
|
||||
game.r#continue().insomniac();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().beholder();
|
||||
game.mark(game.character_by_player_id(power_seer).character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
game.mark_for_execution(
|
||||
game.living_villager_excl(protect.player_id())
|
||||
.character_id(),
|
||||
);
|
||||
|
||||
game.execute().title().vindicator();
|
||||
game.mark(game.character_by_player_id(shapeshifter).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().wolf_pack_kill();
|
||||
game.mark(game.character_by_player_id(empath).character_id());
|
||||
game.r#continue().r#continue();
|
||||
|
||||
game.next().title().shapeshifter();
|
||||
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
ActionResponse::Shapeshift,
|
||||
)))
|
||||
.expect("shapeshift");
|
||||
// game.r#continue().r#continue();
|
||||
|
||||
assert_eq!(
|
||||
game.next(),
|
||||
ActionPrompt::RoleChange {
|
||||
character_id: game.character_by_player_id(empath).identity(),
|
||||
new_role: RoleTitle::Werewolf
|
||||
}
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().seer();
|
||||
game.mark(game.character_by_player_id(shapeshifter).character_id());
|
||||
game.r#continue().seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().arcanist();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.mark(game.character_by_player_id(shapeshifter).character_id());
|
||||
game.r#continue().arcanist();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().adjudicator();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().adjudicator();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().power_seer();
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().gravedigger();
|
||||
game.mark(game.character_by_player_id(guardian).character_id());
|
||||
assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::Guardian));
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().mortician();
|
||||
game.mark(game.character_by_player_id(guardian).character_id());
|
||||
assert_eq!(game.r#continue().mortician(), DiedToTitle::Wolfpack);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().maple_wolf();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().hunter();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().insomniac();
|
||||
game.r#continue().insomniac();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().beholder();
|
||||
game.mark(game.character_by_player_id(gravedigger).character_id());
|
||||
assert_eq!(game.r#continue().gravedigger(), Some(RoleTitle::Guardian));
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
game.mark_for_execution(game.character_by_player_id(vindicator).character_id());
|
||||
game.execute().title().wolf_pack_kill();
|
||||
game.mark(game.living_villager().character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().seer();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.r#continue().seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().arcanist();
|
||||
game.mark(game.character_by_player_id(insomniac).character_id());
|
||||
game.mark(game.character_by_player_id(empath).character_id());
|
||||
game.r#continue().arcanist();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().adjudicator();
|
||||
game.mark(game.character_by_player_id(empath).character_id());
|
||||
game.r#continue().adjudicator();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().power_seer();
|
||||
game.mark(game.character_by_player_id(empath).character_id());
|
||||
game.r#continue().power_seer();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().gravedigger();
|
||||
game.mark(game.character_by_player_id(shapeshifter).character_id());
|
||||
assert_eq!(game.r#continue().gravedigger(), None);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().mortician();
|
||||
game.mark(game.character_by_player_id(werewolf).character_id());
|
||||
assert_eq!(
|
||||
game.r#continue().mortician(),
|
||||
DiedToTitle::GuardianProtecting
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().maple_wolf();
|
||||
game.mark(game.character_by_player_id(hunter).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().hunter();
|
||||
game.mark(game.character_by_player_id(empath).character_id());
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().insomniac();
|
||||
game.r#continue().insomniac();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next().title().beholder();
|
||||
game.mark(game.character_by_player_id(mortician).character_id());
|
||||
assert_eq!(
|
||||
game.r#continue().mortician(),
|
||||
DiedToTitle::GuardianProtecting
|
||||
);
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.story()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait ActionPromptTitleExt {
|
||||
fn wolf_pack_kill(&self);
|
||||
fn cover_of_darkness(&self);
|
||||
fn wolves_intro(&self);
|
||||
fn role_change(&self);
|
||||
fn seer(&self);
|
||||
fn protector(&self);
|
||||
fn arcanist(&self);
|
||||
fn gravedigger(&self);
|
||||
fn hunter(&self);
|
||||
fn militia(&self);
|
||||
fn maple_wolf(&self);
|
||||
fn guardian(&self);
|
||||
fn shapeshifter(&self);
|
||||
fn alphawolf(&self);
|
||||
fn direwolf(&self);
|
||||
fn masons_wake(&self);
|
||||
fn masons_leader_recruit(&self);
|
||||
fn beholder(&self);
|
||||
fn vindicator(&self);
|
||||
fn pyremaster(&self);
|
||||
fn empath(&self);
|
||||
fn adjudicator(&self);
|
||||
fn lone_wolf(&self);
|
||||
fn insomniac(&self);
|
||||
fn power_seer(&self);
|
||||
fn mortician(&self);
|
||||
}
|
||||
|
||||
impl ActionPromptTitleExt for ActionPromptTitle {
|
||||
fn mortician(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Mortician);
|
||||
}
|
||||
fn cover_of_darkness(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::CoverOfDarkness);
|
||||
}
|
||||
fn wolves_intro(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::WolvesIntro);
|
||||
}
|
||||
fn role_change(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::RoleChange);
|
||||
}
|
||||
fn seer(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Seer);
|
||||
}
|
||||
fn protector(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Protector);
|
||||
}
|
||||
fn arcanist(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Arcanist);
|
||||
}
|
||||
fn gravedigger(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Gravedigger);
|
||||
}
|
||||
fn hunter(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Hunter);
|
||||
}
|
||||
fn militia(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Militia);
|
||||
}
|
||||
fn maple_wolf(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::MapleWolf);
|
||||
}
|
||||
fn guardian(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Guardian);
|
||||
}
|
||||
fn shapeshifter(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Shapeshifter);
|
||||
}
|
||||
fn alphawolf(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::AlphaWolf);
|
||||
}
|
||||
fn direwolf(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::DireWolf);
|
||||
}
|
||||
fn wolf_pack_kill(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::WolfPackKill);
|
||||
}
|
||||
fn masons_wake(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::MasonsWake)
|
||||
}
|
||||
fn masons_leader_recruit(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::MasonLeaderRecruit)
|
||||
}
|
||||
fn beholder(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Beholder)
|
||||
}
|
||||
fn vindicator(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Vindicator)
|
||||
}
|
||||
fn pyremaster(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::PyreMaster)
|
||||
}
|
||||
fn adjudicator(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Adjudicator)
|
||||
}
|
||||
fn empath(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Empath)
|
||||
}
|
||||
fn lone_wolf(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::LoneWolfKill)
|
||||
}
|
||||
fn insomniac(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::Insomniac)
|
||||
}
|
||||
fn power_seer(&self) {
|
||||
assert_eq!(*self, ActionPromptTitle::PowerSeer)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait ActionResultExt {
|
||||
fn sleep(&self);
|
||||
fn r#continue(&self);
|
||||
fn seer(&self) -> Alignment;
|
||||
fn insomniac(&self) -> Visits;
|
||||
fn arcanist(&self) -> AlignmentEq;
|
||||
fn adjudicator(&self) -> Killer;
|
||||
fn role_blocked(&self);
|
||||
fn gravedigger(&self) -> Option<RoleTitle>;
|
||||
fn power_seer(&self) -> Powerful;
|
||||
fn mortician(&self) -> DiedToTitle;
|
||||
fn empath(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ActionResultExt for ActionResult {
|
||||
fn empath(&self) -> bool {
|
||||
match self {
|
||||
Self::Empath { scapegoat } => *scapegoat,
|
||||
resp => panic!("expected empath, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn mortician(&self) -> DiedToTitle {
|
||||
match self {
|
||||
Self::Mortician(role) => *role,
|
||||
resp => panic!("expected mortician, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn gravedigger(&self) -> Option<RoleTitle> {
|
||||
match self {
|
||||
Self::GraveDigger(role) => *role,
|
||||
resp => panic!("expected gravedigger, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn adjudicator(&self) -> Killer {
|
||||
match self {
|
||||
Self::Adjudicator { killer } => *killer,
|
||||
resp => panic!("expected adjudicator, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn power_seer(&self) -> Powerful {
|
||||
match self {
|
||||
Self::PowerSeer { powerful } => *powerful,
|
||||
resp => panic!("expected power seer, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn sleep(&self) {
|
||||
assert_eq!(*self, ActionResult::GoBackToSleep)
|
||||
}
|
||||
|
||||
fn role_blocked(&self) {
|
||||
assert_eq!(*self, ActionResult::RoleBlocked)
|
||||
}
|
||||
|
||||
fn r#continue(&self) {
|
||||
assert_eq!(*self, ActionResult::Continue)
|
||||
}
|
||||
|
||||
fn seer(&self) -> Alignment {
|
||||
match self {
|
||||
ActionResult::Seer(a) => *a,
|
||||
_ => panic!("expected a seer result"),
|
||||
}
|
||||
}
|
||||
|
||||
fn arcanist(&self) -> AlignmentEq {
|
||||
match self {
|
||||
ActionResult::Arcanist(same) => *same,
|
||||
_ => panic!("expected an arcanist result"),
|
||||
}
|
||||
}
|
||||
|
||||
fn insomniac(&self) -> Visits {
|
||||
match self {
|
||||
ActionResult::Insomniac(v) => v.clone(),
|
||||
_ => panic!("expected an insomniac result"),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub trait AlignmentExt {
|
||||
fn village(&self);
|
||||
fn wolves(&self);
|
||||
}
|
||||
|
||||
impl AlignmentExt for Alignment {
|
||||
fn village(&self) {
|
||||
assert_eq!(*self, Alignment::Village)
|
||||
}
|
||||
|
||||
fn wolves(&self) {
|
||||
assert_eq!(*self, Alignment::Wolves)
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub trait ServerToHostMessageExt {
|
||||
fn prompt(self) -> ActionPrompt;
|
||||
fn result(self) -> ActionResult;
|
||||
fn daytime(self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||
}
|
||||
|
||||
impl ServerToHostMessageExt for ServerToHostMessage {
|
||||
fn daytime(self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
|
||||
match self {
|
||||
Self::Daytime {
|
||||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
resp => panic!("expected daytime, got {resp:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt(self) -> ActionPrompt {
|
||||
match self {
|
||||
Self::ActionPrompt(prompt, _) => prompt,
|
||||
Self::Daytime { .. } => panic!("{}", "[got daytime]"),
|
||||
msg => panic!("expected server message <<{msg:?}>> to be an ActionPrompt"),
|
||||
}
|
||||
}
|
||||
|
||||
fn result(self) -> ActionResult {
|
||||
match self {
|
||||
Self::ActionResult(_, res) => res,
|
||||
msg => panic!("expected server message <<{msg:?}>> to be an ActionResult"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait GameExt {
|
||||
fn villager_character_ids(&self) -> Box<[CharacterId]>;
|
||||
fn character_by_player_id(&self, player_id: PlayerId) -> Character;
|
||||
fn character_by_character_id(&self, character_id: CharacterId) -> Character;
|
||||
fn next(&mut self) -> ActionPrompt;
|
||||
fn r#continue(&mut self) -> ActionResult;
|
||||
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||
fn mark(&mut self, mark: CharacterId) -> ActionPrompt;
|
||||
fn mark_and_check(&mut self, mark: CharacterId);
|
||||
fn response(&mut self, resp: ActionResponse) -> ActionResult;
|
||||
fn execute(&mut self) -> ActionPrompt;
|
||||
fn mark_for_execution(
|
||||
&mut self,
|
||||
target: CharacterId,
|
||||
) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||
fn living_villager_excl(&self, excl: PlayerId) -> Character;
|
||||
fn living_villager(&self) -> Character;
|
||||
#[allow(unused)]
|
||||
fn get_state(&mut self) -> ServerToHostMessage;
|
||||
fn next_expect_game_over(&mut self) -> GameOver;
|
||||
}
|
||||
|
||||
impl GameExt for Game {
|
||||
fn next_expect_game_over(&mut self) -> GameOver {
|
||||
match self
|
||||
.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::GameOver(outcome) => outcome,
|
||||
resp => panic!("expected game to be over, got: {resp:?}"),
|
||||
}
|
||||
}
|
||||
fn get_state(&mut self) -> ServerToHostMessage {
|
||||
self.process(HostGameMessage::GetState).unwrap()
|
||||
}
|
||||
fn living_villager(&self) -> Character {
|
||||
self.village()
|
||||
.characters()
|
||||
.into_iter()
|
||||
.find(|c| c.alive() && matches!(c.role_title(), RoleTitle::Villager))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn living_villager_excl(&self, excl: PlayerId) -> Character {
|
||||
self.village()
|
||||
.characters()
|
||||
.into_iter()
|
||||
.find(|c| {
|
||||
c.alive() && matches!(c.role_title(), RoleTitle::Villager) && c.player_id() != excl
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn villager_character_ids(&self) -> Box<[CharacterId]> {
|
||||
self.village()
|
||||
.characters()
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
(c.alive() && matches!(c.role_title(), RoleTitle::Villager))
|
||||
.then_some(c.character_id())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn character_by_player_id(&self, player_id: PlayerId) -> Character {
|
||||
self.village()
|
||||
.character_by_player_id(player_id)
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn character_by_character_id(&self, character_id: CharacterId) -> Character {
|
||||
self.village()
|
||||
.character_by_id(character_id)
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn r#continue(&mut self) -> ActionResult {
|
||||
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
ActionResponse::Continue,
|
||||
)))
|
||||
.unwrap()
|
||||
.result()
|
||||
}
|
||||
|
||||
fn mark(&mut self, mark: CharacterId) -> ActionPrompt {
|
||||
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
ActionResponse::MarkTarget(mark),
|
||||
)))
|
||||
.unwrap()
|
||||
.prompt()
|
||||
}
|
||||
|
||||
fn mark_and_check(&mut self, mark: CharacterId) {
|
||||
let prompt = self.mark(mark);
|
||||
match prompt {
|
||||
ActionPrompt::TraitorIntro { .. }
|
||||
| ActionPrompt::Insomniac { .. }
|
||||
| ActionPrompt::MasonsWake { .. }
|
||||
| ActionPrompt::ElderReveal { .. }
|
||||
| ActionPrompt::CoverOfDarkness
|
||||
| ActionPrompt::WolvesIntro { .. }
|
||||
| ActionPrompt::RoleChange { .. }
|
||||
| ActionPrompt::Shapeshifter { .. } => panic!("expected a prompt with a mark"),
|
||||
ActionPrompt::Arcanist { .. } => panic!("wrong call for arcanist"),
|
||||
|
||||
ActionPrompt::Bloodletter {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::LoneWolfKill {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Seer {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Adjudicator {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::PowerSeer {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Mortician {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Beholder {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::MasonLeaderRecruit {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Empath {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Vindicator {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::PyreMaster {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Protector {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Gravedigger {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Hunter {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Militia {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::MapleWolf {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::Guardian {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::WolfPackKill {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::AlphaWolf {
|
||||
marked: Some(marked),
|
||||
..
|
||||
}
|
||||
| ActionPrompt::DireWolf {
|
||||
marked: Some(marked),
|
||||
..
|
||||
} => assert_eq!(marked, mark, "marked character"),
|
||||
ActionPrompt::Bloodletter { marked: None, .. }
|
||||
| ActionPrompt::Seer { marked: None, .. }
|
||||
| ActionPrompt::Adjudicator { marked: None, .. }
|
||||
| ActionPrompt::PowerSeer { marked: None, .. }
|
||||
| ActionPrompt::Mortician { marked: None, .. }
|
||||
| ActionPrompt::Beholder { marked: None, .. }
|
||||
| ActionPrompt::MasonLeaderRecruit { marked: None, .. }
|
||||
| ActionPrompt::Empath { marked: None, .. }
|
||||
| ActionPrompt::Vindicator { marked: None, .. }
|
||||
| ActionPrompt::PyreMaster { marked: None, .. }
|
||||
| ActionPrompt::Protector { marked: None, .. }
|
||||
| ActionPrompt::Gravedigger { marked: None, .. }
|
||||
| ActionPrompt::Hunter { marked: None, .. }
|
||||
| ActionPrompt::Militia { marked: None, .. }
|
||||
| ActionPrompt::MapleWolf { marked: None, .. }
|
||||
| ActionPrompt::Guardian { marked: None, .. }
|
||||
| ActionPrompt::WolfPackKill { marked: None, .. }
|
||||
| ActionPrompt::AlphaWolf { marked: None, .. }
|
||||
| ActionPrompt::DireWolf { marked: None, .. }
|
||||
| ActionPrompt::LoneWolfKill { marked: None, .. } => panic!("no mark"),
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> ActionPrompt {
|
||||
self.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap()
|
||||
.prompt()
|
||||
}
|
||||
|
||||
fn next_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
|
||||
match self
|
||||
.process(HostGameMessage::Night(HostNightMessage::Next))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::Daytime {
|
||||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
res => panic!("unexpected response to next_expect_day: {res:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&mut self, resp: ActionResponse) -> ActionResult {
|
||||
self.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
|
||||
resp,
|
||||
)))
|
||||
.unwrap()
|
||||
.result()
|
||||
}
|
||||
|
||||
fn mark_for_execution(
|
||||
&mut self,
|
||||
target: CharacterId,
|
||||
) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
|
||||
match self
|
||||
.process(HostGameMessage::Day(HostDayMessage::MarkForExecution(
|
||||
target,
|
||||
)))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::Daytime {
|
||||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
res => panic!("unexpected response to mark_for_execution: {res:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(&mut self) -> ActionPrompt {
|
||||
assert_eq!(
|
||||
self.process(HostGameMessage::Day(HostDayMessage::Execute))
|
||||
.unwrap()
|
||||
.prompt(),
|
||||
ActionPrompt::CoverOfDarkness
|
||||
);
|
||||
self.r#continue().r#continue();
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ pub mod client {
|
|||
}
|
||||
pub mod host {
|
||||
mod host;
|
||||
pub mod story_test;
|
||||
pub use host::*;
|
||||
}
|
||||
pub mod test_remote;
|
||||
|
|
@ -27,18 +26,3 @@ pub mod test_remote;
|
|||
|
||||
const DEBUG_URL: &str = "ws://192.168.1.162:8080/connect/";
|
||||
const LIVE_URL: &str = "wss://wolf.emilis.dev/connect/";
|
||||
|
||||
pub mod story_test {
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components::Story;
|
||||
#[function_component]
|
||||
pub fn StoryTest() -> Html {
|
||||
let story = crate::clients::host::story_test::test_story();
|
||||
html! {
|
||||
<div class="content">
|
||||
<Story story={story}/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
use werewolves_proto::message::CharacterState;
|
||||
// Copyright (C) 2025 Emilis Bliūdžius
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components::host::DaytimePlayerList;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct CharacterStatesProps {
|
||||
pub states: Box<[CharacterState]>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn CharacterStatesReadOnly(CharacterStatesProps { states }: &CharacterStatesProps) -> Html {
|
||||
let marked: Box<[_]> = Box::new([]);
|
||||
html! {
|
||||
<DaytimePlayerList
|
||||
marked={marked}
|
||||
characters={states.clone()}
|
||||
on_mark={|_|()}
|
||||
big_screen=false
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
|
@ -22,16 +22,16 @@ use werewolves_proto::{
|
|||
};
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components::{
|
||||
AssociatedIcon, Button, Icon, IconType, Identity, PartialAssociatedIcon,
|
||||
};
|
||||
use crate::components::{AssociatedIcon, Button, Icon, IconType, Identity, PartialAssociatedIcon};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct DaytimePlayerListProps {
|
||||
pub day: NonZeroU8,
|
||||
#[prop_or_default]
|
||||
pub day: Option<NonZeroU8>,
|
||||
pub characters: Box<[CharacterState]>,
|
||||
pub marked: Box<[CharacterId]>,
|
||||
pub on_execute: Callback<()>,
|
||||
#[prop_or_default]
|
||||
pub on_execute: Option<Callback<()>>,
|
||||
pub on_mark: Callback<CharacterId>,
|
||||
pub big_screen: bool,
|
||||
}
|
||||
|
|
@ -60,7 +60,9 @@ pub fn DaytimePlayerList(
|
|||
Some(died_to) => match died_to.date_time() {
|
||||
GameTime::Day { .. } => Some(MarkState::Dead),
|
||||
GameTime::Night { number } => {
|
||||
if number == day.get() - 1 {
|
||||
if let Some(day) = day.as_ref()
|
||||
&& number == day.get() - 1
|
||||
{
|
||||
Some(MarkState::DiedLastNight)
|
||||
} else {
|
||||
Some(MarkState::Dead)
|
||||
|
|
@ -82,16 +84,25 @@ pub fn DaytimePlayerList(
|
|||
} else {
|
||||
"execute"
|
||||
};
|
||||
let button = big_screen.not().then(|| {
|
||||
let button = big_screen
|
||||
.not()
|
||||
.then_some(())
|
||||
.and_then(|_| on_execute.clone())
|
||||
.map(|on_execute| {
|
||||
html! {
|
||||
<Button on_click={on_execute}>
|
||||
{button_text}
|
||||
</Button>
|
||||
}
|
||||
});
|
||||
let day = day.as_ref().map(|day| {
|
||||
html! {
|
||||
<Button on_click={on_execute}>
|
||||
{button_text}
|
||||
</Button>
|
||||
<h2>{"day "}{day.get()}</h2>
|
||||
}
|
||||
});
|
||||
html! {
|
||||
<div class="character-picker">
|
||||
<h2>{"day "}{day.to_string()}</h2>
|
||||
{day}
|
||||
<div class="player-list">
|
||||
{chars}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue