2025-11-05 20:24:51 +00:00
|
|
|
// 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 <https://www.gnu.org/licenses/>.
|
2025-09-28 02:13:34 +01:00
|
|
|
mod kill;
|
2025-10-12 23:48:52 +01:00
|
|
|
pub mod night;
|
2025-06-23 09:48:28 +01:00
|
|
|
mod settings;
|
2025-10-12 23:48:52 +01:00
|
|
|
pub mod story;
|
2025-06-23 09:48:28 +01:00
|
|
|
mod village;
|
|
|
|
|
|
|
|
|
|
use core::{
|
2025-10-12 23:48:52 +01:00
|
|
|
fmt::{Debug, Display},
|
2025-06-23 09:48:28 +01:00
|
|
|
num::NonZeroU8,
|
|
|
|
|
ops::{Deref, Range, RangeBounds},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use rand::{Rng, seq::SliceRandom};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2025-10-06 20:45:15 +01:00
|
|
|
character::CharacterId,
|
2025-06-23 09:48:28 +01:00
|
|
|
error::GameError,
|
2025-10-12 23:48:52 +01:00
|
|
|
game::{
|
|
|
|
|
night::{Night, ServerAction},
|
|
|
|
|
story::{DayDetail, GameActions, GameStory, NightDetails},
|
|
|
|
|
},
|
2025-06-23 09:48:28 +01:00
|
|
|
message::{
|
|
|
|
|
CharacterState, Identification,
|
|
|
|
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
2025-10-17 21:16:10 +01:00
|
|
|
night::ActionResponse,
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
|
|
|
|
};
|
2025-10-05 01:22:55 +01:00
|
|
|
|
2025-10-04 17:50:29 +01:00
|
|
|
pub use {
|
2025-10-06 01:03:16 +01:00
|
|
|
settings::{Category, GameSettings, OrRandom, SetupRole, SetupRoleTitle, SetupSlot, SlotId},
|
2025-10-04 17:50:29 +01:00
|
|
|
village::Village,
|
|
|
|
|
};
|
2025-06-23 09:48:28 +01:00
|
|
|
|
|
|
|
|
type Result<T> = core::result::Result<T, GameError>;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Game {
|
2025-10-12 23:48:52 +01:00
|
|
|
history: GameStory,
|
2025-06-23 09:48:28 +01:00
|
|
|
state: GameState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Game {
|
|
|
|
|
pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> {
|
2025-10-12 23:48:52 +01:00
|
|
|
let village = Village::new(players, settings)?;
|
2025-06-23 09:48:28 +01:00
|
|
|
Ok(Self {
|
2025-10-12 23:48:52 +01:00
|
|
|
history: GameStory::new(village.clone()),
|
2025-06-23 09:48:28 +01:00
|
|
|
state: GameState::Night {
|
2025-10-12 23:48:52 +01:00
|
|
|
night: Night::new(village)?,
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<ServerToHostMessage> {
|
|
|
|
|
match (&mut self.state, message) {
|
2025-10-09 22:27:21 +01:00
|
|
|
(GameState::Night { night }, HostGameMessage::Night(HostNightMessage::NextPage)) => {
|
|
|
|
|
night.next_page();
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
(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)) => {
|
2025-10-12 23:48:52 +01:00
|
|
|
let time = village.time();
|
2025-06-23 09:48:28 +01:00
|
|
|
if let Some(outcome) = village.execute(marked)? {
|
2025-10-12 23:48:52 +01:00
|
|
|
log::warn!("adding to history for {}", village.time());
|
|
|
|
|
self.history.add(
|
|
|
|
|
village.time(),
|
|
|
|
|
GameActions::DayDetails(
|
|
|
|
|
marked.iter().map(|c| DayDetail::Execute(*c)).collect(),
|
|
|
|
|
),
|
|
|
|
|
)?;
|
2025-06-23 09:48:28 +01:00
|
|
|
return Ok(ServerToHostMessage::GameOver(outcome));
|
|
|
|
|
}
|
|
|
|
|
let night = Night::new(village.clone())?;
|
2025-10-12 23:48:52 +01:00
|
|
|
log::warn!("adding to history for {time}");
|
|
|
|
|
self.history.add(
|
|
|
|
|
time,
|
|
|
|
|
GameActions::DayDetails(
|
|
|
|
|
marked
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|mark| DayDetail::Execute(*mark))
|
|
|
|
|
.collect(),
|
|
|
|
|
),
|
|
|
|
|
)?;
|
2025-06-23 09:48:28 +01:00
|
|
|
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 {
|
2025-10-05 10:54:47 +01:00
|
|
|
player_id: c.player_id(),
|
2025-10-02 17:52:12 +01:00
|
|
|
identity: c.identity(),
|
2025-10-06 20:45:15 +01:00
|
|
|
role: c.role_title(),
|
2025-06-23 09:48:28 +01:00
|
|
|
died_to: c.died_to().cloned(),
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
2025-10-12 23:48:52 +01:00
|
|
|
day: match village.time() {
|
|
|
|
|
GameTime::Day { number } => number,
|
|
|
|
|
GameTime::Night { number: _ } => unreachable!(),
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
2025-10-09 22:27:21 +01:00
|
|
|
settings: village.settings(),
|
2025-06-23 09:48:28 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
(GameState::Night { night }, HostGameMessage::GetState) => {
|
|
|
|
|
if let Some(res) = night.current_result() {
|
|
|
|
|
return Ok(ServerToHostMessage::ActionResult(
|
2025-10-02 17:52:12 +01:00
|
|
|
night.current_character().map(|c| c.identity()),
|
2025-06-23 09:48:28 +01:00
|
|
|
res.clone(),
|
|
|
|
|
));
|
|
|
|
|
}
|
2025-10-09 22:27:21 +01:00
|
|
|
if let Some((prompt, page)) = night.current_prompt() {
|
|
|
|
|
return Ok(ServerToHostMessage::ActionPrompt(prompt.clone(), page));
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
match night.next() {
|
|
|
|
|
Ok(_) => self.process(HostGameMessage::GetState),
|
|
|
|
|
Err(GameError::NightOver) => {
|
2025-10-12 23:48:52 +01:00
|
|
|
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,
|
|
|
|
|
)),
|
|
|
|
|
)?;
|
2025-06-23 09:48:28 +01:00
|
|
|
self.state = GameState::Day {
|
|
|
|
|
village,
|
|
|
|
|
marked: Vec::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
|
|
|
|
Err(err) => Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-17 21:16:10 +01:00
|
|
|
(
|
|
|
|
|
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,
|
|
|
|
|
)),
|
|
|
|
|
},
|
2025-06-23 09:48:28 +01:00
|
|
|
(
|
|
|
|
|
GameState::Night { night },
|
|
|
|
|
HostGameMessage::Night(HostNightMessage::ActionResponse(resp)),
|
|
|
|
|
) => match night.received_response(resp.clone()) {
|
2025-10-09 22:27:21 +01:00
|
|
|
Ok(ServerAction::Prompt(prompt)) => Ok(ServerToHostMessage::ActionPrompt(
|
|
|
|
|
prompt,
|
|
|
|
|
night.page().unwrap_or_default(),
|
|
|
|
|
)),
|
2025-10-03 22:47:38 +01:00
|
|
|
Ok(ServerAction::Result(res)) => Ok(ServerToHostMessage::ActionResult(
|
2025-10-02 17:52:12 +01:00
|
|
|
night.current_character().map(|c| c.identity()),
|
2025-06-23 09:48:28 +01:00
|
|
|
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),
|
2025-10-12 23:48:52 +01:00
|
|
|
(GameState::Day { .. }, HostGameMessage::PreviousState) => {
|
|
|
|
|
Err(GameError::NoPreviousDuringDay)
|
2025-09-28 02:13:34 +01:00
|
|
|
}
|
|
|
|
|
(GameState::Night { night }, HostGameMessage::PreviousState) => {
|
|
|
|
|
night.previous_state()?;
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 23:48:52 +01:00
|
|
|
pub fn story(&self) -> GameStory {
|
|
|
|
|
self.history.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
pub fn game_over(&self) -> Option<GameOver> {
|
|
|
|
|
self.state.game_over()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn game_state(&self) -> &GameState {
|
|
|
|
|
&self.state
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-07 21:18:31 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
|
pub fn game_state_mut(&mut self) -> &mut GameState {
|
|
|
|
|
&mut self.state
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-03 00:00:39 +01:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2025-06-23 09:48:28 +01:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub enum GameState {
|
|
|
|
|
Day {
|
|
|
|
|
village: Village,
|
|
|
|
|
marked: Vec<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
Night {
|
|
|
|
|
night: Night,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GameState {
|
|
|
|
|
pub fn game_over(&self) -> Option<GameOver> {
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-08 00:44:55 +00:00
|
|
|
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"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Pool<T>
|
|
|
|
|
where
|
|
|
|
|
T: Debug + Clone,
|
|
|
|
|
{
|
|
|
|
|
pub pool: Vec<T>,
|
|
|
|
|
pub range: Range<u8>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> Pool<T>
|
|
|
|
|
where
|
|
|
|
|
T: Debug + Clone,
|
|
|
|
|
{
|
|
|
|
|
pub const fn new(pool: Vec<T>, range: Range<u8>) -> Self {
|
|
|
|
|
Self { pool, range }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn collapse(mut self, rng: &mut impl Rng, max: u8) -> Vec<T> {
|
|
|
|
|
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<T> Deref for Pool<T>
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 23:48:52 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
|
|
|
|
|
pub enum GameTime {
|
2025-06-23 09:48:28 +01:00
|
|
|
Day { number: NonZeroU8 },
|
|
|
|
|
Night { number: u8 },
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 23:48:52 +01:00
|
|
|
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 {
|
2025-06-23 09:48:28 +01:00
|
|
|
fn default() -> Self {
|
2025-10-12 23:48:52 +01:00
|
|
|
GameTime::Day {
|
2025-06-23 09:48:28 +01:00
|
|
|
number: NonZeroU8::new(1).unwrap(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 23:48:52 +01:00
|
|
|
impl GameTime {
|
2025-06-23 09:48:28 +01:00
|
|
|
pub const fn is_day(&self) -> bool {
|
2025-10-12 23:48:52 +01:00
|
|
|
matches!(self, GameTime::Day { number: _ })
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn is_night(&self) -> bool {
|
2025-10-12 23:48:52 +01:00
|
|
|
matches!(self, GameTime::Night { number: _ })
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn next(self) -> Self {
|
|
|
|
|
match self {
|
2025-10-12 23:48:52 +01:00
|
|
|
GameTime::Day { number } => GameTime::Night {
|
2025-06-23 09:48:28 +01:00
|
|
|
number: number.get(),
|
|
|
|
|
},
|
2025-10-12 23:48:52 +01:00
|
|
|
GameTime::Night { number } => GameTime::Day {
|
2025-06-23 09:48:28 +01:00
|
|
|
number: NonZeroU8::new(number + 1).unwrap(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-12 23:48:52 +01:00
|
|
|
|
|
|
|
|
pub const fn previous(self) -> Option<Self> {
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|