2025-06-23 09:48:28 +01:00
|
|
|
use core::ops::Not;
|
2025-10-04 09:26:37 +01:00
|
|
|
use std::sync::Arc;
|
2025-06-23 09:48:28 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
LogError,
|
2025-10-04 09:26:37 +01:00
|
|
|
communication::{Comms, connect::ConnectUpdate, lobby::LobbyComms},
|
2025-10-02 17:52:12 +01:00
|
|
|
connection::JoinedPlayers,
|
2025-09-26 21:15:52 +01:00
|
|
|
lobby::{Lobby, LobbyPlayers},
|
2025-06-23 09:48:28 +01:00
|
|
|
runner::{IdentifiedClientMessage, Message},
|
|
|
|
|
};
|
|
|
|
|
use tokio::{sync::broadcast::Receiver, time::Instant};
|
|
|
|
|
use werewolves_proto::{
|
|
|
|
|
error::GameError,
|
|
|
|
|
game::{Game, GameOver, Village},
|
|
|
|
|
message::{
|
|
|
|
|
ClientMessage, Identification, ServerMessage,
|
2025-10-02 17:52:12 +01:00
|
|
|
host::{HostGameMessage, HostMessage, ServerToHostMessage},
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
|
|
|
|
player::{Character, PlayerId},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type Result<T> = core::result::Result<T, GameError>;
|
|
|
|
|
|
|
|
|
|
pub struct GameRunner {
|
|
|
|
|
game: Game,
|
|
|
|
|
comms: Comms,
|
2025-10-04 09:26:37 +01:00
|
|
|
connect_recv: ConnectUpdate,
|
2025-09-26 21:15:52 +01:00
|
|
|
player_sender: LobbyPlayers,
|
2025-06-23 09:48:28 +01:00
|
|
|
roles_revealed: bool,
|
|
|
|
|
joined_players: JoinedPlayers,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GameRunner {
|
2025-09-26 21:15:52 +01:00
|
|
|
pub fn new(
|
2025-06-23 09:48:28 +01:00
|
|
|
game: Game,
|
|
|
|
|
comms: Comms,
|
2025-09-26 21:15:52 +01:00
|
|
|
player_sender: LobbyPlayers,
|
2025-10-04 09:26:37 +01:00
|
|
|
connect_recv: ConnectUpdate,
|
2025-06-23 09:48:28 +01:00
|
|
|
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 {
|
2025-09-26 21:15:52 +01:00
|
|
|
let mut lobby = Lobby::new(
|
2025-06-23 09:48:28 +01:00
|
|
|
self.joined_players,
|
|
|
|
|
LobbyComms::new(self.comms, self.connect_recv),
|
2025-09-26 21:15:52 +01:00
|
|
|
);
|
|
|
|
|
lobby.set_players_in_lobby(self.player_sender);
|
|
|
|
|
lobby
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
2025-10-02 17:52:12 +01:00
|
|
|
.filter_map(|(a, ackd)| ackd.then_some(a.identity()))
|
2025-06-23 09:48:28 +01:00
|
|
|
.collect(),
|
|
|
|
|
waiting: acks
|
|
|
|
|
.iter()
|
2025-10-02 17:52:12 +01:00
|
|
|
.filter_map(|(a, ackd)| ackd.not().then_some(a.identity()))
|
2025-06-23 09:48:28 +01:00
|
|
|
.collect(),
|
|
|
|
|
})
|
|
|
|
|
.log_err();
|
|
|
|
|
};
|
|
|
|
|
(update_host)(&acks, &mut self.comms);
|
2025-09-26 21:15:52 +01:00
|
|
|
let notify_of_role = |player_id: &PlayerId, village: &Village, sender: &LobbyPlayers| {
|
2025-06-23 09:48:28 +01:00
|
|
|
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);
|
2025-10-04 09:26:37 +01:00
|
|
|
let mut connect_list: Arc<[PlayerId]> = Arc::new([]);
|
2025-06-23 09:48:28 +01:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
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();
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
Message::Client(IdentifiedClientMessage {
|
|
|
|
|
identity:
|
|
|
|
|
Identification {
|
|
|
|
|
player_id,
|
|
|
|
|
public: _,
|
|
|
|
|
},
|
|
|
|
|
message: _,
|
2025-10-04 09:26:37 +01:00
|
|
|
}) => (notify_of_role)(&player_id, self.game.village(), &self.player_sender),
|
|
|
|
|
Message::ConnectedList(c) => {
|
|
|
|
|
let newly_connected = c.iter().filter(|c| connect_list.contains(*c));
|
|
|
|
|
for connected in newly_connected {
|
|
|
|
|
(notify_of_role)(connected, self.game.village(), &self.player_sender)
|
|
|
|
|
}
|
|
|
|
|
connect_list = c;
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2025-09-30 13:07:59 +01:00
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
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();
|
|
|
|
|
}
|
2025-10-04 09:26:37 +01:00
|
|
|
Message::ConnectedList(_) => {}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|