werewolves/werewolves-server/src/game.rs

338 lines
12 KiB
Rust
Raw Normal View History

use core::ops::Not;
use crate::{
LogError,
communication::{Comms, lobby::LobbyComms},
connection::JoinedPlayers,
lobby::{Lobby, LobbyPlayers},
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, 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: LobbyPlayers,
roles_revealed: bool,
joined_players: JoinedPlayers,
}
impl GameRunner {
pub fn new(
game: Game,
comms: Comms,
player_sender: LobbyPlayers,
connect_recv: Receiver<(PlayerId, bool)>,
joined_players: JoinedPlayers,
) -> Self {
Self {
game,
comms,
connect_recv,
player_sender,
joined_players,
roles_revealed: false,
}
}
pub const fn comms(&mut self) -> &mut Comms {
&mut self.comms
}
pub fn into_lobby(self) -> Lobby {
let mut lobby = Lobby::new(
self.joined_players,
LobbyComms::new(self.comms, self.connect_recv),
);
lobby.set_players_in_lobby(self.player_sender);
lobby
}
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.identity()))
.collect(),
waiting: acks
.iter()
.filter_map(|(a, ackd)| ackd.not().then_some(a.identity()))
.collect(),
})
.log_err();
};
(update_host)(&acks, &mut self.comms);
let notify_of_role = |player_id: &PlayerId, village: &Village, sender: &LobbyPlayers| {
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);
}
2025-10-02 20:19:55 +01:00
Message::Client(IdentifiedClientMessage {
identity:
Identification {
player_id,
public: _,
},
message: ClientMessage::GetState,
}) => {
let sender =
if let Some(sender) = self.joined_players.get_sender(&player_id).await {
sender
} else {
continue;
};
if acks.iter().any(|(c, d)| c.player_id() == &player_id && *d) {
// already ack'd just disconnect
sender.send(ServerMessage::Disconnect).log_debug();
continue;
}
if let Some(char) = self
.game
.village()
.characters()
.iter()
.find(|c| c.player_id() == &player_id)
{
sender
.send(ServerMessage::GameStart {
role: char.role().initial_shown_role(),
})
.log_debug();
} else if let Some(sender) = self.joined_players.get_sender(&player_id).await {
sender.send(ServerMessage::GameInProgress).log_debug();
}
}
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);
2025-10-02 20:19:55 +01:00
if let Some(sender) = self.joined_players.get_sender(&player_id).await {
sender.send(ServerMessage::Disconnect).log_debug();
}
}
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(_) => {}
}
}
2025-10-03 00:00:39 +01:00
for char in self.game.village().characters() {
if let Some(sender) = self.joined_players.get_sender(char.player_id()).await {
let _ = sender.send(ServerMessage::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);
}
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 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
}
}