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 SubmitWordMsg{ word: String, nick: String } #[derive(Message)] #[rtype(result = "Result")] struct GetGame; #[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 => 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) } } } } pub struct GameLobby { connected_players: BTreeMap>, game_id: String, game: game::Game, } impl Actor for GameLobby { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { self.game.new_round(); } } impl Handler for GameLobby { type Result = Answer; fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result { 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()) } } 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 { self.game.submit_creation(&swm.nick, swm.word); if self.game.all_submitted() { 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() } } 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_opaque_message(&self, nick: &str) -> GameData { 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::Creating => { GameStateData::Creating{ question: "Ein Gerät um Elche zu häuten".to_owned(), available_chars: vec!['a', 'b', 'c'] } }, game::GameState::Guessing => { self.create_guessing() }, } } } pub fn create_guessing(&self) -> GameStateData { let chars = self.game.round.exercises.iter() .map(|x| x.letters.clone()) .collect::>(); let words_with_chars = self.game.players.iter() .map(|p| p.submitted_word.clone().unwrap_or("".to_owned())) .zip(chars) .collect::>(); let mut questions = self.game.round.exercises.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, gu: GameUpdate, ctx: &mut Self::Context) -> Self::Result { self.send_message(&UpdateMessage::GameState(gu.game_data), 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::SubmitWord{ word } => { match &self.game_lobby { Some(lobby) => { if let Some(nick) = &self.nick { lobby.do_send(SubmitWordMsg{ word: word, nick: nick.clone() }); } }, None => { } } }, ClientMessage::SubmitGuess{ guesses } => { } } } else { println!("error parsing json"); } } }