reimplement previous
This commit is contained in:
parent
17f583539d
commit
62d650fb34
|
|
@ -198,6 +198,10 @@ impl Character {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn role_changes(&self) -> &[RoleChange] {
|
||||||
|
&self.role_changes
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_wolf(&self) -> bool {
|
pub const fn is_wolf(&self) -> bool {
|
||||||
self.role.wolf()
|
self.role.wolf()
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ impl<'a> ChangesLookup<'a> {
|
||||||
.contains(&idx)
|
.contains(&idx)
|
||||||
.not()
|
.not()
|
||||||
.then_some(match c {
|
.then_some(match c {
|
||||||
NightChange::Shapeshift { source } => Some(source),
|
NightChange::Shapeshift { source, .. } => Some(source),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,11 @@ impl Game {
|
||||||
&self.state
|
&self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn game_state_mut(&mut self) -> &mut GameState {
|
||||||
|
&mut self.state
|
||||||
|
}
|
||||||
|
|
||||||
pub fn previous_game_states(&self) -> &[GameState] {
|
pub fn previous_game_states(&self) -> &[GameState] {
|
||||||
&self.previous
|
&self.previous
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use crate::{
|
||||||
role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleBlock, RoleTitle},
|
role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleBlock, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Extract)]
|
#[derive(Debug, Clone, Serialize, PartialEq, Deserialize, Extract)]
|
||||||
pub enum NightChange {
|
pub enum NightChange {
|
||||||
RoleChange(CharacterId, RoleTitle),
|
RoleChange(CharacterId, RoleTitle),
|
||||||
HunterTarget {
|
HunterTarget {
|
||||||
|
|
@ -39,6 +39,7 @@ pub enum NightChange {
|
||||||
},
|
},
|
||||||
Shapeshift {
|
Shapeshift {
|
||||||
source: CharacterId,
|
source: CharacterId,
|
||||||
|
into: CharacterId,
|
||||||
},
|
},
|
||||||
Protection {
|
Protection {
|
||||||
target: CharacterId,
|
target: CharacterId,
|
||||||
|
|
@ -234,6 +235,7 @@ enum NightState {
|
||||||
Active {
|
Active {
|
||||||
current_prompt: ActionPrompt,
|
current_prompt: ActionPrompt,
|
||||||
current_result: Option<ActionResult>,
|
current_result: Option<ActionResult>,
|
||||||
|
current_changes: Vec<NightChange>,
|
||||||
},
|
},
|
||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
@ -243,8 +245,7 @@ pub struct Night {
|
||||||
village: Village,
|
village: Village,
|
||||||
night: u8,
|
night: u8,
|
||||||
action_queue: VecDeque<ActionPrompt>,
|
action_queue: VecDeque<ActionPrompt>,
|
||||||
used_actions: Vec<(ActionPrompt, ActionResult)>,
|
used_actions: Vec<(ActionPrompt, ActionResult, Vec<NightChange>)>,
|
||||||
changes: Vec<NightChange>,
|
|
||||||
night_state: NightState,
|
night_state: NightState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,14 +292,12 @@ impl Night {
|
||||||
// let current_prompt = action_queue.pop_front().ok_or(GameError::NoNightActions)?;
|
// let current_prompt = action_queue.pop_front().ok_or(GameError::NoNightActions)?;
|
||||||
let night_state = NightState::Active {
|
let night_state = NightState::Active {
|
||||||
current_prompt: ActionPrompt::CoverOfDarkness,
|
current_prompt: ActionPrompt::CoverOfDarkness,
|
||||||
|
current_changes: Vec::new(),
|
||||||
current_result: None,
|
current_result: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let changes = Self::automatic_changes(&village, night);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
night,
|
night,
|
||||||
changes,
|
|
||||||
village,
|
village,
|
||||||
night_state,
|
night_state,
|
||||||
action_queue,
|
action_queue,
|
||||||
|
|
@ -337,39 +336,40 @@ impl Night {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous_state(&mut self) -> Result<()> {
|
pub fn previous_state(&mut self) -> Result<()> {
|
||||||
return Err(GameError::NoPreviousState);
|
let (current_prompt, current_result, current_changes) = match &mut self.night_state {
|
||||||
let (prev_act, prev_result) = self.used_actions.pop().ok_or(GameError::NoPreviousState)?;
|
|
||||||
log::info!("loading previous prompt: {prev_act:?}");
|
|
||||||
match &self.night_state {
|
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt,
|
current_prompt,
|
||||||
current_result: Some(current_result),
|
current_result,
|
||||||
} => {
|
current_changes,
|
||||||
log::info!("removing current result: {current_result:?}");
|
} => (current_prompt, current_result, current_changes),
|
||||||
self.night_state = NightState::Active {
|
NightState::Complete => return Err(GameError::NightOver),
|
||||||
current_prompt: current_prompt.clone(),
|
};
|
||||||
current_result: None,
|
if let Some((prompt, _, changes)) = self.used_actions.pop() {
|
||||||
};
|
// Remove the shapeshifter role change from the queue
|
||||||
}
|
if let ActionPrompt::Shapeshifter {
|
||||||
NightState::Active {
|
character_id: ss_char,
|
||||||
current_prompt,
|
} = &prompt
|
||||||
current_result: None,
|
&& let Some(change) = changes.first()
|
||||||
} => {
|
&& let NightChange::Shapeshift { source, into } = change
|
||||||
log::info!("pushing current prompt to front of action queue: {current_prompt:?}");
|
&& ss_char.character_id == *source
|
||||||
self.action_queue.push_front(current_prompt.clone());
|
&& let Some(next) = self.action_queue.pop_front()
|
||||||
self.night_state = NightState::Active {
|
&& let ActionPrompt::RoleChange {
|
||||||
current_prompt: prev_act,
|
character_id: role_change_char,
|
||||||
current_result: None,
|
..
|
||||||
}
|
} = &next
|
||||||
}
|
&& role_change_char.character_id != *into
|
||||||
NightState::Complete => {
|
{
|
||||||
self.night_state = NightState::Active {
|
// put it back in
|
||||||
current_prompt: prev_act,
|
self.action_queue.push_front(next);
|
||||||
current_result: None,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
// panic!("{:#?}", self.action_queue.pop_front());
|
||||||
|
*current_prompt = prompt;
|
||||||
|
*current_result = None;
|
||||||
|
*current_changes = Vec::new();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(GameError::NoPreviousState)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -382,8 +382,10 @@ impl Night {
|
||||||
return Err(GameError::NotEndOfNight);
|
return Err(GameError::NotEndOfNight);
|
||||||
}
|
}
|
||||||
let mut new_village = self.village.clone();
|
let mut new_village = self.village.clone();
|
||||||
let mut changes = ChangesLookup::new(&self.changes);
|
let mut all_changes = Self::automatic_changes(&self.village, self.night);
|
||||||
for change in self.changes.iter() {
|
all_changes.append(&mut self.changes_from_actions().into_vec());
|
||||||
|
let mut changes = ChangesLookup::new(&all_changes);
|
||||||
|
for change in all_changes.iter() {
|
||||||
match change {
|
match change {
|
||||||
NightChange::ElderReveal { elder } => {
|
NightChange::ElderReveal { elder } => {
|
||||||
new_village.character_by_id_mut(*elder)?.elder_reveal()
|
new_village.character_by_id_mut(*elder)?.elder_reveal()
|
||||||
|
|
@ -418,10 +420,14 @@ impl Night {
|
||||||
kill.apply_to_village(&mut new_village)?;
|
kill.apply_to_village(&mut new_village)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NightChange::Shapeshift { source } => {
|
NightChange::Shapeshift { source, into } => {
|
||||||
if let Some(target) = changes.wolf_pack_kill_target()
|
if let Some(target) = changes.wolf_pack_kill_target()
|
||||||
&& changes.protected(target).is_none()
|
&& changes.protected(target).is_none()
|
||||||
{
|
{
|
||||||
|
if *target != *into {
|
||||||
|
log::error!("shapeshift into({into}) != target({target})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let ss = new_village.character_by_id_mut(*source).unwrap();
|
let ss = new_village.character_by_id_mut(*source).unwrap();
|
||||||
ss.shapeshifter_mut().unwrap().replace(*target);
|
ss.shapeshifter_mut().unwrap().replace(*target);
|
||||||
ss.kill(DiedTo::Shapeshift {
|
ss.kill(DiedTo::Shapeshift {
|
||||||
|
|
@ -538,29 +544,33 @@ impl Night {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_shapeshift(&mut self, source: &CharacterId) -> Result<()> {
|
fn apply_shapeshift(&mut self, source: &CharacterId) -> Result<()> {
|
||||||
if let Some(kill_target) = self.changes.iter().find_map(|c| match c {
|
if let Some(kill_target) = self
|
||||||
NightChange::Kill {
|
.changes_from_actions()
|
||||||
target,
|
.into_iter()
|
||||||
died_to:
|
.find_map(|c| match c {
|
||||||
DiedTo::Wolfpack {
|
NightChange::Kill {
|
||||||
night: _,
|
target,
|
||||||
killing_wolf: _,
|
died_to:
|
||||||
},
|
DiedTo::Wolfpack {
|
||||||
} => Some(*target),
|
night: _,
|
||||||
_ => None,
|
killing_wolf: _,
|
||||||
}) {
|
},
|
||||||
if self.changes.iter().any(|c| match c {
|
} => Some(target),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if self.changes_from_actions().into_iter().any(|c| match c {
|
||||||
NightChange::Protection {
|
NightChange::Protection {
|
||||||
target,
|
target,
|
||||||
protection: _,
|
protection: _,
|
||||||
} => target == &kill_target,
|
} => target == kill_target,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
// there is protection, so the kill doesn't happen -> no shapeshift
|
// there is protection, so the kill doesn't happen -> no shapeshift
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.changes.iter_mut().any(|c| {
|
if self.changes_from_actions().into_iter().any(|c| {
|
||||||
matches!(
|
matches!(
|
||||||
c,
|
c,
|
||||||
NightChange::Kill {
|
NightChange::Kill {
|
||||||
|
|
@ -572,16 +582,28 @@ impl Night {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
self.changes.push(NightChange::Kill {
|
match &mut self.night_state {
|
||||||
target: *source,
|
NightState::Active {
|
||||||
died_to: DiedTo::Shapeshift {
|
current_changes, ..
|
||||||
into: kill_target,
|
} => current_changes.push(NightChange::Kill {
|
||||||
night: NonZeroU8::new(self.night).unwrap(),
|
target: *source,
|
||||||
},
|
died_to: DiedTo::Shapeshift {
|
||||||
});
|
into: kill_target,
|
||||||
|
night: NonZeroU8::new(self.night).unwrap(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_ => return Err(GameError::InvalidMessageForGameState),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &mut self.night_state {
|
||||||
|
NightState::Active {
|
||||||
|
current_changes, ..
|
||||||
|
} => current_changes.push(NightChange::Shapeshift {
|
||||||
|
source: *source,
|
||||||
|
into: kill_target,
|
||||||
|
}),
|
||||||
|
_ => return Err(GameError::InvalidMessageForGameState),
|
||||||
}
|
}
|
||||||
self.changes
|
|
||||||
.push(NightChange::Shapeshift { source: *source });
|
|
||||||
self.action_queue.push_front(ActionPrompt::RoleChange {
|
self.action_queue.push_front(ActionPrompt::RoleChange {
|
||||||
new_role: RoleTitle::Werewolf,
|
new_role: RoleTitle::Werewolf,
|
||||||
character_id: self.village.character_by_id(kill_target)?.identity(),
|
character_id: self.village.character_by_id(kill_target)?.identity(),
|
||||||
|
|
@ -617,10 +639,11 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt: _,
|
current_prompt: _,
|
||||||
current_result,
|
current_result,
|
||||||
|
..
|
||||||
} => current_result.replace(result.clone()),
|
} => current_result.replace(result.clone()),
|
||||||
NightState::Complete => return Err(GameError::NightOver),
|
NightState::Complete => return Err(GameError::NightOver),
|
||||||
};
|
};
|
||||||
if let NightChange::Shapeshift { source } = &change {
|
if let NightChange::Shapeshift { source, into } = &change {
|
||||||
// needs to be resolved _now_ so that the target can be woken
|
// needs to be resolved _now_ so that the target can be woken
|
||||||
// for the role change with the wolves
|
// for the role change with the wolves
|
||||||
self.apply_shapeshift(source)?;
|
self.apply_shapeshift(source)?;
|
||||||
|
|
@ -639,7 +662,12 @@ impl Night {
|
||||||
{
|
{
|
||||||
result = self.apply_mason_recruit(*mason_leader, *recruiting)?;
|
result = self.apply_mason_recruit(*mason_leader, *recruiting)?;
|
||||||
}
|
}
|
||||||
self.changes.push(change);
|
match &mut self.night_state {
|
||||||
|
NightState::Active {
|
||||||
|
current_changes, ..
|
||||||
|
} => current_changes.push(change),
|
||||||
|
NightState::Complete => return Err(GameError::InvalidMessageForGameState),
|
||||||
|
}
|
||||||
Ok(ServerAction::Result(result))
|
Ok(ServerAction::Result(result))
|
||||||
}
|
}
|
||||||
BlockResolvedOutcome::ActionComplete(result, None) => {
|
BlockResolvedOutcome::ActionComplete(result, None) => {
|
||||||
|
|
@ -647,6 +675,7 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt: _,
|
current_prompt: _,
|
||||||
current_result,
|
current_result,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
current_result.replace(result.clone());
|
current_result.replace(result.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -693,13 +722,13 @@ impl Night {
|
||||||
(
|
(
|
||||||
ResponseOutcome::ActionComplete(ActionComplete {
|
ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::GoBackToSleep,
|
result: ActionResult::GoBackToSleep,
|
||||||
change: Some(NightChange::Shapeshift { source }),
|
change: Some(NightChange::Shapeshift { source, into }),
|
||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
_,
|
_,
|
||||||
) => Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
) => Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::Continue,
|
result: ActionResult::Continue,
|
||||||
change: Some(NightChange::Shapeshift { source }),
|
change: Some(NightChange::Shapeshift { source, into }),
|
||||||
})),
|
})),
|
||||||
(
|
(
|
||||||
ResponseOutcome::ActionComplete(ActionComplete {
|
ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
|
|
@ -725,12 +754,12 @@ impl Night {
|
||||||
ResponseOutcome::ActionComplete(ActionComplete { result, change }) => {
|
ResponseOutcome::ActionComplete(ActionComplete { result, change }) => {
|
||||||
match self.current_prompt().ok_or(GameError::NightOver)?.unless() {
|
match self.current_prompt().ok_or(GameError::NightOver)?.unless() {
|
||||||
Some(Unless::TargetBlocked(unless_blocked)) => {
|
Some(Unless::TargetBlocked(unless_blocked)) => {
|
||||||
if self.changes.iter().any(|c| match c {
|
if self.changes_from_actions().into_iter().any(|c| match c {
|
||||||
NightChange::RoleBlock {
|
NightChange::RoleBlock {
|
||||||
source: _,
|
source: _,
|
||||||
target,
|
target,
|
||||||
block_type: _,
|
block_type: _,
|
||||||
} => target == &unless_blocked,
|
} => target == unless_blocked,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
Ok(BlockResolvedOutcome::ActionComplete(
|
Ok(BlockResolvedOutcome::ActionComplete(
|
||||||
|
|
@ -742,12 +771,12 @@ impl Night {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Unless::TargetsBlocked(unless_blocked1, unless_blocked2)) => {
|
Some(Unless::TargetsBlocked(unless_blocked1, unless_blocked2)) => {
|
||||||
if self.changes.iter().any(|c| match c {
|
if self.changes_from_actions().into_iter().any(|c| match c {
|
||||||
NightChange::RoleBlock {
|
NightChange::RoleBlock {
|
||||||
source: _,
|
source: _,
|
||||||
target,
|
target,
|
||||||
block_type: _,
|
block_type: _,
|
||||||
} => target == &unless_blocked1 || target == &unless_blocked2,
|
} => target == unless_blocked1 || target == unless_blocked2,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
Ok(BlockResolvedOutcome::ActionComplete(
|
Ok(BlockResolvedOutcome::ActionComplete(
|
||||||
|
|
@ -769,10 +798,12 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt: _,
|
current_prompt: _,
|
||||||
current_result: Some(_),
|
current_result: Some(_),
|
||||||
|
..
|
||||||
} => return Err(GameError::NightNeedsNext),
|
} => return Err(GameError::NightNeedsNext),
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt,
|
current_prompt,
|
||||||
current_result: None,
|
current_result: None,
|
||||||
|
..
|
||||||
} => current_prompt,
|
} => current_prompt,
|
||||||
NightState::Complete => return Err(GameError::NightOver),
|
NightState::Complete => return Err(GameError::NightOver),
|
||||||
};
|
};
|
||||||
|
|
@ -791,6 +822,17 @@ impl Night {
|
||||||
result: ActionResult::GoBackToSleep,
|
result: ActionResult::GoBackToSleep,
|
||||||
change: Some(NightChange::Shapeshift {
|
change: Some(NightChange::Shapeshift {
|
||||||
source: source.character_id,
|
source: source.character_id,
|
||||||
|
into: self
|
||||||
|
.changes_from_actions()
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|c| match c {
|
||||||
|
NightChange::Kill {
|
||||||
|
target,
|
||||||
|
died_to: DiedTo::Wolfpack { .. },
|
||||||
|
} => Some(target),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.ok_or(GameError::InvalidTarget)?,
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
_ => Err(GameError::InvalidMessageForGameState),
|
_ => Err(GameError::InvalidMessageForGameState),
|
||||||
|
|
@ -1019,9 +1061,24 @@ impl Night {
|
||||||
ActionPrompt::Shapeshifter { character_id } => {
|
ActionPrompt::Shapeshifter { character_id } => {
|
||||||
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::GoBackToSleep,
|
result: ActionResult::GoBackToSleep,
|
||||||
change: Some(NightChange::Shapeshift {
|
change: match &resp {
|
||||||
source: character_id.character_id,
|
ActionResponse::Continue => None,
|
||||||
}),
|
ActionResponse::Shapeshift => Some(NightChange::Shapeshift {
|
||||||
|
source: character_id.character_id,
|
||||||
|
into: self
|
||||||
|
.changes_from_actions()
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|c| match c {
|
||||||
|
NightChange::Kill {
|
||||||
|
target,
|
||||||
|
died_to: DiedTo::Wolfpack { .. },
|
||||||
|
} => Some(target),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.ok_or(GameError::InvalidTarget)?,
|
||||||
|
}),
|
||||||
|
_ => return Err(GameError::InvalidMessageForGameState),
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
ActionPrompt::AlphaWolf {
|
ActionPrompt::AlphaWolf {
|
||||||
|
|
@ -1092,7 +1149,7 @@ impl Night {
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if let Some(result) = self.used_actions.iter().find_map(|(prompt, result)| {
|
if let Some(result) = self.used_actions.iter().find_map(|(prompt, result, _)| {
|
||||||
prompt.matches_beholding(*marked).then_some(result)
|
prompt.matches_beholding(*marked).then_some(result)
|
||||||
}) {
|
}) {
|
||||||
Ok(ActionComplete {
|
Ok(ActionComplete {
|
||||||
|
|
@ -1222,6 +1279,7 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt: _,
|
current_prompt: _,
|
||||||
current_result,
|
current_result,
|
||||||
|
..
|
||||||
} => current_result.as_ref(),
|
} => current_result.as_ref(),
|
||||||
NightState::Complete => None,
|
NightState::Complete => None,
|
||||||
}
|
}
|
||||||
|
|
@ -1232,6 +1290,7 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt,
|
current_prompt,
|
||||||
current_result: _,
|
current_result: _,
|
||||||
|
..
|
||||||
} => Some(current_prompt),
|
} => Some(current_prompt),
|
||||||
NightState::Complete => None,
|
NightState::Complete => None,
|
||||||
}
|
}
|
||||||
|
|
@ -1242,6 +1301,7 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt,
|
current_prompt,
|
||||||
current_result: _,
|
current_result: _,
|
||||||
|
..
|
||||||
} => match current_prompt {
|
} => match current_prompt {
|
||||||
ActionPrompt::Insomniac { character_id, .. }
|
ActionPrompt::Insomniac { character_id, .. }
|
||||||
| ActionPrompt::LoneWolfKill { character_id, .. }
|
| ActionPrompt::LoneWolfKill { character_id, .. }
|
||||||
|
|
@ -1290,13 +1350,18 @@ impl Night {
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt,
|
current_prompt,
|
||||||
current_result: Some(result),
|
current_result: Some(result),
|
||||||
|
current_changes,
|
||||||
} => {
|
} => {
|
||||||
self.used_actions
|
self.used_actions.push((
|
||||||
.push((current_prompt.clone(), result.clone()));
|
current_prompt.clone(),
|
||||||
|
result.clone(),
|
||||||
|
current_changes.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
NightState::Active {
|
NightState::Active {
|
||||||
current_prompt: _,
|
current_prompt: _,
|
||||||
current_result: None,
|
current_result: None,
|
||||||
|
..
|
||||||
} => return Err(GameError::AwaitingResponse),
|
} => return Err(GameError::AwaitingResponse),
|
||||||
NightState::Complete => return Err(GameError::NightOver),
|
NightState::Complete => return Err(GameError::NightOver),
|
||||||
}
|
}
|
||||||
|
|
@ -1311,6 +1376,7 @@ impl Night {
|
||||||
self.night_state = NightState::Active {
|
self.night_state = NightState::Active {
|
||||||
current_prompt: prompt,
|
current_prompt: prompt,
|
||||||
current_result: None,
|
current_result: None,
|
||||||
|
current_changes: Vec::new(),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
self.night_state = NightState::Complete;
|
self.night_state = NightState::Complete;
|
||||||
|
|
@ -1319,15 +1385,20 @@ impl Night {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn changes(&self) -> &[NightChange] {
|
fn changes_from_actions(&self) -> Box<[NightChange]> {
|
||||||
self.changes.as_slice()
|
self.used_actions
|
||||||
|
.iter()
|
||||||
|
.map(|(_, _, act)| act.into_iter())
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_visits_for(&self, visit_char: CharacterId) -> Visits {
|
pub fn get_visits_for(&self, visit_char: CharacterId) -> Visits {
|
||||||
Visits::new(
|
Visits::new(
|
||||||
self.used_actions
|
self.used_actions
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(prompt, _)| match prompt {
|
.filter_map(|(prompt, _, _)| match prompt {
|
||||||
ActionPrompt::Arcanist {
|
ActionPrompt::Arcanist {
|
||||||
character_id,
|
character_id,
|
||||||
marked: (Some(marked1), Some(marked2)),
|
marked: (Some(marked1), Some(marked2)),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod night_order;
|
mod night_order;
|
||||||
|
mod previous;
|
||||||
mod role;
|
mod role;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#[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},
|
||||||
|
message::{
|
||||||
|
host::ServerToHostMessage,
|
||||||
|
night::{ActionPrompt, ActionPromptTitle, ActionResponse},
|
||||||
|
},
|
||||||
|
role::RoleTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn previous_shapeshifter_undone_redone() {
|
||||||
|
let players = gen_players(1..21);
|
||||||
|
let shapeshifter_player_id = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::Shapeshifter, shapeshifter_player_id);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
settings.fill_remaining_slots_with_villagers(20);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
let ss_target = game.living_villager();
|
||||||
|
game.mark(ss_target.character_id());
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
|
||||||
|
game.next().title().shapeshifter();
|
||||||
|
game.response(ActionResponse::Shapeshift).r#continue();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
game.next(),
|
||||||
|
ActionPrompt::RoleChange {
|
||||||
|
character_id: ss_target.identity(),
|
||||||
|
new_role: RoleTitle::Werewolf
|
||||||
|
}
|
||||||
|
);
|
||||||
|
match game.game_state_mut() {
|
||||||
|
GameState::Night { night } => night.previous_state().unwrap(),
|
||||||
|
GameState::Day { .. } => unreachable!(),
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
game.get_state(),
|
||||||
|
ServerToHostMessage::ActionPrompt(ActionPrompt::Shapeshifter {
|
||||||
|
character_id: game
|
||||||
|
.character_by_player_id(shapeshifter_player_id)
|
||||||
|
.identity()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
game.response(ActionResponse::Shapeshift).r#continue();
|
||||||
|
assert_eq!(
|
||||||
|
game.next(),
|
||||||
|
ActionPrompt::RoleChange {
|
||||||
|
character_id: ss_target.identity(),
|
||||||
|
new_role: RoleTitle::Werewolf
|
||||||
|
}
|
||||||
|
);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn previous_shapeshifter_undone_and_changed_to_no() {
|
||||||
|
let players = gen_players(1..21);
|
||||||
|
let shapeshifter_player_id = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::Shapeshifter, shapeshifter_player_id);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
settings.fill_remaining_slots_with_villagers(20);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
let ss_target = game.living_villager();
|
||||||
|
game.mark(ss_target.character_id());
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
|
||||||
|
game.next().title().shapeshifter();
|
||||||
|
game.response(ActionResponse::Shapeshift).r#continue();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
game.next(),
|
||||||
|
ActionPrompt::RoleChange {
|
||||||
|
character_id: ss_target.identity(),
|
||||||
|
new_role: RoleTitle::Werewolf
|
||||||
|
}
|
||||||
|
);
|
||||||
|
match game.game_state_mut() {
|
||||||
|
GameState::Night { night } => night.previous_state().unwrap(),
|
||||||
|
GameState::Day { .. } => unreachable!(),
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
game.get_state(),
|
||||||
|
ServerToHostMessage::ActionPrompt(ActionPrompt::Shapeshifter {
|
||||||
|
character_id: game
|
||||||
|
.character_by_player_id(shapeshifter_player_id)
|
||||||
|
.identity()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
assert_eq!(
|
||||||
|
game.character_by_player_id(ss_target.player_id())
|
||||||
|
.role_changes(),
|
||||||
|
&[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,8 @@ use crate::{
|
||||||
game::{Game, GameSettings, OrRandom, SetupRole, night::NightChange},
|
game::{Game, GameSettings, OrRandom, SetupRole, night::NightChange},
|
||||||
game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players},
|
game_test::{ActionPromptTitleExt, ActionResultExt, GameExt, SettingsExt, gen_players},
|
||||||
message::night::{ActionPrompt, ActionPromptTitle},
|
message::night::{ActionPrompt, ActionPromptTitle},
|
||||||
role::{Alignment, RoleTitle},
|
player::RoleChange,
|
||||||
|
role::{Alignment, Role, RoleTitle},
|
||||||
};
|
};
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
|
|
@ -128,26 +129,18 @@ fn redeemed_scapegoat_role_changes() {
|
||||||
);
|
);
|
||||||
game.r#continue().sleep();
|
game.r#continue().sleep();
|
||||||
|
|
||||||
match game.game_state() {
|
|
||||||
crate::game::GameState::Night { night } => night
|
|
||||||
.changes()
|
|
||||||
.iter()
|
|
||||||
.find(|c| match c {
|
|
||||||
NightChange::RoleChange(char, role) => {
|
|
||||||
char == &scapegoat.character_id() && role == &RoleTitle::Seer
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.expect("no role change"),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
game.next_expect_day();
|
game.next_expect_day();
|
||||||
let day_scapegoat = game
|
|
||||||
.village()
|
let scapegoat = game.character_by_player_id(scapegoat_player_id);
|
||||||
.character_by_id(scapegoat.character_id())
|
assert_eq!(*scapegoat.role(), Role::Seer);
|
||||||
.unwrap();
|
assert_eq!(
|
||||||
assert_eq!(day_scapegoat.role().title(), RoleTitle::Seer);
|
scapegoat.role_changes(),
|
||||||
|
&[RoleChange {
|
||||||
|
role: Role::Scapegoat { redeemed: true },
|
||||||
|
new_role: RoleTitle::Seer,
|
||||||
|
changed_on_night: 2,
|
||||||
|
}]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
|
||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
game::{Game, GameSettings, SetupRole},
|
game::{Game, GameSettings, SetupRole},
|
||||||
game_test::{ActionResultExt, GameExt, ServerToHostMessageExt, gen_players, init_log},
|
game_test::{
|
||||||
|
ActionPromptTitleExt, ActionResultExt, GameExt, ServerToHostMessageExt, SettingsExt,
|
||||||
|
gen_players, init_log,
|
||||||
|
},
|
||||||
message::{
|
message::{
|
||||||
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||||
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult},
|
night::{ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult},
|
||||||
|
|
@ -204,3 +207,30 @@ fn only_1_shapeshift_prompt_if_first_shifts() {
|
||||||
|
|
||||||
game.next_expect_day();
|
game.next_expect_day();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn i_would_simply_refuse() {
|
||||||
|
let players = gen_players(1..21);
|
||||||
|
let shapeshifter_player_id = players[0].player_id;
|
||||||
|
let wolf_player_id = players[1].player_id;
|
||||||
|
let mut settings = GameSettings::empty();
|
||||||
|
settings.add_and_assign(SetupRole::Shapeshifter, shapeshifter_player_id);
|
||||||
|
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
|
||||||
|
settings.fill_remaining_slots_with_villagers(20);
|
||||||
|
let mut game = Game::new(&players, settings).unwrap();
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
|
||||||
|
game.execute().title().wolf_pack_kill();
|
||||||
|
let ss_target = game.living_villager();
|
||||||
|
game.mark(ss_target.character_id());
|
||||||
|
game.r#continue().r#continue();
|
||||||
|
|
||||||
|
game.next().title().shapeshifter();
|
||||||
|
game.r#continue().sleep();
|
||||||
|
|
||||||
|
game.next_expect_day();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ pub enum KillOutcome {
|
||||||
Failed,
|
Failed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct RoleChange {
|
pub struct RoleChange {
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
pub new_role: RoleTitle,
|
pub new_role: RoleTitle,
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ pub enum ArcanistCheck {
|
||||||
pub const MAPLE_WOLF_ABSTAIN_LIMIT: NonZeroU8 = NonZeroU8::new(3).unwrap();
|
pub const MAPLE_WOLF_ABSTAIN_LIMIT: NonZeroU8 = NonZeroU8::new(3).unwrap();
|
||||||
pub const PYREMASTER_VILLAGER_KILLS_TO_DIE: NonZeroU8 = NonZeroU8::new(2).unwrap();
|
pub const PYREMASTER_VILLAGER_KILLS_TO_DIE: NonZeroU8 = NonZeroU8::new(2).unwrap();
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum RoleBlock {
|
pub enum RoleBlock {
|
||||||
Direwolf,
|
Direwolf,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue