werewolves/werewolves-proto/src/game/kill.rs

259 lines
7.7 KiB
Rust
Raw Normal View History

2025-10-03 00:00:39 +01:00
use core::{num::NonZeroU8, ops::Not};
use super::Result;
use crate::{
diedto::DiedTo,
error::GameError,
2025-10-03 00:00:39 +01:00
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 {
2025-10-03 00:00:39 +01:00
KillOutcome::Single(character_id, died_to) => {
village
.character_by_id_mut(character_id)
2025-10-03 00:00:39 +01:00
.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,
2025-10-05 10:54:47 +01:00
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<KillOutcome> {
match protection {
Protection::Guardian {
source,
guarding: true,
} => Some(KillOutcome::Guarding {
original_killer: killer,
2025-10-05 10:54:47 +01:00
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<Option<KillOutcome>> {
if let DiedTo::MapleWolf {
source,
night,
starves_if_fails: true,
} = died_to
&& let Some(protection) = changes.protected_take(target)
{
return Ok(Some(
2025-10-05 10:54:47 +01:00
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(
2025-10-05 10:54:47 +01:00
killing_wolf.character_id(),
died_to,
target,
&protection,
*night,
));
}
None => {
// Wolf kill went through -- can kill shifter
return Ok(Some(KillOutcome::Single(
2025-10-05 10:54:47 +01:00
*ss_source,
DiedTo::Shapeshift {
2025-10-05 10:54:47 +01:00
into: *target,
night: *night,
},
)));
}
};
}
let protection = match changes.protected_take(target) {
Some(prot) => prot,
2025-10-05 10:54:47 +01:00
None => return Ok(Some(KillOutcome::Single(*target, died_to.clone()))),
};
2025-10-03 00:00:39 +01:00
match protection {
Protection::Guardian {
source,
guarding: true,
} => Ok(Some(KillOutcome::Guarding {
2025-10-05 10:54:47 +01:00
original_killer: *died_to
.killer()
2025-10-05 10:54:47 +01:00
.ok_or(GameError::GuardianInvalidOriginalKill)?,
original_target: *target,
original_kill: died_to.clone(),
2025-10-05 10:54:47 +01:00
guardian: source,
night: NonZeroU8::new(night).unwrap(),
})),
Protection::Guardian {
source: _,
guarding: false,
}
| Protection::Protector { source: _ } => Ok(None),
}
}
pub struct ChangesLookup<'a>(&'a [NightChange], Vec<usize>);
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()
})
}
2025-10-03 00:00:39 +01:00
pub fn protected_take(&mut self, target: &CharacterId) -> Option<Protection> {
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);
2025-10-03 00:00:39 +01:00
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()
})
}
}