2025-09-28 02:13:34 +01:00
|
|
|
mod kill;
|
2025-06-23 09:48:28 +01:00
|
|
|
mod night;
|
|
|
|
|
mod settings;
|
|
|
|
|
mod village;
|
|
|
|
|
|
|
|
|
|
use core::{
|
|
|
|
|
fmt::Debug,
|
|
|
|
|
num::NonZeroU8,
|
|
|
|
|
ops::{Deref, Range, RangeBounds},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use rand::{Rng, seq::SliceRandom};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
error::GameError,
|
|
|
|
|
game::night::Night,
|
|
|
|
|
message::{
|
|
|
|
|
CharacterState, Identification,
|
|
|
|
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
|
|
|
|
},
|
|
|
|
|
player::CharacterId,
|
|
|
|
|
};
|
|
|
|
|
pub use {settings::GameSettings, village::Village};
|
|
|
|
|
|
|
|
|
|
type Result<T> = core::result::Result<T, GameError>;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Game {
|
|
|
|
|
previous: Vec<GameState>,
|
2025-09-28 02:13:34 +01:00
|
|
|
next: Vec<GameState>,
|
2025-06-23 09:48:28 +01:00
|
|
|
state: GameState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Game {
|
|
|
|
|
pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> {
|
|
|
|
|
Ok(Self {
|
2025-09-28 02:13:34 +01:00
|
|
|
next: Vec::new(),
|
2025-06-23 09:48:28 +01:00
|
|
|
previous: Vec::new(),
|
|
|
|
|
state: GameState::Night {
|
|
|
|
|
night: Night::new(Village::new(players, settings)?)?,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
(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)) => {
|
|
|
|
|
if let Some(outcome) = village.execute(marked)? {
|
|
|
|
|
return Ok(ServerToHostMessage::GameOver(outcome));
|
|
|
|
|
}
|
|
|
|
|
let night = Night::new(village.clone())?;
|
|
|
|
|
self.previous.push(self.state.clone());
|
|
|
|
|
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().clone(),
|
2025-10-02 17:52:12 +01:00
|
|
|
identity: c.identity(),
|
2025-06-23 09:48:28 +01:00
|
|
|
role: c.role().title(),
|
|
|
|
|
died_to: c.died_to().cloned(),
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
day: match village.date_time() {
|
|
|
|
|
DateTime::Day { number } => number,
|
|
|
|
|
DateTime::Night { number: _ } => unreachable!(),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
(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(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if let Some(prompt) = night.current_prompt() {
|
2025-09-30 13:07:59 +01:00
|
|
|
return Ok(ServerToHostMessage::ActionPrompt(prompt.clone()));
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
match night.next() {
|
|
|
|
|
Ok(_) => self.process(HostGameMessage::GetState),
|
|
|
|
|
Err(GameError::NightOver) => {
|
|
|
|
|
let village = night.collect_completed()?;
|
|
|
|
|
self.previous.push(self.state.clone());
|
|
|
|
|
self.state = GameState::Day {
|
|
|
|
|
village,
|
|
|
|
|
marked: Vec::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
|
|
|
|
Err(err) => Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(
|
|
|
|
|
GameState::Night { night },
|
|
|
|
|
HostGameMessage::Night(HostNightMessage::ActionResponse(resp)),
|
|
|
|
|
) => match night.received_response(resp.clone()) {
|
|
|
|
|
Ok(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-09-28 02:13:34 +01:00
|
|
|
(
|
|
|
|
|
GameState::Day {
|
|
|
|
|
village: _,
|
|
|
|
|
marked: _,
|
|
|
|
|
},
|
|
|
|
|
HostGameMessage::PreviousState,
|
|
|
|
|
) => {
|
|
|
|
|
let mut prev = self.previous.pop().ok_or(GameError::NoPreviousState)?;
|
|
|
|
|
log::info!("previous state loaded: {prev:?}");
|
|
|
|
|
core::mem::swap(&mut prev, &mut self.state);
|
|
|
|
|
self.next.push(prev);
|
|
|
|
|
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
|
|
|
|
(GameState::Night { night }, HostGameMessage::PreviousState) => {
|
|
|
|
|
night.previous_state()?;
|
|
|
|
|
self.process(HostGameMessage::GetState)
|
|
|
|
|
}
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous_game_states(&self) -> &[GameState] {
|
|
|
|
|
&self.previous
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
|
|
|
pub enum DateTime {
|
|
|
|
|
Day { number: NonZeroU8 },
|
|
|
|
|
Night { number: u8 },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for DateTime {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
DateTime::Day {
|
|
|
|
|
number: NonZeroU8::new(1).unwrap(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DateTime {
|
|
|
|
|
pub const fn is_day(&self) -> bool {
|
|
|
|
|
matches!(self, DateTime::Day { number: _ })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn is_night(&self) -> bool {
|
|
|
|
|
matches!(self, DateTime::Night { number: _ })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn next(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
DateTime::Day { number } => DateTime::Night {
|
|
|
|
|
number: number.get(),
|
|
|
|
|
},
|
|
|
|
|
DateTime::Night { number } => DateTime::Day {
|
|
|
|
|
number: NonZeroU8::new(number + 1).unwrap(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|