use core::{num::NonZeroU8, ops::Not}; use super::Result; use crate::{ diedto::DiedTo, error::GameError, game::{Village, night::NightChange}, player::{CharacterId, Protection}, }; #[derive(Debug, PartialEq)] pub enum KillOutcome { Single(CharacterId, DiedTo), Guarding { original_killer: CharacterId, original_target: CharacterId, original_kill: DiedTo, guardian: CharacterId, night: NonZeroU8, }, } impl KillOutcome { pub fn apply_to_village(self, village: &mut Village) -> Result<()> { match self { KillOutcome::Single(character_id, died_to) => { village .character_by_id_mut(character_id) .ok_or(GameError::InvalidTarget)? .kill(died_to); Ok(()) } KillOutcome::Guarding { original_killer, original_target, original_kill, guardian, night, } => { // check if guardian exists before we mutably borrow killer, which would // prevent us from borrowing village to check after. village .character_by_id(guardian) .ok_or(GameError::InvalidTarget)?; village .character_by_id_mut(original_killer) .ok_or(GameError::InvalidTarget)? .kill(DiedTo::GuardianProtecting { night, source: guardian, protecting: original_target, protecting_from: original_killer, protecting_from_cause: Box::new(original_kill.clone()), }); village .character_by_id_mut(guardian) .ok_or(GameError::InvalidTarget)? .kill(original_kill); Ok(()) } } } } fn resolve_protection( killer: CharacterId, killed_with: &DiedTo, target: &CharacterId, protection: &Protection, night: NonZeroU8, ) -> Option { match protection { Protection::Guardian { source, guarding: true, } => Some(KillOutcome::Guarding { original_killer: killer, guardian: *source, original_target: *target, original_kill: killed_with.clone(), night, }), Protection::Guardian { source: _, guarding: false, } | Protection::Protector { source: _ } => None, } } pub fn resolve_kill( changes: &mut ChangesLookup<'_>, target: &CharacterId, died_to: &DiedTo, night: u8, village: &Village, ) -> Result> { if let DiedTo::MapleWolf { source, night, starves_if_fails: true, } = died_to && let Some(protection) = changes.protected_take(target) { return Ok(Some( resolve_protection(*source, died_to, target, &protection, *night).unwrap_or( KillOutcome::Single(*source, DiedTo::MapleWolfStarved { night: *night }), ), )); } if let DiedTo::Wolfpack { night, killing_wolf, } = died_to && let Some(ss_source) = changes.shapeshifter() { let killing_wolf = village .character_by_id(*killing_wolf) .ok_or(GameError::InvalidTarget)?; match changes.protected_take(target) { Some(protection) => { return Ok(resolve_protection( killing_wolf.character_id(), died_to, target, &protection, *night, )); } None => { // Wolf kill went through -- can kill shifter return Ok(Some(KillOutcome::Single( *ss_source, DiedTo::Shapeshift { into: *target, night: *night, }, ))); } }; } let protection = match changes.protected_take(target) { Some(prot) => prot, None => return Ok(Some(KillOutcome::Single(*target, died_to.clone()))), }; match protection { Protection::Guardian { source, guarding: true, } => Ok(Some(KillOutcome::Guarding { original_killer: *died_to .killer() .ok_or(GameError::GuardianInvalidOriginalKill)?, original_target: *target, original_kill: died_to.clone(), guardian: source, night: NonZeroU8::new(night).unwrap(), })), Protection::Guardian { source: _, guarding: false, } | Protection::Protector { source: _ } => Ok(None), } } pub struct ChangesLookup<'a>(&'a [NightChange], Vec); impl<'a> ChangesLookup<'a> { pub fn new(changes: &'a [NightChange]) -> Self { Self(changes, Vec::new()) } pub fn killed(&self, target: &CharacterId) -> Option<&'a DiedTo> { self.0.iter().enumerate().find_map(|(idx, c)| { self.1 .contains(&idx) .not() .then(|| match c { NightChange::Kill { target: t, died_to } => (t == target).then_some(died_to), _ => None, }) .flatten() }) } pub fn protected_take(&mut self, target: &CharacterId) -> Option { if let Some((idx, c)) = self.0.iter().enumerate().find_map(|(idx, c)| { self.1 .contains(&idx) .not() .then(|| match c { NightChange::Protection { target: t, protection, } => (t == target).then_some((idx, protection)), _ => None, }) .flatten() }) { self.1.push(idx); Some(c.clone()) } else { None } } pub fn protected(&self, target: &CharacterId) -> Option<&'a Protection> { self.0.iter().enumerate().find_map(|(idx, c)| { self.1 .contains(&idx) .not() .then(|| match c { NightChange::Protection { target: t, protection, } => (t == target).then_some(protection), _ => None, }) .flatten() }) } pub fn shapeshifter(&self) -> Option<&'a CharacterId> { self.0.iter().enumerate().find_map(|(idx, c)| { self.1 .contains(&idx) .not() .then_some(match c { NightChange::Shapeshift { source } => Some(source), _ => None, }) .flatten() }) } pub fn wolf_pack_kill_target(&self) -> Option<&'a CharacterId> { self.0.iter().enumerate().find_map(|(idx, c)| { self.1 .contains(&idx) .not() .then_some(match c { NightChange::Kill { target, died_to: DiedTo::Wolfpack { night: _, killing_wolf: _, }, } => Some(target), _ => None, }) .flatten() }) } }