# HG changeset patch # User alfadur # Date 1530056086 -10800 # Node ID 87a6cad20c90c5324b7bfdbf07945fa1a17eecb1 # Parent 5fb27f94fc3b8085f125dc04ba0a735d794fb321 Implement game start & engine messages diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/main.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] diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/protocol/messages.rs --- 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), + 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], diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/protocol/parser.rs --- 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)) diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/server/actions.rs --- 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, protocol: Option, skip_self: bool @@ -90,6 +92,9 @@ RemoveTeam(String), RemoveClientTeams, SendRoomUpdate(Option), + 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()); } diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/server/client.rs --- 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, pub clan: Option, 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, } diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/server/handlers/inroom.rs --- 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<::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) -> 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::>()); + 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!") } } diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/server/room.rs --- 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, @@ -64,7 +68,7 @@ pub ready_players_number: u8, pub teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig, - pub game_info: Option<()> + pub game_info: Option } 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 { + 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 { diff -r 5fb27f94fc3b -r 87a6cad20c90 gameServer2/src/utils.rs --- 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(msg: T) -> String + where T: Iterator + Clone +{ + let mut tmp = Vec::new(); + tmp.push(msg.clone().count() as u8); + tmp.extend(msg); + encode(&tmp) } \ No newline at end of file