werewolves/werewolves-server/src/game.rs

325 lines
11 KiB
Rust
Raw Normal View History

use core::ops::Not;
use crate::{
LogError,
communication::{Comms, lobby::LobbyComms},
connection::{InGameToken, JoinedPlayers},
lobby::{Lobby, PlayerIdSender},
runner::{IdentifiedClientMessage, Message},
};
use tokio::{sync::broadcast::Receiver, time::Instant};
use werewolves_proto::{
error::GameError,
game::{Game, GameOver, Village},
message::{
ClientMessage, Identification, ServerMessage,
host::{HostGameMessage, HostMessage, HostNightMessage, ServerToHostMessage},
},
player::{Character, PlayerId},
};
type Result<T> = core::result::Result<T, GameError>;
pub struct GameRunner {
game: Game,
comms: Comms,
connect_recv: Receiver<(PlayerId, bool)>,
player_sender: PlayerIdSender,
roles_revealed: bool,
joined_players: JoinedPlayers,
_release_token: InGameToken,
cover_of_darkness: bool,
}
impl GameRunner {
pub const fn new(
game: Game,
comms: Comms,
player_sender: PlayerIdSender,
connect_recv: Receiver<(PlayerId, bool)>,
joined_players: JoinedPlayers,
release_token: InGameToken,
) -> Self {
Self {
game,
comms,
connect_recv,
player_sender,
joined_players,
roles_revealed: false,
_release_token: release_token,
cover_of_darkness: true,
}
}
pub const fn comms(&mut self) -> &mut Comms {
&mut self.comms
}
pub fn into_lobby(self) -> Lobby {
Lobby::new(
self.joined_players,
LobbyComms::new(self.comms, self.connect_recv),
)
}
pub const fn proto_game(&self) -> &Game {
&self.game
}
pub async fn role_reveal(&mut self) {
for char in self.game.village().characters() {
if let Err(err) = self.player_sender.send_if_present(
char.player_id(),
ServerMessage::GameStart {
role: char.role().initial_shown_role(),
},
) {
log::warn!(
"failed sending role info to [{}]({}): {err}",
char.player_id(),
char.name()
)
}
}
let mut acks = self
.game
.village()
.characters()
.into_iter()
.map(|c| (c, false))
.collect::<Box<[_]>>();
let update_host = |acks: &[(Character, bool)], comms: &mut Comms| {
comms
.host()
.send(ServerToHostMessage::WaitingForRoleRevealAcks {
ackd: acks
.iter()
.filter_map(|(a, ackd)| ackd.then_some(a.target()))
.collect(),
waiting: acks
.iter()
.filter_map(|(a, ackd)| ackd.not().then_some(a.target()))
.collect(),
})
.log_err();
};
(update_host)(&acks, &mut self.comms);
let notify_of_role = |player_id: &PlayerId, village: &Village, sender: &PlayerIdSender| {
if let Some(char) = village.character_by_player_id(player_id) {
sender
.send_if_present(
player_id,
ServerMessage::GameStart {
role: char.role().initial_shown_role(),
},
)
.log_debug();
}
};
let mut last_err_log = tokio::time::Instant::now() - tokio::time::Duration::from_secs(60);
while acks.iter().any(|(_, ackd)| !*ackd) {
let msg = match self.comms.message().await {
Ok(msg) => msg,
Err(err) => {
if (tokio::time::Instant::now() - last_err_log).as_secs() >= 30 {
log::error!("recv during role_reveal: {err}");
last_err_log = tokio::time::Instant::now();
}
continue;
}
};
match msg {
Message::Host(HostMessage::ForceRoleAckFor(char_id)) => {
if let Some((c, ackd)) =
acks.iter_mut().find(|(c, _)| c.character_id() == &char_id)
{
*ackd = true;
(notify_of_role)(c.player_id(), self.game.village(), &self.player_sender);
}
(update_host)(&acks, &mut self.comms);
}
Message::Host(_) => {
(update_host)(&acks, &mut self.comms);
}
Message::Client(IdentifiedClientMessage {
identity:
Identification {
player_id,
public: _,
},
message: ClientMessage::RoleAck,
}) => {
if let Some((_, ackd)) =
acks.iter_mut().find(|(t, _)| t.player_id() == &player_id)
{
*ackd = true;
self.player_sender
.send_if_present(&player_id, ServerMessage::Sleep)
.log_debug();
}
(update_host)(&acks, &mut self.comms);
}
Message::Client(IdentifiedClientMessage {
identity:
Identification {
player_id,
public: _,
},
message: _,
})
| Message::Connect(player_id) => {
(notify_of_role)(&player_id, self.game.village(), &self.player_sender)
}
Message::Disconnect(_) => {}
}
}
self.roles_revealed = true;
}
pub async fn next(&mut self) -> Option<GameOver> {
let msg = self.comms.host().recv().await.expect("host channel closed");
match self.host_message(msg) {
Ok(resp) => {
self.comms.host().send(resp).log_warn();
}
Err(err) => {
self.comms
.host()
.send(ServerToHostMessage::Error(err))
.log_warn();
}
}
self.game.game_over()
}
pub fn host_message(&mut self, message: HostMessage) -> Result<ServerToHostMessage> {
if !self.roles_revealed {
return Err(GameError::NeedRoleReveal);
}
if self.cover_of_darkness {
match &message {
HostMessage::GetState | HostMessage::InGame(HostGameMessage::GetState) => {
return Ok(ServerToHostMessage::CoverOfDarkness);
}
HostMessage::InGame(HostGameMessage::Night(HostNightMessage::Next)) => {
self.cover_of_darkness = false;
return self.host_message(HostMessage::GetState);
}
_ => return Err(GameError::InvalidMessageForGameState),
};
}
match message {
HostMessage::GetState => self.game.process(HostGameMessage::GetState),
HostMessage::InGame(msg) => self.game.process(msg),
HostMessage::Lobby(_) | HostMessage::NewLobby | HostMessage::ForceRoleAckFor(_) => {
Err(GameError::InvalidMessageForGameState)
}
HostMessage::Echo(echo) => Ok(echo),
}
}
}
pub struct GameEnd {
game: Option<GameRunner>,
result: GameOver,
last_error_log: Instant,
}
impl GameEnd {
pub fn new(game: GameRunner, result: GameOver) -> Self {
Self {
result,
game: Some(game),
last_error_log: Instant::now() - core::time::Duration::from_secs(60),
}
}
const fn game(&mut self) -> Result<&mut GameRunner> {
match self.game.as_mut() {
Some(game) => Ok(game),
None => Err(GameError::InactiveGameObject),
}
}
pub fn end_screen(&mut self) -> Result<()> {
let result = self.result;
for char in self.game()?.game.village().characters() {
self.game()?
.player_sender
.send_if_present(char.player_id(), ServerMessage::GameOver(result))
.log_debug();
}
self.game()?
.comms
.host()
.send(ServerToHostMessage::GameOver(result))
.log_warn();
Ok(())
}
pub async fn next(&mut self) -> Option<Lobby> {
let msg = match self.game().unwrap().comms.message().await {
Ok(msg) => msg,
Err(err) => {
if (Instant::now() - self.last_error_log).as_secs() >= 30 {
log::error!("getting message: {err}");
self.last_error_log = Instant::now();
}
return None;
}
};
match msg {
Message::Host(HostMessage::Echo(msg)) => {
self.game().unwrap().comms.host().send(msg).log_debug();
}
Message::Host(HostMessage::GetState) => {
let result = self.result;
self.game()
.unwrap()
.comms
.host()
.send(ServerToHostMessage::GameOver(result))
.log_debug()
}
Message::Host(HostMessage::NewLobby) => {
self.game()
.unwrap()
.comms
.host()
.send(ServerToHostMessage::Lobby(Box::new([])))
.log_debug();
let lobby = self.game.take().unwrap().into_lobby();
return Some(lobby);
}
Message::Host(_) => self
.game()
.unwrap()
.comms
.host()
.send(ServerToHostMessage::Error(
GameError::InvalidMessageForGameState,
))
.log_debug(),
Message::Client(IdentifiedClientMessage {
identity,
message: _,
}) => {
let result = self.result;
self.game()
.unwrap()
.player_sender
.send_if_present(&identity.player_id, ServerMessage::GameOver(result))
.log_debug();
}
Message::Connect(_) | Message::Disconnect(_) => {}
}
None
}
}