// 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 .
mod apply;
use core::num::NonZeroU8;
use rand::Rng;
use serde::{Deserialize, Serialize};
use super::Result;
use crate::{
character::{Character, CharacterId},
diedto::DiedTo,
error::GameError,
game::{GameOver, GameSettings, GameTime},
message::{CharacterIdentity, Identification, night::ActionPrompt},
player::PlayerId,
role::{Role, RoleTitle},
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Village {
characters: Box<[Character]>,
time: GameTime,
settings: GameSettings,
}
impl Village {
pub fn new(players: &[Identification], settings: GameSettings) -> Result {
if settings.min_players_needed() > players.len() {
return Err(GameError::TooManyRoles {
players: players.len() as u8,
roles: settings.min_players_needed() as u8,
});
}
let mut characters = settings.assign(players)?;
assert_eq!(characters.len(), players.len());
characters.sort_by_key(|l| l.number());
Ok(Self {
settings,
characters,
time: GameTime::Night { number: 0 },
})
}
pub fn settings(&self) -> GameSettings {
self.settings.clone()
}
pub fn killing_wolf(&self) -> Option<&Character> {
let mut wolves = self
.characters
.iter()
.filter(|c| c.alive() && c.is_wolf())
.collect::>();
wolves.sort_by_key(|w| w.killing_wolf_order());
wolves.first().copied()
}
pub fn wolf_revert_prompt(&self) -> Option {
self.killing_wolf()
.filter(|killing_wolf| RoleTitle::Werewolf != killing_wolf.role_title())
.map(|killing_wolf| ActionPrompt::RoleChange {
character_id: killing_wolf.identity(),
new_role: RoleTitle::Werewolf,
})
}
pub fn wolf_pack_kill(&self) -> Option {
let night = match self.time {
GameTime::Day { .. } => return None,
GameTime::Night { number } => number,
};
let no_kill_due_to_disease = self
.characters
.iter()
.filter(|d| matches!(d.role_title(), RoleTitle::Diseased))
.any(|d| match d.died_to() {
Some(DiedTo::Wolfpack {
night: diseased_death_night,
..
}) => (diseased_death_night.get() + 1) == night,
_ => false,
});
(night > 0 && !no_kill_due_to_disease).then_some(ActionPrompt::WolfPackKill {
marked: None,
living_villagers: self.living_villagers(),
})
}
pub const fn time(&self) -> GameTime {
self.time
}
pub fn find_by_character_id_mut(
&mut self,
character_id: CharacterId,
) -> Option<&mut Character> {
self.characters
.iter_mut()
.find(|c| c.character_id() == character_id)
}
fn living_wolves_count(&self) -> usize {
self.characters
.iter()
.filter(|c| c.is_wolf() && c.alive())
.count()
}
fn living_villager_count(&self) -> usize {
self.characters
.iter()
.filter(|c| c.is_village() && c.alive())
.count()
}
pub fn is_game_over(&self) -> Option {
let wolves = self.living_wolves_count();
let villagers = self.living_villager_count();
let weightlifters = self
.living_characters_by_role(RoleTitle::Weightlifter)
.len();
if weightlifters > 0 && wolves == 1 && villagers == 1 {
return Some(GameOver::VillageWins);
}
if wolves == 0 {
return Some(GameOver::VillageWins);
}
if wolves >= villagers {
return Some(GameOver::WolvesWin);
}
None
}
pub fn execute(&mut self, characters: &[CharacterId]) -> Result