Implement game start & engine messages
authoralfadur
Wed, 27 Jun 2018 02:34:46 +0300
changeset 13423 87a6cad20c90
parent 13422 5fb27f94fc3b
child 13424 d8354cb98b98
Implement game start & engine messages
gameServer2/src/main.rs
gameServer2/src/protocol/messages.rs
gameServer2/src/protocol/parser.rs
gameServer2/src/server/actions.rs
gameServer2/src/server/client.rs
gameServer2/src/server/handlers/inroom.rs
gameServer2/src/server/room.rs
gameServer2/src/utils.rs
--- a/gameServer2/src/main.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/main.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -1,11 +1,13 @@
 #![allow(unused_imports)]
 #![deny(bare_trait_objects)]
 #![warn(unreachable_pub)]
+#![feature(slice_patterns)]
 
 extern crate rand;
 extern crate mio;
 extern crate slab;
 extern crate netbuf;
+extern crate base64;
 #[macro_use]
 extern crate nom;
 #[macro_use]
--- a/gameServer2/src/protocol/messages.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/protocol/messages.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -92,6 +92,9 @@
     TeamColor(String, u8),
     HedgehogsNumber(String, u8),
     ConfigEntry(String, Vec<String>),
+    RunGame,
+    ForwardEngineMessage(String),
+    RoundFinished,
 
     ServerMessage(String),
     Warning(String),
@@ -259,6 +262,9 @@
             HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
             ConfigEntry(name, values) =>
                 construct_message(&["CFG", name], &values),
+            RunGame => msg!["RUN_GAME"],
+            ForwardEngineMessage(em) => msg!["EM", em],
+            RoundFinished => msg!["ROUND_FINISHED"],
             ChatMsg(nick, msg) => msg!["CHAT", nick, msg],
             ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
             Warning(msg) => msg!["WARNING", msg],
--- a/gameServer2/src/protocol/parser.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/protocol/parser.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -36,7 +36,7 @@
     | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
     | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
     | do_parse!(tag!("START_GAME")     >> (StartGame))
-    | do_parse!(tag!("ROUNDFINISHED")  >> (RoundFinished))
+    | do_parse!(tag!("ROUNDFINISHED")  >> m: opt_param >> (RoundFinished))
     | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
     | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
     | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
--- a/gameServer2/src/server/actions.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/server/actions.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -1,9 +1,10 @@
 use std::{
-    io, io::Write
+    io, io::Write,
+    iter::once
 };
 use super::{
     server::HWServer,
-    room::RoomId,
+    room::{RoomId, GameInfo},
     client::{ClientId, HWClient},
     room::HWRoom,
     handlers
@@ -13,10 +14,11 @@
     HWServerMessage,
     HWServerMessage::*
 };
+use utils::to_engine_msg;
 
 pub enum Destination {
     ToSelf,
-        ToAll {
+    ToAll {
         room_id: Option<RoomId>,
         protocol: Option<u32>,
         skip_self: bool
@@ -90,6 +92,9 @@
     RemoveTeam(String),
     RemoveClientTeams,
     SendRoomUpdate(Option<String>),
+    StartRoomGame(RoomId),
+    SendTeamRemovalMessage(String),
+    FinishRoomGame(RoomId),
     Warn(String),
     ProtocolError(String)
 }
@@ -309,8 +314,59 @@
                 Vec::new()
             };
             server.react(client_id, actions);
+        },
+        StartRoomGame(room_id) => {
+            let actions = {
+                let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server.clients.iter()
+                    .map(|(id, c)| (id, c.nick.clone())).unzip();
+                let room = &mut server.rooms[room_id];
+
+                if !room.has_multiple_clans() {
+                    vec![Warn("The game can't be started with less than two clans!".to_string())]
+                } else if room.game_info.is_some() {
+                    vec![Warn("The game is already in progress".to_string())]
+                } else {
+                    room.game_info = Some(GameInfo {
+                        teams_in_game: room.teams.len() as u8
+                    });
+                    for id in room_clients {
+                        let c = &mut server.clients[id];
+                        c.is_in_game = true;
+                        c.team_indices = room.client_team_indices(c.id);
+                    }
+                    vec![RunGame.send_all().in_room(room.id).action(),
+                         SendRoomUpdate(None),
+                         ClientFlags("+g".to_string(), room_nicks)
+                             .send_all().in_room(room.id).action()]
+                }
+            };
+            server.react(client_id, actions);
         }
-
+        SendTeamRemovalMessage(team_name) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if let Some(ref mut info) = r.game_info {
+                    let msg = once(b'F').chain(team_name.bytes());
+                    actions.push(ForwardEngineMessage(to_engine_msg(msg)).
+                        send_all().in_room(r.id).but_self().action());
+                    info.teams_in_game -= 1;
+                    if info.teams_in_game == 0 {
+                        actions.push(FinishRoomGame(r.id));
+                    }
+                }
+            }
+            server.react(client_id, actions);
+        }
+        FinishRoomGame(room_id) => {
+            let actions = {
+                let r = &mut server.rooms[room_id];
+                r.game_info = None;
+                r.ready_players_number = 0;
+                vec![SendRoomUpdate(None),
+                     RoundFinished.send_all().in_room(r.id).action()]
+            };
+            server.react(client_id, actions);
+        }
         Warn(msg) => {
             run_action(server, client_id, Warning(msg).send_self().action());
         }
--- a/gameServer2/src/server/client.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/server/client.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -7,7 +7,9 @@
     pub protocol_number: u32,
     pub is_master: bool,
     pub is_ready: bool,
+    pub is_in_game: bool,
     pub teams_in_game: u8,
+    pub team_indices: Vec<u8>,
     pub clan: Option<u8>,
     pub is_joined_mid_game: bool,
 }
@@ -21,7 +23,9 @@
             protocol_number: 0,
             is_master: false,
             is_ready: false,
+            is_in_game: false,
             teams_in_game: 0,
+            team_indices: Vec::new(),
             clan: None,
             is_joined_mid_game: false,
         }
--- a/gameServer2/src/server/handlers/inroom.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/server/handlers/inroom.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -12,6 +12,53 @@
 };
 use utils::is_name_illegal;
 use std::mem::swap;
+use base64::{encode, decode};
+
+#[derive(Clone)]
+struct ByMsg<'a> {
+    messages: &'a[u8]
+}
+
+impl <'a> Iterator for ByMsg<'a> {
+    type Item = &'a[u8];
+
+    fn next(&mut self) -> Option<<Self as Iterator>::Item> {
+        if let Some(size) = self.messages.get(0) {
+            let (msg, next) = self.messages.split_at(*size as usize + 1);
+            self.messages = next;
+            Some(msg)
+        } else {
+            None
+        }
+    }
+}
+
+fn by_msg(source: &Vec<u8>) -> ByMsg {
+    ByMsg {messages: &source[..]}
+}
+
+const VALID_MESSAGES: &[u8] =
+    b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A";
+const NON_TIMED_MESSAGES: &[u8] = b"M#hb";
+
+fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool {
+    if let [size, typ, body..] = msg {
+        VALID_MESSAGES.contains(typ) &&
+            match body {
+                [1...8, team, ..] if *typ == b'h' => team_indices.contains(team),
+                _ => *typ != b'h'
+            }
+    } else {
+        false
+    }
+}
+
+fn is_msg_empty(msg: &[u8]) -> bool {
+    match msg {
+        [_, b'+', ..] => true,
+        _ => false
+    }
+}
 
 pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     use protocol::messages::HWProtocolMessage::*;
@@ -76,7 +123,7 @@
                     actions.push(Warn("Too many hedgehogs!".to_string()))
                 } else if r.find_team(|t| t.name == info.name) != None {
                     actions.push(Warn("There's already a team with same name in the list.".to_string()))
-                } else if r.game_info != None {
+                } else if r.game_info.is_some() {
                     actions.push(Warn("Joining not possible: Round is in progress.".to_string()))
                 } else {
                     let team = r.add_team(c.id, info);
@@ -178,6 +225,51 @@
             };
             server.react(client_id, actions);
         }
+        StartGame => {
+            let actions = if let (_, Some(r)) = server.client_and_room(client_id) {
+                vec![StartRoomGame(r.id)]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        }
+        EngineMessage(em) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.teams_in_game > 0 {
+                    let decoding = decode(&em[..]).unwrap();
+                    let messages = by_msg(&decoding);
+                    let valid = messages.clone().filter(|m| is_msg_valid(m, &c.team_indices));
+                    let _non_empty = messages.filter(|m| !is_msg_empty(m));
+                    let _last_valid_timed_msg = valid.clone().scan(None, |res, msg| match msg {
+                        [_, b'+', ..] => Some(msg),
+                        [_, typ, ..] if NON_TIMED_MESSAGES.contains(typ) => *res,
+                        _ => None
+                    }).next();
+
+                    let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::<Vec<_>>());
+                    if !em_response.is_empty() {
+                        actions.push(ForwardEngineMessage(em_response)
+                            .send_all().in_room(r.id).but_self().action());
+                    }
+                }
+            }
+            server.react(client_id, actions)
+        }
+        RoundFinished => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_in_game {
+                    c.is_in_game = false;
+                    actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]).
+                        send_all().in_room(r.id).action());
+                    for team in r.client_teams(c.id) {
+                        actions.push(SendTeamRemovalMessage(team.name.clone()));
+                    }
+                }
+            }
+            server.react(client_id, actions)
+        }
         _ => warn!("Unimplemented!")
     }
 }
--- a/gameServer2/src/server/room.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/server/room.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -51,6 +51,10 @@
     }
 }
 
+pub struct GameInfo {
+    pub teams_in_game: u8
+}
+
 pub struct HWRoom {
     pub id: RoomId,
     pub master_id: Option<ClientId>,
@@ -64,7 +68,7 @@
     pub ready_players_number: u8,
     pub teams: Vec<(ClientId, TeamInfo)>,
     config: RoomConfig,
-    pub game_info: Option<()>
+    pub game_info: Option<GameInfo>
 }
 
 impl HWRoom {
@@ -127,6 +131,12 @@
         self.teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
     }
 
+    pub fn client_team_indices(&self, client_id: ClientId) -> Vec<u8> {
+        self.teams.iter().enumerate()
+            .filter(move |(_, (id, _))| *id == client_id)
+            .map(|(i, _)| i as u8).collect()
+    }
+
     pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> {
         self.teams.iter().find(|(_, t)| t.name == team_name)
             .map(|(id, t)| (*id, &t.name[..]))
@@ -136,6 +146,11 @@
         self.client_teams(owner_id).nth(0).map(|t| t.color)
     }
 
+    pub fn has_multiple_clans(&self) -> bool {
+        self.teams.iter().min_by_key(|(_, t)| t.color) !=
+            self.teams.iter().max_by_key(|(_, t)| t.color)
+    }
+
     pub fn set_config(&mut self, cfg: GameCfg) {
         let c = &mut self.config;
         match cfg {
--- a/gameServer2/src/utils.rs	Tue Jun 26 23:22:38 2018 +0300
+++ b/gameServer2/src/utils.rs	Wed Jun 27 02:34:46 2018 +0300
@@ -1,4 +1,6 @@
+use std::iter::Iterator;
 use mio;
+use base64::{encode};
 
 pub const PROTOCOL_VERSION : u32 = 3;
 pub const SERVER: mio::Token = mio::Token(1000000000 + 0);
@@ -9,4 +11,13 @@
         name.chars().any(|c|
             "$()*+?[]^{|}\x7F".contains(c) ||
                 '\x00' <= c && c <= '\x1F')
+}
+
+pub fn to_engine_msg<T>(msg: T) -> String
+    where T: Iterator<Item = u8> + Clone
+{
+    let mut tmp = Vec::new();
+    tmp.push(msg.clone().count() as u8);
+    tmp.extend(msg);
+    encode(&tmp)
 }
\ No newline at end of file