werewolves/werewolves-proto/src/game/settings.rs

269 lines
8.2 KiB
Rust

// Copyright (C) 2025-2026 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/>.
mod settings_role;
use std::collections::HashMap;
use rand::seq::SliceRandom;
pub use settings_role::*;
use super::Result;
use serde::{Deserialize, Serialize};
use crate::{character::Character, error::GameError, message::Identification, role::RoleTitle};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GameSettings {
roles: Vec<SetupSlot>,
next_order: u32,
}
impl Default for GameSettings {
fn default() -> Self {
Self {
roles: vec![SetupSlot::new(RoleTitle::Werewolf, 0)],
next_order: 1,
}
}
}
impl GameSettings {
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);
}
}
pub fn wolves_count(&self) -> usize {
self.roles
.iter()
.filter(|s| s.role.category().is_wolves())
.count()
}
pub fn slots(&self) -> &[SetupSlot] {
&self.roles
}
pub fn get_slot_by_id(&self, slot_id: SlotId) -> Option<&SetupSlot> {
self.roles.iter().find(|s| s.slot_id == slot_id)
}
pub fn village_roles_count(&self) -> usize {
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
.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);
}
}
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();
}
}
}
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))
.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<[_]>>>()
}
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,
});
}
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));
}
}
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(())
}
pub fn check(&self) -> Result<()> {
if self.wolves_count() == 0 {
return Err(GameError::NoWolves);
}
let mentor_count = self
.roles
.iter()
.filter(|r| Into::<RoleTitle>::into(r.role.clone()).is_mentor())
.count();
self.roles.iter().try_for_each(|s| match &s.role {
SetupRole::Apprentice { to: None } => (mentor_count > 0)
.then_some(())
.ok_or(GameError::NoApprenticeMentor),
SetupRole::Apprentice { to: Some(role) } => role
.is_mentor()
.then_some(())
.ok_or(GameError::NotAMentor(*role)),
_ => Ok(()),
})?;
Ok(())
}
pub fn min_players_needed(&self) -> usize {
(2 * self.wolves_count() + 1).max(3)
}
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;
}
}
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))
{
self.roles.swap_remove(idx);
self.sort_roles();
}
}
fn sort_roles(&mut self) {
self.roles
.sort_by(|l, r| l.created_order.cmp(&r.created_order).reverse());
}
}