use std::collections::BTreeMap; use std::collections::BTreeSet; use std::time::{Duration, Instant}; use std::sync::{Mutex, RwLock, Arc}; use actix::dev::{MessageResponse, ResponseChannel}; use actix::prelude::*; use actix_web::{web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; use rand::thread_rng; use rand::seq::SliceRandom; use crate::game; use crate::websocket; use crate::websocket::*; const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); #[derive(Message)] #[rtype(result = "Answer")] struct JoinRequest{ lobby_id: String, nick: String, p: Addr } #[derive(Message)] #[rtype(result = "Answer")] struct CreateLobbyRequest{ lobby_id: String, p: Addr } #[derive(Message)] #[rtype(result = "()")] struct ReadyMsg(String); #[derive(Message)] #[rtype(result = "()")] struct SubmitWordMsg{ word: String, nick: String } #[derive(Message)] #[rtype(result = "()")] struct SubmitGuessMsg{ guesses: Vec<(String, String)>, nick: String } #[derive(Message)] #[rtype(result = "()")] struct ResultMsg{ results: RoundResultData } #[derive(Message)] #[rtype(result = "Result")] struct GetGame; #[derive(Message)] #[rtype(result = "()")] struct NoSuchLobby(String); #[derive(Message)] #[rtype(result = "()")] struct LobbyJoined(Addr); #[derive(Message)] #[rtype(result = "()")] struct GameUpdate{ game_data: GameData } enum Answer { LobbyJoined(Addr), LobbyCreated(Addr), LobbyAlreadyExists, NoSuchLobby, } impl MessageResponse for Answer where A: Actor, M: Message, { fn handle>(self, _: &mut A::Context, tx: Option) { if let Some(tx) = tx { tx.send(self); } } } impl MessageResponse for game::Game where A: Actor, M: Message, { fn handle>(self, _: &mut A::Context, tx: Option) { if let Some(tx) = tx { tx.send(self); } } } pub struct Server { pub lobbies: BTreeMap>, } impl Actor for Server { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { } } impl Handler for Server { type Result = Answer; fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result { let mb_lobby = self.lobbies.get(&jr.lobby_id); match mb_lobby { Some(lobby) => { let _sent = lobby.do_send(jr); Answer::LobbyJoined(lobby.clone()) }, None => { jr.p.do_send(NoSuchLobby(jr.lobby_id)); Answer::NoSuchLobby } } } } impl Handler for Server { type Result = Answer; fn handle(&mut self, clr: CreateLobbyRequest, ctx: &mut Self::Context) -> Self::Result { let existing_lobby = self.lobbies.get(&clr.lobby_id); match existing_lobby { Some(_) => Answer::LobbyAlreadyExists, None => { let lobby = GameLobby::new(clr.lobby_id.clone()); let lobby_addr = lobby.start(); self.lobbies.insert(clr.lobby_id.clone(), lobby_addr.clone()); Answer::LobbyCreated(lobby_addr) } } } } #[derive(PartialEq)] enum LobbyState { Starting, Creating, Guessing, Revealing } pub struct GameLobby { connected_players: BTreeMap>, game_id: String, game: game::Game, waiting_players: BTreeMap>, ready_players: Vec, lobby_state: LobbyState } impl Actor for GameLobby { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { self.lobby_state = LobbyState::Starting; } } impl Handler for GameLobby { type Result = Answer; fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result { if self.lobby_state == LobbyState::Starting { jr.p.do_send(LobbyJoined(ctx.address())); self.connected_players.insert(jr.nick.clone(), jr.p); self.game.player_join(jr.nick); self.send_game_to_all(); Answer::LobbyJoined(ctx.address()) } else { self.waiting_players.insert(jr.nick.clone(), jr.p); Answer::NoSuchLobby } } } impl Handler for GameLobby { type Result = (); fn handle(&mut self, rm: ReadyMsg, ctx: &mut Self::Context) -> Self::Result { if !self.ready_players.contains(&rm.0) { self.ready_players.push(rm.0); } if self.ready_players.len() >= self.connected_players.len() { self.set_state(LobbyState::Creating); self.send_game_to_all(); } } } impl Handler for GameLobby { type Result = Result; fn handle(&mut self, gg: GetGame, ctx: &mut Self::Context) -> Self::Result { Ok(self.game.clone()) } } impl Handler for GameLobby { type Result = (); fn handle(&mut self, swm: SubmitWordMsg, ctx: &mut Self::Context) -> Self::Result { let correct = self.game.submit_creation(&swm.nick, swm.word); if self.game.all_words_submitted() { self.set_state(LobbyState::Guessing); self.game.next_state(); self.send_game_to_all(); } } } impl Handler for GameLobby { type Result = (); fn handle(&mut self, sgm: SubmitGuessMsg, ctx: &mut Self::Context) -> Self::Result { self.game.submit_guess(&sgm.nick, sgm.guesses); if self.game.all_guesses_submitted() { self.set_state(LobbyState::Revealing); let results = self.create_result_data(); self.broadcast_results(results); self.game.next_state(); self.send_game_to_all(); } } } impl GameLobby { pub fn new(gi: String) -> Self { GameLobby { connected_players: BTreeMap::new(), game_id: gi, game: game::Game::new(), waiting_players: BTreeMap::new(), ready_players: Vec::new(), lobby_state: LobbyState::Starting, } } fn set_state(&mut self, new_state: LobbyState) { match new_state { LobbyState::Starting => { for (nick, _addr) in &self.waiting_players { self.game.player_join(nick.clone()); } self.connected_players.append(&mut self.waiting_players); self.ready_players.clear(); }, LobbyState::Creating => { self.game.start_round(); }, _ => {} } self.lobby_state = new_state; } pub fn send_game_to_all(&self) { for (nick, player) in &self.connected_players { let game_state = self.create_opaque_message(nick); player.do_send(GameUpdate{ game_data: game_state }); } } pub fn create_result_data(&self) -> RoundResultData { let results_table = self.game.create_results(); let words = self.game.players.iter() .map(|p| p.submitted_word.clone().unwrap()) .collect::>(); let questions = self.game.players.iter() .map(|x| x.creating_exercise.as_ref().unwrap()) .chain(self.game.additional_questions.iter()) .map(|x| x.question.clone()) .collect::>(); let solutions = words.iter() .map(|x| x.clone()) .zip(questions.iter().map(|x| x.clone())) .collect::>(); let guesses = self.game.create_results(); RoundResultData { words, questions, solutions, guesses } } pub fn broadcast_results(&self, results: RoundResultData) { for (_nick, player) in &self.connected_players { player.do_send(ResultMsg{ results: results.clone() }); } } pub fn create_opaque_message(&self, nick: &str) -> GameData { let player = self.game.players.iter() .find(|p| p.nick == nick); GameData { players: self.game.players.iter() .map(|p| websocket::PlayerData{ nick: p.nick.clone(), points: 0 }) .collect::>(), state_data: match self.game.state { game::GameState::Starting => { GameStateData::Starting }, game::GameState::Creating => { GameStateData::Creating{ question: player.unwrap().creating_exercise.as_ref().unwrap().question.clone(), available_chars: player.unwrap().creating_exercise.as_ref().unwrap().letters.clone() } }, game::GameState::Guessing => { self.create_guessing() }, } } } pub fn check_optionals(&self) { for p in &self.game.players { println!("{:?}", p); } } pub fn create_guessing(&self) -> GameStateData { self.check_optionals(); let words_with_chars = self.game.players.iter() .map(|p| (p.submitted_word.clone().unwrap(), p.creating_exercise.clone().unwrap().letters)) .collect::>(); let mut questions = self.game.players.iter() .map(|p| p.creating_exercise.clone().unwrap().question) .collect::>(); questions.append(&mut self.game.additional_questions.iter() .map(|x| x.question.clone()) .collect::>() ); questions.shuffle(&mut thread_rng()); GameStateData::Guessing { submitted_words: words_with_chars, questions: questions } } } /// /// connection to one single client /// pub struct GameConnection { heartbeat: Instant, nick: Option, game_id: Option, server: Addr, game_lobby: Option> } impl Actor for GameConnection { type Context = ws::WebsocketContext; fn started(&mut self, ctx: &mut Self::Context) { self.initiate_heartbeat(ctx); } } impl Handler for GameConnection { type Result = (); fn handle(&mut self, gu: LobbyJoined, ctx: &mut Self::Context) -> Self::Result { self.game_lobby = Some(gu.0); } } impl Handler for GameConnection { type Result = (); fn handle(&mut self, nsl: NoSuchLobby, ctx: &mut Self::Context) -> Self::Result { self.send_message(&UpdateMessage::GameNotFound{ game_id: nsl.0 }, ctx); } } impl Handler for GameConnection { type Result = (); fn handle(&mut self, gu: GameUpdate, ctx: &mut Self::Context) -> Self::Result { self.send_message(&UpdateMessage::GameState(gu.game_data), ctx); } } impl Handler for GameConnection { type Result = (); fn handle(&mut self, rm: ResultMsg, ctx: &mut Self::Context) -> Self::Result { self.send_message(&UpdateMessage::RoundResult(rm.results), ctx); } } impl StreamHandler> for GameConnection { fn handle( &mut self, msg: Result, ctx: &mut Self::Context, ) { match msg { Ok(ws::Message::Ping(msg)) => { self.heartbeat = Instant::now(); ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => { self.heartbeat = Instant::now(); } Ok(ws::Message::Text(text)) => { println!("hmmm: {:?}", text); self.received_message(&text, ctx); }, Ok(ws::Message::Binary(bin)) => ctx.binary(bin), Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } } impl GameConnection { pub fn new(server_addr: Addr) -> Self { GameConnection { heartbeat: Instant::now(), nick: None, game_id: None, server: server_addr, game_lobby: None } } pub fn initiate_heartbeat(&self, ctx: &mut ::Context) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { if Instant::now().duration_since(act.heartbeat) > CLIENT_TIMEOUT { //println!("Websocket Client heartbeat failed, disconnecting!"); ctx.stop(); return; } ctx.ping(b""); }); } pub fn send_message(&self, m: &UpdateMessage, ctx: &mut ::Context) { let txt = serde_json::to_string(m).unwrap(); println!("{:?}", txt); ctx.text(txt); } pub fn received_message(&mut self, text: &str, ctx: &mut ::Context) { let parsed: Result = serde_json::from_str(text); if let Ok(msg) = parsed { match msg { ClientMessage::CreateGame{game_id, nick} => { self.game_id = Some(game_id.clone()); self.nick = Some(nick); self.server.do_send(CreateLobbyRequest{ lobby_id: game_id.clone(), p: ctx.address() }); }, ClientMessage::Join{game_id, nick} => { self.server.do_send(JoinRequest{ lobby_id: game_id.clone(), nick: nick.clone(), p: ctx.address() }); self.game_id = Some(game_id.clone()); self.nick = Some(nick); }, ClientMessage::Ready => { if let Some(lobby) = &self.game_lobby { if let Some(nick) = &self.nick { lobby.do_send(ReadyMsg(nick.clone())); } } }, ClientMessage::SubmitWord{ word } => { if let Some(lobby) = &self.game_lobby { if let Some(nick) = &self.nick { lobby.do_send(SubmitWordMsg{ word: word, nick: nick.clone() }); } } }, ClientMessage::SubmitGuess{ guesses } => { if let Some(lobby) = &self.game_lobby { if let Some(nick) = &self.nick { lobby.do_send(SubmitGuessMsg{ guesses: guesses, nick: nick.clone() }); } } } } } else { println!("error parsing json"); } } }