2026-02-01 22:18:27 +00:00
|
|
|
// Copyright (C) 2025-2026 Emilis Bliūdžius
|
2025-11-05 20:24:51 +00:00
|
|
|
//
|
|
|
|
|
// 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/>.
|
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-06 20:45:15 +01:00
|
|
|
use crate::{character::Character, error::GameError, message::Identification, 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
|
|
|
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)
|
2025-10-05 10:54:47 +01:00
|
|
|
.ok_or(GameError::AssignedPlayerMissing(*assign_to))
|
2025-10-04 17:50:29 +01:00
|
|
|
.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)
|
|
|
|
|
{
|
2025-10-05 10:54:47 +01:00
|
|
|
return Err(GameError::AssignedPlayerMissing(*assigned));
|
2025-10-04 17:50:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2025-10-06 01:03:16 +01:00
|
|
|
SetupRole::Apprentice { to: None } => (mentor_count > 0)
|
2025-10-04 17:50:29 +01:00
|
|
|
.then_some(())
|
|
|
|
|
.ok_or(GameError::NoApprenticeMentor),
|
2025-10-06 20:45:15 +01:00
|
|
|
SetupRole::Apprentice { to: Some(role) } => role
|
2025-10-04 17:50:29 +01:00
|
|
|
.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 {
|
2025-11-16 18:51:19 +00:00
|
|
|
(2 * self.wolves_count() + 1).max(3)
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|