Nicolas Winkler %!s(int64=4) %!d(string=hai) anos
pai
achega
4f6d2d299b
Modificáronse 10 ficheiros con 141 adicións e 54 borrados
  1. 10 4
      Cargo.toml
  2. 11 4
      src/datasource.rs
  3. 20 7
      src/game.rs
  4. 35 10
      src/main.rs
  5. 24 7
      src/server/gamelobby.rs
  6. 4 8
      src/server/server.rs
  7. 2 9
      src/websocket.rs
  8. 14 1
      static/comm.js
  9. 2 1
      static/index.html
  10. 19 3
      static/style.css

+ 10 - 4
Cargo.toml

@@ -4,6 +4,8 @@ version = "0.1.0"
 authors = ["Nicolas Winkler <nicolas.winkler@gmx.ch>"]
 edition = "2018"
 
+[features]
+with_sqlite = ["r2d2_sqlite", "r2d2", "rusqlite"]
 
 [dependencies]
 actix = "0.10"
@@ -18,10 +20,14 @@ bytes = "0.5.3"
 serde = "1.0"
 serde_json = "1.0"
 rand = "0.7.3"
-lazy_static = "1.4.0"
-r2d2_sqlite = "0.17.0"
-r2d2 = "0.8.9"
+toml = "0.4.2"
+clap = "2.33.3"
+
+
+r2d2_sqlite = { version = "0.17.0", optional = true }
+r2d2 = { version = "0.8.9", optional = true }
 
 [dependencies.rusqlite]
 version = "0.24.2"
-features = ["bundled"]
+features = ["bundled"]
+optional = true

+ 11 - 4
src/datasource.rs

@@ -1,7 +1,3 @@
-use r2d2_sqlite::SqliteConnectionManager;
-use r2d2::Pool;
-#[macro_use]
-use rusqlite::params;
 use std::sync::Arc;
 use std::fs::File;
 use std::io::{self, BufRead};
@@ -9,6 +5,14 @@ use rand::thread_rng;
 use rand::seq::SliceRandom;
 use rand::distributions::{Distribution, WeightedIndex};
 
+#[cfg(feature = "with_sqlite")]
+use r2d2_sqlite::SqliteConnectionManager;
+#[cfg(feature = "with_sqlite")]
+use r2d2::Pool;
+#[cfg(feature = "with_sqlite")]
+#[macro_use]
+use rusqlite::params;
+
 pub fn create_array_source(filename: &str) -> Arc<dyn DataSource<String>> {
     let file = File::open(filename).unwrap();
     let lines = io::BufReader::new(file).lines();
@@ -49,10 +53,12 @@ impl<T> ArraySource<T> {
     }
 }
 
+#[cfg(feature = "with_sqlite")]
 pub struct SqliteSource {
     pool: Pool<SqliteConnectionManager>
 }
 
+#[cfg(feature = "with_sqlite")]
 impl DataSource<String> for SqliteSource {
     fn get_ith(& self, i: usize) -> Option<String> {
         match self.pool.get() {
@@ -86,6 +92,7 @@ impl DataSource<String> for SqliteSource {
     }
 }
 
+#[cfg(feature = "with_sqlite")]
 impl SqliteSource {
     pub fn open(db_name: &str) -> Result<Self, r2d2::Error> {
         Ok(SqliteSource {

+ 20 - 7
src/game.rs

@@ -47,7 +47,7 @@ impl Game {
     }
 
     pub fn check_letters(word: &str, letters: &Vec<char>) -> bool {
-        let allowedChars = [" ", "-", "'"];
+        let allowed_chars = [" ", "-", "'"];
         if word.len() == 0 {
             return false;
         }
@@ -61,7 +61,7 @@ impl Game {
         for c in word.to_uppercase().chars() {
             let upper = String::from(c);
             let count = countmap.get(&upper);
-            if allowedChars.iter().any(|x| &upper == *x) {
+            if allowed_chars.iter().any(|x| &upper == *x) {
                 continue;
             }
             if let Some(&v) = count {
@@ -178,7 +178,7 @@ impl Game {
         self.players.retain(|p| p.nick != nick);
     }
 
-    pub fn create_results(&self) -> Vec<Vec<Vec<String>>> {
+    pub fn create_results(&self) -> (Vec<Vec<Vec<String>>>, Vec<(String, i64)>) {
         let mut result: Vec<Vec<Vec<String>>> = Vec::new();
         let mut questions = self.players.iter()
                         .map(|p| p.creating_exercise.clone().unwrap().question)
@@ -187,15 +187,23 @@ impl Game {
                                 .map(|x| x.clone())
                                 .collect::<Vec<_>>()
                         );
-        for player in &self.players {
+
+        let mut player_points: Vec<i64> = vec![0; self.players.len()];
+        for (player, p_id) in self.players.iter().zip(0..) {
             let mut line: Vec<Vec<String>> = Vec::new();
             let word = player.submitted_word.as_ref().unwrap();
-            for question in &questions {
+            for (question, q_id) in questions.iter().zip(0..) {
                 let mut cell: Vec<String> = Vec::new();
-                for p in &self.players {
+                for (p, inner_p) in self.players.iter().zip(0..) {
                     if let Some(guess) = &p.submitted_guess {
                         if let Some(_corr) = guess.iter().find(|(w, q)| w == word && q == question) {
                             cell.push(p.nick.clone());
+
+                            // if solution is correct and not its own
+                            if p_id == q_id && p_id != inner_p {
+                                player_points[p_id] += 1;
+                                player_points[inner_p] += 1;
+                            }
                         }
                     }
                 }
@@ -203,7 +211,12 @@ impl Game {
             }
             result.push(line);
         }
-        return result;
+        let point_list = self.players.iter()
+                                     .map(|p| p.nick.clone())
+                                     .zip(player_points)
+                                     .collect::<Vec<_>>();
+
+        return (result, point_list);
     }
 }
 

+ 35 - 10
src/main.rs

@@ -1,5 +1,4 @@
 use std::collections::BTreeMap;
-use std::sync::Arc;
 use actix::prelude::*;
 use actix_files as fs;
 use actix_web::{middleware, web, App, HttpServer};
@@ -7,36 +6,62 @@ use actix_web::{middleware, web, App, HttpServer};
 mod websocket;
 mod game;
 mod datasource;
+mod config;
 mod server {
     pub mod server;
     pub mod messages;
     pub mod gamelobby;
 }
-
 use crate::server::server::{Server, ws_initiate};
 
+
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
+
+    let args = clap::App::new("Acorn server")
+        .version("0.1.0")
+        .author("Nicolas Winkler <nicolas.winkler@gmx.ch>")
+        .about("Server to play Elchhäutgerät games")
+        .arg(clap::Arg::with_name("config")
+                 .short("c")
+                 .long("config")
+                 .takes_value(true)
+                 .help("config file"))
+        .get_matches();
+    
+    let config_path = args.value_of("config").unwrap_or("config.toml");
+    let config_txt = std::fs::read_to_string(config_path).expect("could not open config file");
+    let config = config::parse_config(&config_txt).expect("invalid config file");
+
+    let server_bind_addr = config.server_config.ip + ":" +
+        &match config.server_config.port {
+            Some(p) => p.to_string(),
+            None => "8000".to_owned()
+        };
+
     std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
     env_logger::init();
 
     let server = Server {
         lobbies: BTreeMap::new(),
-        default_data: datasource::create_array_source("questions.txt")
+        default_data: datasource::create_array_source(&config.default_questions)
     };
     let server_addr = server.start();
 
-    HttpServer::new(move || {
+    let server = HttpServer::new(move || {
         App::new()
             .app_data(web::Data::new(server_addr.clone()))
-            // enable logger
             .wrap(middleware::Logger::default())
-            // websocket route
             .service(web::resource("/ws/").route(web::get().to(ws_initiate)))
-            // static files
             .service(fs::Files::new("/", "static/").index_file("index.html"))
     })
-    .bind("127.0.0.1:8000")?
-    .run()
-    .await
+    .bind(server_bind_addr)?;
+
+    let server = if let Some(t) = config.server_config.workers {
+        server.workers(t as usize)
+    } else {
+        server
+    };
+
+    server.run().await
 }

+ 24 - 7
src/server/gamelobby.rs

@@ -28,6 +28,7 @@ pub struct GameLobby {
     connected_players: BTreeMap<String, Addr<GameConnection>>,
     game_id: String,
     game: game::Game,
+    player_points: BTreeMap<String, i64>,
     waiting_players: BTreeMap<String, Addr<GameConnection>>,
     ready_players: Vec<String>,
     lobby_state: LobbyState,
@@ -111,8 +112,14 @@ impl Handler<SubmitGuessMsg> for GameLobby {
     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();
+
+            for (nick, pts) in &results.point_list {
+                self.add_player_points(nick, *pts);
+            }
+
             self.broadcast_results(results);
 
             self.game.next_state();
@@ -128,6 +135,7 @@ impl GameLobby {
             connected_players: BTreeMap::new(),
             game_id: gi,
             game: game::Game::new(),
+            player_points: BTreeMap::new(),
             waiting_players: BTreeMap::new(),
             ready_players: Vec::new(),
             lobby_state: LobbyState::Starting,
@@ -137,7 +145,6 @@ impl GameLobby {
     }
 
     fn set_state(&mut self, new_state: LobbyState) {
-
         match new_state {
             LobbyState::Starting => {
                 for (nick, _addr) in &self.waiting_players {
@@ -149,7 +156,6 @@ impl GameLobby {
             LobbyState::Creating => {
                 let s = &mut self.shuffler;
                 let ld = &self.letter_distribution;
-                let mut index = 0;
                 self.game.start_round(|| s.get(), || ld.get(4, 6));
             },
             _ => {}
@@ -157,6 +163,18 @@ impl GameLobby {
         self.lobby_state = new_state;
     }
 
+    pub fn get_player_points(&self, nick: &str) -> i64 {
+        *self.player_points.get(nick).unwrap_or(&0)
+    }
+
+    pub fn add_player_points(&mut self, nick: &str, points: i64) {
+        let entry = self.player_points.get_mut(nick);
+        match entry {
+            Some(x) => *x += points,
+            None => { self.player_points.insert(nick.to_owned(), points); },
+        }
+    }
+
     pub fn send_game_to_all(&self) {
         for (nick, player) in &self.connected_players {
             let game_state = self.create_opaque_message(nick);
@@ -165,7 +183,7 @@ impl GameLobby {
     }
 
     pub fn create_result_data(&self) -> RoundResultData {
-        let results_table = self.game.create_results();
+        let (guesses, point_list) = self.game.create_results();
 
         let words = self.game.players.iter()
                         .map(|p| p.submitted_word.clone().unwrap())
@@ -179,13 +197,12 @@ impl GameLobby {
                         .zip(questions.iter().map(|x| x.clone()))
                         .collect::<Vec<_>>();
 
-        let guesses = self.game.create_results();
-
         RoundResultData {
             words,
             questions,
             solutions,
-            guesses
+            guesses,
+            point_list
         }
     }
 
@@ -201,7 +218,7 @@ impl GameLobby {
                         .find(|p| p.nick == nick);
         GameData {
             players: self.game.players.iter()
-                        .map(|p| websocket::PlayerData{ nick: p.nick.clone(), points: 0 })
+                        .map(|p| websocket::PlayerData{ nick: p.nick.clone(), points: self.get_player_points(&p.nick) })
                         .collect::<Vec<_>>(),
             state_data:
                 match self.game.state {

+ 4 - 8
src/server/server.rs

@@ -1,13 +1,9 @@
 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 std::sync::{Arc};
 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};
@@ -32,13 +28,13 @@ pub struct Server {
 
 impl Actor for Server {
     type Context = Context<Self>;
-    fn started(&mut self, ctx: &mut Self::Context) {
+    fn started(&mut self, _ctx: &mut Self::Context) {
     }
 }
 
 impl Handler<JoinRequest> for Server {
     type Result = ();
-    fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result {
+    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) => {
@@ -73,7 +69,7 @@ impl Handler<CreateLobbyRequest> for Server {
 
 impl Handler<LobbyFinished> for Server {
     type Result = ();
-    fn handle(&mut self, lf: LobbyFinished, ctx: &mut Self::Context) -> Self::Result {
+    fn handle(&mut self, lf: LobbyFinished, _ctx: &mut Self::Context) -> Self::Result {
         self.lobbies.remove(&lf.0);
     }
 }

+ 2 - 9
src/websocket.rs

@@ -1,13 +1,5 @@
 use serde::{Serialize, Deserialize};
 
-use actix::prelude::*;
-use actix::Message;
-use actix_web::{web, Error, HttpRequest, HttpResponse};
-use actix_web_actors::ws;
-
-use crate::server::server::Server;
-use crate::server::server::GameConnection;
-
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "snake_case")]
 pub enum ClientMessage {
@@ -32,7 +24,7 @@ pub enum GameStateData {
 #[serde(rename_all = "snake_case")]
 pub struct PlayerData {
     pub nick: String,
-    pub points: i32
+    pub points: i64
 }
 
 #[derive(Serialize, Deserialize)]
@@ -49,6 +41,7 @@ pub struct RoundResultData {
     pub questions: Vec<String>,
     pub solutions: Vec<(String, String)>,
     pub guesses: Vec<Vec<Vec<String>>>,
+    pub point_list: Vec<(String, i64)>
 }
 
 #[derive(Serialize)]

+ 14 - 1
static/comm.js

@@ -225,7 +225,20 @@ $(function() {
             $table.append(wordline);
         }
 
-        $('#results').html($table);
+        $('#guesses-table').html($table);
+        $('#points-table').html("");
+
+        var header = "<tr>";
+        var points = "<tr>";
+        var pl = result.point_list;
+        for(var i = 0; i < pl.length; i++) {
+            header += "<td>" + pl[i][0] + "</td>";
+            points += "<td>" + pl[i][1] + "</td>";
+        }
+        header += "</tr>";
+        points += "</tr>";
+        $('#points-table').append(header);
+        $('#points-table').append(points);
 
         setView('results');
     }

+ 2 - 1
static/index.html

@@ -89,7 +89,8 @@
             </form>
         </div>
         <div id="results" style="margin-top: 20px; display:none">
-        
+            <div id="guesses-table"></div>
+            <table id="points-table" class="points-table"></table>
         </div>
         <div id="status">
         </div>

+ 19 - 3
static/style.css

@@ -4,6 +4,7 @@
     --color-dark: #353535;
     --color-darker: #202020;
 
+    --color2-bright: #b32525;
     --color2: #921010;
     --color2-darker: #5a0e11;
 
@@ -86,6 +87,8 @@ body {
 input[type="button"], input[type="submit"], button {
     border: none;
     border-radius: 4px;
+    -moz-outline-radius: 4px;
+
     color: white;
     padding: 8px 15px;
     text-align: center;
@@ -106,9 +109,12 @@ input[type="button"]:hover, input[type="submit"]:hover, button:hover {
     border: none;
 }
 
+
 input[type="text"], textarea {
     border: 1px solid var(--color3);
     border-radius: 4px;
+    -moz-outline-radius: 4px;
+
     color: white;
     padding: 5px 10px;
     text-decoration: none;
@@ -249,17 +255,27 @@ label {
 }
 
 .result-table {
-    font-family: Arial, Helvetica, sans-serif;
     border-collapse: collapse;
     margin: 12px 0px;
-    width: 70%;
 }
 
-td, th {
+.result-table td, th {
     border: 1px solid #2b2b4d;
     padding: 8px;
 }
 
+.points-table {
+    border-collapse: collapse;
+    margin: 12px 0px;
+}
+
+.points-table th {
+    border: 1px solid #2b2b4d;
+    font-size: larger;
+    background-color: var(--color-darker);
+    padding: 8px;
+}
+
 .result-correct {
     background-color: #005218;
 }