// 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 kill; pub mod night; mod settings; pub mod story; mod village; use core::{ fmt::{Debug, Display}, num::NonZeroU8, ops::{Deref, Range, RangeBounds}, }; use rand::{Rng, seq::SliceRandom}; use serde::{Deserialize, Serialize}; use crate::{ character::CharacterId, error::GameError, game::{ night::{Night, ServerAction}, story::{DayDetail, GameActions, GameStory, NightDetails}, }, message::{ CharacterState, Identification, host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage}, night::ActionResponse, }, }; pub use { settings::{Category, GameSettings, OrRandom, SetupRole, SetupRoleTitle, SetupSlot, SlotId}, village::Village, }; type Result = core::result::Result; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Game { history: GameStory, state: GameState, } impl Game { pub fn new(players: &[Identification], settings: GameSettings) -> Result { let village = Village::new(players, settings)?; Ok(Self { history: GameStory::new(village.clone()), state: GameState::Night { night: Night::new(village)?, }, }) } pub const fn village(&self) -> &Village { match &self.state { GameState::Day { village, marked: _ } => village, GameState::Night { night } => night.village(), } } pub fn process(&mut self, message: HostGameMessage) -> Result { match (&mut self.state, message) { (GameState::Night { night }, HostGameMessage::Night(HostNightMessage::NextPage)) => { night.next_page(); self.process(HostGameMessage::GetState) } (GameState::Night { night }, HostGameMessage::Night(HostNightMessage::Next)) => { night.next()?; self.process(HostGameMessage::GetState) } ( GameState::Day { village: _, marked }, HostGameMessage::Day(HostDayMessage::MarkForExecution(target)), ) => { match marked .iter() .enumerate() .find_map(|(idx, mark)| (mark == &target).then_some(idx)) { Some(idx) => { marked.swap_remove(idx); } None => marked.push(target), } self.process(HostGameMessage::GetState) } (GameState::Day { village, marked }, HostGameMessage::Day(HostDayMessage::Execute)) => { let time = village.time(); if let Some(outcome) = village.execute(marked)? { log::warn!("adding to history for {}", village.time()); self.history.add( village.time(), GameActions::DayDetails( marked.iter().map(|c| DayDetail::Execute(*c)).collect(), ), )?; return Ok(ServerToHostMessage::GameOver(outcome)); } let night = Night::new(village.clone())?; log::warn!("adding to history for {time}"); self.history.add( time, GameActions::DayDetails( marked .iter() .map(|mark| DayDetail::Execute(*mark)) .collect(), ), )?; self.state = GameState::Night { night }; self.process(HostGameMessage::GetState) } (GameState::Day { village, marked }, HostGameMessage::GetState) => { if let Some(outcome) = village.is_game_over() { return Ok(ServerToHostMessage::GameOver(outcome)); } Ok(ServerToHostMessage::Daytime { marked: marked.clone().into_boxed_slice(), characters: village .characters() .into_iter() .map(|c| CharacterState { player_id: c.player_id(), identity: c.identity(), role: c.role_title(), died_to: c.died_to().cloned(), }) .collect(), day: match village.time() { GameTime::Day { number } => number, GameTime::Night { number: _ } => unreachable!(), }, settings: village.settings(), }) } (GameState::Night { night }, HostGameMessage::GetState) => { if let Some(res) = night.current_result() { return Ok(ServerToHostMessage::ActionResult( night.current_character().map(|c| c.identity()), res.clone(), )); } if let Some((prompt, page)) = night.current_prompt() { return Ok(ServerToHostMessage::ActionPrompt(prompt.clone(), page)); } match night.next() { Ok(_) => self.process(HostGameMessage::GetState), Err(GameError::NightOver) => { let changes = night.collect_changes()?; let village = night.village().with_night_changes(&changes)?; log::warn!("adding to history for {}", night.village().time()); self.history.add( night.village().time(), GameActions::NightDetails(NightDetails::new( &night.used_actions(), changes, )), )?; self.state = GameState::Day { village, marked: Vec::new(), }; self.process(HostGameMessage::GetState) } Err(err) => Err(err), } } ( GameState::Night { night }, HostGameMessage::Night(HostNightMessage::ActionResponse(ActionResponse::Continue)), ) => match night.continue_result()? { ServerAction::Prompt(prompt) => Ok(ServerToHostMessage::ActionPrompt( prompt, night.page().unwrap_or_default(), )), ServerAction::Result(result) => Ok(ServerToHostMessage::ActionResult( night.current_character().map(|c| c.identity()), result, )), }, ( GameState::Night { night }, HostGameMessage::Night(HostNightMessage::ActionResponse(resp)), ) => match night.received_response(resp.clone()) { Ok(ServerAction::Prompt(prompt)) => Ok(ServerToHostMessage::ActionPrompt( prompt, night.page().unwrap_or_default(), )), Ok(ServerAction::Result(res)) => Ok(ServerToHostMessage::ActionResult( night.current_character().map(|c| c.identity()), res, )), Err(GameError::NightNeedsNext) => match night.next() { Ok(_) => self.process(HostGameMessage::Night( HostNightMessage::ActionResponse(resp), )), Err(GameError::NightOver) => { // since the block handling HostGameMessage::GetState for night // already manages the NightOver state, just invoke it self.process(HostGameMessage::GetState) } Err(err) => Err(err), }, Err(err) => Err(err), }, (GameState::Night { night: _ }, HostGameMessage::Day(_)) | ( GameState::Day { village: _, marked: _, }, HostGameMessage::Night(_), ) => Err(GameError::InvalidMessageForGameState), (GameState::Day { .. }, HostGameMessage::PreviousState) => { Err(GameError::NoPreviousDuringDay) } (GameState::Night { night }, HostGameMessage::PreviousState) => { night.previous_state()?; self.process(HostGameMessage::GetState) } } } pub fn story(&self) -> GameStory { self.history.clone() } pub fn game_over(&self) -> Option { self.state.game_over() } pub fn game_state(&self) -> &GameState { &self.state } #[cfg(test)] pub fn game_state_mut(&mut self) -> &mut GameState { &mut self.state } } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum GameState { Day { village: Village, marked: Vec, }, Night { night: Night, }, } impl GameState { pub fn game_over(&self) -> Option { match self { GameState::Day { village, marked: _ } => village.is_game_over(), GameState::Night { night: _ } => None, } } } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum GameOver { VillageWins, WolvesWin, } impl Display for GameOver { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GameOver::VillageWins => f.write_str("village wins"), GameOver::WolvesWin => f.write_str("wolves win"), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Pool where T: Debug + Clone, { pub pool: Vec, pub range: Range, } impl Pool where T: Debug + Clone, { pub const fn new(pool: Vec, range: Range) -> Self { Self { pool, range } } pub fn collapse(mut self, rng: &mut impl Rng, max: u8) -> Vec { let range = match self.range.end_bound() { core::ops::Bound::Included(end) => { if max < *end { self.range.start..max + 1 } else { self.range.clone() } } core::ops::Bound::Excluded(end) => { if max <= *end { self.range.start..max + 1 } else { self.range.clone() } } core::ops::Bound::Unbounded => self.range.start..max + 1, }; let count = rng.random_range(range); self.pool.shuffle(rng); self.pool.truncate(count as _); self.pool } } impl Deref for Pool where T: Debug + Clone, { type Target = [T]; fn deref(&self) -> &Self::Target { &self.pool } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Maybe { Yes, No, Maybe, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] pub enum GameTime { Day { number: NonZeroU8 }, Night { number: u8 }, } impl Display for GameTime { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GameTime::Day { number } => write!(f, "Day {number}"), GameTime::Night { number } => write!(f, "Night {number}"), } } } impl Default for GameTime { fn default() -> Self { GameTime::Day { number: NonZeroU8::new(1).unwrap(), } } } impl GameTime { pub const fn is_day(&self) -> bool { matches!(self, GameTime::Day { number: _ }) } pub const fn is_night(&self) -> bool { matches!(self, GameTime::Night { number: _ }) } pub const fn next(self) -> Self { match self { GameTime::Day { number } => GameTime::Night { number: number.get(), }, GameTime::Night { number } => GameTime::Day { number: NonZeroU8::new(number + 1).unwrap(), }, } } pub const fn previous(self) -> Option { match self { GameTime::Day { number } => Some(GameTime::Night { number: number.get() - 1, }), GameTime::Night { number } => match NonZeroU8::new(number) { Some(number) => Some(GameTime::Day { number }), None => None, }, } } }