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