diff --git a/werewolves-proto/src/game/mod.rs b/werewolves-proto/src/game/mod.rs index e26cd8a..6744a8a 100644 --- a/werewolves-proto/src/game/mod.rs +++ b/werewolves-proto/src/game/mod.rs @@ -1,5 +1,5 @@ mod kill; -mod night; +pub(crate) mod night; mod settings; mod village; @@ -21,6 +21,7 @@ use crate::{ }, player::CharacterId, }; + pub use { settings::{Category, GameSettings, OrRandom, SetupRole, SetupSlot, SlotId}, village::Village, diff --git a/werewolves-proto/src/game/night.rs b/werewolves-proto/src/game/night.rs index fdf0b72..690aad0 100644 --- a/werewolves-proto/src/game/night.rs +++ b/werewolves-proto/src/game/night.rs @@ -565,7 +565,22 @@ impl Night { _ => Err(GameError::InvalidMessageForGameState), }; } - ActionResponse::Continue => {} + ActionResponse::Continue => { + if let ActionPrompt::RoleChange { + character_id, + new_role, + } = current_prompt + { + return Ok(ResponseOutcome::ActionComplete(ActionComplete { + result: ActionResult::GoBackToSleep, + change: Some(NightChange::RoleChange( + character_id.character_id.clone(), + *new_role, + )), + unless: None, + })); + } + } }; match current_prompt { diff --git a/werewolves-proto/src/game_test/mod.rs b/werewolves-proto/src/game_test/mod.rs index 7f4dafa..4731399 100644 --- a/werewolves-proto/src/game_test/mod.rs +++ b/werewolves-proto/src/game_test/mod.rs @@ -1,18 +1,23 @@ mod night_order; use crate::{ + diedto::DiedTo, error::GameError, - game::{Game, GameSettings, SetupRole}, + game::{Game, GameSettings, OrRandom, SetupRole, night::NightChange}, message::{ CharacterState, Identification, PublicIdentity, host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage}, night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult}, }, player::{CharacterId, PlayerId}, - role::RoleTitle, + role::{Alignment, Role, RoleTitle}, }; use colored::Colorize; -use core::{num::NonZeroU8, ops::Range}; +use core::{ + char, + num::{NonZero, NonZeroU8}, + ops::Range, +}; #[allow(unused)] use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; use std::io::Write; @@ -20,6 +25,7 @@ use std::io::Write; trait ActionResultExt { fn sleep(&self); fn r#continue(&self); + fn seer(&self) -> Alignment; } impl ActionResultExt for ActionResult { @@ -30,6 +36,13 @@ impl ActionResultExt for ActionResult { fn r#continue(&self) { assert_eq!(*self, ActionResult::Continue) } + + fn seer(&self) -> Alignment { + match self { + ActionResult::Seer(a) => a.clone(), + _ => panic!("expected a seer result"), + } + } } trait ServerToHostMessageExt { @@ -210,7 +223,10 @@ fn gen_players(range: Range) -> Box<[Identification]> { #[test] fn starts_with_wolf_intro() { let players = gen_players(1..10); - let settings = GameSettings::default(); + let mut settings = GameSettings::default(); + for _ in 0..8 { + settings.new_slot(RoleTitle::Villager); + } let mut game = Game::new(&players, settings).unwrap(); let resp = game.process(HostGameMessage::GetState).unwrap(); assert_eq!( @@ -225,6 +241,9 @@ fn no_wolf_kill_n1() { let mut settings = GameSettings::default(); settings.new_slot(RoleTitle::Shapeshifter); settings.new_slot(RoleTitle::Protector); + for _ in 0..7 { + settings.new_slot(RoleTitle::Villager); + } if let Some(slot) = settings .slots() .iter() @@ -266,7 +285,10 @@ fn no_wolf_kill_n1() { #[test] fn yes_wolf_kill_n2() { let players = gen_players(1..10); - let settings = GameSettings::default(); + let mut settings = GameSettings::default(); + for _ in 0..8 { + settings.new_slot(RoleTitle::Villager); + } let mut game = Game::new(&players, settings).unwrap(); assert_eq!( game.process(HostGameMessage::Night(HostNightMessage::ActionResponse( @@ -350,6 +372,9 @@ fn protect_stops_shapeshift() { let mut settings = GameSettings::default(); settings.new_slot(RoleTitle::Shapeshifter); settings.new_slot(RoleTitle::Protector); + for _ in 0..7 { + settings.new_slot(RoleTitle::Villager); + } if let Some(slot) = settings .slots() .iter() @@ -496,6 +521,9 @@ fn wolfpack_kill_all_targets_valid() { init_log(); let players = gen_players(1..10); let mut settings = GameSettings::default(); + for _ in 0..8 { + settings.new_slot(RoleTitle::Villager); + } settings.new_slot(RoleTitle::Shapeshifter); if let Some(slot) = settings .slots() @@ -569,6 +597,9 @@ fn only_1_shapeshift_prompt_if_first_shifts() { let mut settings = GameSettings::default(); settings.new_slot(RoleTitle::Shapeshifter); settings.new_slot(RoleTitle::Shapeshifter); + for _ in 0..7 { + settings.new_slot(RoleTitle::Villager); + } if let Some(slot) = settings .slots() .iter() @@ -614,3 +645,173 @@ fn only_1_shapeshift_prompt_if_first_shifts() { game.next_expect_day(); } + +#[test] +fn redeemed_scapegoat_role_changes() { + let players = gen_players(1..10); + let scapegoat_player_id = players[0].player_id.clone(); + let seer_player_id = players[1].player_id.clone(); + let wolf_player_id = players[2].player_id.clone(); + let wolf_target_2_player_id = players[3].player_id.clone(); + let mut settings = GameSettings::default(); + { + let scapegoat_slot = settings.new_slot(RoleTitle::Scapegoat); + let mut scapegoat_slot = settings + .slots() + .iter() + .find(|s| s.slot_id == scapegoat_slot) + .unwrap() + .clone(); + scapegoat_slot.role = SetupRole::Scapegoat { + redeemed: OrRandom::Determined(true), + }; + scapegoat_slot.assign_to = Some(scapegoat_player_id.clone()); + settings.update_slot(scapegoat_slot); + } + { + let mut slot = settings + .slots() + .iter() + .find(|s| matches!(s.role, SetupRole::Werewolf)) + .unwrap() + .clone(); + slot.assign_to = Some(wolf_player_id.clone()); + settings.update_slot(slot); + } + { + let slot = settings.new_slot(RoleTitle::Seer); + let mut slot = settings + .slots() + .iter() + .find(|s| s.slot_id == slot) + .unwrap() + .clone(); + slot.assign_to = Some(seer_player_id.clone()); + settings.update_slot(slot); + } + for _ in 0..6 { + settings.new_slot(RoleTitle::Villager); + } + let mut game = Game::new(&players, settings).unwrap(); + game.r#continue().r#continue(); + assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro); + game.r#continue().sleep(); + assert_eq!(game.next().title(), ActionPromptTitle::Seer); + let wolf_char_id = game + .village() + .characters() + .into_iter() + .find(|c| c.player_id() == &wolf_player_id) + .unwrap() + .character_id() + .clone(); + game.mark_and_check(&wolf_char_id, |p| match p { + ActionPrompt::Seer { + marked: Some(marked), + .. + } => marked == &wolf_char_id, + _ => false, + }); + assert_eq!(game.r#continue().seer(), Alignment::Wolves); + game.next_expect_day(); + + assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness); + game.r#continue().r#continue(); + assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill); + let seer = game + .village() + .characters() + .into_iter() + .find(|c| c.player_id() == &seer_player_id) + .unwrap() + .character_id() + .clone(); + + game.mark_and_check(&seer, |p| match p { + ActionPrompt::WolfPackKill { + marked: Some(t), .. + } => *t == seer, + _ => false, + }); + game.r#continue().sleep(); + + assert_eq!(game.next().title(), ActionPromptTitle::Seer); + game.mark_and_check(&wolf_char_id, |p| match p { + ActionPrompt::Seer { + marked: Some(marked), + .. + } => marked == &wolf_char_id, + _ => false, + }); + assert_eq!(game.r#continue().seer(), Alignment::Wolves); + game.next_expect_day(); + + assert_eq!( + *game + .village() + .character_by_id(&seer) + .unwrap() + .died_to() + .unwrap(), + DiedTo::Wolfpack { + killing_wolf: wolf_char_id.clone(), + night: NonZero::new(1).unwrap() + } + ); + assert_eq!(game.execute().title(), ActionPromptTitle::CoverOfDarkness); + game.r#continue().r#continue(); + + assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill); + let wolf_target_2 = game + .village() + .characters() + .iter() + .find(|c| c.player_id() == &wolf_target_2_player_id) + .unwrap() + .character_id() + .clone(); + game.mark_and_check(&wolf_target_2, |r| match r { + ActionPrompt::WolfPackKill { + marked: Some(marked), + .. + } => marked == &wolf_target_2, + _ => false, + }); + game.r#continue().sleep(); + let scapegoat = game + .village() + .characters() + .into_iter() + .find(|c| c.player_id() == &scapegoat_player_id) + .unwrap() + .clone(); + assert_eq!( + game.next(), + ActionPrompt::RoleChange { + character_id: scapegoat.identity(), + new_role: RoleTitle::Seer + } + ); + game.r#continue().sleep(); + + match game.game_state() { + crate::game::GameState::Night { night } => night + .changes() + .iter() + .find(|c| match c { + NightChange::RoleChange(char, role) => { + char == scapegoat.character_id() && role == &RoleTitle::Seer + } + _ => false, + }) + .expect("no role change"), + _ => unreachable!(), + }; + + game.next_expect_day(); + let day_scapegoat = game + .village() + .character_by_id(scapegoat.character_id()) + .unwrap(); + assert_eq!(day_scapegoat.role().title(), RoleTitle::Seer); +} diff --git a/werewolves-proto/src/message/night.rs b/werewolves-proto/src/message/night.rs index 5148898..d68fc12 100644 --- a/werewolves-proto/src/message/night.rs +++ b/werewolves-proto/src/message/night.rs @@ -121,7 +121,7 @@ pub enum ActionPrompt { } impl ActionPrompt { - pub fn with_mark(&self, mark: CharacterId) -> Result { + pub(crate) fn with_mark(&self, mark: CharacterId) -> Result { let mut prompt = self.clone(); match &mut prompt { ActionPrompt::WolvesIntro { .. } diff --git a/werewolves/index.scss b/werewolves/index.scss index a2f4271..5724957 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -1075,4 +1075,6 @@ input { flex-wrap: wrap; justify-content: space-around; row-gap: 10px; + + font-size: 2em; } diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index a52d88e..ae79f62 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -473,10 +473,36 @@ impl Component for Host { HostMessage::InGame(HostGameMessage::PreviousState), self.send.clone(), ); + let client_click = Callback::from(|_| { + if let Some(loc) = gloo::utils::document().location() { + let _ = loc.replace("/"); + } + }); + let screen = if self.big_screen { + let to_normal = Callback::from(|_| { + if let Some(loc) = gloo::utils::document().location() { + let _ = loc.replace("/host"); + } + }); + html! { + + } + } else { + let to_big = Callback::from(|_| { + if let Some(loc) = gloo::utils::document().location() { + let _ = loc.replace("/host/big"); + } + }); + html! { + + } + }; html! { } }); diff --git a/werewolves/src/components/client/nav.rs b/werewolves/src/components/client/nav.rs index d9d020d..2cb0ec3 100644 --- a/werewolves/src/components/client/nav.rs +++ b/werewolves/src/components/client/nav.rs @@ -140,19 +140,32 @@ pub fn ClientNav(ClientNavProps { message_callback }: &ClientNavProps) -> Html { } }; - let cb = message_callback.clone(); - let forgor = move |_| { - PlayerId::delete(); - PublicIdentity::delete(); - cb.emit(ClientMessage::Goodbye); - let _ = gloo::utils::window().location().reload(); - }; + let debug = option_env!("DEBUG").map(|_| { + let cb = message_callback.clone(); + let forgor = move |_| { + PlayerId::delete(); + PublicIdentity::delete(); + cb.emit(ClientMessage::Goodbye); + let _ = gloo::utils::window().location().reload(); + }; + let host_click = Callback::from(|_| { + if let Some(loc) = gloo::utils::document().location() { + let _ = loc.replace("/host"); + } + }); + html! { + <> + + + + } + }); html! { } }