Mostly implement voting
authoralfadur
Sat, 07 Jul 2018 20:22:31 +0300
changeset 13478 d79795acaa73
parent 13477 f748a72432f2
child 13479 38f9097b6bbc
Mostly implement voting
gameServer2/src/protocol/messages.rs
gameServer2/src/protocol/parser.rs
gameServer2/src/protocol/test.rs
gameServer2/src/server/actions.rs
gameServer2/src/server/client.rs
gameServer2/src/server/coretypes.rs
gameServer2/src/server/handlers/inroom.rs
gameServer2/src/server/handlers/lobby.rs
gameServer2/src/server/handlers/loggingin.rs
gameServer2/src/server/network.rs
gameServer2/src/server/room.rs
gameServer2/src/server/server.rs
--- a/gameServer2/src/protocol/messages.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/protocol/messages.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,4 +1,7 @@
-use server::coretypes::{ServerVar, GameCfg, TeamInfo, HedgehogInfo};
+use server::coretypes::{
+    ServerVar, GameCfg, TeamInfo,
+    HedgehogInfo, VoteType
+};
 use std::{ops, convert::From, iter::once};
 
 #[derive(PartialEq, Eq, Clone, Debug)]
@@ -56,9 +59,9 @@
     Fix,
     Unfix,
     Greeting(String),
-    CallVote(Option<(String, Option<String>)>),
-    Vote(String),
-    ForceVote(String),
+    CallVote(Option<VoteType>),
+    Vote(bool),
+    ForceVote(bool),
     Save(String, String),
     Delete(String),
     SaveRoom(String),
@@ -90,6 +93,7 @@
     TeamColor(String, u8),
     HedgehogsNumber(String, u8),
     ConfigEntry(String, Vec<String>),
+    Kicked,
     RunGame,
     ForwardEngineMessage(Vec<String>),
     RoundFinished,
@@ -101,6 +105,10 @@
     Unreachable,
 }
 
+pub fn server_chat(msg: &str) -> HWServerMessage  {
+    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg: msg.to_string() }
+}
+
 impl GameCfg {
     pub fn to_protocol(&self) -> (String, Vec<String>) {
         use server::coretypes::GameCfg::*;
@@ -280,6 +288,7 @@
             HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
             ConfigEntry(name, values) =>
                 construct_message(&["CFG", name], &values),
+            Kicked => msg!["KICKED"],
             RunGame => msg!["RUN_GAME"],
             ForwardEngineMessage(em) =>
                 construct_message(&["EM"], &em),
--- a/gameServer2/src/protocol/parser.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/protocol/parser.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -18,7 +18,7 @@
     test::gen_proto_msg
 };
 use server::coretypes::{
-    HedgehogInfo, TeamInfo, GameCfg
+    HedgehogInfo, TeamInfo, GameCfg, VoteType
 };
 
 named!(end_of_message, tag!("\n\n"));
@@ -26,6 +26,9 @@
 named!(  a_line<&[u8], String>, map!(str_line, String::from));
 named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
 named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
+named!(yes_no_line<&[u8], bool>, alt!(
+      do_parse!(tag_no_case!("YES") >> (true))
+    | do_parse!(tag_no_case!("NO") >> (false))));
 named!(opt_param<&[u8], Option<String> >, alt!(
       do_parse!(peek!(tag!("\n\n")) >> (None))
     | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
@@ -42,6 +45,18 @@
               h5: hog_line >> eol >> h6: hog_line >> eol >>
               h7: hog_line >> eol >> h8: hog_line >>
               ([h1, h2, h3, h4, h5, h6, h7, h8])));
+named!(voting<&[u8], VoteType>, alt!(
+      do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
+        (VoteType::Kick(n)))
+    | do_parse!(tag_no_case!("MAP") >>
+        n: opt!(preceded!(spaces, a_line)) >>
+        (VoteType::Map(n)))
+    | do_parse!(tag_no_case!("PAUSE") >>
+        (VoteType::Pause))
+    | do_parse!(tag_no_case!("NEWSEED") >>
+        (VoteType::NewSeed))
+    | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
+        (VoteType::HedgehogsPerTeam(n)))));
 
 /** Recognizes messages which do not take any parameters */
 named!(basic_message<&[u8], HWProtocolMessage>, alt!(
@@ -94,10 +109,12 @@
     | do_parse!(tag_no_case!("GLOBAL")   >> spaces >> m: a_line  >> (Global(m)))
     | do_parse!(tag_no_case!("WATCH")    >> spaces >> i: a_line  >> (Watch(i)))
     | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line  >> (Greeting(m)))
-    | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: a_line  >> (Vote(m)))
-    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: a_line  >> (ForceVote(m)))
+    | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: yes_no_line >> (Vote(m)))
+    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: yes_no_line >> (ForceVote(m)))
     | do_parse!(tag_no_case!("INFO")     >> spaces >> n: a_line  >> (Info(n)))
     | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
+    | do_parse!(tag_no_case!("CALLVOTE") >>
+        v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
     | do_parse!(
         tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
         v: str_line >>
--- a/gameServer2/src/protocol/test.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/protocol/test.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -166,8 +166,8 @@
         47 => Unfix(),
         48 => Greeting(Ascii),
         //49 => CallVote(Option<(String, Option<String>)>),
-        50 => Vote(Ascii),
-        51 => ForceVote(Ascii),
+        50 => Vote(bool),
+        51 => ForceVote(bool),
         //52 => Save(String, String),
         53 => Delete(Ascii),
         54 => SaveRoom(Ascii),
--- a/gameServer2/src/server/actions.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/actions.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,19 +1,21 @@
 use std::{
     io, io::Write,
     iter::once,
-    mem::swap
+    mem::replace
 };
 use super::{
     server::HWServer,
-    room::{RoomId, GameInfo},
-    client::{ClientId, HWClient},
+    room::{GameInfo},
+    client::HWClient,
+    coretypes::{ClientId, RoomId, VoteType},
     room::HWRoom,
     handlers
 };
 use protocol::messages::{
     HWProtocolMessage,
     HWServerMessage,
-    HWServerMessage::*
+    HWServerMessage::*,
+    server_chat
 };
 use utils::to_engine_msg;
 
@@ -103,6 +105,8 @@
     SendTeamRemovalMessage(String),
     FinishRoomGame(RoomId),
     SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool},
+    AddVote{vote: bool, is_forced: bool},
+    ApplyVoting(VoteType, RoomId),
     Warn(String),
     ProtocolError(String)
 }
@@ -324,6 +328,80 @@
             }
             server.react(client_id, actions);
         }
+        AddVote{vote, is_forced} => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let mut result = None;
+                if let Some(ref mut voting) = r.voting {
+                    if is_forced || voting.votes.iter().find(|(id, _)| client_id == *id).is_none() {
+                        actions.push(server_chat("Your vote has been counted.").send_self().action());
+                        voting.votes.push((client_id, vote));
+                        let i = voting.votes.iter();
+                        let pro = i.clone().filter(|(_, v)| *v).count();
+                        let contra = i.filter(|(_, v)| !*v).count();
+                        let success_quota = voting.voters.len() / 2 + 1;
+                        if is_forced && vote || pro >= success_quota {
+                            result = Some(true);
+                        } else if is_forced && !vote || contra > voting.voters.len() - success_quota {
+                            result = Some(false);
+                        }
+                    } else {
+                        actions.push(server_chat("You already have voted.").send_self().action());
+                    }
+                } else {
+                    actions.push(server_chat("There's no voting going on.").send_self().action());
+                }
+
+                if let Some(res) = result {
+                    actions.push(server_chat("Voting closed.")
+                        .send_all().in_room(r.id).action());
+                    let voting = replace(&mut r.voting, None).unwrap();
+                    if res {
+                        actions.push(ApplyVoting(voting.kind, r.id));
+                    }
+                }
+            }
+
+            server.react(client_id, actions);
+        }
+        ApplyVoting(kind, room_id) => {
+            let mut actions = Vec::new();
+            let mut id = client_id;
+            match kind {
+                VoteType::Kick(nick) => {
+                    if let Some(c) = server.find_client(&nick) {
+                        if c.room_id == Some(room_id) {
+                            id = c.id;
+                            actions.push(Kicked.send_self().action());
+                            actions.push(MoveToLobby("kicked".to_string()));
+                        }
+                    }
+                },
+                VoteType::Map(_) => {
+                    unimplemented!();
+                },
+                VoteType::Pause => {
+                    if let Some(ref mut info) = server.room(client_id).unwrap().game_info {
+                        info.is_paused = !info.is_paused;
+                        actions.push(server_chat("Pause toggled.")
+                            .send_all().in_room(room_id).action());
+                        actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
+                            .send_all().in_room(room_id).action());
+                    }
+                },
+                VoteType::NewSeed => {
+                    unimplemented!();
+                },
+                VoteType::HedgehogsPerTeam(number) => {
+                    let r = &mut server.rooms[room_id];
+                    let nicks = r.set_hedgehogs_number(number);
+                    actions.extend(nicks.into_iter().map(|n|
+                        HedgehogsNumber(n, number).send_all().in_room(room_id).action()
+                    ));
+                },
+            }
+            server.react(id, actions);
+        }
         MoveToLobby(msg) => {
             let mut actions = Vec::new();
             let lobby_id = server.lobby_id;
@@ -470,10 +548,10 @@
         }
         FinishRoomGame(room_id) => {
             let mut actions = Vec::new();
-            let mut old_info = None;
+            let old_info;
             {
                 let r = &mut server.rooms[room_id];
-                swap(&mut old_info, &mut r.game_info);
+                old_info = replace(&mut r.game_info, None);
                 r.game_info = None;
                 r.ready_players_number = 1;
                 actions.push(SendRoomUpdate(None));
--- a/gameServer2/src/server/client.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/client.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,4 +1,4 @@
-pub type ClientId = usize;
+use super::coretypes::ClientId;
 
 pub struct HWClient {
     pub id: ClientId,
--- a/gameServer2/src/server/coretypes.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/coretypes.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,3 +1,6 @@
+pub type ClientId = usize;
+pub type RoomId = usize;
+
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum ServerVar {
     MOTDNew(String),
@@ -39,3 +42,29 @@
     pub name: String,
     pub hat: String,
 }
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum VoteType {
+    Kick(String),
+    Map(Option<String>),
+    Pause,
+    NewSeed,
+    HedgehogsPerTeam(u8)
+}
+
+#[derive(Clone, Debug)]
+pub struct Voting {
+    pub ttl: u32,
+    pub voters: Vec<ClientId>,
+    pub votes: Vec<(ClientId, bool)>,
+    pub kind: VoteType
+}
+
+impl Voting {
+    pub fn new(kind: VoteType, voters: Vec<ClientId>) -> Voting {
+        Voting {
+            kind, voters, ttl: 2,
+            votes: Vec::new()
+        }
+    }
+}
\ No newline at end of file
--- a/gameServer2/src/server/handlers/inroom.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/handlers/inroom.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -2,11 +2,12 @@
 
 use protocol::messages::{
     HWProtocolMessage,
-    HWServerMessage::*
+    HWServerMessage::*,
+    server_chat
 };
 use server::{
+    coretypes::{ClientId, Voting, VoteType},
     server::HWServer,
-    client::ClientId,
     room::HWRoom,
     actions::{Action, Action::*}
 };
@@ -262,6 +263,76 @@
             };
             server.react(client_id, actions);
         }
+        CallVote(None) => {
+            server.react(client_id, vec![
+                server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>")
+                    .send_self().action()])
+        }
+        CallVote(Some(kind)) => {
+            let (room_id, is_in_game) = server.room(client_id)
+                .map(|r| (r.id, r.game_info.is_some())).unwrap();
+            let error = match &kind {
+                VoteType::Kick(nick) => {
+                    if server.find_client(&nick).filter(|c| c.room_id == Some(room_id)).is_some() {
+                        None
+                    } else {
+                        Some("/callvote kick: No such user!")
+                    }
+                },
+                VoteType::Map(None) => {
+                    Some("/callvote map: Not implemented")
+                },
+                VoteType::Map(Some(name)) => {
+                    Some("/callvote map: Not implemented")
+                },
+                VoteType::Pause => {
+                    if is_in_game {
+                        None
+                    } else {
+                        Some("/callvote pause: No game in progress!")
+                    }
+                },
+                VoteType::NewSeed => {
+                    None
+                },
+                VoteType::HedgehogsPerTeam(number) => {
+                    match number {
+                        1...8 => None,
+                        _ => Some("/callvote hedgehogs: Specify number from 1 to 8.")
+                    }
+                },
+            };
+            match error {
+                None => {
+                    let voting = Voting::new(kind, server.room_clients(client_id));
+                    server.room(client_id).unwrap().voting = Some(voting);
+                    server.react(client_id, vec![
+                        server_chat("New voting started: ")
+                            .send_all().in_room(room_id).action(),
+                        AddVote{ vote: true, is_forced: false}]);
+                }
+                Some(msg) => {
+                    server.react(client_id, vec![
+                        server_chat(msg).send_self().action()])
+                }
+            }
+        }
+        Vote(vote) => {
+            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
+                vec![AddVote{ vote, is_forced: false }]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        }
+        ForceVote(vote) => {
+            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
+                vec![AddVote{ vote, is_forced: c.is_admin} ]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        }
         StartGame => {
             let actions = if let (_, Some(r)) = server.client_and_room(client_id) {
                 vec![StartRoomGame(r.id)]
--- a/gameServer2/src/server/handlers/lobby.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/handlers/lobby.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -2,7 +2,7 @@
 
 use server::{
     server::HWServer,
-    client::ClientId,
+    coretypes::ClientId,
     actions::{Action, Action::*}
 };
 use protocol::messages::{
--- a/gameServer2/src/server/handlers/loggingin.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/handlers/loggingin.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -2,7 +2,7 @@
 
 use server::{
     server::HWServer,
-    client::ClientId,
+    coretypes::ClientId,
     actions::{Action, Action::*}
 };
 use protocol::messages::{
--- a/gameServer2/src/server/network.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/network.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -4,7 +4,7 @@
     io, io::{Error, ErrorKind, Write},
     net::{SocketAddr, IpAddr, Ipv4Addr},
     collections::HashSet,
-    mem::swap
+    mem::{swap, replace}
 };
 
 use mio::{
@@ -18,7 +18,7 @@
 use protocol::{ProtocolDecoder, messages::*};
 use super::{
     server::{HWServer},
-    client::ClientId
+    coretypes::ClientId
 };
 
 const MAX_BYTES_PER_READ: usize = 2048;
@@ -277,8 +277,7 @@
 
     pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> {
         if self.has_pending_operations() {
-            let mut cache = Vec::new();
-            swap(&mut cache, &mut self.pending_cache);
+            let mut cache = replace(&mut self.pending_cache, Vec::new());
             cache.extend(self.pending.drain());
             for (id, state) in cache.drain(..) {
                 match state {
--- a/gameServer2/src/server/room.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/room.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,11 +1,10 @@
 use std::{iter};
 use server::{
-    coretypes::{TeamInfo, GameCfg, GameCfg::*},
-    client::{ClientId, HWClient}
+    coretypes::{ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting},
+    client::{HWClient}
 };
 
 const MAX_HEDGEHOGS_IN_ROOM: u8 = 48;
-pub type RoomId = usize;
 
 #[derive(Clone)]
 struct Ammo {
@@ -122,6 +121,7 @@
     pub ready_players_number: u8,
     pub teams: Vec<(ClientId, TeamInfo)>,
     config: RoomConfig,
+    pub voting: Option<Voting>,
     pub game_info: Option<GameInfo>
 }
 
@@ -141,6 +141,7 @@
             ready_players_number: 0,
             teams: Vec::new(),
             config: RoomConfig::new(),
+            voting: None,
             game_info: None
         }
     }
@@ -173,6 +174,23 @@
         }
     }
 
+    pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec<String> {
+        let mut names = Vec::new();
+        let teams = match self.game_info {
+            Some(ref mut info) => &mut info.teams_at_start,
+            None => &mut self.teams
+        };
+
+        if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM {
+            for (_, team) in teams.iter_mut() {
+                team.hedgehogs_number = n;
+                names.push(team.name.clone())
+            };
+            self.default_hedgehog_number = n;
+        }
+        names
+    }
+
     pub fn find_team_and_owner_mut<F>(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)>
         where F: Fn(&TeamInfo) -> bool {
         self.teams.iter_mut().find(|(_, t)| f(t)).map(|(id, t)| (*id, t))
--- a/gameServer2/src/server/server.rs	Fri Jul 06 21:03:03 2018 +0300
+++ b/gameServer2/src/server/server.rs	Sat Jul 07 20:22:31 2018 +0300
@@ -1,7 +1,8 @@
 use slab;
 use utils;
 use super::{
-    client::*, room::*, actions, handlers,
+    client::HWClient, room::HWRoom, actions, handlers,
+    coretypes::{ClientId, RoomId},
     actions::{Destination, PendingMessage}
 };
 use protocol::messages::*;
@@ -105,6 +106,14 @@
         self.rooms.iter_mut().find(|(_, r)| r.name == name).map(|(_, r)| r)
     }
 
+    pub fn find_client(&self, nick: &str) -> Option<&HWClient> {
+        self.clients.iter().find(|(_, c)| c.nick == nick).map(|(_, c)| c)
+    }
+
+    pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> {
+        self.clients.iter_mut().find(|(_, c)| c.nick == nick).map(|(_, c)| c)
+    }
+
     pub fn select_clients<F>(&self, f: F) -> Vec<ClientId>
         where F: Fn(&(usize, &HWClient)) -> bool {
         self.clients.iter().filter(f)