apprentice now wakes in role-appropriate night order

This commit is contained in:
emilis 2025-11-07 20:51:40 +00:00
parent 082f0bba38
commit 8b894b4c8c
No known key found for this signature in database
5 changed files with 133 additions and 32 deletions

View File

@ -195,6 +195,15 @@ impl Default for ActionComplete {
}
}
fn night_sort_order(
left_prompt: &ActionPrompt,
right_prompt: &ActionPrompt,
) -> core::cmp::Ordering {
left_prompt
.partial_cmp(right_prompt)
.unwrap_or(core::cmp::Ordering::Equal)
}
enum Unless {
TargetBlocked(CharacterId),
TargetsBlocked(CharacterId, CharacterId),
@ -279,15 +288,14 @@ impl Night {
.flatten()
.chain(village.wolf_pack_kill())
.collect::<Vec<_>>();
action_queue.sort_by(|left_prompt, right_prompt| {
left_prompt
.partial_cmp(right_prompt)
.unwrap_or(core::cmp::Ordering::Equal)
});
action_queue.sort_by(night_sort_order);
let mut action_queue = VecDeque::from({
// insert actions for role-changed roles
let mut expanded_queue = Vec::new();
let mut role_changes = Vec::new();
// here we replace the role change prompts with the prompt they *would* have gotten
// (if any). if they wouldn't get a prompt, just add the role change prompt back in
for action in action_queue {
match &action {
ActionPrompt::RoleChange {
@ -296,8 +304,15 @@ impl Night {
} => {
let char = village.character_by_id(character_id.character_id)?;
let as_role = char.as_role(new_role.title_to_role_excl_apprentice());
let prompts = as_role.night_action_prompts(&village)?;
if prompts.is_empty() {
// they wouldn't get a prompt alongside the role change, so just add
// the role change prompt back in
expanded_queue.push(action);
for prompt in as_role.night_action_prompts(&village)? {
continue;
}
role_changes.push((character_id.character_id, *new_role));
for prompt in prompts {
expanded_queue.push(prompt);
}
}
@ -307,7 +322,23 @@ impl Night {
}
}
}
expanded_queue
expanded_queue.sort_by(night_sort_order);
let mut expanded_queue_with_role_changes =
Vec::with_capacity(expanded_queue.len() + role_changes.len());
for prompt in expanded_queue {
if let Some(char) = prompt.character_id()
&& let Some((_, role)) = role_changes.iter().find(|(c, _)| *c == char)
{
expanded_queue_with_role_changes.push(ActionPrompt::RoleChange {
character_id: village.character_by_id(char)?.identity(),
new_role: *role,
});
expanded_queue_with_role_changes.push(prompt);
} else {
expanded_queue_with_role_changes.push(prompt);
}
}
expanded_queue_with_role_changes
});
if night == 0 {

View File

@ -699,18 +699,7 @@ fn yes_wolf_kill_n2() {
.unwrap(),
ServerToHostMessage::ActionResult(None, ActionResult::Continue)
);
assert!(matches!(
game.process(HostGameMessage::Night(HostNightMessage::Next))
.unwrap(),
ServerToHostMessage::ActionPrompt(
ActionPrompt::WolfPackKill {
living_villagers: _,
marked: _,
},
0
)
));
game.next().title().wolf_pack_kill();
}
#[test]

View File

@ -0,0 +1,79 @@
// Copyright (C) 2025 Emilis Bliūdžius
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use core::num::NonZeroU8;
#[allow(unused)]
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
use crate::{
diedto::DiedTo,
game::{Game, GameSettings, OrRandom, SetupRole},
game_test::{
ActionPromptTitleExt, ActionResultExt, AlignmentExt, GameExt, ServerToHostMessageExt,
SettingsExt, gen_players,
},
message::{
host::{HostDayMessage, HostGameMessage},
night::{ActionPrompt, ActionPromptTitle, ActionResult},
},
role::{Role, RoleTitle},
};
#[test]
fn beholder_appropriate_prompt_position() {
let players = gen_players(1..10);
let apprentice = players[0].player_id;
let beholder = players[1].player_id;
let wolf_player_id = players[2].player_id;
let mut settings = GameSettings::empty();
settings.add_and_assign(
SetupRole::Apprentice {
to: Some(RoleTitle::Beholder),
},
apprentice,
);
settings.add_and_assign(SetupRole::Beholder, beholder);
settings.add_and_assign(SetupRole::Werewolf, wolf_player_id);
settings.fill_remaining_slots_with_villagers(9);
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();
game.mark(game.character_by_player_id(beholder).character_id());
game.r#continue().sleep();
game.next().title().beholder();
game.mark(game.character_by_player_id(wolf_player_id).character_id());
game.r#continue().sleep();
game.next_expect_day();
game.execute().title().wolf_pack_kill();
game.mark(game.living_villager().character_id());
game.r#continue().sleep();
assert_eq!(
game.next(),
ActionPrompt::RoleChange {
character_id: game.character_by_player_id(apprentice).identity(),
new_role: RoleTitle::Beholder
}
);
game.r#continue().r#continue();
game.next().title().beholder();
}

View File

@ -12,6 +12,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod apprentice;
mod beholder;
mod black_knight;
mod diseased;

View File

@ -118,8 +118,20 @@ fn redeemed_scapegoat_role_changes() {
night: NonZero::new(1).unwrap()
}
);
assert_eq!(game.execute().title(), ActionPromptTitle::WolfPackKill);
let wolf_target_2 = game
.village()
.characters()
.iter()
.find(|c| c.player_id() == wolf_target_2_player_id)
.unwrap()
.character_id();
game.mark_and_check(wolf_target_2);
game.r#continue().sleep();
assert_eq!(
game.execute(),
game.next(),
ActionPrompt::RoleChange {
character_id: game.character_by_player_id(scapegoat_player_id).identity(),
new_role: RoleTitle::Seer
@ -131,17 +143,6 @@ fn redeemed_scapegoat_role_changes() {
assert_eq!(game.r#continue().seer(), Alignment::Wolves);
game.r#continue().sleep();
assert_eq!(game.next().title(), ActionPromptTitle::WolfPackKill);
let wolf_target_2 = game
.village()
.characters()
.iter()
.find(|c| c.player_id() == wolf_target_2_player_id)
.unwrap()
.character_id();
game.mark_and_check(wolf_target_2);
game.r#continue().sleep();
game.next_expect_day();
let scapegoat = game.character_by_player_id(scapegoat_player_id);