Nicolas Winkler преди 4 години
родител
ревизия
0f300ac30e
променени са 10 файла, в които са добавени 586 реда и са изтрити 523 реда
  1. 3 1
      Cargo.toml
  2. 33 2
      src/datasource.rs
  3. 1 1
      src/game.rs
  4. 11 5
      src/main.rs
  5. 0 504
      src/server.rs
  6. 232 0
      src/server/gamelobby.rs
  7. 77 0
      src/server/messages.rs
  8. 215 0
      src/server/server.rs
  9. 2 9
      src/websocket.rs
  10. 12 1
      static/comm.js

+ 3 - 1
Cargo.toml

@@ -17,4 +17,6 @@ futures = "0.3.1"
 bytes = "0.5.3"
 bytes = "0.5.3"
 serde = "1.0"
 serde = "1.0"
 serde_json = "1.0"
 serde_json = "1.0"
-rand = "0.7.3"
+rand = "0.7.3"
+rusqlite = "0.24.2"
+lazy_static = "1.4.0"

+ 33 - 2
src/datasource.rs

@@ -1,10 +1,9 @@
-
+use rusqlite::Connection;
 
 
 pub trait DataSource<T> {
 pub trait DataSource<T> {
     fn get_ith<'a>(&'a self, i: usize) -> Option<&'a T>;
     fn get_ith<'a>(&'a self, i: usize) -> Option<&'a T>;
 }
 }
 
 
-
 pub struct ArraySource<T> {
 pub struct ArraySource<T> {
     words: Vec<T>
     words: Vec<T>
 }
 }
@@ -22,3 +21,35 @@ impl<T> ArraySource<T> {
         }
         }
     }
     }
 }
 }
+
+pub struct SqliteSource {
+    conn: Connection
+}
+
+impl DataSource<String> for SqliteSource {
+    fn get_ith<'a>(&'a self, i: usize) -> Option<&'a String> {
+        let word: rusqlite::Result<String> = self.conn.query_row(
+            &format!(
+                "SELECT question
+                 FROM questions
+                 WHERE id={};", i),
+            rusqlite::NO_PARAMS,
+            |row| row.get(0));
+        
+        //word.ok().as_ref()
+        None
+    }
+}
+
+impl SqliteSource {
+    pub fn open(db_name: &str) -> Result<Self, rusqlite::Error> {
+        Ok(SqliteSource {
+            conn: Connection::open(db_name)?
+        })
+    }
+}
+
+lazy_static! {
+    //static ref default_source: Box<dyn DataSource<String>> =
+    //    Box::new(SqliteSource::open("questions.db").unwrap_or(ArraySource::create(vec!["".to_owned()])))
+}

+ 1 - 1
src/game.rs

@@ -56,7 +56,7 @@ impl Game {
         for c in word.chars() {
         for c in word.chars() {
             let count = countmap.get(&c);
             let count = countmap.get(&c);
             if let Some(&v) = count {
             if let Some(&v) = count {
-                if v < 0 {
+                if v <= 0 {
                     return false;
                     return false;
                 }
                 }
                 countmap.insert(c, v - 1);
                 countmap.insert(c, v - 1);

+ 11 - 5
src/main.rs

@@ -1,22 +1,28 @@
 use std::collections::BTreeMap;
 use std::collections::BTreeMap;
-use std::sync::Mutex;
 use actix::prelude::*;
 use actix::prelude::*;
 use actix_files as fs;
 use actix_files as fs;
 use actix_web::{middleware, web, App, HttpServer};
 use actix_web::{middleware, web, App, HttpServer};
 
 
+#[macro_use]
+extern crate lazy_static;
+
 mod websocket;
 mod websocket;
 mod game;
 mod game;
-mod server;
 mod datasource;
 mod datasource;
-//use crate::websocket::ws_initiate;
+mod server {
+    pub mod server;
+    pub mod messages;
+    pub mod gamelobby;
+}
 
 
+use crate::server::server::{Server, ws_initiate};
 
 
 #[actix_web::main]
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
 async fn main() -> std::io::Result<()> {
     std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
     std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
     env_logger::init();
     env_logger::init();
 
 
-    let mut server = server::Server { lobbies: BTreeMap::new() };
+    let server = Server { lobbies: BTreeMap::new() };
     let server_addr = server.start();
     let server_addr = server.start();
 
 
     HttpServer::new(move || {
     HttpServer::new(move || {
@@ -25,7 +31,7 @@ async fn main() -> std::io::Result<()> {
             // enable logger
             // enable logger
             .wrap(middleware::Logger::default())
             .wrap(middleware::Logger::default())
             // websocket route
             // websocket route
-            .service(web::resource("/ws/").route(web::get().to(websocket::ws_initiate)))
+            .service(web::resource("/ws/").route(web::get().to(ws_initiate)))
             // static files
             // static files
             .service(fs::Files::new("/", "static/").index_file("index.html"))
             .service(fs::Files::new("/", "static/").index_file("index.html"))
     })
     })

+ 0 - 504
src/server.rs

@@ -1,504 +0,0 @@
-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::*;
-use crate::datasource;
-
-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<GameConnection> }
-
-#[derive(Message)]
-#[rtype(result = "Answer")]
-struct CreateLobbyRequest{ lobby_id: String, p: Addr<GameConnection> }
-
-#[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<game::Game, ()>")]
-struct GetGame;
-
-#[derive(Message)]
-#[rtype(result = "()")]
-struct NoSuchLobby(String);
-
-#[derive(Message)]
-#[rtype(result = "()")]
-struct LobbyJoined(Addr<GameLobby>);
-
-#[derive(Message)]
-#[rtype(result = "()")]
-struct GameUpdate{ game_data: GameData }
-
-enum Answer {
-    LobbyJoined(Addr<GameLobby>),
-    LobbyCreated(Addr<GameLobby>),
-    LobbyAlreadyExists,
-    NoSuchLobby,
-}
-
-
-impl<A, M> MessageResponse<A, M> for Answer
-where
-    A: Actor,
-    M: Message<Result = Answer>,
-{
-    fn handle<R: ResponseChannel<M>>(self, _: &mut A::Context, tx: Option<R>) {
-        if let Some(tx) = tx {
-            tx.send(self);
-        }
-    }
-}
-
-
-impl<A, M> MessageResponse<A, M> for game::Game
-where
-    A: Actor,
-    M: Message<Result = game::Game>,
-{
-    fn handle<R: ResponseChannel<M>>(self, _: &mut A::Context, tx: Option<R>) {
-        if let Some(tx) = tx {
-            tx.send(self);
-        }
-    }
-}
-
-pub struct Server {
-    pub lobbies: BTreeMap<String, Addr<GameLobby>>,
-}
-
-impl Actor for Server {
-    type Context = Context<Self>;
-    fn started(&mut self, ctx: &mut Self::Context) {
-    }
-}
-
-impl Handler<JoinRequest> 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<CreateLobbyRequest> 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<String, Addr<GameConnection>>,
-    game_id: String,
-    game: game::Game,
-    waiting_players: BTreeMap<String, Addr<GameConnection>>,
-    ready_players: Vec<String>,
-    lobby_state: LobbyState,
-    data_source: Box<dyn datasource::DataSource<String>>,
-}
-
-impl Actor for GameLobby {
-    type Context = Context<Self>;
-    fn started(&mut self, ctx: &mut Self::Context) {
-        self.lobby_state = LobbyState::Starting;
-    }
-}
-
-impl Handler<JoinRequest> 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<ReadyMsg> 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<GetGame> for GameLobby {
-    type Result = Result<game::Game, ()>;
-    fn handle(&mut self, gg: GetGame, ctx: &mut Self::Context) -> Self::Result {
-        Ok(self.game.clone())
-    }
-}
-
-impl Handler<SubmitWordMsg> 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<SubmitGuessMsg> 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,
-            data_source: Box::new(datasource::ArraySource::create(vec![
-                "Delikatessfutter für Hunde".to_owned(),
-                "Ein Hotel für die ganze Familie".to_owned(),
-                "Brasilianischer Superstar".to_owned(),
-                "Buchstabe des griechischen Alphabets".to_owned(),
-            ]))
-        }
-    }
-
-    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 => {
-                let ds = &self.data_source;
-                let mut index = 0;
-                self.game.start_round(|| {
-                    index += 1;
-                    ds.get_ith(index - 1).unwrap().clone()
-                });
-            },
-            _ => {}
-        }
-        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::<Vec<_>>();
-        let questions = self.game.players.iter()
-                        .map(|x| x.creating_exercise.as_ref().unwrap().question.clone())
-                        .chain(self.game.additional_questions.clone().into_iter())
-                        .collect::<Vec<_>>();
-        let solutions = words.iter()
-                        .map(|x| x.clone())
-                        .zip(questions.iter().map(|x| x.clone()))
-                        .collect::<Vec<_>>();
-
-        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::<Vec<_>>(),
-            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::<Vec<_>>();
-
-        let mut questions = self.game.players.iter()
-            .map(|p| p.creating_exercise.clone().unwrap().question)
-            .collect::<Vec<_>>();
-        questions.append(&mut self.game.additional_questions.iter()
-                            .map(|x| x.clone())
-                            .collect::<Vec<_>>()
-                        );
-
-        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<String>,
-    game_id: Option<String>,
-    server: Addr<Server>,
-    game_lobby: Option<Addr<GameLobby>>
-}
-
-impl Actor for GameConnection {
-    type Context = ws::WebsocketContext<Self>;
-
-    fn started(&mut self, ctx: &mut Self::Context) {
-        self.initiate_heartbeat(ctx);
-    }
-}
-
-impl Handler<LobbyJoined> for GameConnection {
-    type Result = ();
-    fn handle(&mut self, gu: LobbyJoined, ctx: &mut Self::Context) -> Self::Result {
-        self.game_lobby = Some(gu.0);
-    }
-}
-
-impl Handler<NoSuchLobby> 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<GameUpdate> 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<ResultMsg> 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<Result<ws::Message, ws::ProtocolError>> for GameConnection {
-    fn handle(
-        &mut self,
-        msg: Result<ws::Message, ws::ProtocolError>,
-        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<Server>) -> Self {
-        GameConnection {
-            heartbeat: Instant::now(),
-            nick: None,
-            game_id: None,
-            server: server_addr,
-            game_lobby: None
-        }
-    }
-
-    pub fn initiate_heartbeat(&self, ctx: &mut <Self as Actor>::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 <Self as Actor>::Context) {
-        let txt = serde_json::to_string(m).unwrap();
-        println!("{:?}", txt);
-        ctx.text(txt);
-    }
-
-    pub fn received_message(&mut self, text: &str, ctx: &mut <Self as Actor>::Context) {
-        let parsed: Result<ClientMessage, _> = 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");
-        }
-    }
-}

+ 232 - 0
src/server/gamelobby.rs

@@ -0,0 +1,232 @@
+use std::collections::BTreeMap;
+
+use rand::thread_rng;
+use rand::seq::SliceRandom;
+
+use actix::prelude::*;
+
+use crate::server::server::{GameConnection, Server};
+use crate::datasource;
+use crate::game;
+use crate::websocket;
+use crate::websocket::*;
+
+use crate::server::messages::*;
+
+#[derive(PartialEq)]
+enum LobbyState {
+    Starting,
+    Creating,
+    Guessing,
+    Revealing
+}
+
+pub struct GameLobby {
+    connected_players: BTreeMap<String, Addr<GameConnection>>,
+    game_id: String,
+    game: game::Game,
+    waiting_players: BTreeMap<String, Addr<GameConnection>>,
+    ready_players: Vec<String>,
+    lobby_state: LobbyState,
+    data_source: Box<dyn datasource::DataSource<String>>,
+}
+
+impl Actor for GameLobby {
+    type Context = Context<Self>;
+    fn started(&mut self, ctx: &mut Self::Context) {
+        self.lobby_state = LobbyState::Starting;
+    }
+}
+
+impl Handler<JoinRequest> 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<ReadyMsg> 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<SubmitWordMsg> 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<SubmitGuessMsg> 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,
+            data_source: Box::new(datasource::ArraySource::create(vec![
+                "Delikatessfutter für Hunde".to_owned(),
+                "Ein Hotel für die ganze Familie".to_owned(),
+                "Brasilianischer Superstar".to_owned(),
+                "Buchstabe des griechischen Alphabets".to_owned(),
+            ]))
+        }
+    }
+
+    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 => {
+                let ds = &self.data_source;
+                let mut index = 0;
+                self.game.start_round(|| {
+                    index += 1;
+                    ds.get_ith(index - 1).unwrap().clone()
+                });
+            },
+            _ => {}
+        }
+        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::<Vec<_>>();
+        let questions = self.game.players.iter()
+                        .map(|x| x.creating_exercise.as_ref().unwrap().question.clone())
+                        .chain(self.game.additional_questions.clone().into_iter())
+                        .collect::<Vec<_>>();
+        let solutions = words.iter()
+                        .map(|x| x.clone())
+                        .zip(questions.iter().map(|x| x.clone()))
+                        .collect::<Vec<_>>();
+
+        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::<Vec<_>>(),
+            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::<Vec<_>>();
+
+        let mut questions = self.game.players.iter()
+            .map(|p| p.creating_exercise.clone().unwrap().question)
+            .collect::<Vec<_>>();
+        questions.append(&mut self.game.additional_questions.iter()
+                            .map(|x| x.clone())
+                            .collect::<Vec<_>>()
+                        );
+
+        questions.shuffle(&mut thread_rng());
+
+        GameStateData::Guessing {
+            submitted_words: words_with_chars,
+            questions: questions
+        }
+    }
+}

+ 77 - 0
src/server/messages.rs

@@ -0,0 +1,77 @@
+use actix::dev::{MessageResponse, ResponseChannel};
+use actix::prelude::*;
+use crate::server::server::GameConnection;
+use crate::server::gamelobby::GameLobby;
+use crate::websocket::*;
+
+#[derive(Message)]
+#[rtype(result = "Answer")]
+pub struct JoinRequest {
+    pub lobby_id: String,
+    pub nick: String,
+    pub p: Addr<GameConnection>
+}
+
+#[derive(Message)]
+#[rtype(result = "Answer")]
+pub struct CreateLobbyRequest {
+    pub lobby_id: String, pub p: Addr<GameConnection>
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct ReadyMsg(pub String);
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SubmitWordMsg {
+    pub word: String,
+    pub nick: String
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SubmitGuessMsg {
+    pub guesses: Vec<(String, String)>,
+    pub nick: String
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct ResultMsg {
+    pub results: RoundResultData
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct NoSuchLobby(pub String);
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct LobbyJoined(pub Addr<GameLobby>);
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct GameUpdate{
+    pub game_data: GameData
+}
+
+pub enum Answer {
+    LobbyJoined(Addr<GameLobby>),
+    LobbyCreated(Addr<GameLobby>),
+    LobbyAlreadyExists,
+    NoSuchLobby,
+}
+
+
+impl<A, M> MessageResponse<A, M> for Answer
+where
+    A: Actor,
+    M: Message<Result = Answer>,
+{
+    fn handle<R: ResponseChannel<M>>(self, _: &mut A::Context, tx: Option<R>) {
+        if let Some(tx) = tx {
+            tx.send(self);
+        }
+    }
+}

+ 215 - 0
src/server/server.rs

@@ -0,0 +1,215 @@
+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 crate::game;
+use crate::websocket;
+use crate::websocket::*;
+use crate::datasource;
+use crate::server::gamelobby::{GameLobby};
+use crate::server::messages::*;
+
+const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
+const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
+
+pub async fn ws_initiate(server: web::Data<Addr<Server>>,
+                         r: HttpRequest,
+                         stream: web::Payload) -> Result<HttpResponse, Error> {
+    println!("{:?}", r);
+    let (addr, res) = ws::start_with_addr(GameConnection::new(server.as_ref().clone()), &r, stream)?;
+    println!("{:?}", res);
+    Ok(res)
+}
+
+pub struct Server {
+    pub lobbies: BTreeMap<String, Addr<GameLobby>>,
+}
+
+impl Actor for Server {
+    type Context = Context<Self>;
+    fn started(&mut self, ctx: &mut Self::Context) {
+    }
+}
+
+impl Handler<JoinRequest> 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<CreateLobbyRequest> 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)
+            }
+        }
+    }
+}
+
+///
+/// connection to one single client
+/// 
+pub struct GameConnection {
+    heartbeat: Instant,
+    nick: Option<String>,
+    game_id: Option<String>,
+    server: Addr<Server>,
+    game_lobby: Option<Addr<GameLobby>>
+}
+
+impl Actor for GameConnection {
+    type Context = ws::WebsocketContext<Self>;
+
+    fn started(&mut self, ctx: &mut Self::Context) {
+        self.initiate_heartbeat(ctx);
+    }
+}
+
+impl Handler<LobbyJoined> for GameConnection {
+    type Result = ();
+    fn handle(&mut self, gu: LobbyJoined, ctx: &mut Self::Context) -> Self::Result {
+        self.game_lobby = Some(gu.0);
+    }
+}
+
+impl Handler<NoSuchLobby> 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<GameUpdate> 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<ResultMsg> 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<Result<ws::Message, ws::ProtocolError>> for GameConnection {
+    fn handle(
+        &mut self,
+        msg: Result<ws::Message, ws::ProtocolError>,
+        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<Server>) -> Self {
+        GameConnection {
+            heartbeat: Instant::now(),
+            nick: None,
+            game_id: None,
+            server: server_addr,
+            game_lobby: None
+        }
+    }
+
+    pub fn initiate_heartbeat(&self, ctx: &mut <Self as Actor>::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 <Self as Actor>::Context) {
+        let txt = serde_json::to_string(m).unwrap();
+        println!("{:?}", txt);
+        ctx.text(txt);
+    }
+
+    pub fn received_message(&mut self, text: &str, ctx: &mut <Self as Actor>::Context) {
+        let parsed: Result<ClientMessage, _> = 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");
+        }
+    }
+}

+ 2 - 9
src/websocket.rs

@@ -5,15 +5,8 @@ use actix::Message;
 use actix_web::{web, Error, HttpRequest, HttpResponse};
 use actix_web::{web, Error, HttpRequest, HttpResponse};
 use actix_web_actors::ws;
 use actix_web_actors::ws;
 
 
-use crate::game;
-use crate::server;
-
-pub async fn ws_initiate(server: web::Data<Addr<server::Server>>, r: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
-    println!("{:?}", r);
-    let (addr, res) = ws::start_with_addr(server::GameConnection::new(server.as_ref().clone()), &r, stream)?;
-    println!("{:?}", res);
-    Ok(res)
-}
+use crate::server::server::Server;
+use crate::server::server::GameConnection;
 
 
 #[derive(Serialize, Deserialize)]
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "snake_case")]
 #[serde(rename_all = "snake_case")]

+ 12 - 1
static/comm.js

@@ -157,6 +157,11 @@ $(function() {
     }
     }
 
 
     function displayResult(result) {
     function displayResult(result) {
+        var sol = result.solutions; 
+        var solution_dict = {};
+        for (var i = 0; i < sol.length; i++) {
+            solution_dict[sol[i][0]] = sol[i][1];
+        }
         var $table = $('<table/>');
         var $table = $('<table/>');
 
 
         var wordline = "<tr><th></th>";
         var wordline = "<tr><th></th>";
@@ -169,7 +174,13 @@ $(function() {
         for(var i = 0; i < result.questions.length; i++) {
         for(var i = 0; i < result.questions.length; i++) {
             var wordline = "<tr><th>" + result.questions[i] + "</th>";
             var wordline = "<tr><th>" + result.questions[i] + "</th>";
             for(var j = 0; j < result.words.length; j++) {
             for(var j = 0; j < result.words.length; j++) {
-                wordline += "<td>" + result.guesses[j][i] + "</td>";
+                if (solution_dict[result.words[j]] == result.questions[i]) {
+                    wordline += "<td bgcolor='lightgreen'>";
+                }
+                else {
+                    wordline += "<td bgcolor='pink'>";
+                }
+                wordline += result.guesses[j][i] + "</td>";
             }
             }
             wordline += "</tr>";
             wordline += "</tr>";
             $table.append(wordline);
             $table.append(wordline);