use core::ops::Not; use crate::{ LogError, communication::{Comms, lobby::LobbyComms}, connection::JoinedPlayers, lobby::{Lobby, LobbyPlayers}, runner::{IdentifiedClientMessage, Message}, }; use futures::SinkExt; 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 = core::result::Result; 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 { // core::mem::drop(self._release_token); 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::>(); 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); } 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); 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(_) => {} } } self.roles_revealed = true; } pub async fn next(&mut self) -> Option { 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 { 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, 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 { 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 } }