diff --git a/src/main.rs b/src/main.rs index bbfd6ac..50e03f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ extern crate serde; #[macro_use] extern crate log; extern crate log4rs; +mod structs; mod messages; mod snake; mod util; @@ -19,6 +20,7 @@ use std::thread; use std::time::Duration; use std::sync::mpsc; use std::sync::Arc; +use messages::{ Inbound }; const HOST: &'static str = "snake.cygni.se"; const PORT: i32 = 80; @@ -58,66 +60,57 @@ struct Client { id_sender: mpsc::Sender } -fn route_msg(client: &mut Client, msg: &String) -> Result<(), ClientError> { +fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> { let snake = &mut client.snake; + let inbound_msg = try!(messages::parse_inbound_msg(str_msg)); - if msg.contains(messages::GAME_ENDED) { - let json_msg: messages::GameEnded = try!(serde_json::from_str(msg)); - snake.on_game_ended(&json_msg); - - if is_training_mode() { + match inbound_msg { + Inbound::GameEnded(msg) => { + snake.on_game_ended(&msg); + if is_training_mode() { + try!(client.out.close(ws::CloseCode::Normal)); + } + }, + Inbound::TournamentEnded(msg) => { + snake.on_tournament_ended(&msg); try!(client.out.close(ws::CloseCode::Normal)); - } - } else if msg.contains(messages::TOURNAMENT_ENDED) { - let json_msg: messages::TournamentEnded = try!(serde_json::from_str(msg)); - snake.on_tournament_ended(&json_msg); - try!(client.out.close(ws::CloseCode::Normal)); - } else if msg.contains(messages::MAP_UPDATE) { - let json_msg: messages::MapUpdate = try!(serde_json::from_str(msg)); - let direction = snake.get_next_move(&json_msg); - - let response = messages::RegisterMove { - type_: String::from(messages::REGISTER_MOVE), - direction: maputil::direction_as_string(&direction), - gameTick: json_msg.gameTick, - receivingPlayerId: json_msg.receivingPlayerId, - gameId: json_msg.gameId - }; - debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", response); - - let response = try!(serde_json::to_string(&response)); - try!(client.out.send(response)); - } else if msg.contains(messages::SNAKE_DEAD) { - let json_msg: messages::SnakeDead = try!(serde_json::from_str(msg)); - snake.on_snake_dead(&json_msg); - } else if msg.contains(messages::GAME_STARTING) { - let json_msg: messages::GameStarting = try!(serde_json::from_str(msg)); - snake.on_game_starting(&json_msg); - } else if msg.contains(messages::PLAYER_REGISTERED) { - let json_msg: messages::PlayerRegistered = try!(serde_json::from_str(msg)); - info!(target: LOG_TARGET, "Successfully registered player"); - - snake.on_player_registered(&json_msg); - - if json_msg.gameMode == "TRAINING" { - let start_msg = messages::StartGame { - type_: String::from(messages::START_GAME) - }; - debug!(target: LOG_TARGET, "Requesting a game start {:?}", start_msg); - - let response = try!(serde_json::to_string(&start_msg)); + }, + Inbound::MapUpdate(msg) => { + let direction = maputil::direction_as_string(&snake.get_next_move(&msg)); + let response = try!(messages::create_register_move_msg(direction, msg)); + debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", response); try!(client.out.send(response)); - }; + }, + Inbound::SnakeDead(msg) => { + snake.on_snake_dead(&msg); + }, + Inbound::GameStarting(msg) => { + snake.on_game_starting(&msg); + }, + Inbound::PlayerRegistered(msg) => { + info!(target: LOG_TARGET, "Successfully registered player"); + snake.on_player_registered(&msg); - try!(client.out_sender.send(client.out.clone())); - try!(client.id_sender.send(json_msg.receivingPlayerId)); - } else if msg.contains(messages::INVALID_PLAYER_NAME) { - let json_msg: messages::InvalidPlayerName = try!(serde_json::from_str(msg)); - snake.on_invalid_playername(&json_msg); - } else if msg.contains(messages::HEART_BEAT_RESPONSE) { - // do nothing - let _: messages::InvalidPlayerName = try!(serde_json::from_str(msg)); - } + if msg.gameMode == "TRAINING" { + let response = try!(messages::create_start_game_msg()); + debug!(target: LOG_TARGET, "Requesting a game start {:?}", response); + try!(client.out.send(response)); + }; + + info!(target: LOG_TARGET, "Starting heart beat"); + try!(client.out_sender.send(client.out.clone())); + try!(client.id_sender.send(msg.receivingPlayerId)); + }, + Inbound::InvalidPlayerName(msg) => { + snake.on_invalid_playername(&msg); + }, + Inbound::HeartBeatResponse(_) => { + // do nothing + }, + Inbound::UnrecognizedMessage => { + + } + }; Ok(()) } @@ -127,16 +120,15 @@ impl ws::Handler for Client { fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { debug!(target: LOG_TARGET, "Connection to Websocket opened"); - let message = messages::PlayRegistration { - type_: String::from(messages::REGISTER_PLAYER_MESSAGE_TYPE), - playerName: self.snake.get_name(), - gameSettings: messages::default_gamesettings() - }; + let parse_msg = messages::create_play_registration_msg(self.snake.get_name()); - info!(target: LOG_TARGET, "Registering player with message: {:?}", message); - - let encoded_message = serde_json::to_string(&message).unwrap(); - self.out.send(encoded_message) + if let Ok(response) = parse_msg { + info!(target: LOG_TARGET, "Registering player with message: {:?}", response); + self.out.send(response) + } else { + error!(target: LOG_TARGET, "Unable to create play registration message {:?}", parse_msg); + self.out.close(ws::CloseCode::Error) + } } fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { @@ -173,43 +165,57 @@ fn start_websocket_thread(id_sender: mpsc::Sender, }) } +fn do_heart_beat(id: String, out: Arc, done_receiver: mpsc::Receiver<()>) { + loop { + thread::sleep(Duration::from_secs(HEART_BEAT_S)); + let rec = done_receiver.try_recv(); + + // if the channel is disconnected or a done message is sent, break the loop + if let Err(e) = rec { + if e == mpsc::TryRecvError::Disconnected { + debug!(target: LOG_TARGET, "Stopping heartbeat due to channel disconnecting"); + break; + } + } else { + debug!(target: LOG_TARGET, "Stopping heartbeat due to finished execution"); + break; + } + + debug!(target: LOG_TARGET, "Sending heartbeat request"); + + let id = id.clone(); + let parsed_msg = messages::create_heart_beat_msg(id); + if let Ok(heart_beat) = parsed_msg { + let send_result = out.send(heart_beat); + if let Err(e) = send_result { + error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e); + } + } else { + error!(target: LOG_TARGET, "Unable to parse heart beat message {:?}", parsed_msg); + } + } +} + +pub fn recv_channels(id_receiver: mpsc::Receiver, + out_receiver: mpsc::Receiver>) + -> Result<(String, Arc), mpsc::RecvError> { + let id = try!(id_receiver.recv()); + let out = try!(out_receiver.recv()); + Ok((id, out)) +} + fn start_heart_beat_thread(id_receiver: mpsc::Receiver, out_receiver: mpsc::Receiver>, done_receiver: mpsc::Receiver<()>) -> thread::JoinHandle<()> { thread::spawn(move || { - let id = id_receiver.recv().unwrap(); - let out = out_receiver.recv().unwrap(); + let res = recv_channels(id_receiver, out_receiver); - debug!(target: LOG_TARGET, "Starting heartbeat"); - - loop { - thread::sleep(Duration::from_secs(HEART_BEAT_S)); - let rec = done_receiver.try_recv(); - - // if the channel is disconnected or a done message is sent, break the loop - if let Err(e) = rec { - if e == mpsc::TryRecvError::Disconnected { - debug!(target: LOG_TARGET, "Stopping heartbeat due to channel disconnecting"); - break; - } - } else { - debug!(target: LOG_TARGET, "Stopping heartbeat due to finished execution"); - break; - } - - let id = id.clone(); - let heart_beat = messages::HeartBeatRequest { - type_: String::from( messages::HEART_BEAT_REQUEST ), - receivingPlayerId: id - }; - - debug!(target: LOG_TARGET, "Sending heartbeat request"); - let request = serde_json::to_string(&heart_beat).unwrap(); - let send_result = out.send(request); - if let Err(e) = send_result { - error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e); - } - } + if let Ok((id, out)) = res { + debug!(target: LOG_TARGET, "Starting heartbeat"); + do_heart_beat(id, out, done_receiver); + } else { + error!(target: LOG_TARGET, "Unable to start heart beat, the channel has been closed."); + }; }) } diff --git a/src/maputil.rs b/src/maputil.rs index 1024b6d..e2b43f6 100644 --- a/src/maputil.rs +++ b/src/maputil.rs @@ -1,4 +1,4 @@ -use messages::{ Map, SnakeInfo }; +use structs::{ Map, SnakeInfo }; use util; #[derive(PartialEq, Debug)] diff --git a/src/messages.rs b/src/messages.rs index 08096bb..37ffa45 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,5 +1,7 @@ -#![allow(non_snake_case)] -//inbound messages +use structs; +use serde_json::{ from_str, to_string, Error }; + +// Inbound pub const GAME_ENDED: &'static str = "se.cygni.snake.api.event.GameEndedEvent"; pub const TOURNAMENT_ENDED: &'static str = @@ -17,181 +19,87 @@ pub const INVALID_PLAYER_NAME: &'static str = pub const HEART_BEAT_RESPONSE: &'static str = "se.cygni.snake.api.request.HeartBeatResponse"; -//outbound messages -pub const REGISTER_PLAYER_MESSAGE_TYPE: &'static str = +// Outbound +const REGISTER_PLAYER_MESSAGE_TYPE: &'static str = "se.cygni.snake.api.request.RegisterPlayer"; -pub const START_GAME: &'static str = +const START_GAME: &'static str = "se.cygni.snake.api.request.StartGame"; -pub const REGISTER_MOVE: &'static str = +const REGISTER_MOVE: &'static str = "se.cygni.snake.api.request.RegisterMove"; -pub const HEART_BEAT_REQUEST: &'static str = +const HEART_BEAT_REQUEST: &'static str = "se.cygni.snake.api.request.HeartBeatRequest"; -// Outbound messages -#[derive(Serialize, Deserialize, Debug)] -pub struct GameSettings { - pub width: String, - pub height: String, - pub maxNoofPlayers: u32, - pub startSnakeLength: u32, - pub timeInMsPerTick: u32, - pub obstaclesEnabled: bool, - pub foodEnabled: bool, - pub edgeWrapsAround: bool, - pub headToTailConsumes: bool, - pub tailConsumeGrows: bool, - pub addFoodLikelihood: u32, - pub removeFoodLikelihood: u32, - pub addObstacleLikelihood: u32, - pub removeObstacleLikelihood: u32, - pub spontaneousGrowthEveryNWorldTick: u32, - pub trainingGame: bool, - pub pointsPerLength: u32, - pub pointsPerFood: u32, - pub pointsPerCausedDeath: u32, - pub pointsPerNibble: u32, - pub pointsLastSnakeLiving: u32, - pub noofRoundsTailProtectedAfterNibble: u32, - pub pointsSuicide: i32, +pub enum Inbound { + GameEnded(structs::GameEnded), + TournamentEnded(structs::TournamentEnded), + MapUpdate(structs::MapUpdate), + SnakeDead(structs::SnakeDead), + GameStarting(structs::GameStarting), + PlayerRegistered(structs::PlayerRegistered), + InvalidPlayerName(structs::InvalidPlayerName), + HeartBeatResponse(structs::HeartBeatResponse), + UnrecognizedMessage } -#[derive(Serialize, Deserialize, Debug)] -pub struct PlayRegistration { - #[serde(rename="type")] - pub type_: String, - pub playerName: String, - pub gameSettings: GameSettings, +pub fn parse_inbound_msg(msg: &String) -> Result { + let msg: Inbound = + if msg.contains(GAME_ENDED) { + Inbound::GameEnded(try!(from_str(msg))) + } else if msg.contains(TOURNAMENT_ENDED) { + Inbound::TournamentEnded(try!(from_str(msg))) + } else if msg.contains(MAP_UPDATE) { + Inbound::MapUpdate(try!(from_str(msg))) + } else if msg.contains(SNAKE_DEAD) { + Inbound::SnakeDead(try!(from_str(msg))) + } else if msg.contains(GAME_STARTING) { + Inbound::GameStarting(try!(from_str(msg))) + } else if msg.contains(PLAYER_REGISTERED) { + Inbound::PlayerRegistered(try!(from_str(msg))) + } else if msg.contains(INVALID_PLAYER_NAME) { + Inbound::InvalidPlayerName(try!(from_str(msg))) + } else if msg.contains(HEART_BEAT_RESPONSE) { + Inbound::HeartBeatResponse(try!(from_str(msg))) + } else { + Inbound::UnrecognizedMessage + }; + + Ok(msg) } -#[derive(Serialize, Deserialize, Debug)] -pub struct RegisterMove { - #[serde(rename="type")] - pub type_: String, - pub direction: String, - pub gameTick: u32, - pub receivingPlayerId: String, - pub gameId: String + +pub fn create_play_registration_msg(name: String) -> Result { + to_string(&structs::PlayRegistration { + type_: String::from(REGISTER_PLAYER_MESSAGE_TYPE), + playerName: name, + gameSettings: default_gamesettings() + }) } -#[derive(Serialize, Deserialize, Debug)] -pub struct StartGame { - #[serde(rename="type")] - pub type_: String, +pub fn create_start_game_msg() -> Result { + to_string(&structs::StartGame { + type_: String::from(START_GAME) + }) } -#[derive(Serialize, Deserialize, Debug)] -pub struct HeartBeatRequest { - #[serde(rename="type")] - pub type_: String, - pub receivingPlayerId: String +pub fn create_register_move_msg(direction: String, request: structs::MapUpdate) -> Result { + to_string(&structs::RegisterMove { + type_: String::from(REGISTER_MOVE), + direction: direction, + gameTick: request.gameTick, + receivingPlayerId: request.receivingPlayerId, + gameId: request.gameId + }) } -//Inbound messages -#[derive(Serialize, Deserialize, Debug)] -pub struct PlayerRegistered { - #[serde(rename="type")] - pub type_: String, - pub gameId: String, - pub gameMode: String, - pub receivingPlayerId: String, - pub name: String, - pub gameSettings: GameSettings +pub fn create_heart_beat_msg(id: String) -> Result { + to_string(&structs::HeartBeatRequest { + type_: String::from( HEART_BEAT_REQUEST ), + receivingPlayerId: id + }) } -#[derive(Serialize, Deserialize, Debug)] -pub struct MapUpdate { - #[serde(rename="type")] - pub type_: String, - pub receivingPlayerId: String, - pub gameId: String, - pub gameTick: u32, - pub map: Map, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct InvalidPlayerName { - #[serde(rename="type")] - pub type_: String, - pub reasonCode: u32, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GameEnded { - #[serde(rename="type")] - pub type_: String, - pub receivingPlayerId: String, - pub playerWinnerId: String, - pub gameId: String, - pub gameTick: u32, - pub map: Map, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SnakeDead { - #[serde(rename="type")] - pub type_: String, - pub playerId: String, - pub x: u32, - pub y: u32, - pub gameId: String, - pub gameTick: u32, - pub deathReason: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GameStarting { - #[serde(rename="type")] - pub type_: String, - pub receivingPlayerId: String, - pub gameId: String, - pub noofPlayers: u32, - pub width: u32, - pub height: u32, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct HeartBeatResponse { - #[serde(rename="type")] - pub type_: String, - pub receivingPlayerId: Option -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct TournamentEnded { - #[serde(rename="type")] - pub type_: String, - pub playerWinnerId: String, - pub gameId: String, - pub gameResult: String, - pub tournamentName: String, - pub tournamentId: String, - pub gameTick: Option -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Map { - #[serde(rename="type")] - pub type_: String, - pub width: i32, - pub height: i32, - pub worldTick: u32, - pub snakeInfos: Vec, - pub foodPositions: Vec, - pub obstaclePositions: Vec, - pub receivingPlayerId: Option, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct SnakeInfo { - pub name: String, - pub points: i32, - pub positions: Vec, - pub tailProtectedForGameTicks: u32, - pub id: String -} - -pub fn default_gamesettings() -> GameSettings { - GameSettings { +pub fn default_gamesettings() -> structs::GameSettings { + structs::GameSettings { width: String::from("MEDIUM"), height: String::from("MEDIUM"), maxNoofPlayers: 5, diff --git a/src/snake.rs b/src/snake.rs index 25eabf7..c827368 100644 --- a/src/snake.rs +++ b/src/snake.rs @@ -1,4 +1,4 @@ -use messages; +use structs::{ MapUpdate, GameEnded, TournamentEnded, SnakeDead, GameStarting, PlayerRegistered, InvalidPlayerName}; use maputil::{ Direction }; use util::{ translate_positions }; @@ -16,7 +16,7 @@ impl Snake { String::from("rusty-snake") } - pub fn get_next_move(&self, msg: &messages::MapUpdate) -> Direction { + pub fn get_next_move(&self, msg: &MapUpdate) -> Direction { info!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick); let ref map = msg.map; @@ -43,27 +43,27 @@ impl Snake { direction } - pub fn on_game_ended(&self, msg: &messages::GameEnded) { + pub fn on_game_ended(&self, msg: &GameEnded) { info!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); } - pub fn on_tournament_ended(&self, msg: &messages::TournamentEnded) { + pub fn on_tournament_ended(&self, msg: &TournamentEnded) { info!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); } - pub fn on_snake_dead(&self, msg: &messages::SnakeDead) { + pub fn on_snake_dead(&self, msg: &SnakeDead) { info!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason); } - pub fn on_game_starting(&self, _: &messages::GameStarting) { + pub fn on_game_starting(&self, _: &GameStarting) { } - pub fn on_player_registered(&self, _: &messages::PlayerRegistered) { + pub fn on_player_registered(&self, _: &PlayerRegistered) { } - pub fn on_invalid_playername(&self, _: &messages::InvalidPlayerName) { + pub fn on_invalid_playername(&self, _: &InvalidPlayerName) { } } diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..d0913e1 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,162 @@ +#![allow(non_snake_case)] + +#[derive(Serialize, Deserialize, Debug)] +pub struct GameSettings { + pub width: String, + pub height: String, + pub maxNoofPlayers: u32, + pub startSnakeLength: u32, + pub timeInMsPerTick: u32, + pub obstaclesEnabled: bool, + pub foodEnabled: bool, + pub edgeWrapsAround: bool, + pub headToTailConsumes: bool, + pub tailConsumeGrows: bool, + pub addFoodLikelihood: u32, + pub removeFoodLikelihood: u32, + pub addObstacleLikelihood: u32, + pub removeObstacleLikelihood: u32, + pub spontaneousGrowthEveryNWorldTick: u32, + pub trainingGame: bool, + pub pointsPerLength: u32, + pub pointsPerFood: u32, + pub pointsPerCausedDeath: u32, + pub pointsPerNibble: u32, + pub pointsLastSnakeLiving: u32, + pub noofRoundsTailProtectedAfterNibble: u32, + pub pointsSuicide: i32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PlayRegistration { + #[serde(rename="type")] + pub type_: String, + pub playerName: String, + pub gameSettings: GameSettings, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RegisterMove { + #[serde(rename="type")] + pub type_: String, + pub direction: String, + pub gameTick: u32, + pub receivingPlayerId: String, + pub gameId: String +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct StartGame { + #[serde(rename="type")] + pub type_: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HeartBeatRequest { + #[serde(rename="type")] + pub type_: String, + pub receivingPlayerId: String +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PlayerRegistered { + #[serde(rename="type")] + pub type_: String, + pub gameId: String, + pub gameMode: String, + pub receivingPlayerId: String, + pub name: String, + pub gameSettings: GameSettings +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MapUpdate { + #[serde(rename="type")] + pub type_: String, + pub receivingPlayerId: String, + pub gameId: String, + pub gameTick: u32, + pub map: Map, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct InvalidPlayerName { + #[serde(rename="type")] + pub type_: String, + pub reasonCode: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GameEnded { + #[serde(rename="type")] + pub type_: String, + pub receivingPlayerId: String, + pub playerWinnerId: String, + pub gameId: String, + pub gameTick: u32, + pub map: Map, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SnakeDead { + #[serde(rename="type")] + pub type_: String, + pub playerId: String, + pub x: u32, + pub y: u32, + pub gameId: String, + pub gameTick: u32, + pub deathReason: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GameStarting { + #[serde(rename="type")] + pub type_: String, + pub receivingPlayerId: String, + pub gameId: String, + pub noofPlayers: u32, + pub width: u32, + pub height: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HeartBeatResponse { + #[serde(rename="type")] + pub type_: String, + pub receivingPlayerId: Option +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct TournamentEnded { + #[serde(rename="type")] + pub type_: String, + pub playerWinnerId: String, + pub gameId: String, + pub gameResult: String, + pub tournamentName: String, + pub tournamentId: String, + pub gameTick: Option +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Map { + #[serde(rename="type")] + pub type_: String, + pub width: i32, + pub height: i32, + pub worldTick: u32, + pub snakeInfos: Vec, + pub foodPositions: Vec, + pub obstaclePositions: Vec, + pub receivingPlayerId: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct SnakeInfo { + pub name: String, + pub points: i32, + pub positions: Vec, + pub tailProtectedForGameTicks: u32, + pub id: String +}