skip prompt button
This commit is contained in:
parent
2b704ddff6
commit
060180579d
|
|
@ -171,6 +171,10 @@ impl Game {
|
|||
settings: village.settings(),
|
||||
})
|
||||
}
|
||||
(GameState::Night { night }, HostGameMessage::Night(HostNightMessage::SkipAction)) => {
|
||||
night.skip_action()?;
|
||||
self.process(HostGameMessage::GetState)
|
||||
}
|
||||
(GameState::Night { night }, HostGameMessage::GetState) => {
|
||||
if let Some(res) = night.current_result() {
|
||||
return Ok(ServerToHostMessage::ActionResult(
|
||||
|
|
|
|||
|
|
@ -421,6 +421,23 @@ impl Night {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn skip_action(&mut self) -> Result<()> {
|
||||
match &mut self.night_state {
|
||||
NightState::Active {
|
||||
current_result,
|
||||
current_changes,
|
||||
..
|
||||
} => {
|
||||
*current_result = CurrentResult::GoBackToSleepAfterShown {
|
||||
result_with_data: ActionResult::SkippedByHost,
|
||||
};
|
||||
current_changes.clear();
|
||||
}
|
||||
NightState::Complete => return Err(GameError::NightOver),
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
|
||||
fn remove_reverted_prompts(
|
||||
mut action_queue: VecDeque<ActionPrompt>,
|
||||
reverting: CharacterId,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub struct NightChoice {
|
|||
|
||||
impl NightChoice {
|
||||
pub fn new(prompt: ActionPrompt, result: ActionResult, village: &Village) -> Option<Self> {
|
||||
Some(Self {
|
||||
(result != ActionResult::SkippedByHost).then_some(Self {
|
||||
prompt: StoryActionPrompt::new(prompt, village)?,
|
||||
result: StoryActionResult::new(result),
|
||||
})
|
||||
|
|
@ -107,6 +107,7 @@ pub enum StoryActionResult {
|
|||
impl StoryActionResult {
|
||||
pub fn new(result: ActionResult) -> Option<Self> {
|
||||
Some(match result {
|
||||
ActionResult::SkippedByHost => return None,
|
||||
ActionResult::ShiftFailed => Self::ShiftFailed,
|
||||
ActionResult::BeholderSawNothing => Self::BeholderSawNothing,
|
||||
ActionResult::BeholderSawEverything => Self::BeholderSawEverything,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ mod night_order;
|
|||
mod previous;
|
||||
mod revert;
|
||||
mod role;
|
||||
mod skip;
|
||||
|
||||
use crate::{
|
||||
character::{Character, CharacterId},
|
||||
|
|
@ -339,9 +340,30 @@ pub trait GameExt {
|
|||
fn next_expect_game_over(&mut self) -> GameOver;
|
||||
fn prev(&mut self) -> ServerToHostMessage;
|
||||
fn mark_villager(&mut self) -> ActionPrompt;
|
||||
fn skip(&mut self) -> ActionPrompt;
|
||||
fn skip_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8);
|
||||
}
|
||||
|
||||
impl GameExt for Game {
|
||||
fn skip(&mut self) -> ActionPrompt {
|
||||
self.process(HostGameMessage::Night(HostNightMessage::SkipAction))
|
||||
.unwrap()
|
||||
.prompt()
|
||||
}
|
||||
fn skip_expect_day(&mut self) -> (Box<[CharacterState]>, Box<[CharacterId]>, NonZeroU8) {
|
||||
match self
|
||||
.process(HostGameMessage::Night(HostNightMessage::SkipAction))
|
||||
.unwrap()
|
||||
{
|
||||
ServerToHostMessage::Daytime {
|
||||
characters,
|
||||
marked,
|
||||
day,
|
||||
..
|
||||
} => (characters, marked, day),
|
||||
other => panic!("expected daytime, got {other:?}"),
|
||||
}
|
||||
}
|
||||
fn prev(&mut self) -> ServerToHostMessage {
|
||||
self.process(HostGameMessage::PreviousState).unwrap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (C) 2026 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/>.
|
||||
#[allow(unused)]
|
||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||
|
||||
use crate::{
|
||||
game::{Game, GameSettings, SetupRole},
|
||||
game_test::{
|
||||
ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players, init_log,
|
||||
},
|
||||
message::night::ActionPromptTitle,
|
||||
};
|
||||
|
||||
#[test]
|
||||
pub fn skip_wolf_kill_all_villagers() {
|
||||
init_log();
|
||||
let players = gen_players(1..21);
|
||||
let mut player_ids = players.iter().map(|p| p.player_id);
|
||||
let wolf = player_ids.next().unwrap();
|
||||
let mut settings = GameSettings::empty();
|
||||
settings.add_and_assign(SetupRole::Werewolf, wolf);
|
||||
settings.fill_remaining_slots_with_villagers(players.len());
|
||||
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();
|
||||
game.execute().title().wolf_pack_kill();
|
||||
game.skip_expect_day();
|
||||
|
||||
assert!(game.village().characters().into_iter().all(|c| c.alive()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn skip_wolf_kill_has_seer() {
|
||||
init_log();
|
||||
let players = gen_players(1..21);
|
||||
let mut player_ids = players.iter().map(|p| p.player_id);
|
||||
let wolf = player_ids.next().unwrap();
|
||||
let seer = player_ids.next().unwrap();
|
||||
let mut settings = GameSettings::empty();
|
||||
settings.add_and_assign(SetupRole::Werewolf, wolf);
|
||||
settings.add_and_assign(SetupRole::Seer, seer);
|
||||
settings.fill_remaining_slots_with_villagers(players.len());
|
||||
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().title().seer();
|
||||
game.mark(game.character_by_player_id(wolf).character_id());
|
||||
game.r#continue();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
game.execute().title().wolf_pack_kill();
|
||||
game.skip().title().seer();
|
||||
game.mark(game.character_by_player_id(wolf).character_id());
|
||||
game.r#continue();
|
||||
game.r#continue().sleep();
|
||||
|
||||
game.next_expect_day();
|
||||
|
||||
assert!(game.village().characters().into_iter().all(|c| c.alive()))
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ pub enum HostNightMessage {
|
|||
ActionResponse(ActionResponse),
|
||||
Next,
|
||||
NextPage,
|
||||
SkipAction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -623,6 +623,7 @@ pub enum ActionResult {
|
|||
GoBackToSleep,
|
||||
ShiftFailed,
|
||||
Continue,
|
||||
SkippedByHost,
|
||||
}
|
||||
|
||||
impl ActionResult {
|
||||
|
|
@ -660,7 +661,8 @@ impl ActionResult {
|
|||
| ActionResult::Mortician(_, _)
|
||||
| ActionResult::Insomniac(_)
|
||||
| ActionResult::GoBackToSleep
|
||||
| ActionResult::Continue => return None,
|
||||
| ActionResult::Continue
|
||||
| ActionResult::SkippedByHost => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -560,12 +560,40 @@ impl Component for Host {
|
|||
}
|
||||
_ => None,
|
||||
};
|
||||
let skip_btn = match &self.state {
|
||||
HostState::Prompt(_, _) | HostState::Result(_, _) => {
|
||||
let on_skip_click = callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::Night(HostNightMessage::SkipAction)),
|
||||
self.send.clone(),
|
||||
);
|
||||
let on_skip_click = {
|
||||
Callback::from(move |_| {
|
||||
on_skip_click.emit(());
|
||||
crate::components::modal::close_modal_by_id("skip-button");
|
||||
})
|
||||
};
|
||||
|
||||
Some(html! {
|
||||
<crate::components::modal::Dialog
|
||||
id={"skip-button"}
|
||||
button={html!{{"skip"}}}
|
||||
close_button=false
|
||||
>
|
||||
<h2>{"skip the current prompt?"}</h2>
|
||||
<p>{"if this is the final prompt of the night, you may not be able to go back"}</p>
|
||||
<Button on_click={on_skip_click}>{"skip prompt"}</Button>
|
||||
</crate::components::modal::Dialog>
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let nav = self.big_screen.not().then(|| {
|
||||
html! {
|
||||
<nav class="host-nav" style="z-index: 3;">
|
||||
{previous_btn}
|
||||
{view_roles_btn}
|
||||
{override_screens_btn}
|
||||
{skip_btn}
|
||||
</nav>
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html {
|
|||
</CoverOfDarkness>
|
||||
};
|
||||
}
|
||||
ActionResult::Continue => {
|
||||
ActionResult::SkippedByHost | ActionResult::Continue => {
|
||||
props.on_complete.emit(HostMessage::GetState);
|
||||
return html! {
|
||||
<CoverOfDarkness />
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ pub enum TestScreen {
|
|||
impl From<ActionResultTitle> for TestScreen {
|
||||
fn from(value: ActionResultTitle) -> Self {
|
||||
TestScreen::Result(match value {
|
||||
ActionResultTitle::SkippedByHost => ActionResult::SkippedByHost,
|
||||
ActionResultTitle::RoleBlocked => ActionResult::RoleBlocked,
|
||||
ActionResultTitle::Drunk => ActionResult::Drunk,
|
||||
ActionResultTitle::Seer => ActionResult::Seer(identity(), Alignment::Village),
|
||||
|
|
@ -421,7 +422,9 @@ fn result_class(result: &ActionResult) -> Option<&'static str> {
|
|||
| ActionResultTitle::BeholderSawEverything
|
||||
| ActionResultTitle::Seer => Some("intel"),
|
||||
ActionResultTitle::ShiftFailed => Some("wolves"),
|
||||
ActionResultTitle::GoBackToSleep | ActionResultTitle::Continue => None,
|
||||
ActionResultTitle::GoBackToSleep
|
||||
| ActionResultTitle::Continue
|
||||
| ActionResultTitle::SkippedByHost => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ pub fn ResultScreenTest(
|
|||
| ActionResult::ShiftFailed
|
||||
| ActionResult::Continue
|
||||
| ActionResult::Drunk
|
||||
| ActionResult::RoleBlocked => html! {},
|
||||
| ActionResult::RoleBlocked
|
||||
| ActionResult::SkippedByHost => html! {},
|
||||
ActionResult::Seer(target, alignment) => {
|
||||
let all = Alignment::ALL
|
||||
.into_iter()
|
||||
|
|
|
|||
Loading…
Reference in New Issue