2025-10-04 17:50:29 +01:00
|
|
|
mod settings_role;
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
use rand::seq::SliceRandom;
|
|
|
|
|
pub use settings_role::*;
|
|
|
|
|
|
|
|
|
|
use super::Result;
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
use crate::{error::GameError, message::Identification, player::Character, role::RoleTitle};
|
2025-06-23 09:48:28 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
pub struct GameSettings {
|
2025-10-04 17:50:29 +01:00
|
|
|
roles: Vec<SetupSlot>,
|
|
|
|
|
next_order: u32,
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for GameSettings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2025-10-04 17:50:29 +01:00
|
|
|
roles: vec![SetupSlot::new(RoleTitle::Werewolf, 0)],
|
|
|
|
|
next_order: 1,
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GameSettings {
|
2025-10-05 10:52:37 +01:00
|
|
|
pub fn empty() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
roles: vec![],
|
|
|
|
|
next_order: 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn fill_remaining_slots_with_villagers(&mut self, player_count: usize) {
|
|
|
|
|
if self.roles.len() >= player_count {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for _ in 0..(player_count - self.roles.len()) {
|
|
|
|
|
self.new_slot(RoleTitle::Villager);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
pub fn wolves_count(&self) -> usize {
|
|
|
|
|
self.roles
|
|
|
|
|
.iter()
|
2025-10-04 17:50:29 +01:00
|
|
|
.filter(|s| s.role.category().is_wolves())
|
|
|
|
|
.count()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn slots(&self) -> &[SetupSlot] {
|
|
|
|
|
&self.roles
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 10:52:37 +01:00
|
|
|
pub fn get_slot_by_id(&self, slot_id: SlotId) -> Option<&SetupSlot> {
|
|
|
|
|
self.roles.iter().find(|s| s.slot_id == slot_id)
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
pub fn village_roles_count(&self) -> usize {
|
2025-10-04 17:50:29 +01:00
|
|
|
log::warn!(
|
|
|
|
|
"wolves: {} total: {}",
|
|
|
|
|
self.wolves_count(),
|
|
|
|
|
self.roles.len()
|
|
|
|
|
);
|
|
|
|
|
self.roles.len() - self.wolves_count()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_assignments_not_in_list(&mut self, list: &[Identification]) {
|
|
|
|
|
self.roles.iter_mut().for_each(|r| {
|
|
|
|
|
if let Some(pid) = &r.assign_to
|
|
|
|
|
&& !list.iter().any(|i| i.player_id == *pid)
|
|
|
|
|
{
|
|
|
|
|
r.assign_to.take();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_duplicate_assignments(&mut self) {
|
|
|
|
|
let assignments = self
|
|
|
|
|
.roles
|
2025-06-23 09:48:28 +01:00
|
|
|
.iter()
|
2025-10-04 17:50:29 +01:00
|
|
|
.filter_map(|r| r.assign_to.as_ref())
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect::<Box<[_]>>();
|
|
|
|
|
let mut assignment_counter = HashMap::new();
|
|
|
|
|
for assign in assignments {
|
|
|
|
|
if let Some(counter) = assignment_counter.get_mut(&assign) {
|
|
|
|
|
*counter += 1;
|
|
|
|
|
} else {
|
|
|
|
|
assignment_counter.insert(assign, 1usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let to_remove = assignment_counter
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(|(pid, cnt)| (cnt > 1).then_some(pid))
|
|
|
|
|
.collect::<Box<[_]>>();
|
|
|
|
|
|
|
|
|
|
for role in self.roles.iter_mut() {
|
|
|
|
|
if let Some(pid) = role.assign_to.as_ref()
|
|
|
|
|
&& to_remove.contains(pid)
|
|
|
|
|
{
|
|
|
|
|
role.assign_to.take();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
pub fn assign(&self, players: &[Identification]) -> Result<Box<[Character]>> {
|
|
|
|
|
self.check_with_player_list(players)?;
|
|
|
|
|
|
|
|
|
|
let roles_in_game = self
|
|
|
|
|
.roles
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|r| r.role.clone().into())
|
|
|
|
|
.collect::<Box<[RoleTitle]>>();
|
|
|
|
|
|
|
|
|
|
let with_assigned_roles = self
|
|
|
|
|
.roles
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|s| {
|
|
|
|
|
s.assign_to.as_ref().map(|assign_to| {
|
|
|
|
|
players
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|pid| pid.player_id == *assign_to)
|
|
|
|
|
.ok_or(GameError::AssignedPlayerMissing(assign_to.clone()))
|
|
|
|
|
.map(|id| (id, s))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Result<Box<[_]>>>()?;
|
|
|
|
|
|
|
|
|
|
let mut random_assign_players = players
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|p| {
|
|
|
|
|
!with_assigned_roles
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|(r, _)| r.player_id == p.player_id)
|
|
|
|
|
})
|
|
|
|
|
.collect::<Box<[_]>>();
|
|
|
|
|
|
|
|
|
|
random_assign_players.shuffle(&mut rand::rng());
|
|
|
|
|
|
|
|
|
|
with_assigned_roles
|
|
|
|
|
.into_iter()
|
|
|
|
|
.chain(
|
|
|
|
|
random_assign_players
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(self.roles.iter().filter(|s| s.assign_to.is_none())),
|
|
|
|
|
)
|
|
|
|
|
.map(|(id, slot)| slot.clone().into_character(id.clone(), &roles_in_game))
|
|
|
|
|
.collect::<Result<Box<[_]>>>()
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
pub fn check_with_player_list(&self, players: &[Identification]) -> Result<()> {
|
|
|
|
|
self.check()?;
|
|
|
|
|
let (p_len, r_len) = (players.len(), self.roles.len());
|
|
|
|
|
if p_len > r_len {
|
|
|
|
|
return Err(GameError::TooManyPlayers {
|
|
|
|
|
got: p_len.min(0xFF) as u8,
|
|
|
|
|
need: self.roles.len().min(0xFF) as u8,
|
|
|
|
|
});
|
|
|
|
|
} else if p_len < r_len {
|
|
|
|
|
return Err(GameError::TooManyRoles {
|
|
|
|
|
players: p_len.min(0xFF) as u8,
|
|
|
|
|
roles: r_len.min(0xFF) as u8,
|
2025-06-23 09:48:28 +01:00
|
|
|
});
|
|
|
|
|
}
|
2025-10-04 17:50:29 +01:00
|
|
|
|
|
|
|
|
for role in self.roles.iter() {
|
|
|
|
|
if let Some(assigned) = role.assign_to.as_ref()
|
|
|
|
|
&& !players.iter().any(|p| p.player_id == *assigned)
|
|
|
|
|
{
|
|
|
|
|
return Err(GameError::AssignedPlayerMissing(assigned.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let assignments = self
|
|
|
|
|
.roles
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|r| r.assign_to.as_ref())
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect::<Box<[_]>>();
|
|
|
|
|
let mut assignment_counter = HashMap::new();
|
|
|
|
|
for assign in assignments {
|
|
|
|
|
if let Some(counter) = assignment_counter.get_mut(&assign) {
|
|
|
|
|
*counter += 1;
|
|
|
|
|
} else {
|
|
|
|
|
assignment_counter.insert(assign, 1usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some((assign, cnt)) = assignment_counter.into_iter().find(|(_, cnt)| *cnt > 1) {
|
|
|
|
|
let ident = players
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|i| i.player_id == assign)
|
|
|
|
|
.ok_or(GameError::AssignedPlayerMissing(assign))?;
|
|
|
|
|
return Err(GameError::AssignedMultipleTimes(ident.public.clone(), cnt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn check(&self) -> Result<()> {
|
|
|
|
|
if self.wolves_count() == 0 {
|
|
|
|
|
return Err(GameError::NoWolves);
|
|
|
|
|
}
|
2025-10-04 17:50:29 +01:00
|
|
|
let mentor_count = self
|
2025-06-23 09:48:28 +01:00
|
|
|
.roles
|
|
|
|
|
.iter()
|
2025-10-04 17:50:29 +01:00
|
|
|
.filter(|r| Into::<RoleTitle>::into(r.role.clone()).is_mentor())
|
|
|
|
|
.count();
|
|
|
|
|
self.roles.iter().try_for_each(|s| match &s.role {
|
|
|
|
|
SetupRole::Apprentice { specifically: None } => (mentor_count > 0)
|
|
|
|
|
.then_some(())
|
|
|
|
|
.ok_or(GameError::NoApprenticeMentor),
|
|
|
|
|
SetupRole::Apprentice {
|
|
|
|
|
specifically: Some(role),
|
|
|
|
|
} => role
|
|
|
|
|
.is_mentor()
|
|
|
|
|
.then_some(())
|
|
|
|
|
.ok_or(GameError::NotAMentor(*role)),
|
|
|
|
|
_ => Ok(()),
|
|
|
|
|
})?;
|
2025-06-23 09:48:28 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn min_players_needed(&self) -> usize {
|
|
|
|
|
let (wolves, villagers) = (self.wolves_count(), self.village_roles_count());
|
|
|
|
|
|
|
|
|
|
if wolves > villagers {
|
|
|
|
|
wolves + 1 + wolves
|
|
|
|
|
} else if wolves < villagers {
|
|
|
|
|
wolves + villagers
|
|
|
|
|
} else {
|
|
|
|
|
wolves + villagers + 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
pub fn new_slot(&mut self, role: RoleTitle) -> SlotId {
|
|
|
|
|
let slot = SetupSlot::new(role, self.next_order);
|
|
|
|
|
self.next_order += 1;
|
|
|
|
|
let slot_id = slot.slot_id;
|
|
|
|
|
self.roles.push(slot);
|
|
|
|
|
self.sort_roles();
|
|
|
|
|
slot_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_slot(&mut self, slot: SetupSlot) {
|
|
|
|
|
if let Some(old_slot) = self.roles.iter_mut().find(|r| r.slot_id == slot.slot_id) {
|
|
|
|
|
*old_slot = slot;
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
pub fn remove_slot(&mut self, slot_id: SlotId) {
|
|
|
|
|
if let Some(idx) = self
|
|
|
|
|
.roles
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find_map(|(idx, slot)| (slot.slot_id == slot_id).then_some(idx))
|
2025-06-23 09:48:28 +01:00
|
|
|
{
|
2025-10-04 17:50:29 +01:00
|
|
|
self.roles.swap_remove(idx);
|
|
|
|
|
self.sort_roles();
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 17:50:29 +01:00
|
|
|
|
|
|
|
|
fn sort_roles(&mut self) {
|
|
|
|
|
self.roles
|
|
|
|
|
.sort_by(|l, r| l.created_order.cmp(&r.created_order).reverse());
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|