Nicolas Winkler vor 4 Jahren
Ursprung
Commit
f01ff0afda
5 geänderte Dateien mit 139 neuen und 54 gelöschten Zeilen
  1. 2 1
      Cargo.toml
  2. 48 15
      src/game.rs
  3. 63 33
      src/server.rs
  4. 3 2
      src/websocket.rs
  5. 23 3
      static/index.html

+ 2 - 1
Cargo.toml

@@ -16,4 +16,5 @@ env_logger = "0.7"
 futures = "0.3.1"
 bytes = "0.5.3"
 serde = "1.0"
-serde_json = "1.0"
+serde_json = "1.0"
+rand = "0.7.3"

+ 48 - 15
src/game.rs

@@ -4,11 +4,10 @@ use actix::{Actor, Addr, Context};
 use crate::websocket;
 use crate::server;
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Serialize, Deserialize, Clone, PartialEq)]
 pub enum GameState {
     Creating,
-    Guessing(usize),
-    Revealing
+    Guessing
 }
 
 #[derive(Serialize, Deserialize, Clone)]
@@ -25,7 +24,8 @@ pub struct Round {
 #[derive(Serialize, Clone)]
 pub struct Player {
     pub nick: String,
-    submitted_word: Option<String>,
+    pub submitted_word: Option<String>,
+    pub submitted_guess: Option<Vec<(String, String)>>,
 }
 
 #[derive(Serialize, Clone)]
@@ -69,27 +69,60 @@ impl Game {
         }
     }
 
-    pub fn next_state(&mut self) {
+    pub fn submit_guess(&mut self, player_nick: &str, guess: Vec<(String, String)>) -> bool {
         match self.state {
-            GameState::Creating => {
-                self.state = GameState::Guessing(0);
-            },
-            GameState::Guessing(index) => {
-                if index + 1 < self.players.len() {
-                    self.state = GameState::Guessing(index + 1);
+            GameState::Guessing => {
+                if let Some(player) = self.get_player(player_nick) {
+                    player.submitted_guess = Some(guess);
+                    true
                 }
                 else {
-                    self.state = GameState::Creating;
+                    false
                 }
             },
-            GameState::Revealing{} => {
-                
+            _ => false
+        }
+    }
+
+    pub fn all_submitted(&self) -> bool {
+        match self.state {
+            GameState::Creating => {
+                self.players.iter().map(|p| p.submitted_word.is_some()).fold(true, |a, b| a && b)
+            },
+            GameState::Guessing => {
+                self.players.iter().map(|p| p.submitted_guess.is_some()).fold(true, |a, b| a && b)
             }
         }
     }
 
+    pub fn next_state(&mut self) {
+        if self.state == GameState::Guessing {
+            self.clear_submissions();
+        }
+        self.state = match self.state {
+            GameState::Creating => GameState::Guessing,
+            GameState::Guessing => GameState::Creating
+        }
+    }
+
+    pub fn new_round(&mut self) {
+        self.round.exercises = vec![
+            CreatingEx{ question: "An Alphabet".to_owned(), letters: vec!['a', 'b', 'c'] },
+            CreatingEx{ question: "A Betabet".to_owned(), letters: vec!['b', 'c', 'd'] },
+            CreatingEx{ question: "A Gammaabet".to_owned(), letters: vec!['c', 'd', 'e'] },
+            CreatingEx{ question: "A Deltaabet".to_owned(), letters: vec!['d', 'e', 'f'] },
+        ];
+    }
+
+    pub fn clear_submissions(&mut self) {
+        for p in &mut self.players {
+            p.submitted_word = None;
+            p.submitted_guess = None;
+        }
+    }
+
     pub fn player_join(&mut self, nick: String) {
-        self.players.push(Player{ nick: nick, submitted_word: None });
+        self.players.push(Player{ nick: nick, submitted_word: None, submitted_guess: None });
     }
 }
 

+ 63 - 33
src/server.rs

@@ -6,6 +6,8 @@ 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::*;
@@ -29,6 +31,11 @@ struct SubmitWordMsg{ word: String, nick: String }
 #[rtype(result = "Result<game::Game, ()>")]
 struct GetGame;
 
+
+#[derive(Message)]
+#[rtype(result = "()")]
+struct LobbyJoined(Addr<GameLobby>);
+
 #[derive(Message)]
 #[rtype(result = "()")]
 struct GameUpdate{ game_data: GameData }
@@ -115,16 +122,17 @@ pub struct GameLobby {
 impl Actor for GameLobby {
     type Context = Context<Self>;
     fn started(&mut self, ctx: &mut Self::Context) {
+        self.game.new_round();
     }
 }
 
 impl Handler<JoinRequest> 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())
     }
 }
@@ -140,6 +148,10 @@ impl Handler<SubmitWordMsg> 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();
+        }
     }
 }
 
@@ -162,7 +174,39 @@ impl GameLobby {
     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::<Vec<_>>(),
-            state_data: GameStateData::Creating{ available_chars: vec!['a', 'b', 'c'] }
+            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::<Vec<_>>();
+        
+        let words_with_chars = self.game.players.iter()
+            .map(|p| p.submitted_word.clone().unwrap_or("".to_owned()))
+            .zip(chars)
+            .collect::<Vec<_>>();
+
+        let mut questions = self.game.round.exercises.iter()
+            .map(|x| x.question.clone())
+            .collect::<Vec<_>>();
+
+        questions.shuffle(&mut thread_rng());
+
+        GameStateData::Guessing {
+            submitted_words: words_with_chars,
+            questions: questions
         }
     }
 }
@@ -186,6 +230,13 @@ impl Actor for GameConnection {
     }
 }
 
+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<GameUpdate> for GameConnection {
     type Result = ();
     fn handle(&mut self, gu: GameUpdate, ctx: &mut Self::Context) -> Self::Result {
@@ -199,7 +250,6 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for GameConnection {
         msg: Result<ws::Message, ws::ProtocolError>,
         ctx: &mut Self::Context,
     ) {
-        // process websocket messages
         match msg {
             Ok(ws::Message::Ping(msg)) => {
                 self.heartbeat = Instant::now();
@@ -251,48 +301,28 @@ impl GameConnection {
     }
 
     pub fn received_message(&mut self, text: &str, ctx: &mut <Self as Actor>::Context) {
-        println!("hm: {:?}", text);
         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);
-                    let lobby_addr = self.server.do_send(CreateLobbyRequest{ lobby_id: game_id.clone(), p: ctx.address() });
-                    /*match lobby_addr {
-                        Ok(Answer::LobbyAlreadyExists) => {
-                            self.send_message(&UpdateMessage::GameAlreadyExists{ game_id: game_id.clone() }, ctx);
-                        },
-                        Ok(Answer::LobbyCreated(lobby_addr)) => {
-                            let gm = lobby_addr.do_send(GetGame);
-                            self.send_message(&UpdateMessage::GameState(gm.unwrap()), ctx);
-                        },
-                        _ => {
-                            println!("internal error creating lobby");
-                        }
-                    }*/
+                    self.server.do_send(CreateLobbyRequest{ lobby_id: game_id.clone(), p: ctx.address() });
                 },
                 ClientMessage::Join{game_id, nick} => {
-                    let lobby_addr = self.server.do_send(JoinRequest{ lobby_id: game_id.clone(), nick: nick.clone(), p: ctx.address() });
+                    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);
-                    /*match lobby_addr {
-                        Ok(Answer::NoSuchLobby) => {
-                            self.send_message(&UpdateMessage::GameNotFound{ game_id: game_id.clone() }, ctx);
-                        },
-                        Ok(Answer::LobbyJoined(lobby_addr)) => {
-                            //let gm = lobby_addr.send(GetGame));
-                            //self.send_message(&UpdateMessage::GameState(gm.unwrap()), ctx);
-                        },
-                        _ => {
-                            println!("internal error joining lobby");
-                        }
-                    }*/
                 },
                 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() });
+                    match &self.game_lobby {
+                        Some(lobby) => {
+                            if let Some(nick) = &self.nick {
+                                lobby.do_send(SubmitWordMsg{ word: word, nick: nick.clone() });
+                            }
+                        },
+                        None => {
+                            
                         }
                     }
                 },

+ 3 - 2
src/websocket.rs

@@ -27,8 +27,9 @@ pub enum ClientMessage {
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "snake_case")]
 pub enum GameStateData {
-    Creating{ available_chars: Vec<char> },
-    Guessing{ submitted_words: Vec<String>, questions: Vec<String> },
+    Starting,
+    Creating{ question: String, available_chars: Vec<char> },
+    Guessing{ submitted_words: Vec<(String, Vec<char>)>, questions: Vec<String> },
     Revealing{  },
 }
 

+ 23 - 3
static/index.html

@@ -38,7 +38,7 @@
                         word: word
                     }
                 };
-                connect(msg);
+                send(msg);
             });
 
             function connect(initial_message) {
@@ -58,6 +58,12 @@
                 };
             }
 
+            function send(message) {
+                if (connection != null) {
+                    connection.send(JSON.stringify(message));
+                }
+            }
+
             function disconnect() {
                 connection.close();
                 connection = null;
@@ -82,7 +88,16 @@
                                 playerlist += ", ";
                         }
                         $('#status').html("Players: " + playerlist);
-                        $('#createform').toggle();
+
+                        var creating = gs.state_data.creating;
+                        var chars = creating.available_chars;
+                        $('#question').val(creating.question);
+                        $('#letters').val(chars.join());
+                        $('#createform').show();
+                    }
+                    else if (gs.state_data.guessing != null) {
+                        $('#createform').hide();
+                        $('#guessing').show();
                     }
                 }
                 else {
@@ -103,11 +118,16 @@
 </form>
 
 <form id="createform" style="display:none">
-    <label>Create a word with the following letters</label>
+    <input id="question" type="text" readonly="readonly" /><br>
+    <label for="letters">Create a word with the following letters</label>
+    <input id="letters" type="text" readonly="readonly" /><br>
     <label for="word">Word:</label>
     <input id="word" type="text" /><br>
     <input id="submit" type="button" value="Submit Word" />
 </form>
+<div id="guessing" style="display:none">
+    You now have to guess!
+</div>
 <div id="status">
 </div>
 </body>