diff --git a/werewolves-proto/src/game/night.rs b/werewolves-proto/src/game/night.rs index d8b7328..7aae4f6 100644 --- a/werewolves-proto/src/game/night.rs +++ b/werewolves-proto/src/game/night.rs @@ -343,6 +343,57 @@ impl Night { Ok(new_village) } + fn apply_shapeshift(&mut self, source: &CharacterId) -> Result { + if let Some(kill_target) = self.changes.iter().find_map(|c| match c { + NightChange::Kill { + target, + died_to: DiedTo::Wolfpack { night: _ }, + } => Some(target.clone()), + _ => None, + }) { + if self.changes.iter().any(|c| match c { + NightChange::Protection { + target, + protection: _, + } => target == &kill_target, + _ => false, + }) { + // there is protection, so the kill doesn't happen -> no shapeshift + return Ok(ActionResult::GoBackToSleep); + } + if let Some(kill) = self.changes.iter_mut().find(|c| { + matches!( + c, + NightChange::Kill { + target: _, + died_to: DiedTo::Wolfpack { night: _ } + } + ) + }) { + *kill = NightChange::Kill { + target: source.clone(), + died_to: DiedTo::Shapeshift { + into: kill_target.clone(), + night: NonZeroU8::new(self.night).unwrap(), + }, + } + } + self.changes.push(NightChange::Shapeshift { + source: source.clone(), + }); + self.action_queue.push_front(( + ActionPrompt::RoleChange { + new_role: RoleTitle::Werewolf, + }, + self.village + .find_by_character_id(&kill_target) + .unwrap() + .clone(), + )); + } + Ok(ActionResult::GoBackToSleep) + } + pub fn received_response(&mut self, resp: ActionResponse) -> Result { if let ( NightState::Active { @@ -367,6 +418,10 @@ impl Night { } => current_result.replace(result.clone()), NightState::Complete => return Err(GameError::NightOver), }; + if let NightChange::Shapeshift { source } = &change { + // needs to be resolved _now_ + return self.apply_shapeshift(source); + } self.changes.push(change); Ok(result) } diff --git a/werewolves-proto/src/message/night.rs b/werewolves-proto/src/message/night.rs index dcbc1c4..c26b600 100644 --- a/werewolves-proto/src/message/night.rs +++ b/werewolves-proto/src/message/night.rs @@ -118,10 +118,7 @@ pub enum ActionResult { Seer(Alignment), Arcanist { same: bool }, GraveDigger(Option), - WolvesMustBeUnanimous, - WaitForOthersToVote, GoBackToSleep, - RoleRevealDone, WolvesIntroDone, } diff --git a/werewolves-proto/src/player.rs b/werewolves-proto/src/player.rs index 1910407..510b0cd 100644 --- a/werewolves-proto/src/player.rs +++ b/werewolves-proto/src/player.rs @@ -36,6 +36,9 @@ impl CharacterId { pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } + pub const fn from_u128(v: u128) -> Self { + Self(uuid::Uuid::from_u128(v)) + } } impl Display for CharacterId { diff --git a/werewolves/src/components/action/binary.rs b/werewolves/src/components/action/binary.rs new file mode 100644 index 0000000..119dc4a --- /dev/null +++ b/werewolves/src/components/action/binary.rs @@ -0,0 +1,32 @@ +use yew::prelude::*; + +use crate::components::Button; + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct BinaryChoiceProps { + pub on_chosen: Callback, + #[prop_or_default] + pub children: Html, +} + +#[function_component] +pub fn BinaryChoice( + BinaryChoiceProps { + on_chosen, + children, + }: &BinaryChoiceProps, +) -> Html { + let on_chosen_yes = on_chosen.clone(); + let yes = move |_| on_chosen_yes.emit(true); + let on_chosen = on_chosen.clone(); + let no = move |_| on_chosen.emit(false); + html! { +
+ {children.clone()} +
+ + +
+
+ } +} diff --git a/werewolves/src/components/action/prompt.rs b/werewolves/src/components/action/prompt.rs index 4df021f..8386cc8 100644 --- a/werewolves/src/components/action/prompt.rs +++ b/werewolves/src/components/action/prompt.rs @@ -1,4 +1,4 @@ -use core::ops::Not; +use core::{num::NonZeroU8, ops::Not}; use werewolves_proto::{ message::{ @@ -12,7 +12,7 @@ use yew::prelude::*; use crate::components::{ Identity, - action::{SingleTarget, TargetSelection, WolvesIntro}, + action::{BinaryChoice, SingleTarget, TargetSelection, WolvesIntro}, }; #[derive(Debug, Clone, PartialEq, Properties)] @@ -162,7 +162,19 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { } } - ActionPrompt::Shapeshifter => todo!(), + ActionPrompt::Shapeshifter => { + let on_complete = props.on_complete.clone(); + let on_select = move |shift| { + on_complete.emit(HostMessage::InGame(HostGameMessage::Night( + HostNightMessage::ActionResponse(ActionResponse::Shapeshifter(shift)), + ))); + }; + html! { + +

{"shapeshift?"}

+
+ } + } ActionPrompt::AlphaWolf { living_villagers } => { let on_complete = props.on_complete.clone(); let on_select = props.big_screen.not().then(|| { diff --git a/werewolves/src/components/action/result.rs b/werewolves/src/components/action/result.rs index d9583e4..7a9892a 100644 --- a/werewolves/src/components/action/result.rs +++ b/werewolves/src/components/action/result.rs @@ -1,10 +1,11 @@ use core::ops::Not; +use convert_case::{Case, Casing}; use werewolves_proto::{ message::{ PublicIdentity, host::{HostGameMessage, HostMessage, HostNightMessage}, - night::{ActionPrompt, ActionResponse, ActionResult}, + night::ActionResult, }, role::Alignment, }; @@ -58,10 +59,30 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html { {cont} }, - ActionResult::Arcanist { same } => todo!(), - ActionResult::GraveDigger(role_title) => todo!(), - ActionResult::WolvesMustBeUnanimous => todo!(), - ActionResult::WaitForOthersToVote => todo!(), + ActionResult::Arcanist { same } => { + let outcome = if *same { "same" } else { "different" }; + html! { +
+ {ident} +

{"the alignments are:"}

+

{outcome}

+ {cont} +
+ } + } + 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! { +
+ {ident} +

{"you see:"}

+

{dig}

+ {cont} +
+ } + } ActionResult::GoBackToSleep => { let next = props.big_screen.not().then(|| { let on_complete = props.on_complete.clone(); @@ -77,7 +98,6 @@ pub fn ActionResultView(props: &ActionResultProps) -> Html { } } - ActionResult::RoleRevealDone => todo!(), ActionResult::WolvesIntroDone => { let on_complete = props.on_complete.clone(); let next = props.big_screen.not().then(|| { diff --git a/werewolves/src/components/action/target.rs b/werewolves/src/components/action/target.rs index 4b6d56d..40d0489 100644 --- a/werewolves/src/components/action/target.rs +++ b/werewolves/src/components/action/target.rs @@ -1,4 +1,4 @@ -use core::{fmt::Debug, marker::PhantomData, ops::Not}; +use core::fmt::Debug; use werewolves_proto::{message::Target, player::CharacterId}; use yew::prelude::*; @@ -93,7 +93,7 @@ impl Component for SingleTarget { if self.selected.len() > 1 { None } else { - let selected = self.selected.iter().next().cloned(); + let selected = self.selected.first().cloned(); let on_click = on_click.clone(); Some(Callback::from(move |_| on_click.emit(selected.clone()))) }