diff --git a/werewolves-proto/src/character.rs b/werewolves-proto/src/character.rs index 418ad3e..b609024 100644 --- a/werewolves-proto/src/character.rs +++ b/werewolves-proto/src/character.rs @@ -250,7 +250,7 @@ impl Character { .is_empty() .not() .then_some(ActionPrompt::MasonsWake { - leader: self.character_id(), + leader: self.identity(), masons: recruits.clone(), }) .into_iter() @@ -396,9 +396,20 @@ impl Character { living_villagers: village.living_players_excluding(self.character_id()), marked: None, }, - Role::DireWolf => ActionPrompt::DireWolf { + Role::DireWolf { + last_blocked: Some(last_blocked), + } => ActionPrompt::DireWolf { character_id: self.identity(), - living_players: village.living_players(), + living_players: village + .living_players_excluding(self.character_id()) + .into_iter() + .filter(|c| c.character_id != *last_blocked) + .collect(), + marked: None, + }, + Role::DireWolf { .. } => ActionPrompt::DireWolf { + character_id: self.identity(), + living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter { @@ -718,6 +729,28 @@ impl Character { } } + pub const fn direwolf<'a>(&'a self) -> Result> { + let title = self.role.title(); + match &self.role { + Role::DireWolf { last_blocked } => Ok(Direwolf(last_blocked)), + _ => Err(GameError::InvalidRole { + expected: RoleTitle::DireWolf, + got: title, + }), + } + } + + pub const fn direwolf_mut<'a>(&'a mut self) -> Result> { + let title = self.role.title(); + match &mut self.role { + Role::DireWolf { last_blocked } => Ok(DirewolfMut(last_blocked)), + _ => Err(GameError::InvalidRole { + expected: RoleTitle::DireWolf, + got: title, + }), + } + } + pub const fn initial_shown_role(&self) -> RoleTitle { self.role.initial_shown_role() } @@ -758,6 +791,7 @@ decl_ref_and_mut!( Empath, EmpathMut: bool; BlackKnight, BlackKnightMut: Option; Guardian, GuardianMut: Option; + Direwolf, DirewolfMut: Option; ); pub struct BlackKnightKill<'a> { diff --git a/werewolves-proto/src/game/settings/settings_role.rs b/werewolves-proto/src/game/settings/settings_role.rs index f923d17..5149e4b 100644 --- a/werewolves-proto/src/game/settings/settings_role.rs +++ b/werewolves-proto/src/game/settings/settings_role.rs @@ -169,7 +169,7 @@ impl SetupRoleTitle { }, SetupRoleTitle::Werewolf => Role::Werewolf, SetupRoleTitle::AlphaWolf => Role::AlphaWolf { killed: None }, - SetupRoleTitle::DireWolf => Role::DireWolf, + SetupRoleTitle::DireWolf => Role::DireWolf { last_blocked: None }, SetupRoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None }, SetupRoleTitle::Adjudicator => Role::Adjudicator, SetupRoleTitle::PowerSeer => Role::PowerSeer, @@ -269,7 +269,7 @@ impl SetupRole { }, SetupRole::Werewolf => Role::Werewolf, SetupRole::AlphaWolf => Role::AlphaWolf { killed: None }, - SetupRole::DireWolf => Role::DireWolf, + SetupRole::DireWolf => Role::DireWolf { last_blocked: None }, SetupRole::Shapeshifter => Role::Shapeshifter { shifted_into: None }, SetupRole::MasonLeader { recruits_available } => Role::MasonLeader { recruits_available: recruits_available.get(), diff --git a/werewolves-proto/src/game/story.rs b/werewolves-proto/src/game/story.rs index 39308c4..d433796 100644 --- a/werewolves-proto/src/game/story.rs +++ b/werewolves-proto/src/game/story.rs @@ -292,7 +292,7 @@ impl StoryActionPrompt { chosen: marked, }, ActionPrompt::MasonsWake { leader, masons } => Self::MasonsWake { - leader, + leader: leader.character_id, masons: masons.into_iter().map(|c| c.character_id).collect(), }, ActionPrompt::MasonLeaderRecruit { @@ -413,6 +413,19 @@ impl GameStory { Ok(()) } + pub fn final_village(&self) -> Result { + let mut village = self.starting_village.clone(); + for (_, actions) in self.iter() { + village = match actions { + GameActions::DayDetails(day_details) => village.with_day_changes(day_details)?, + GameActions::NightDetails(night_details) => { + village.with_night_changes(&night_details.changes)? + } + }; + } + Ok(village) + } + pub fn village_at(&self, at_time: GameTime) -> Result> { let mut village = self.starting_village.clone(); for (time, actions) in self.iter() { diff --git a/werewolves-proto/src/game/village.rs b/werewolves-proto/src/game/village.rs index 85faf3d..2785147 100644 --- a/werewolves-proto/src/game/village.rs +++ b/werewolves-proto/src/game/village.rs @@ -299,7 +299,7 @@ impl RoleTitle { }, RoleTitle::Werewolf => Role::Werewolf, RoleTitle::AlphaWolf => Role::AlphaWolf { killed: None }, - RoleTitle::DireWolf => Role::DireWolf, + RoleTitle::DireWolf => Role::DireWolf { last_blocked: None }, RoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None }, RoleTitle::Protector => Role::Protector { last_protected: None, diff --git a/werewolves-proto/src/game/village/apply.rs b/werewolves-proto/src/game/village/apply.rs index 871ecb8..a070320 100644 --- a/werewolves-proto/src/game/village/apply.rs +++ b/werewolves-proto/src/game/village/apply.rs @@ -9,7 +9,7 @@ use crate::{ story::DayDetail, }, player::Protection, - role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleTitle}, + role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleBlock, RoleTitle}, }; type Result = core::result::Result; @@ -100,7 +100,18 @@ impl Village { }); } - NightChange::RoleBlock { .. } | NightChange::Protection { .. } => {} + NightChange::RoleBlock { + source, + target, + block_type: RoleBlock::Direwolf, + } => { + new_village + .character_by_id_mut(*source)? + .direwolf_mut()? + .replace(*target); + } + + NightChange::Protection { .. } => {} NightChange::MasonRecruit { mason_leader, recruiting, diff --git a/werewolves-proto/src/game_test/mod.rs b/werewolves-proto/src/game_test/mod.rs index a3668c8..dfa865d 100644 --- a/werewolves-proto/src/game_test/mod.rs +++ b/werewolves-proto/src/game_test/mod.rs @@ -217,14 +217,14 @@ impl ActionResultExt for ActionResult { fn arcanist(&self) -> bool { match self { ActionResult::Arcanist(same) => same.same(), - _ => panic!("expected an arcanist result"), + resp => panic!("expected an arcanist result, got {resp:?}"), } } fn insomniac(&self) -> Visits { match self { ActionResult::Insomniac(v) => v.clone(), - _ => panic!("expected an insomniac result"), + resp => panic!("expected an insomniac result, got {resp:?}"), } } } @@ -298,9 +298,13 @@ pub trait GameExt { #[allow(unused)] fn get_state(&mut self) -> ServerToHostMessage; fn next_expect_game_over(&mut self) -> GameOver; + fn prev(&mut self) -> ServerToHostMessage; } impl GameExt for Game { + fn prev(&mut self) -> ServerToHostMessage { + self.process(HostGameMessage::PreviousState).unwrap() + } fn next_expect_game_over(&mut self) -> GameOver { match self .process(HostGameMessage::Night( diff --git a/werewolves-proto/src/game_test/previous.rs b/werewolves-proto/src/game_test/previous.rs index dda96c1..ccf4e12 100644 --- a/werewolves-proto/src/game_test/previous.rs +++ b/werewolves-proto/src/game_test/previous.rs @@ -1,13 +1,20 @@ +use core::num::NonZeroU8; + #[allow(unused)] use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; use crate::{ - game::{Game, GameSettings, GameState, SetupRole}, - game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players}, + diedto::DiedToTitle, + game::{Game, GameSettings, GameState, OrRandom, SetupRole}, + game_test::{ + ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players, init_log, + }, message::{ + Identification, PublicIdentity, host::ServerToHostMessage, night::{ActionPrompt, ActionPromptTitle, ActionResponse}, }, + player::PlayerId, role::RoleTitle, }; @@ -125,3 +132,258 @@ fn previous_shapeshifter_undone_and_changed_to_no() { &[] ); } + +#[test] +fn previous_prompt() { + init_log(); + 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::>(); + 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, + ) = ( + (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()), + ); + 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.fill_remaining_slots_with_villagers(players.len()); + + 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 mut 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.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.next().title().adjudicator(); + game.mark(game.character_by_player_id(werewolf).character_id()); + game.r#continue().adjudicator(); + + game.next().title().power_seer(); + match game.prev() { + ServerToHostMessage::ActionPrompt( + ActionPrompt::Adjudicator { + character_id, + marked: Some(mark), + .. + }, + _, + ) => { + assert_eq!( + character_id, + game.character_by_player_id(adjudicator).identity() + ); + assert_eq!(mark, game.character_by_player_id(werewolf).character_id()); + } + resp => panic!("expected adjudicator prompt, got {resp:?}"), + } + match game.prev() { + ServerToHostMessage::ActionPrompt( + ActionPrompt::Arcanist { + character_id, + marked: (Some(mark1), Some(mark2)), + .. + }, + _, + ) => { + assert_eq!( + character_id, + game.character_by_player_id(arcanist).identity() + ); + assert_eq!(mark1, game.character_by_player_id(seer).character_id()); + assert_eq!(mark2, game.character_by_player_id(werewolf).character_id()); + } + resp => panic!("expected arcanist prompt, got {resp:?}"), + } + game.r#continue().role_blocked(); + game.next().title().adjudicator(); + game.r#continue().adjudicator(); + + game.next().title().power_seer(); + game.mark(game.character_by_player_id(werewolf).character_id()); + game.r#continue().power_seer(); + + game.next().title().beholder(); + game.mark(game.character_by_player_id(arcanist).character_id()); + game.r#continue().role_blocked(); + + 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.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.next().title().adjudicator(); + game.mark(game.character_by_player_id(seer).character_id()); + game.r#continue().adjudicator(); + + game.next().title().power_seer(); + game.mark(game.living_villager().character_id()); + game.r#continue().power_seer(); + + 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.next().title().mortician(); + game.mark(game.character_by_player_id(dire_wolf).character_id()); + assert_eq!(game.r#continue().mortician(), DiedToTitle::Execution); + + game.next().title().empath(); + game.mark(game.living_villager().character_id()); + assert!(!game.r#continue().empath()); + + 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.next().title().beholder(); + game.mark(game.character_by_player_id(power_seer).character_id()); + game.r#continue().power_seer(); + + game.next_expect_day(); +} diff --git a/werewolves-proto/src/game_test/role/mason.rs b/werewolves-proto/src/game_test/role/mason.rs index 94a7bf7..5647847 100644 --- a/werewolves-proto/src/game_test/role/mason.rs +++ b/werewolves-proto/src/game_test/role/mason.rs @@ -47,7 +47,7 @@ fn recruits_decrement() { ActionPrompt::MasonsWake { leader: game .character_by_player_id(mason_leader_player_id) - .character_id(), + .identity(), masons: Box::new([ game.character_by_player_id(mason_leader_player_id) .identity(), @@ -105,7 +105,7 @@ fn dies_recruiting_wolf() { ActionPrompt::MasonsWake { leader: game .character_by_player_id(mason_leader_player_id) - .character_id(), + .identity(), masons: Box::new([game .character_by_player_id(mason_leader_player_id) .identity()]) @@ -165,7 +165,7 @@ fn masons_wake_even_if_leader_died() { ActionPrompt::MasonsWake { leader: game .character_by_player_id(mason_leader_player_id) - .character_id(), + .identity(), masons: Box::new([ game.character_by_player_id(mason_leader_player_id) .identity(), @@ -192,7 +192,7 @@ fn masons_wake_even_if_leader_died() { ActionPrompt::MasonsWake { leader: game .character_by_player_id(mason_leader_player_id) - .character_id(), + .identity(), masons: Box::new([ game.character_by_player_id(black_knight_player_id) .identity(), @@ -220,7 +220,7 @@ fn masons_wake_even_if_leader_died() { ActionPrompt::MasonsWake { leader: game .character_by_player_id(mason_leader_player_id) - .character_id(), + .identity(), masons: Box::new([ game.character_by_player_id(black_knight_player_id) .identity(), diff --git a/werewolves-proto/src/message/night.rs b/werewolves-proto/src/message/night.rs index cba53be..1fefd79 100644 --- a/werewolves-proto/src/message/night.rs +++ b/werewolves-proto/src/message/night.rs @@ -137,7 +137,7 @@ pub enum ActionPrompt { }, #[checks(ActionType::MasonsWake)] MasonsWake { - leader: CharacterId, + leader: CharacterIdentity, masons: Box<[CharacterIdentity]>, }, #[checks(ActionType::MasonRecruit)] diff --git a/werewolves-proto/src/role.rs b/werewolves-proto/src/role.rs index e0cd2f9..5580efb 100644 --- a/werewolves-proto/src/role.rs +++ b/werewolves-proto/src/role.rs @@ -248,7 +248,7 @@ pub enum Role { #[checks(Killer::Killer)] #[checks(Powerful::Powerful)] #[checks("wolf")] - DireWolf, + DireWolf { last_blocked: Option }, #[checks(Alignment::Wolves)] #[checks(Killer::Killer)] #[checks(Powerful::Powerful)] @@ -299,7 +299,7 @@ impl Role { Role::Werewolf => KillingWolfOrder::Werewolf, Role::AlphaWolf { .. } => KillingWolfOrder::AlphaWolf, - Role::DireWolf => KillingWolfOrder::DireWolf, + Role::DireWolf { .. } => KillingWolfOrder::DireWolf, Role::Shapeshifter { .. } => KillingWolfOrder::Shapeshifter, Role::LoneWolf => KillingWolfOrder::LoneWolf, }) @@ -311,7 +311,7 @@ impl Role { | Role::PowerSeer | Role::Beholder | Role::Adjudicator - | Role::DireWolf + | Role::DireWolf { .. } | Role::Arcanist | Role::Seer => true, @@ -377,7 +377,7 @@ impl Role { | Role::Adjudicator | Role::Scapegoat { redeemed: true } | Role::Shapeshifter { .. } - | Role::DireWolf + | Role::DireWolf { .. } | Role::AlphaWolf { killed: None } | Role::Arcanist | Role::Protector { .. } diff --git a/werewolves/img/diseased.svg b/werewolves/img/diseased.svg new file mode 100644 index 0000000..e082975 --- /dev/null +++ b/werewolves/img/diseased.svg @@ -0,0 +1,80 @@ + + + + diff --git a/werewolves/img/gravedigger.svg b/werewolves/img/gravedigger.svg index a2a5937..c7e307b 100644 --- a/werewolves/img/gravedigger.svg +++ b/werewolves/img/gravedigger.svg @@ -2,9 +2,9 @@ + transform="translate(-94.492686,-224.65327)"> diff --git a/werewolves/img/heart.svg b/werewolves/img/heart.svg index 4ee9d84..39effc2 100644 --- a/werewolves/img/heart.svg +++ b/werewolves/img/heart.svg @@ -2,9 +2,9 @@ + d="m 73.525842,205.79755 c -1.802133,1.72521 -2.255654,-1.4788 -4.749977,-1.5277 -2.4873,-0.0488 -3.071191,3.13767 -4.791545,1.34061 -1.725212,-1.80213 1.478793,-2.25565 1.527701,-4.74998 0.04877,-2.4873 -3.137671,-3.07119 -1.340613,-4.79154 1.802133,-1.72521 2.255655,1.47879 4.749978,1.5277 2.487299,0.0488 3.07119,-3.13767 4.791545,-1.34061 1.725212,1.80213 -1.478794,2.25565 -1.527702,4.74997 -0.04877,2.4873 3.137671,3.07119 1.340613,4.79155 z" /> diff --git a/werewolves/img/hunter.svg b/werewolves/img/hunter.svg index efb905b..1b11b6c 100644 --- a/werewolves/img/hunter.svg +++ b/werewolves/img/hunter.svg @@ -23,16 +23,29 @@ inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" - showgrid="true" - inkscape:zoom="0.5" - inkscape:cx="-77" - inkscape:cy="466" + showgrid="false" + inkscape:zoom="2.8284272" + inkscape:cx="35.885668" + inkscape:cy="411.71291" inkscape:window-width="1918" - inkscape:window-height="1061" + inkscape:window-height="1042" inkscape:window-x="0" inkscape:window-y="17" inkscape:window-maximized="0" - inkscape:current-layer="layer4"> + id="g166-2" + inkscape:path-effect="#path-effect166-2" + style="fill:#ff0707;fill-opacity:0.697154;stroke:#c10000;stroke-width:0.4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.728,0,0,1.728,-72.928813,-210.62831)" + inkscape:export-filename="hunter.svg" + inkscape:export-xdpi="900.08" + inkscape:export-ydpi="900.08"> diff --git a/werewolves/img/icons.svg b/werewolves/img/icons.svg new file mode 100644 index 0000000..3659807 --- /dev/null +++ b/werewolves/img/icons.svg @@ -0,0 +1,1458 @@ + + + + diff --git a/werewolves/img/mortician.svg b/werewolves/img/mortician.svg new file mode 100644 index 0000000..3c80c1a --- /dev/null +++ b/werewolves/img/mortician.svg @@ -0,0 +1,87 @@ + + + + diff --git a/werewolves/img/pyremaster.svg b/werewolves/img/pyremaster.svg new file mode 100644 index 0000000..56bdd45 --- /dev/null +++ b/werewolves/img/pyremaster.svg @@ -0,0 +1,95 @@ + + + + diff --git a/werewolves/img/roleblock.svg b/werewolves/img/roleblock.svg new file mode 100644 index 0000000..e43846b --- /dev/null +++ b/werewolves/img/roleblock.svg @@ -0,0 +1,77 @@ + + + + diff --git a/werewolves/img/scapegoat.svg b/werewolves/img/scapegoat.svg new file mode 100644 index 0000000..d2e1fc7 --- /dev/null +++ b/werewolves/img/scapegoat.svg @@ -0,0 +1,68 @@ + + + + diff --git a/werewolves/img/skull.svg b/werewolves/img/skull.svg index b0598e9..7a5fc3a 100644 --- a/werewolves/img/skull.svg +++ b/werewolves/img/skull.svg @@ -2,9 +2,9 @@ + inkscape:export-ydpi="900.08" /> diff --git a/werewolves/img/sword.svg b/werewolves/img/sword.svg new file mode 100644 index 0000000..ee1054a --- /dev/null +++ b/werewolves/img/sword.svg @@ -0,0 +1,80 @@ + + + + diff --git a/werewolves/index.scss b/werewolves/index.scss index 00cb29d..3774bf3 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -25,6 +25,13 @@ $defensive_border_faint: color.change($defensive_border, $alpha: 0.3); $intel_border_faint: color.change($intel_border, $alpha: 0.3); $starts_as_villager_border_faint: color.change($starts_as_villager_border, $alpha: 0.3); +$wolves_color_faint: color.change($wolves_color, $alpha: 0.1); +$village_color_faint: color.change($village_color, $alpha: 0.1); +$offensive_color_faint: color.change($offensive_color, $alpha: 0.1); +$defensive_color_faint: color.change($defensive_color, $alpha: 0.1); +$intel_color_faint: color.change($intel_color, $alpha: 0.1); +$starts_as_villager_color_faint: color.change($starts_as_villager_color, $alpha: 0.1); + @mixin flexbox() { display: -webkit-box; @@ -353,9 +360,26 @@ button { .character { text-align: center; border: 3px solid rgba(0, 0, 0, 0.4); + // min-width: 20%; + flex-shrink: 1; .role { - font-size: 2rem; + font-size: 1.5rem; + + } + + &.wolves { + padding-top: 20px; + padding-bottom: 20px; + + &>.role { + margin: 0; + } + + display: flex; + flex-direction: column; + justify-content: center; + min-width: 15vw; } } @@ -805,16 +829,23 @@ error { } .binary { + margin: 0; + padding: 0; + .button-container { - background-color: $village_color; - border: 3px solid darken($village_color, 20%); text-align: center; padding: 0; margin: 0; display: flex; flex: 1 1 0; + gap: 20px; + margin-top: 20px; + justify-content: space-around; + width: 100%; button { + background-color: $wolves_color_faint; + border: 3px solid $wolves_border_faint; font-size: 3rem; font-weight: bold; align-self: center; @@ -822,8 +853,14 @@ error { width: 100%; height: 100%; margin: 0; + + &:hover { + background-color: $wolves_border_faint; + color: white; + } } } + } input { @@ -911,6 +948,7 @@ input { width: 100%; align-items: center; color: white; + height: 90vh; $marked_bg: color.change($wolves_color, $alpha: 0.3); $marked_border: color.change($wolves_color, $alpha: 1.0); $village_bg: color.change($village_color, $alpha: 0.3); @@ -921,6 +959,9 @@ input { flex-direction: row; flex-wrap: wrap; justify-content: space-evenly; + align-items: center; + flex-grow: 1; + align-content: center; } .character { @@ -1037,6 +1078,11 @@ input { color: white; background-color: $village_border; } + + &.faint { + border: 1px solid $village_border_faint; + background-color: $village_color_faint; + } } .wolves { @@ -1050,6 +1096,7 @@ input { &.faint { border: 1px solid $wolves_border_faint; + background-color: $wolves_color_faint; } } @@ -1064,6 +1111,7 @@ input { &.faint { border: 1px solid $intel_border_faint; + background-color: $intel_color_faint; } } @@ -1078,6 +1126,7 @@ input { &.faint { border: 1px solid $defensive_border_faint; + background-color: $defensive_color_faint; } } @@ -1092,6 +1141,7 @@ input { &.faint { border: 1px solid $offensive_border_faint; + background-color: $offensive_color_faint; } } @@ -1106,6 +1156,7 @@ input { &.faint { border: 1px solid $starts_as_villager_border_faint; + background-color: $starts_as_villager_color_faint; } } @@ -1330,6 +1381,8 @@ input { display: flex; flex-direction: column; flex-wrap: nowrap; + align-items: center; + width: 100%; } .page { @@ -1339,9 +1392,6 @@ input { align-items: center; width: 100%; height: 100%; - // width: ; - - & .next {} } .signin { @@ -1374,9 +1424,17 @@ input { .story { - user-select: text; + .cast { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + } .time-period { + user-select: text; + .day { display: flex; flex-direction: column; @@ -1462,30 +1520,6 @@ input { border: 1px solid rgba(255, 255, 255, 0.3); } - - &.wolves { - background-color: color.change($wolves_color, $alpha: 0.1); - } - - &.intel { - background-color: color.change($intel_color, $alpha: 0.1); - } - - &.defensive { - background-color: color.change($defensive_color, $alpha: 0.1); - } - - &.offensive { - background-color: color.change($offensive_color, $alpha: 0.1); - } - - &.village { - background-color: color.change($village_color, $alpha: 0.1); - } - - &.starts-as-villager { - background-color: color.change($starts_as_villager_color, $alpha: 0.1); - } } .alignment-eq { @@ -1513,6 +1547,10 @@ input { justify-items: baseline; gap: 5px; + .number { + color: rgba(255, 255, 0, 0.7); + } + .role { display: flex; flex-direction: row; @@ -1595,3 +1633,58 @@ input { flex-wrap: nowrap; width: 100%; } + + +.role-page { + width: 100%; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: center; + + h1 { + text-align: center; + // align-self: flex-start; + } + + .information { + font-size: 1.2em; + padding-left: 5%; + padding-right: 5%; + } + + .yellow { + color: yellow; + } + + .red { + color: red; + } +} + +.icons { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + gap: 30px; +} + +.icon-info { + flex-shrink: 1; +} + +.info-player-list { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + width: 100%; + justify-content: center; + gap: 10px; + + &.masons { + font-size: 2em; + } +} diff --git a/werewolves/src/clients/host/story_test.rs b/werewolves/src/clients/host/story_test.rs index 811b82e..4fd0221 100644 --- a/werewolves/src/clients/host/story_test.rs +++ b/werewolves/src/clients/host/story_test.rs @@ -59,6 +59,7 @@ pub fn test_story() -> GameStory { empath, scapegoat, hunter, + diseased, ) = ( (SetupRole::Werewolf, players_iter.next().unwrap()), (SetupRole::DireWolf, players_iter.next().unwrap()), @@ -83,6 +84,7 @@ pub fn test_story() -> GameStory { 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); @@ -103,6 +105,7 @@ pub fn test_story() -> GameStory { 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()); let ( diff --git a/werewolves/src/components/action/binary.rs b/werewolves/src/components/action/binary.rs index 0c6aa68..48cfa34 100644 --- a/werewolves/src/components/action/binary.rs +++ b/werewolves/src/components/action/binary.rs @@ -27,7 +27,7 @@ pub fn BinaryChoice( html! {
{children.clone()} -
+
diff --git a/werewolves/src/components/action/picker.rs b/werewolves/src/components/action/picker.rs index 726fc5d..a9c2fae 100644 --- a/werewolves/src/components/action/picker.rs +++ b/werewolves/src/components/action/picker.rs @@ -1,10 +1,7 @@ -use werewolves_proto::{ - character::CharacterId, - message::{CharacterIdentity, PublicIdentity}, -}; +use werewolves_proto::{character::CharacterId, message::CharacterIdentity}; use yew::prelude::*; -use crate::components::{Button, Identity}; +use crate::components::{Button, CharacterTargetCard}; #[derive(Debug, Clone, PartialEq, Properties)] pub struct TargetPickerProps { @@ -72,10 +69,9 @@ pub fn TargetCard( .map(|cb| Callback::from(move |_| cb.emit(click_target))) .unwrap_or_default(); let marked = marked.then_some("marked"); - let ident: PublicIdentity = target.into(); html! { } } diff --git a/werewolves/src/components/action/prompt.rs b/werewolves/src/components/action/prompt.rs index c70e26a..93f6356 100644 --- a/werewolves/src/components/action/prompt.rs +++ b/werewolves/src/components/action/prompt.rs @@ -12,9 +12,12 @@ use werewolves_proto::{ }; use yew::prelude::*; -use crate::components::{ - Button, CoverOfDarkness, Identity, - action::{BinaryChoice, TargetPicker, WolvesIntro}, +use crate::{ + components::{ + Button, CoverOfDarkness, Identity, + action::{BinaryChoice, TargetPicker, WolvesIntro}, + }, + pages::MasonsWake, }; #[derive(Debug, Clone, PartialEq, Properties)] @@ -109,58 +112,27 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { /> }; } - ActionPrompt::ElderReveal { character_id } => { - return html! { -
- {identity_html(props, Some(character_id))} -

{"you are the elder"}

- {cont} -
- }; + ActionPrompt::MasonsWake { .. } => { + props + .on_complete + .emit(HostMessage::InGame(HostGameMessage::Night( + HostNightMessage::ActionResponse(ActionResponse::Continue), + ))); + props + .on_complete + .emit(HostMessage::InGame(HostGameMessage::Night( + HostNightMessage::Next, + ))); + props.on_complete.emit(HostMessage::GetState); + return html! {}; } - ActionPrompt::Insomniac { character_id } => { - return html! { -
- {identity_html(props, Some(character_id))} -

{"you are the insomniac"}

- {cont} -
- }; - } - ActionPrompt::RoleChange { - character_id, - new_role, - } => { - return html! { -
- {identity_html(props, Some(character_id))} -

{"your role has changed"}

-

{new_role.to_string()}

- {cont} -
- }; - } - - ActionPrompt::MasonsWake { leader, masons } => { - let masons = masons - .into_iter() - .map(|c| { - let leader = (c.character_id == *leader).then_some("leader"); - let ident: PublicIdentity = c.into(); - html! { - - } - }) - .collect::(); - return html! { -
-

{"these are the masons"}

-
- {masons} -
- {cont} -
- }; + ActionPrompt::RoleChange { .. } + | ActionPrompt::ElderReveal { .. } + | ActionPrompt::Insomniac { .. } => { + if let Some(cb) = continue_callback { + cb.emit(()); + } + return html! {}; } ActionPrompt::Guardian { @@ -408,10 +380,22 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { } }); return html! { -
+
{identity_html(props, Some(character_id))} +

{"SHAPESHIFTER"}

+
+

+ {"WOULD YOU LIKE TO USE YOUR "} + {"ONCE PER GAME"} + {" SHAPESHIFT ABILITY?"} +

+

+ {"YOU WILL DIE"}{", AND THE "} + {"TARGET OF THE WOLFPACK KILL"} + {" SHALL INSTEAD BECOME A WOLF"} +

+
-

{"shapeshift?"}

}; @@ -434,14 +418,16 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { Some(character_id), living_players, marked.iter().cloned().collect(), - html! {{"dire wolf"}}, + html! {{"direwolf"}}, ), }; - + let role_info = props.big_screen.not().then_some(html! { +

{role_info}

+ }); html! {
{identity_html(props, character_id)} -

{role_info}

+ {role_info} Html { />
} - - // match &props.prompt { - // ActionPrompt::CoverOfDarkness => { - // let on_complete = props.on_complete.clone(); - // let next = props.big_screen.not().then(|| { - // Callback::from(move |_| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::ClearCoverOfDarkness), - // ))) - // }) - // }); - // return html! { - // - // }; - // } - // ActionPrompt::WolvesIntro { wolves } => { - // let on_complete = props.on_complete.clone(); - // let on_complete = Callback::from(move |_| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse( - // werewolves_proto::message::night::ActionResponse::WolvesIntroAck, - // ), - // ))) - // }); - // html! { - // - // } - // } - // ActionPrompt::Seer { - // character_id, - // living_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: CharacterId| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Seer(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::RoleChange { - // character_id, - // new_role, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_click = Callback::from(move |_| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::RoleChangeAck), - // ))) - // }); - // let cont = props.big_screen.not().then(|| { - // html! { - // - // } - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - //

{"your role has changed"}

- //

{new_role.to_string()}

- // {cont} - //
- // } - // } - // ActionPrompt::Protector { - // character_id, - // targets, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: CharacterId| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Protector(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::Arcanist { - // character_id, - // living_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |(t1, t2): (CharacterId, CharacterId)| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Arcanist(t1, t2)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::Gravedigger { - // character_id, - // dead_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: CharacterId| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Gravedigger(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::Hunter { - // character_id, - // current_target, - // living_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: CharacterId| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Hunter(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //

- // {"current target: "}{current_target.clone().map(|t| html!{ - // ::into(t)} /> - // }).unwrap_or_else(|| html!{{"none"}})} - //

- //
- //
- // } - // } - // ActionPrompt::Militia { - // character_id, - // living_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: Option| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Militia(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::MapleWolf { - // character_id, - // kill_or_die, - // living_players, - // marked, - // } => { - // let kill_or_die = kill_or_die.then(|| { - // html! { - // {"if you fail to eat tonight, you will starve"} - // } - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - // {kill_or_die} - // - //
- // } - // } - // ActionPrompt::Guardian { - // character_id, - // previous, - // living_players, - // marked, - // } => { - // let last_protect = previous.as_ref().map(|prev| match prev { - // PreviousGuardianAction::Protect(target) => { - // html! { - // <> - // {"last night you protected: "} - // ::into(target)}/> - // - // } - // } - // PreviousGuardianAction::Guard(target) => html! { - // <> - // {"last night you guarded: "} - // ::into(target)}/> - // - // }, - // }); - // let marked = marked.iter().cloned().collect(); - - // html! { - //
- // {identity_html(props, Some(&character_id))} - //

{"guardian"}

- // {last_protect} - // - //
- // } - // } - // ActionPrompt::WolfPackKill { living_villagers } => { - // let on_complete = props.on_complete.clone(); - - // html! { - // - // } - // } - // ActionPrompt::Shapeshifter { character_id } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then_some({ - // move |shift| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Shapeshifter(shift)), - // ))); - // } - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //

{"shapeshift?"}

- //
- //
- // } - // } - // ActionPrompt::AlphaWolf { - // character_id, - // living_villagers, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: Option| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::AlphaWolf(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // ActionPrompt::DireWolf { - // character_id, - // living_players, - // } => { - // let on_complete = props.on_complete.clone(); - // let on_select = props.big_screen.not().then(|| { - // Callback::from(move |target: CharacterId| { - // on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - // HostNightMessage::ActionResponse(ActionResponse::Direwolf(target)), - // ))); - // }) - // }); - // html! { - //
- // {identity_html(props, Some(&character_id))} - // - //
- // } - // } - // } } diff --git a/werewolves/src/components/action/result.rs b/werewolves/src/components/action/result.rs index 4927ec6..3088d15 100644 --- a/werewolves/src/components/action/result.rs +++ b/werewolves/src/components/action/result.rs @@ -1,17 +1,20 @@ use core::ops::Not; use convert_case::{Case, Casing}; -use werewolves_proto::{ - message::{ - PublicIdentity, - host::{HostGameMessage, HostMessage, HostNightMessage}, - night::ActionResult, - }, - role::Alignment, +use werewolves_proto::message::{ + PublicIdentity, + host::{HostGameMessage, HostMessage, HostNightMessage}, + night::ActionResult, }; use yew::prelude::*; -use crate::components::{Button, CoverOfDarkness, Icon, IconSource, Identity}; +use crate::{ + components::{Button, CoverOfDarkness, Icon, IconSource, Identity}, + pages::{ + AdjudicatorResult, ArcanistResult, EmpathResult, GravediggerResultPage, InsomniacResult, + MorticianResultPage, PowerSeerResult, RoleblockPage, SeerResult, + }, +}; #[derive(Debug, Clone, PartialEq, Properties)] pub struct ActionResultProps { @@ -42,128 +45,42 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html { .then(|| html! {}); let body = match &props.result { ActionResult::PowerSeer { powerful } => { - let inactive = powerful.powerful().not().then_some("inactive"); - let text = if powerful.powerful() { - "powerful" - } else { - "not powerful" - }; html! { - <> - -

{text}

- + } } ActionResult::Adjudicator { killer } => { - let text = if killer.killer() { - "is a killer" - } else { - "is NOT a killer" - }; html! { - <> -

{"your target..."}

- -

{text}

- + } } ActionResult::Mortician(died_to) => html! { -

{"cause of death: "}{died_to.to_string().to_case(Case::Title)}

+ }, - ActionResult::Empath { scapegoat: true } => html! { - <> -

{"was the scapegoat!"}

-

{"tag! you're it!"}

- - }, - ActionResult::Empath { scapegoat: false } => html! { -

{"not the scapegoat"}

+ ActionResult::Empath { scapegoat } => html! { + }, ActionResult::Insomniac(visits) => { - let visits = visits - .iter() - .map(|v| { - let ident: PublicIdentity = v.clone().into(); - html! { - - } - }) - .collect::(); html! { - <> -

{"tonight you were visited by..."}

-
- {visits} -
- + } } ActionResult::RoleBlocked => { html! { -

{"you were role blocked"}

+ } } ActionResult::Seer(alignment) => html! { - <> -

{"the alignment was"}

- {match alignment { - Alignment::Village => html!{ - <> - -

{"village"}

- - }, - Alignment::Wolves => html!{ - <> - -

{"wolves"}

- - }, - }} - + }, ActionResult::Arcanist(same) => { - let outcome = if same.same() { - html! { - <> -
- - - - -
-

{"the same"}

- - } - } else { - html! { - <> -
- - -
-

{"different"}

- - } - }; html! { - <> -

{"the alignments are:"}

-

{outcome}

- + } } ActionResult::GraveDigger(role_title) => { - let dig = role_title - .map(|r| r.to_string().to_case(Case::Title)) - .unwrap_or_else(|| String::from("an empty grave")); html! { - <> -

{"you see:"}

-

{dig}

- + } } ActionResult::GoBackToSleep => { diff --git a/werewolves/src/components/action/wolves.rs b/werewolves/src/components/action/wolves.rs index fbe8197..8187804 100644 --- a/werewolves/src/components/action/wolves.rs +++ b/werewolves/src/components/action/wolves.rs @@ -27,7 +27,7 @@ pub fn WolvesIntro(props: &WolvesIntroProps) -> Html {
{ props.wolves.iter().map(|w| html!{ -
+

{w.1.to_string()}

::into(&w.0)} />
diff --git a/werewolves/src/components/character.rs b/werewolves/src/components/character.rs index 0409d57..735dcc1 100644 --- a/werewolves/src/components/character.rs +++ b/werewolves/src/components/character.rs @@ -1,5 +1,5 @@ use convert_case::{Case, Casing}; -use werewolves_proto::{character::Character, game::SetupRole}; +use werewolves_proto::{character::Character, game::SetupRole, message::CharacterIdentity}; use yew::prelude::*; use crate::components::{Icon, IconSource, IconType, PartialAssociatedIcon}; @@ -7,10 +7,14 @@ use crate::components::{Icon, IconSource, IconType, PartialAssociatedIcon}; #[derive(Debug, Clone, PartialEq, Properties)] pub struct CharacterCardProps { pub char: Character, + #[prop_or_default] + pub dead: bool, + #[prop_or_default] + pub faint: bool, } #[function_component] -pub fn CharacterCard(CharacterCardProps { char }: &CharacterCardProps) -> Html { +pub fn CharacterCard(CharacterCardProps { faint, char, dead }: &CharacterCardProps) -> Html { let class = Into::::into(char.role_title()) .category() .class(); @@ -28,10 +32,18 @@ pub fn CharacterCard(CharacterCardProps { char }: &CharacterCardProps) -> Html { IconSource::Wolves }); + let dead = dead.then(|| { + html! { + + } + }); + let faint = faint.then_some("faint"); + html! { - - + +
+ {dead}
{role} @@ -42,3 +54,20 @@ pub fn CharacterCard(CharacterCardProps { char }: &CharacterCardProps) -> Html {
} } + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct CharacterTargetCardProps { + pub ident: CharacterIdentity, +} + +#[function_component] +pub fn CharacterTargetCard(CharacterTargetCardProps { ident }: &CharacterTargetCardProps) -> Html { + let name = ident.name.clone(); + + html! { + + {ident.number.get()} + {name} + + } +} diff --git a/werewolves/src/components/icon.rs b/werewolves/src/components/icon.rs index fac9953..f2b8d08 100644 --- a/werewolves/src/components/icon.rs +++ b/werewolves/src/components/icon.rs @@ -4,43 +4,51 @@ use werewolves_proto::{ }; use yew::prelude::*; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum IconSource { - Village, - Wolves, - Killer, - Powerful, - ListItem, - Skull, - Heart, - Shield, - ShieldAndSword, - Seer, - Hunter, - MapleWolf, - Gravedigger, - PowerSeer, +macro_rules! decl_icon { + ($($name:ident: $path:literal,)*) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum IconSource { + $( + $name, + )* + } + + impl IconSource { + pub const fn source(&self) -> &'static str { + match self { + $( + Self::$name => $path, + )* + } + } + } + }; } +decl_icon!( + Village: "/img/village.svg", + Wolves: "/img/wolf.svg", + Killer: "/img/killer.svg", + Powerful: "/img/powerful.svg", + ListItem: "/img/li.svg", + Skull: "/img/skull.svg", + Heart: "/img/heart.svg", + Shield: "/img/shield.svg", + ShieldAndSword: "/img/shield-and-sword.svg", + Seer: "/img/seer.svg", + Hunter: "/img/hunter.svg", + MapleWolf: "/img/maple-wolf.svg", + Gravedigger: "/img/gravedigger.svg", + PowerSeer: "/img/power-seer.svg", + Scapegoat: "/img/scapegoat.svg", + Diseased: "/img/diseased.svg", + Mortician: "/img/mortician.svg", + Pyremaster: "/img/pyremaster.svg", + Sword: "/img/sword.svg", + Roleblock: "/img/roleblock.svg", +); + impl IconSource { - pub const fn source(&self) -> &'static str { - match self { - IconSource::Village => "/img/village.svg", - IconSource::Wolves => "/img/wolf.svg", - IconSource::Killer => "/img/killer.svg", - IconSource::Powerful => "/img/powerful.svg", - IconSource::ListItem => "/img/li.svg", - IconSource::Skull => "/img/skull.svg", - IconSource::Heart => "/img/heart.svg", - IconSource::Shield => "/img/shield.svg", - IconSource::ShieldAndSword => "/img/shield-and-sword.svg", - IconSource::Seer => "/img/seer.svg", - IconSource::Hunter => "/img/hunter.svg", - IconSource::MapleWolf => "/img/maple-wolf.svg", - IconSource::Gravedigger => "/img/gravedigger.svg", - IconSource::PowerSeer => "/img/power-seer.svg", - } - } pub const fn class(&self) -> Option<&'static str> { match self { IconSource::Killer => Some("killer"), @@ -53,6 +61,7 @@ impl IconSource { #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum IconType { Small, + Informational, #[default] RoleCheck, } @@ -61,6 +70,7 @@ impl IconType { pub const fn class(&self) -> &'static str { match self { IconType::Small => "icon", + IconType::Informational => "icon-info", IconType::RoleCheck => "check-icon", } } @@ -122,27 +132,27 @@ impl AssociatedIcon for Powerful { impl PartialAssociatedIcon for RoleTitle { fn icon(&self) -> Option { Some(match self { - RoleTitle::Scapegoat - | RoleTitle::Arcanist + RoleTitle::Arcanist | RoleTitle::Adjudicator - | RoleTitle::Mortician | RoleTitle::Beholder | RoleTitle::MasonLeader - | RoleTitle::Diseased | RoleTitle::BlackKnight | RoleTitle::Weightlifter - | RoleTitle::PyreMaster - | RoleTitle::Militia | RoleTitle::Apprentice | RoleTitle::Elder | RoleTitle::Insomniac - | RoleTitle::Werewolf | RoleTitle::AlphaWolf | RoleTitle::DireWolf | RoleTitle::Shapeshifter | RoleTitle::LoneWolf | RoleTitle::Villager => return None, + RoleTitle::Werewolf => IconSource::Wolves, + RoleTitle::Militia => IconSource::Sword, + RoleTitle::PyreMaster => IconSource::Pyremaster, + RoleTitle::Mortician => IconSource::Mortician, + RoleTitle::Diseased => IconSource::Diseased, + RoleTitle::Scapegoat => IconSource::Scapegoat, RoleTitle::PowerSeer => IconSource::PowerSeer, RoleTitle::Gravedigger => IconSource::Gravedigger, RoleTitle::MapleWolf => IconSource::MapleWolf, @@ -160,13 +170,13 @@ impl PartialAssociatedIcon for DiedToTitle { match self { DiedToTitle::Execution => Some(IconSource::Skull), DiedToTitle::MapleWolf | DiedToTitle::MapleWolfStarved => Some(IconSource::MapleWolf), - DiedToTitle::Militia => Some(IconSource::Killer), + DiedToTitle::Militia => Some(IconSource::Sword), DiedToTitle::Wolfpack => None, DiedToTitle::AlphaWolf => None, DiedToTitle::Shapeshift => None, DiedToTitle::Hunter => Some(IconSource::Hunter), DiedToTitle::GuardianProtecting => Some(IconSource::ShieldAndSword), - DiedToTitle::PyreMaster => None, + DiedToTitle::PyreMaster => Some(IconSource::Pyremaster), DiedToTitle::PyreMasterLynchMob => None, DiedToTitle::MasonLeaderRecruitFail => None, DiedToTitle::LoneWolf => None, diff --git a/werewolves/src/components/story.rs b/werewolves/src/components/story.rs index baec8ac..6dc656a 100644 --- a/werewolves/src/components/story.rs +++ b/werewolves/src/components/story.rs @@ -29,14 +29,21 @@ pub struct StoryProps { #[function_component] pub fn Story(StoryProps { story }: &StoryProps) -> Html { - let characters = Rc::new( + let final_characters = story - .starting_village + .final_village() + .unwrap_or_else(|_| story.starting_village.clone()) .characters() .into_iter() - .map(|c| (c.character_id(), c)) - .collect::>(), - ); + .map(|c| { + let dead =c.alive().not(); + html! { + <> + + + } + }) + .collect::(); let bits = story .iter() .map(|(time, changes)| { @@ -51,7 +58,12 @@ pub fn Story(StoryProps { story }: &StoryProps) -> Html { }).ok().flatten() .map(|v| Rc::new(v.characters().into_iter() .map(|c| (c.character_id(), c)) - .collect::>())).unwrap_or_else(|| characters.clone()); + .collect::>())) + .unwrap_or_else(|| + Rc::new(story.starting_village + .characters().into_iter() + .map(|c| (c.character_id(), c)) + .collect::>())); let changes = match changes { GameActions::DayDetails(day_changes) => { let execute_list = if day_changes.is_empty() { @@ -66,7 +78,7 @@ pub fn Story(StoryProps { story }: &StoryProps) -> Html { .map(|c| { html! { // - + // } }) @@ -134,6 +146,9 @@ pub fn Story(StoryProps { story }: &StoryProps) -> Html { .collect::(); html! {
+
+ {final_characters} +
{bits}
} @@ -156,9 +171,9 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha html! { <> - + {"is now"} - + } }) @@ -170,7 +185,7 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha html! { <> - + {"died to"} @@ -183,9 +198,9 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha .map(|(source, target)| { html! { <> - + {"role blocked"} - + } }) @@ -196,9 +211,9 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha .map(|(source, into)| { html! { <> - + {"shapeshifted into"} - + } }) @@ -209,7 +224,7 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha .map(|elder| { html! { <> - + {"learned they are the Elder"} } @@ -221,9 +236,9 @@ fn StoryNightChange(StoryNightChangeProps { change, characters }: &StoryNightCha .map(|(empath, scapegoat)| { html! { <> - + {"found the scapegoat in"} - + {"and took on their curse"} } @@ -304,7 +319,7 @@ fn StoryNightResult(StoryNightResultProps { result, characters }: &StoryNightRes .filter_map(|c| characters.get(c)) .map(|c| { html! { - + } }) .collect::(); @@ -353,9 +368,9 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho .map(|(char, chosen)| { html! { <> - + {action} - + } }) @@ -375,11 +390,11 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho .map(|(arcanist, chosen1, chosen2)| { html! { <> - + {"compared"} - + {"and"} - + } }), @@ -389,13 +404,13 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho .filter_map(|m| characters.get(m)) .map(|c| { html! { - + } }) .collect::(); html! { <> - + {"'s masons"} {masons} {"convened in secret"} @@ -447,9 +462,9 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho .map(|(char, chosen)| { html! { <> - + {"invited"} - + {"for dinner"} } @@ -486,7 +501,7 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho <> {"attempted a kill on"} - + } }) @@ -495,7 +510,7 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho characters.get(character_id).map(|shifter| { html! { <> - + {"decided to shapeshift into the wolf kill target"} } @@ -517,7 +532,7 @@ fn StoryNightChoice(StoryNightChoiceProps { choice, characters }: &StoryNightCho characters.get(character_id).map(|insomniac| { html! { <> - + {"witnessed visits from"} } diff --git a/werewolves/src/pages/role_change.rs b/werewolves/src/pages/role_change.rs new file mode 100644 index 0000000..8b84d24 --- /dev/null +++ b/werewolves/src/pages/role_change.rs @@ -0,0 +1,36 @@ +use convert_case::{Case, Casing}; +use werewolves_proto::{ + game::SetupRole, + role::{Alignment, RoleTitle}, +}; +use yew::prelude::*; + +use crate::components::{AssociatedIcon, Icon, IconSource, IconType, PartialAssociatedIcon}; + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct RoleChangePageProps { + pub role: RoleTitle, +} + +#[function_component] +pub fn RoleChangePage(RoleChangePageProps { role }: &RoleChangePageProps) -> Html { + let class = Into::::into(*role).category().class(); + let icon = role.icon().map(|icon| { + html! { +

+ +

+ } + }); + html! { +
+

{"ROLE CHANGE"}

+
+

{"YOUR ROLE HAS CHANGED"}

+ {icon} +

{"YOUR NEW ROLE IS"}

+

{role.to_string().to_case(Case::Upper)}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page.rs b/werewolves/src/pages/role_page.rs index df2ba2b..9bf7f74 100644 --- a/werewolves/src/pages/role_page.rs +++ b/werewolves/src/pages/role_page.rs @@ -1,10 +1,16 @@ use core::ops::Not; use std::rc::Rc; -use werewolves_proto::message::{PublicIdentity, night::ActionPrompt}; +use werewolves_proto::{ + message::{CharacterIdentity, PublicIdentity, night::ActionPrompt}, + role::PreviousGuardianAction, +}; use yew::prelude::*; -use crate::components::Identity; +use crate::{ + components::Identity, + pages::{RoleChangePage, WolfpackKillPage}, +}; werewolves_macros::include_path!("werewolves/src/pages/role_page"); pub trait RolePage { @@ -13,28 +19,194 @@ pub trait RolePage { impl RolePage for ActionPrompt { fn role_pages(&self, big_screen: bool) -> Rc<[yew::Html]> { + let ident = |character_id: &CharacterIdentity| { + big_screen.not().then(|| { + html! { + ::into(character_id)} /> + } + }) + }; match self { - ActionPrompt::Beholder { character_id, .. } => { - let ident = big_screen.not().then(|| { - html! { - ::into(character_id)} /> - } - }); - Rc::new([ - html! { - <> - {ident.clone()} - - - }, - html! { - <> - {ident} - - - }, - ]) - } + ActionPrompt::Beholder { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::DireWolf { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Adjudicator { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Seer { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::PowerSeer { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Arcanist { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Protector { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Empath { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Hunter { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::PyreMaster { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Militia { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::MapleWolf { + character_id, + kill_or_die, + .. + } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::MasonLeaderRecruit { + character_id, + recruits_left, + .. + } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::MasonsWake { leader, masons } => Rc::new([html! { + <> + {ident(leader)} + + + }]), + ActionPrompt::AlphaWolf { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Insomniac { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::ElderReveal { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Guardian { + character_id, + previous: None, + .. + } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Guardian { + character_id, + previous: Some(PreviousGuardianAction::Protect(target)), + .. + } => Rc::new([ + html! { + <> + {ident(character_id)} + + + }, + html! { + <> + {ident(character_id)} + + + }, + ]), + ActionPrompt::Guardian { + character_id, + previous: Some(PreviousGuardianAction::Guard(target)), + .. + } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Mortician { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::RoleChange { + character_id, + new_role, + } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::WolfPackKill { .. } => Rc::new([html! { + <> + + + }]), + ActionPrompt::Gravedigger { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), + ActionPrompt::Vindicator { character_id, .. } => Rc::new([html! { + <> + {ident(character_id)} + + + }]), _ => Rc::new([]), } } diff --git a/werewolves/src/pages/role_page/adjudicator.rs b/werewolves/src/pages/role_page/adjudicator.rs new file mode 100644 index 0000000..b77b215 --- /dev/null +++ b/werewolves/src/pages/role_page/adjudicator.rs @@ -0,0 +1,50 @@ +use core::ops::Not; + +use werewolves_proto::role::Killer; +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn AdjudicatorPage1() -> Html { + html! { +
+

{"ADJUDICATOR"}

+
+

{"PICK A PLAYER"}

+

+ + +

+

{"YOU WILL CHECK IF THEY APPEAR AS A KILLER"}

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct AdjudicatorResultProps { + pub killer: Killer, +} + +#[function_component] +pub fn AdjudicatorResult(AdjudicatorResultProps { killer }: &AdjudicatorResultProps) -> Html { + let text = match killer { + Killer::Killer => "IS A KILLER", + Killer::NotKiller => "IS NOT A KILLER", + }; + html! { +
+

{"ADJUDICATOR"}

+
+

{"YOUR TARGET"}

+

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/alpha_wolf.rs b/werewolves/src/pages/role_page/alpha_wolf.rs new file mode 100644 index 0000000..c6e955d --- /dev/null +++ b/werewolves/src/pages/role_page/alpha_wolf.rs @@ -0,0 +1,21 @@ +use yew::prelude::*; + +#[function_component] +pub fn AlphaWolfPage1() -> Html { + html! { +
+

{"ALPHA WOLF"}

+
+

+ {"IF YOU WISH TO USE YOUR "} + {"ONCE PER GAME"} + {" KILL ABILITY"} +

+

+ {"POINT AT YOUR TARGET "} + {"OR GO BACK TO SLEEP"} +

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/arcanist.rs b/werewolves/src/pages/role_page/arcanist.rs new file mode 100644 index 0000000..a5163c1 --- /dev/null +++ b/werewolves/src/pages/role_page/arcanist.rs @@ -0,0 +1,68 @@ +use werewolves_proto::role::AlignmentEq; +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn ArcanistPage1() -> Html { + html! { +
+

{"ARCANIST"}

+
+

{"PICK TWO PLAYERS"}

+

+ + + + +

+

{"YOU WILL CHECK IF THEIR ALIGNMENTS ARE THE SAME OR DIFFERENT"}

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct ArcanistResultProps { + pub alignment_eq: AlignmentEq, +} + +#[function_component] +pub fn ArcanistResult(ArcanistResultProps { alignment_eq }: &ArcanistResultProps) -> Html { + let text = match alignment_eq { + AlignmentEq::Same => "THE SAME", + AlignmentEq::Different => "DIFFERENT", + }; + let icons = match alignment_eq { + AlignmentEq::Same => html! { + <> + + + {"OR"} + + + + }, + AlignmentEq::Different => html! { + <> + + + {"OR"} + + + + }, + }; + html! { +
+

{"ARCANIST"}

+
+

{"YOUR TARGETS APPEAR AS"}

+

+ {icons} +

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/beholder.rs b/werewolves/src/pages/role_page/beholder.rs index 5032d48..c0386d4 100644 --- a/werewolves/src/pages/role_page/beholder.rs +++ b/werewolves/src/pages/role_page/beholder.rs @@ -3,19 +3,12 @@ use yew::prelude::*; #[function_component] pub fn BeholderPage1() -> Html { html! { -
-

{"this is a beholder"}

-

{"there's information on this page"}

-
- } -} - -#[function_component] -pub fn BeholderPage2() -> Html { - html! { -
-

{"more information on beholder"}

-

{"idk, hi?"}

+
+

{"BEHOLDER"}

+
+

{"PICK A PLAYER"}

+

{"IF THEY ARE AN INTEL ROLE, YOU WILL SEE WHAT THEY SAW TONIGHT"}

+
} } diff --git a/werewolves/src/pages/role_page/direwolf.rs b/werewolves/src/pages/role_page/direwolf.rs new file mode 100644 index 0000000..f6db888 --- /dev/null +++ b/werewolves/src/pages/role_page/direwolf.rs @@ -0,0 +1,15 @@ +use yew::prelude::*; + +#[function_component] +pub fn DirewolfPage1() -> Html { + html! { +
+

{"DIREWOLF"}

+
+

{"CHOOSE A TARGET"}

+

{"ANY VISITORS TO THIS TARGET WILL BE ROLE BLOCKED"}

+

{"YOU CANNOT CHOOSE YOURSELF OR THE SAME TARGET AS LAST NIGHT"}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/elder.rs b/werewolves/src/pages/role_page/elder.rs new file mode 100644 index 0000000..65ec7b3 --- /dev/null +++ b/werewolves/src/pages/role_page/elder.rs @@ -0,0 +1,21 @@ +use yew::prelude::*; + +#[function_component] +pub fn ElderPage1() -> Html { + html! { +
+

{"ELDER"}

+
+

{"YOU ARE THE ELDER"}

+

+ {"IF YOU ARE EXECUTED BY THE VILLAGE FROM NOW ON "} + {"ALL POWER ROLES WILL BE LOST"} +

+

+ {"YOU STARTED THE GAME WITH PROTECTION FROM A NIGHT "} + {"DEATH — THIS MAY OR MAY NOT STILL BE INTACT"} +

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/empath.rs b/werewolves/src/pages/role_page/empath.rs new file mode 100644 index 0000000..dcb02a9 --- /dev/null +++ b/werewolves/src/pages/role_page/empath.rs @@ -0,0 +1,48 @@ +use core::ops::Not; + +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn EmpathPage1() -> Html { + html! { +
+

{"EMPATH"}

+
+

{"PICK A PLAYER"}

+

{"YOU WILL CHECK IF THEY ARE THE SCAPEGOAT"}

+

{"AND IF THEY ARE, TAKE ON THEIR CURSE"}

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct EmpathResultProps { + pub scapegoat: bool, +} + +#[function_component] +pub fn EmpathResult(EmpathResultProps { scapegoat }: &EmpathResultProps) -> Html { + let text = match scapegoat { + true => "THE SCAPEGOAT", + false => "NOT THE SCAPEGOAT", + }; + html! { +
+

{"EMPATH"}

+
+

{"YOUR TARGET IS"}

+

+ +

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/gravedigger.rs b/werewolves/src/pages/role_page/gravedigger.rs new file mode 100644 index 0000000..5a66b52 --- /dev/null +++ b/werewolves/src/pages/role_page/gravedigger.rs @@ -0,0 +1,74 @@ +use convert_case::{Case, Casing}; +use werewolves_proto::role::RoleTitle; +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType, PartialAssociatedIcon}; + +#[function_component] +pub fn GravediggerPage1() -> Html { + html! { +
+

{"GRAVEDIGGER"}

+
+

+ {"PICK A "} + {"DEAD"} + {" PLAYER"} +

+
+ +
+

+ {"YOU WILL LEARN THEIR ROLE"} +

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct GravediggerResultPageProps { + pub role: Option, +} + +#[function_component] +pub fn GravediggerResultPage( + GravediggerResultPageProps { role }: &GravediggerResultPageProps, +) -> Html { + let text = role + .as_ref() + .map(|r| { + html! { +

+ {"YOU DIG UP THE BODY OF A "} + + {r.to_string().to_case(Case::Upper)} + +

+ } + }) + .unwrap_or_else(|| { + html! { +

{"BUT INSTEAD YOU FIND AN EMPTY GRAVE"}

+ } + }); + let icon = role + .as_ref() + .and_then(|i| i.icon()) + .unwrap_or(IconSource::Gravedigger); + html! { +
+

{"GRAVEDIGGER"}

+
+

{"YOU CHECK THE ROLE OF YOUR TARGET"}

+

+ +

+ {text} +
+
+ } +} diff --git a/werewolves/src/pages/role_page/guardian.rs b/werewolves/src/pages/role_page/guardian.rs new file mode 100644 index 0000000..282cd14 --- /dev/null +++ b/werewolves/src/pages/role_page/guardian.rs @@ -0,0 +1,76 @@ +use werewolves_proto::message::CharacterIdentity; +use yew::prelude::*; + +use crate::components::{CharacterTargetCard, Icon, IconSource, IconType}; + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct GuardianPageProps { + pub previous: CharacterIdentity, +} + +#[function_component] +pub fn GuardianPageNoPrevProtect() -> Html { + html! { +
+

{"GUARDIAN"}

+
+

{"PICK A PLAYER"}

+

+ +

+

{"CHOOSE SOMEONE TO PROTECT FROM DEATH"}

+
+
+ } +} + +#[function_component] +pub fn GuardianPagePreviousProtect1(GuardianPageProps { previous }: &GuardianPageProps) -> Html { + html! { +
+

{"GUARDIAN"}

+
+

{"LAST TIME YOU PROTECTED"}

+
+ +
+

{"IF YOU PROTECT THEM AGAIN, YOU WILL INSTEAD GUARD THEM"}

+
+
+ } +} + +#[function_component] +pub fn GuardianPagePreviousProtect2(GuardianPageProps { previous }: &GuardianPageProps) -> Html { + html! { +
+

{"GUARDIAN"}

+
+

{"LAST TIME YOU PROTECTED"}

+
+ +
+

{"IF ATTACKED WHILE GUARDED, YOU AND THEIR ATTACKER WILL INSTEAD DIE"}

+
+
+ } +} + +#[function_component] +pub fn GuardianPagePreviousGuard(GuardianPageProps { previous }: &GuardianPageProps) -> Html { + html! { +
+

{"GUARDIAN"}

+
+

{"LAST TIME YOU GUARDED"}

+
+ +
+

+ +

+

{"YOU CANNOT PROTECT THEM TONIGHT"}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/hunter.rs b/werewolves/src/pages/role_page/hunter.rs new file mode 100644 index 0000000..bad94d6 --- /dev/null +++ b/werewolves/src/pages/role_page/hunter.rs @@ -0,0 +1,22 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn HunterPage1() -> Html { + html! { +
+

{"HUNTER"}

+
+

{"SET A HUNTER'S TRAP ON A PLAYER"}

+
+ +
+

+ {"IF YOU DIE TONIGHT, OR ARE EXECUTED TOMORROW"} + {"THIS PLAYER WILL DIE AT NIGHT AS WELL"} +

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/insomniac.rs b/werewolves/src/pages/role_page/insomniac.rs new file mode 100644 index 0000000..cedf899 --- /dev/null +++ b/werewolves/src/pages/role_page/insomniac.rs @@ -0,0 +1,47 @@ +use werewolves_proto::message::night::Visits; +use yew::prelude::*; + +use crate::components::CharacterTargetCard; + +#[function_component] +pub fn InsomniacPage1() -> Html { + html! { +
+

{"INSOMNIAC"}

+
+

{"YOUR POOR SLEEP RESULTS IN BEING WOKEN BY VISITORS IN THE NIGHT"}

+

+ {"YOU WILL REMEMBER WHO VISITED YOU TONIGHT"} +

+
+
+ } +} + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct InsomniacResultProps { + pub visits: Visits, +} + +#[function_component] +pub fn InsomniacResult(InsomniacResultProps { visits }: &InsomniacResultProps) -> Html { + let visitors = visits + .iter() + .map(|visitor| { + html! { + + } + }) + .collect::(); + html! { +
+

{"INSOMNIAC"}

+
+

{"YOU WERE VISITED IN THE NIGHT BY:"}

+
+ {visitors} +
+
+
+ } +} diff --git a/werewolves/src/pages/role_page/maple_wolf.rs b/werewolves/src/pages/role_page/maple_wolf.rs new file mode 100644 index 0000000..a3a6f57 --- /dev/null +++ b/werewolves/src/pages/role_page/maple_wolf.rs @@ -0,0 +1,41 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct MapleWolfPage1Props { + pub starving: bool, +} + +#[function_component] +pub fn MapleWolfPage1(MapleWolfPage1Props { starving }: &MapleWolfPage1Props) -> Html { + let starving = starving + .then_some(html! { + <> +

{"YOU ARE STARVING"}

+

{"IF YOU FAIL TO EAT TONIGHT, YOU WILL DIE"}

+ + }) + .unwrap_or_else(|| { + html! { +

+ {"IF YOU DON'T EAT FOR TOO LONG, YOU WILL "} + {"STARVE"} +

+ } + }); + html! { +
+

{"MAPLE WOLF"}

+
+

+ {"YOU CAN CHOOSE TO EAT A PLAYER TONIGHT"} +

+
+ +
+ {starving} +
+
+ } +} diff --git a/werewolves/src/pages/role_page/mason.rs b/werewolves/src/pages/role_page/mason.rs new file mode 100644 index 0000000..c738e93 --- /dev/null +++ b/werewolves/src/pages/role_page/mason.rs @@ -0,0 +1,81 @@ +use core::num::NonZeroU8; + +use werewolves_proto::message::CharacterIdentity; +use yew::prelude::*; + +use crate::components::CharacterTargetCard; + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct MasonRecruitPage1Props { + pub recruits_left: NonZeroU8, +} + +#[function_component] +pub fn MasonRecruitPage1( + MasonRecruitPage1Props { recruits_left }: &MasonRecruitPage1Props, +) -> Html { + let recruitments = match recruits_left.get() { + 0 => unreachable!(), + 1 => html! { + <> + {1} + {" RECRUITMENT"} + + }, + num => html! { + <> + {num} + {" RECRUITMENTS"} + + }, + }; + html! { +
+

{"MASON LEADER"}

+
+

{"YOU HAVE "}{recruitments}{" LEFT"}

+

+ {"ANYONE YOU RECRUIT INTO THE MASONS WILL WAKE WITH YOU "} + {"EVERY NIGHT"} + {", AS LONG AS THEY ARE ALIVE AND REMAIN VILLAGE ALIGNED"} +

+

{"WOULD YOU LIKE TO RECRUIT TONIGHT?"}

+
+
+ } +} + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct MasonsWakeProps { + pub leader: CharacterIdentity, + pub masons: Box<[CharacterIdentity]>, +} + +#[function_component] +pub fn MasonsWake(MasonsWakeProps { leader, masons }: &MasonsWakeProps) -> Html { + let title = html! { + <> + {"MASONS OF "} + {leader.name.clone()} + + }; + let masons = masons + .iter() + .map(|mason| { + html! { + + } + }) + .collect::(); + html! { +
+

{title}

+
+

{"YOU ARE ALL MEMBERS "}

+
+ {masons} +
+
+
+ } +} diff --git a/werewolves/src/pages/role_page/militia.rs b/werewolves/src/pages/role_page/militia.rs new file mode 100644 index 0000000..8a03ddf --- /dev/null +++ b/werewolves/src/pages/role_page/militia.rs @@ -0,0 +1,26 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn MilitiaPage1() -> Html { + html! { +
+

{"MILITIA"}

+
+

+ {"IF YOU WISH TO USE YOUR "} + {"ONCE PER GAME"} + {" KILL ABILITY"} +

+
+ +
+

+ {"POINT AT YOUR TARGET "} + {"OR GO BACK TO SLEEP"} +

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/mortician.rs b/werewolves/src/pages/role_page/mortician.rs new file mode 100644 index 0000000..542d203 --- /dev/null +++ b/werewolves/src/pages/role_page/mortician.rs @@ -0,0 +1,69 @@ +use werewolves_proto::diedto::DiedToTitle; +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType, PartialAssociatedIcon}; + +#[function_component] +pub fn MorticianPage1() -> Html { + html! { +
+

{"MORTICIAN"}

+
+

+ {"PICK A "} + {"DEAD"} + {" PLAYER"} +

+
+ +
+

+ {"YOU WILL LEARN THE CAUSE "} + {"OF THEIR DEATH"} +

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct MorticianResultPageProps { + pub died_to: DiedToTitle, +} + +#[function_component] +pub fn MorticianResultPage( + MorticianResultPageProps { died_to }: &MorticianResultPageProps, +) -> Html { + let text = match died_to { + DiedToTitle::Execution => "Execution", + DiedToTitle::MapleWolf => "Maple Wolf", + DiedToTitle::MapleWolfStarved => "Starvation", + DiedToTitle::Militia => "Militia Shot", + DiedToTitle::Wolfpack => "Wolfpack", + DiedToTitle::AlphaWolf => "Alpha Wolf", + DiedToTitle::Shapeshift => "Shapeshifting", + DiedToTitle::Hunter => "Hunter Trap", + DiedToTitle::GuardianProtecting => "Guardian", + DiedToTitle::PyreMaster => "Pyre Master", + DiedToTitle::PyreMasterLynchMob => "An Angry Mob of Villagers Against Fire", + DiedToTitle::MasonLeaderRecruitFail => "Occupational Hazard (Mason Recruit Fail)", + DiedToTitle::LoneWolf => "Lone Wolf", + }; + let icon = died_to.icon().unwrap_or(IconSource::Mortician); + html! { +
+

{"MORTICIAN"}

+
+

{"YOUR TARGET DIED TO"}

+

+ +

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/power_seer.rs b/werewolves/src/pages/role_page/power_seer.rs new file mode 100644 index 0000000..1d5c349 --- /dev/null +++ b/werewolves/src/pages/role_page/power_seer.rs @@ -0,0 +1,52 @@ +use core::ops::Not; + +use werewolves_proto::role::Powerful; +use yew::prelude::*; + +use crate::components::{AssociatedIcon, Icon, IconSource, IconType}; + +#[function_component] +pub fn PowerSeerPage1() -> Html { + html! { +
+

{"POWER SEER"}

+
+

{"PICK A PLAYER"}

+

+ + +

+

{"YOU WILL CHECK IF THEY ARE POWERFUL"}

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct PowerSeerResultProps { + pub powerful: Powerful, +} + +#[function_component] +pub fn PowerSeerResult(PowerSeerResultProps { powerful }: &PowerSeerResultProps) -> Html { + let text = match powerful { + Powerful::Powerful => "POWERFUL", + Powerful::NotPowerful => "NOT POWERFUL", + }; + html! { +
+

{"POWER SEER"}

+
+

{"YOUR TARGET APPEARS AS"}

+

+ +

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/protector.rs b/werewolves/src/pages/role_page/protector.rs new file mode 100644 index 0000000..a94f4f1 --- /dev/null +++ b/werewolves/src/pages/role_page/protector.rs @@ -0,0 +1,19 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn ProtectorPage1() -> Html { + html! { +
+

{"PROTECTOR"}

+
+

{"PICK A PLAYER"}

+

+ +

+

{"YOU WILL PROTECT THEM FROM A DEATH TONIGHT"}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/pyremaster.rs b/werewolves/src/pages/role_page/pyremaster.rs new file mode 100644 index 0000000..dcedf35 --- /dev/null +++ b/werewolves/src/pages/role_page/pyremaster.rs @@ -0,0 +1,22 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn PyremasterPage1() -> Html { + html! { +
+

{"PYREMASTER"}

+
+

{"IF YOU WISH TO THROW A PLAYER ON THE PYRE"}

+
+ +
+

+ {"IF YOU KILL TWO GOOD VILLAGERS LIKE THIS "} + {"YOU WILL DIE AS WELL"} +

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/seer.rs b/werewolves/src/pages/role_page/seer.rs new file mode 100644 index 0000000..bf02fc4 --- /dev/null +++ b/werewolves/src/pages/role_page/seer.rs @@ -0,0 +1,49 @@ +use werewolves_proto::role::Alignment; +use yew::prelude::*; + +use crate::components::{AssociatedIcon, Icon, IconSource, IconType}; + +#[function_component] +pub fn SeerPage1() -> Html { + html! { +
+

{"SEER"}

+
+

{"PICK A PLAYER"}

+

+ + +

+

{"YOU WILL CHECK IF THEY APPEAR AS A VILLAGER OR PART OF THE WOLFPACK"}

+
+
+ } +} + +#[derive(Debug, Clone, Copy, PartialEq, Properties)] +pub struct SeerResultProps { + pub alignment: Alignment, +} + +#[function_component] +pub fn SeerResult(SeerResultProps { alignment }: &SeerResultProps) -> Html { + let text = match alignment { + Alignment::Village => "VILLAGE", + Alignment::Wolves => "WOLFPACK", + }; + html! { +
+

{"SEER"}

+
+

{"YOUR TARGET APPEARS AS"}

+

+ +

+

{text}

+
+
+ } +} diff --git a/werewolves/src/pages/role_page/vindicator.rs b/werewolves/src/pages/role_page/vindicator.rs new file mode 100644 index 0000000..3b0683f --- /dev/null +++ b/werewolves/src/pages/role_page/vindicator.rs @@ -0,0 +1,20 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn VindicatorPage1() -> Html { + html! { +
+

{"VINDICATOR"}

+
+

{"A VILLAGER WAS EXECUTED"}

+

{"PICK A PLAYER"}

+

+ +

+

{"YOU WILL PROTECT THEM FROM A DEATH TONIGHT"}

+
+
+ } +} diff --git a/werewolves/src/pages/roleblock.rs b/werewolves/src/pages/roleblock.rs new file mode 100644 index 0000000..2c131ac --- /dev/null +++ b/werewolves/src/pages/roleblock.rs @@ -0,0 +1,19 @@ +use yew::prelude::*; + +use crate::components::{Icon, IconSource, IconType}; + +#[function_component] +pub fn RoleblockPage() -> Html { + html! { +
+

{"ROLE BLOCKED"}

+
+

{"YOU WERE ROLE BLOCKED"}

+

+ +

+

{"YOUR NIGHT ACTION DID NOT TAKE PLACE"}

+
+
+ } +} diff --git a/werewolves/src/pages/wolf_pack.rs b/werewolves/src/pages/wolf_pack.rs new file mode 100644 index 0000000..64abb16 --- /dev/null +++ b/werewolves/src/pages/wolf_pack.rs @@ -0,0 +1,22 @@ +use core::ops::Not; + +use werewolves_proto::role::Powerful; +use yew::prelude::*; + +use crate::components::{AssociatedIcon, Icon, IconSource, IconType}; + +#[function_component] +pub fn WolfpackKillPage() -> Html { + html! { +
+

{"WOLF PACK KILL"}

+
+

{"CHOOSE A TARGET TO EAT TONIGHT"}

+

+ +

+

{"WOLVES MUST BE UNANIMOUS"}

+
+
+ } +}