From e274bd37e71d7932a70549af1d588f8d1cf9d471 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Fri, 20 Oct 2017 21:38:03 +0200 Subject: [PATCH] Initial Commit for snake `ten_points_to_slytherin` --- Cargo.toml | 5 +- snake.conf | 2 +- src/main.rs | 429 ++++++++++++++++++++++++------------------------ src/maputil.rs | 365 ++++++++++++++++++++-------------------- src/messages.rs | 111 +++++++------ src/snake.rs | 94 +++++++---- src/structs.rs | 172 +++++++++---------- src/util.rs | 52 +++--- 8 files changed, 641 insertions(+), 589 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8beff49..64b82cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "snakebot_rust" version = "1.1.0" -authors = ["Martin Barksten "] +authors = [ + "Martin Barksten ", + "Joakim Hulthe ", +] license-file = "LICENSE" [dependencies] diff --git a/snake.conf b/snake.conf index 005a154..053648c 100644 --- a/snake.conf +++ b/snake.conf @@ -1,5 +1,5 @@ host = "localhost"; port = 8080; -snake_name = "rusty-snake"; +snake_name = "ten_points_to_slytherin"; venue = "training"; diff --git a/src/main.rs b/src/main.rs index e2b1dd5..5c3cf98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_json; extern crate clap; -extern crate config; +//extern crate config; extern crate log4rs; extern crate rustc_version; extern crate serde; @@ -31,275 +31,276 @@ const LOG_TARGET: &'static str = "client"; const HEART_BEAT_S: u64 = 20; const CONFIG_FILE: &'static str = "snake.conf"; -const DEFAULT_HOST: &'static str = "snake.cygni.se"; -const DEFAULT_PORT: &'static str = "80"; -const DEFAULT_SNAKE_NAME: &'static str = "default-rust-snake-name"; +//const DEFAULT_HOST: &'static str = "snake.cygni.se"; +const DEFAULT_HOST: &'static str = "localhost"; +const DEFAULT_PORT: &'static str = "8080"; +const DEFAULT_SNAKE_NAME: &'static str = "ten_points_to_slytherin"; const DEFAULT_VENUE: &'static str = "training"; #[derive(Clone, Debug)] struct Config { - host: String, - port: i32, - snake_name: String, - venue: String + host: String, + port: i32, + snake_name: String, + venue: String } quick_error! { - #[derive(Debug)] - pub enum ClientError { - Message(err: serde_json::Error) { - from() - } + #[derive(Debug)] + pub enum ClientError { + Message(err: serde_json::Error) { + from() + } - Websocket(err: ws::Error) { - from() - } + Websocket(err: ws::Error) { + from() + } - StringChannel(err: mpsc::SendError) { - from() - } + StringChannel(err: mpsc::SendError) { + from() + } - WebsocketChannel(err: mpsc::SendError>) { - from() - } - } + WebsocketChannel(err: mpsc::SendError>) { + from() + } + } } struct Client { - out: Arc, - snake: Snake, - config: Config, - out_sender: mpsc::Sender>, - id_sender: mpsc::Sender + out: Arc, + snake: Snake, + config: Config, + out_sender: mpsc::Sender>, + id_sender: mpsc::Sender } fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> { - let snake = &mut client.snake; + let snake = &mut client.snake; - match try!(handle_inbound_msg(str_msg)) { - Inbound::GameEnded(msg) => { - snake.on_game_ended(&msg); - if client.config.venue == "training" { - try!(client.out.close(ws::CloseCode::Normal)); - } - }, - Inbound::TournamentEnded(msg) => { - snake.on_tournament_ended(&msg); - try!(client.out.close(ws::CloseCode::Normal)); - }, - Inbound::MapUpdate(msg) => { - let m = render_outbound_message(Outbound::RegisterMove { - direction: snake.get_next_move(&msg), - gameTick: msg.gameTick, - receivingPlayerId: msg.receivingPlayerId, - gameId: msg.gameId }); - debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", m); - try!(client.out.send(m)); - }, - 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); + match try!(handle_inbound_msg(str_msg)) { + Inbound::GameEnded(msg) => { + snake.on_game_ended(&msg); + if client.config.venue == "training" { + try!(client.out.close(ws::CloseCode::Normal)); + } + }, + Inbound::TournamentEnded(msg) => { + snake.on_tournament_ended(&msg); + try!(client.out.close(ws::CloseCode::Normal)); + }, + Inbound::MapUpdate(msg) => { + let m = render_outbound_message(Outbound::RegisterMove { + direction: snake.get_next_move(&msg), + gameTick: msg.gameTick, + receivingPlayerId: msg.receivingPlayerId, + gameId: msg.gameId }); + debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", m); + try!(client.out.send(m)); + }, + 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); - if msg.gameMode == "TRAINING" { - let m = render_outbound_message(Outbound::StartGame); - debug!(target: LOG_TARGET, "Requesting a game start {:?}", m); - try!(client.out.send(m)); - }; + if msg.gameMode == "TRAINING" { + let m = render_outbound_message(Outbound::StartGame); + debug!(target: LOG_TARGET, "Requesting a game start {:?}", m); + try!(client.out.send(m)); + }; - 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::GameLink(msg) => { - info!(target: LOG_TARGET, "Watch game at {}", msg.url); - }, - Inbound::GameResult(msg) => { - info!(target: LOG_TARGET, "We got some game result! {:?}", msg); - }, - Inbound::UnrecognizedMessage => { - error!(target: LOG_TARGET, "Received unrecognized message {:?}", str_msg); - } - }; + 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::GameLink(msg) => { + info!(target: LOG_TARGET, "Watch game at {}", msg.url); + }, + Inbound::GameResult(msg) => { + info!(target: LOG_TARGET, "We got some game result! {:?}", msg); + }, + Inbound::UnrecognizedMessage => { + error!(target: LOG_TARGET, "Received unrecognized message {:?}", str_msg); + } + }; - Ok(()) + Ok(()) } impl ws::Handler for Client { - fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { - debug!(target: LOG_TARGET, "Connection to Websocket opened"); - let m = render_outbound_message(Outbound::ClientInfo); - info!(target: LOG_TARGET, "Sending client info to server: {:?}", m); - try!(self.out.send(m)); - let msg = render_outbound_message(Outbound::RegisterPlayer { - playerName: self.config.snake_name.clone(), - gameSettings: Default::default() }); - info!(target: LOG_TARGET, "Registering player with message: {:?}", msg); - self.out.send(msg) - } + fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { + debug!(target: LOG_TARGET, "Connection to Websocket opened"); + let m = render_outbound_message(Outbound::ClientInfo); + info!(target: LOG_TARGET, "Sending client info to server: {:?}", m); + try!(self.out.send(m)); + let msg = render_outbound_message(Outbound::RegisterPlayer { + playerName: self.config.snake_name.clone(), + gameSettings: Default::default() }); + info!(target: LOG_TARGET, "Registering player with message: {:?}", msg); + self.out.send(msg) + } - fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { - if let ws::Message::Text(text) = msg { - let route_result = route_msg(self, &text); - match route_result { - Err(e) => error!(target: LOG_TARGET, "Got error \'{:?}\' when routing message: {}", e, text), - Ok(_) => debug!(target: LOG_TARGET, "Succeeded in routing message {}", text) - } - } else { - warn!(target: LOG_TARGET, "Unexpectedly received non-string message: {:?}", msg) - } + fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { + if let ws::Message::Text(text) = msg { + let route_result = route_msg(self, &text); + match route_result { + Err(e) => error!(target: LOG_TARGET, "Got error \'{:?}\' when routing message: {}", e, text), + Ok(_) => debug!(target: LOG_TARGET, "Succeeded in routing message {}", text) + } + } else { + warn!(target: LOG_TARGET, "Unexpectedly received non-string message: {:?}", msg) + } - Ok(()) - } + Ok(()) + } } fn read_conf_file() -> Config { - let config_path = Path::new(CONFIG_FILE); - info!(target: LOG_TARGET, "Reading config from file at {:?}", config_path.canonicalize()); - let matches = App::new("Rust snake client") - .version("1.1.0") - .author("Martin Barksten ") - .about("A snake client in the least friendly language.") - .arg(Arg::with_name("host") - .short("h") - .long("host") - .help("The host to connect to") - .takes_value(true) - .default_value(DEFAULT_HOST)) - .arg(Arg::with_name("port") - .short("p") - .long("port") - .help("The port to connect to") - .takes_value(true) - .default_value(DEFAULT_PORT)) - .arg(Arg::with_name("venue") - .short("v") - .long("venue") - .help("The venue (tournament or training)") - .takes_value(true) - .default_value(DEFAULT_VENUE) - .possible_values(&["tournament", "training"])) - .arg(Arg::with_name("snake-name") - .short("n") - .long("snake-name") - .help("The name of the snake") - .takes_value(true) - .default_value(DEFAULT_SNAKE_NAME)) - .get_matches(); + let config_path = Path::new(CONFIG_FILE); + info!(target: LOG_TARGET, "Reading config from file at {:?}", config_path.canonicalize()); + let matches = App::new("Rust snake client") + .version("1.1.0") + .author("Martin Barksten ") + .about("A snake client in the least friendly language.") + .arg(Arg::with_name("host") + .short("h") + .long("host") + .help("The host to connect to") + .takes_value(true) + .default_value(DEFAULT_HOST)) + .arg(Arg::with_name("port") + .short("p") + .long("port") + .help("The port to connect to") + .takes_value(true) + .default_value(DEFAULT_PORT)) + .arg(Arg::with_name("venue") + .short("v") + .long("venue") + .help("The venue (tournament or training)") + .takes_value(true) + .default_value(DEFAULT_VENUE) + .possible_values(&["tournament", "training"])) + .arg(Arg::with_name("snake-name") + .short("n") + .long("snake-name") + .help("The name of the snake") + .takes_value(true) + .default_value(DEFAULT_SNAKE_NAME)) + .get_matches(); - let port = matches.value_of("port").unwrap_or(DEFAULT_PORT).parse::().unwrap(); + let port = matches.value_of("port").unwrap_or(DEFAULT_PORT).parse::().unwrap(); - Config { - host: String::from(matches.value_of("host").unwrap_or(DEFAULT_HOST)), - port: port, - snake_name: String::from(matches.value_of("snake-name").unwrap_or(DEFAULT_SNAKE_NAME)), - venue: String::from(matches.value_of("venue").unwrap_or(DEFAULT_VENUE)) - } + Config { + host: String::from(matches.value_of("host").unwrap_or(DEFAULT_HOST)), + port: port, + snake_name: String::from(matches.value_of("snake-name").unwrap_or(DEFAULT_SNAKE_NAME)), + venue: String::from(matches.value_of("venue").unwrap_or(DEFAULT_VENUE)) + } } fn start_websocket_thread(id_sender: mpsc::Sender, - out_sender: mpsc::Sender>) -> thread::JoinHandle<()> { - thread::spawn(move || { - let config = read_conf_file(); + out_sender: mpsc::Sender>) -> thread::JoinHandle<()> { + thread::spawn(move || { + let config = read_conf_file(); - let connection_url = format!("ws://{}:{}/{}", config.host, config.port, config.venue); - info!(target: LOG_TARGET, "Connecting to {:?}", connection_url); + let connection_url = format!("ws://{}:{}/{}", config.host, config.port, config.venue); + info!(target: LOG_TARGET, "Connecting to {:?}", connection_url); - let result = ws::connect(connection_url, |out| { - Client { - out: Arc::from(out), - snake: snake::Snake, - config: config.clone(), - out_sender: out_sender.clone(), - id_sender: id_sender.clone() - } - }); - debug!(target: LOG_TARGET, "Websocket is done, result {:?}", result); - }) + let result = ws::connect(connection_url, |out| { + Client { + out: Arc::from(out), + snake: snake::Snake, + config: config.clone(), + out_sender: out_sender.clone(), + id_sender: id_sender.clone() + } + }); + debug!(target: LOG_TARGET, "Websocket is done, result {:?}", result); + }) } 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(); + 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; - } + // 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 send_result = out.send(render_outbound_message(Outbound::HeartBeat { receivingPlayerId: id.clone() })); - if let Err(e) = send_result { - error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e); - } - } + debug!(target: LOG_TARGET, "Sending heartbeat request"); + let send_result = out.send(render_outbound_message(Outbound::HeartBeat { receivingPlayerId: id.clone() })); + if let Err(e) = send_result { + error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e); + } + } } 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)) + 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 res = recv_channels(id_receiver, out_receiver); + out_receiver: mpsc::Receiver>, + done_receiver: mpsc::Receiver<()>) -> thread::JoinHandle<()> { + thread::spawn(move || { + let res = recv_channels(id_receiver, out_receiver); - 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."); - }; - }) + 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."); + }; + }) } fn start_client() { - let (id_sender,id_receiver) = mpsc::channel(); - let (out_sender,out_receiver) = mpsc::channel(); - let (done_sender,done_receiver) = mpsc::channel(); + let (id_sender,id_receiver) = mpsc::channel(); + let (out_sender,out_receiver) = mpsc::channel(); + let (done_sender,done_receiver) = mpsc::channel(); - let websocket = start_websocket_thread(id_sender, out_sender); - let heartbeat = start_heart_beat_thread(id_receiver, out_receiver, done_receiver); + let websocket = start_websocket_thread(id_sender, out_sender); + let heartbeat = start_heart_beat_thread(id_receiver, out_receiver, done_receiver); - let websocket_res = websocket.join(); - debug!(target: LOG_TARGET, "Joining Websocket thread gave result {:?}", websocket_res); + let websocket_res = websocket.join(); + debug!(target: LOG_TARGET, "Joining Websocket thread gave result {:?}", websocket_res); - let send_res = done_sender.send(()); - if let Err(e) = send_res { - error!(target: LOG_TARGET, "Unable to send done message, got error {:?}", e); - } + let send_res = done_sender.send(()); + if let Err(e) = send_res { + error!(target: LOG_TARGET, "Unable to send done message, got error {:?}", e); + } - let heartbeat_res = heartbeat.join(); - debug!(target: LOG_TARGET, "Joining heartbeat thread gave result {:?}", heartbeat_res); + let heartbeat_res = heartbeat.join(); + debug!(target: LOG_TARGET, "Joining heartbeat thread gave result {:?}", heartbeat_res); } fn main() { - if let Err(_) = log4rs::init_file("log4rs.toml", Default::default()) { - log4rs::init_file("../log4rs.toml", Default::default()).unwrap(); - } - start_client(); + if let Err(_) = log4rs::init_file("log4rs.toml", Default::default()) { + log4rs::init_file("../log4rs.toml", Default::default()).unwrap(); + } + start_client(); } diff --git a/src/maputil.rs b/src/maputil.rs index e355e40..a81730a 100644 --- a/src/maputil.rs +++ b/src/maputil.rs @@ -1,217 +1,234 @@ use structs::{ Map, SnakeInfo }; use util; use serde::ser::{ Serialize, Serializer }; +use util::{ translate_position }; #[derive(PartialEq, Debug)] pub enum Tile<'a> { - Wall, - Food { coordinate: (i32,i32) }, - Obstacle { coordinate: (i32,i32) }, - Empty { coordinate: (i32,i32) }, - SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo }, - SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo } + Wall, + Food { coordinate: (i32,i32) }, + Obstacle { coordinate: (i32,i32) }, + Empty { coordinate: (i32,i32) }, + SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo }, + SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo } } #[derive(Clone, Copy, Debug)] pub enum Direction { - Down, - Up, - Left, - Right + Down, + Up, + Left, + Right } impl Serialize for Direction { - fn serialize(&self, serializer: S) -> Result - where S: Serializer - { - serializer.serialize_str(match *self { - Direction::Down => "DOWN", - Direction::Up => "UP", - Direction::Left => "LEFT", - Direction::Right => "RIGHT", - }) - } + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_str(match *self { + Direction::Down => "DOWN", + Direction::Up => "UP", + Direction::Left => "LEFT", + Direction::Right => "RIGHT", + }) + } } impl Direction { - pub fn as_movement_delta(&self) -> (i32,i32) { - match *self { - Direction::Down => ( 0, 1), - Direction::Up => ( 0, -1), - Direction::Left => (-1, 0), - Direction::Right => ( 1, 0) - } - } + pub fn as_movement_delta(&self) -> (i32,i32) { + match *self { + Direction::Down => ( 0, 1), + Direction::Up => ( 0, -1), + Direction::Left => (-1, 0), + Direction::Right => ( 1, 0) + } + } + + pub fn from_movement_delta(delta: (i32,i32)) -> Direction { + match delta { + (x, y) if y <= -x.abs() => Direction::Up, + (x, y) if y >= x.abs() => Direction::Down, + (x, y) if x <= -y.abs() => Direction::Left, + (x, y) if x >= y.abs() => Direction::Right, + ( _, _) => { + panic!(format!("({}, {}) is not a valid movement delta.", delta.0, delta.1)) + } + } + } } impl Map { - pub fn inside_map(&self, coordinate: (i32, i32)) -> bool { - let (x,y) = coordinate; - let inside_x = x >= 0 && x < self.width; - let inside_y = y >= 0 && y < self.height; - inside_x && inside_y - } + pub fn inside_map(&self, coordinate: (i32, i32)) -> bool { + let (x,y) = coordinate; + let inside_x = x >= 0 && x < self.width; + let inside_y = y >= 0 && y < self.height; + inside_x && inside_y + } - pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> { - self.snakeInfos.iter().find(|s| &s.id == id) - } + pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> { + self.snakeInfos.iter().find(|s| &s.id == id) + } - pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile { - let position = util::translate_coordinate(coordinate, self.width); - let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position)); + pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile { + let position = util::translate_coordinate(coordinate, self.width); + let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position)); - if self.obstaclePositions.contains(&position) { - Tile::Obstacle { coordinate: coordinate } - } else if self.foodPositions.contains(&position) { - Tile::Food { coordinate: coordinate } - } else if snake_at_tile.is_some() { - let s = snake_at_tile.unwrap(); - if s.positions[0] == position { - Tile::SnakeHead { coordinate: coordinate, snake: s } - } else { - Tile::SnakeBody { coordinate: coordinate, snake: s } - } - } else if !self.inside_map(coordinate) { - Tile::Wall - } else { - Tile::Empty { coordinate: coordinate } - } - } + if self.obstaclePositions.contains(&position) { + Tile::Obstacle { coordinate: coordinate } + } else if self.foodPositions.contains(&position) { + Tile::Food { coordinate: coordinate } + } else if snake_at_tile.is_some() { + let s = snake_at_tile.unwrap(); + if s.positions[0] == position { + Tile::SnakeHead { coordinate: coordinate, snake: s } + } else { + Tile::SnakeBody { coordinate: coordinate, snake: s } + } + } else if !self.inside_map(coordinate) { + Tile::Wall + } else { + Tile::Empty { coordinate: coordinate } + } + } - pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool { - let tile = self.get_tile_at(coordinate); - match tile { - Tile::Empty { coordinate: _ } => true, - Tile::Food { coordinate: _ } => true, - _ => false - } - } + pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool { + let tile = self.get_tile_at(coordinate); + match tile { + Tile::Empty { coordinate: _ } => true, + Tile::Food { coordinate: _ } => true, + Tile::SnakeBody { coordinate: _, snake } => { + snake.tailProtectedForGameTicks == 0 && + coordinate == translate_position(*snake.positions.last().unwrap(), self.width) + }, + _ => false + } + } - pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool { - let (xd,yd) = direction.as_movement_delta(); - let (x,y) = util::translate_position(snake.positions[0], self.width); + pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool { + let (xd,yd) = direction.as_movement_delta(); + let (x,y) = util::translate_position(snake.positions[0], self.width); - self.is_tile_available_for_movement((x+xd,y+yd)) - } + self.is_tile_available_for_movement((x+xd,y+yd)) + } - #[allow(dead_code)] - pub fn is_coordinate_out_of_bounds(&self, coordinate: (i32,i32)) -> bool { - let (x,y) = coordinate; - x < 0 || x >= self.width || y < 0 || y >= self.height - } + #[allow(dead_code)] + pub fn is_coordinate_out_of_bounds(&self, coordinate: (i32,i32)) -> bool { + let (x,y) = coordinate; + x < 0 || x >= self.width || y < 0 || y >= self.height + } } #[cfg(test)] mod test { - use util::{ translate_coordinate }; - use maputil::{ Direction, Tile }; - use structs::{ Map, SnakeInfo }; + use util::{ translate_coordinate }; + use maputil::{ Direction, Tile }; + use structs::{ Map, SnakeInfo }; - const MAP_WIDTH: i32 = 3; + const MAP_WIDTH: i32 = 3; - fn get_snake_one() -> SnakeInfo { - SnakeInfo { - name: String::from("1"), - points: 0, - tailProtectedForGameTicks: 0, - positions: vec![translate_coordinate((1,1), MAP_WIDTH), - translate_coordinate((0,1), MAP_WIDTH)], - id: String::from("1") - } - } + fn get_snake_one() -> SnakeInfo { + SnakeInfo { + name: String::from("1"), + points: 0, + tailProtectedForGameTicks: 0, + positions: vec![translate_coordinate((1,1), MAP_WIDTH), + translate_coordinate((0,1), MAP_WIDTH)], + id: String::from("1") + } + } - fn get_snake_two() -> SnakeInfo { - SnakeInfo { - name: String::from("2"), - points: 0, - tailProtectedForGameTicks: 0, - positions: vec![translate_coordinate((1,2), MAP_WIDTH)], - id: String::from("2") - } - } + fn get_snake_two() -> SnakeInfo { + SnakeInfo { + name: String::from("2"), + points: 0, + tailProtectedForGameTicks: 0, + positions: vec![translate_coordinate((1,2), MAP_WIDTH)], + id: String::from("2") + } + } - // The map used for testing, 1 and 2 represents the snakes - //yx012 - //0 F - //1 11# - //2 2 - fn get_test_map() -> Map { - Map { - width: MAP_WIDTH, - height: MAP_WIDTH, - worldTick: 0, - snakeInfos: vec![get_snake_one(), get_snake_two()], - foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)], - obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)] - } - } + // The map used for testing, 1 and 2 represents the snakes + //yx012 + //0 F + //1 11# + //2 2 + fn get_test_map() -> Map { + Map { + width: MAP_WIDTH, + height: MAP_WIDTH, + worldTick: 0, + snakeInfos: vec![get_snake_one(), get_snake_two()], + foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)], + obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)] + } + } - #[test] - fn snake_can_be_found_by_id() { - let map = get_test_map(); - let id = &get_snake_one().id; - let s = map.get_snake_by_id(id); - let found_id = &s.unwrap().id; - assert_eq!(id, found_id); - } + #[test] + fn snake_can_be_found_by_id() { + let map = get_test_map(); + let id = &get_snake_one().id; + let s = map.get_snake_by_id(id); + let found_id = &s.unwrap().id; + assert_eq!(id, found_id); + } - #[test] - fn tile_is_correctly_found() { - let map = get_test_map(); - let snake_one = get_snake_one(); - let snake_two = get_snake_two(); - let tiles = - vec![ - vec![Tile::Empty{ coordinate: (0,0) }, - Tile::Food{ coordinate: (1,0) }, - Tile::Empty{ coordinate: (2,0) }], - vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one }, - Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one }, - Tile::Obstacle{ coordinate: (2,1)}], - vec![Tile::Empty{ coordinate: (0,2) }, - Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two }, - Tile::Empty{ coordinate:(2,2) }]]; - for y in 0..map.width { - for x in 0..map.height { - assert_eq!(tiles[y as usize][x as usize], - map.get_tile_at((x,y))); - } - } - } + #[test] + fn tile_is_correctly_found() { + let map = get_test_map(); + let snake_one = get_snake_one(); + let snake_two = get_snake_two(); + let tiles = + vec![ + vec![Tile::Empty{ coordinate: (0,0) }, + Tile::Food{ coordinate: (1,0) }, + Tile::Empty{ coordinate: (2,0) }], + vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one }, + Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one }, + Tile::Obstacle{ coordinate: (2,1)}], + vec![Tile::Empty{ coordinate: (0,2) }, + Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two }, + Tile::Empty{ coordinate:(2,2) }]]; + for y in 0..map.width { + for x in 0..map.height { + assert_eq!(tiles[y as usize][x as usize], + map.get_tile_at((x,y))); + } + } + } - #[test] - fn tile_is_correctly_marked_as_movable() { - let map = get_test_map(); - let tiles = vec![vec![true, true, true], - vec![false, false, false], - vec![true, false, true]]; + #[test] + fn tile_is_correctly_marked_as_movable() { + let map = get_test_map(); + let tiles = vec![vec![true, true, true], + vec![false, false, false], + vec![true, false, true]]; - for y in 0..map.height { - for x in 0..map.width { - assert_eq!(tiles[y as usize][x as usize], - map.is_tile_available_for_movement((x,y))); - } - } - } + for y in 0..map.height { + for x in 0..map.width { + assert_eq!(tiles[y as usize][x as usize], + map.is_tile_available_for_movement((x,y))); + } + } + } - #[test] - fn can_snake_move_identifies_correctly() { - let map = get_test_map(); - let id = &get_snake_one().id; - let snake = map.get_snake_by_id(id).unwrap(); + #[test] + fn can_snake_move_identifies_correctly() { + let map = get_test_map(); + let id = &get_snake_one().id; + let snake = map.get_snake_by_id(id).unwrap(); - assert_eq!(true, map.can_snake_move_in_direction(&snake, Direction::Up)); - assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down)); - assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Left)); - assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Right)); - } + assert_eq!(true, map.can_snake_move_in_direction(&snake, Direction::Up)); + assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down)); + assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Left)); + assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Right)); + } - #[test] - fn can_not_move_to_walls() { - let map = get_test_map(); - let id = &get_snake_two().id; - let snake = map.get_snake_by_id(id).unwrap(); + #[test] + fn can_not_move_to_walls() { + let map = get_test_map(); + let id = &get_snake_two().id; + let snake = map.get_snake_by_id(id).unwrap(); - assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down)); - } + assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down)); + } } diff --git a/src/messages.rs b/src/messages.rs index 66e9cdb..4557e3a 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -8,17 +8,17 @@ use std::iter::FromIterator; #[derive(Serialize, Deserialize, Debug)] 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), - GameLink(structs::GameLink), - GameResult(structs::GameResult), - UnrecognizedMessage + GameEnded(structs::GameEnded), + TournamentEnded(structs::TournamentEnded), + MapUpdate(structs::MapUpdate), + SnakeDead(structs::SnakeDead), + GameStarting(structs::GameStarting), + PlayerRegistered(structs::PlayerRegistered), + InvalidPlayerName(structs::InvalidPlayerName), + HeartBeatResponse(structs::HeartBeatResponse), + GameLink(structs::GameLink), + GameResult(structs::GameResult), + UnrecognizedMessage } /// We turn the string into `Inbound` by converting the string into a @@ -34,55 +34,54 @@ pub enum Inbound { /// Like this: {GameResult: } /// pub fn handle_inbound_msg(s: &str) -> Result { - let mut json_value = from_str::(s) - .expect(&format!("Couldn't parse string into JSON: {:?}", s)); - let mut map = json_value.as_object_mut() - .expect(&format!("Couldn't parse string into JSON object: {:?}", s)); - let type_value = map.remove("type").expect(&format!("Couldn't find key `type` in: {:?}", &map)); - let type_str = type_value.as_str().expect(&format!("Couldn't turn JSON Value into string: {:?}", &map)); - let typ = type_str.rsplit('.').next() - .expect(&format!("The type parser needs a dot-separated string, this string lacks dots: {:?}", type_str)) - .replace("Event", ""); - from_value(Value::Object(Map::from_iter(vec![(typ, Value::Object(map.clone()))]))) + let mut json_value = from_str::(s) + .expect(&format!("Couldn't parse string into JSON: {:?}", s)); + let map = json_value.as_object_mut() + .expect(&format!("Couldn't parse string into JSON object: {:?}", s)); + let type_value = map.remove("type").expect(&format!("Couldn't find key `type` in: {:?}", &map)); + let type_str = type_value.as_str().expect(&format!("Couldn't turn JSON Value into string: {:?}", &map)); + let typ = type_str.rsplit('.').next() + .expect(&format!("The type parser needs a dot-separated string, this string lacks dots: {:?}", type_str)) + .replace("Event", ""); + from_value(Value::Object(Map::from_iter(vec![(typ, Value::Object(map.clone()))]))) } pub enum Outbound { - RegisterPlayer{playerName: String, gameSettings: GameSettings}, - StartGame, - RegisterMove{direction: Direction, gameTick: u32, receivingPlayerId: String, gameId: String}, - HeartBeat{receivingPlayerId: String}, - ClientInfo, + RegisterPlayer{playerName: String, gameSettings: GameSettings}, + StartGame, + RegisterMove{direction: Direction, gameTick: u32, receivingPlayerId: String, gameId: String}, + HeartBeat{receivingPlayerId: String}, + ClientInfo, } pub fn render_outbound_message(msg: Outbound) -> String { - (match msg { - Outbound::RegisterPlayer {playerName, gameSettings} => json!({ - "type": "se.cygni.snake.api.request.RegisterPlayer", - "playerName": playerName, - "gameSettings": gameSettings - }), - Outbound::StartGame => json!({ - "type": "se.cygni.snake.api.request.StartGame", - }), - Outbound::RegisterMove {direction, gameTick, receivingPlayerId, gameId} => json!({ - "type": "se.cygni.snake.api.request.RegisterMove", - "direction": direction, - "gameTick": gameTick, - "receivingPlayerId": receivingPlayerId, - "gameId": gameId, - }), - Outbound::HeartBeat {receivingPlayerId} => json!({ - "type": "se.cygni.snake.api.request.HeartBeatRequest", - "receivingPlayerId": receivingPlayerId, - }), - Outbound::ClientInfo => json!({ - "type": "se.cygni.snake.api.request.ClientInfo", - "language": "Rust", - "languageVersion": version().unwrap().to_string(), - "operatingSystem": Target::os(), - "operatingSystemVersion": "???", - "clientVersion": option_env!("CARGO_PKG_VERSION").unwrap_or("0.1337"), - }), - }).to_string() + (match msg { + Outbound::RegisterPlayer {playerName, gameSettings} => json!({ + "type": "se.cygni.snake.api.request.RegisterPlayer", + "playerName": playerName, + "gameSettings": gameSettings + }), + Outbound::StartGame => json!({ + "type": "se.cygni.snake.api.request.StartGame", + }), + Outbound::RegisterMove {direction, gameTick, receivingPlayerId, gameId} => json!({ + "type": "se.cygni.snake.api.request.RegisterMove", + "direction": direction, + "gameTick": gameTick, + "receivingPlayerId": receivingPlayerId, + "gameId": gameId, + }), + Outbound::HeartBeat {receivingPlayerId} => json!({ + "type": "se.cygni.snake.api.request.HeartBeatRequest", + "receivingPlayerId": receivingPlayerId, + }), + Outbound::ClientInfo => json!({ + "type": "se.cygni.snake.api.request.ClientInfo", + "language": "Rust", + "languageVersion": version().unwrap().to_string(), + "operatingSystem": Target::os(), + "operatingSystemVersion": "???", + "clientVersion": option_env!("CARGO_PKG_VERSION").unwrap_or("0.1337"), + }), + }).to_string() } - diff --git a/src/snake.rs b/src/snake.rs index 22e8bdd..840e4a2 100644 --- a/src/snake.rs +++ b/src/snake.rs @@ -1,51 +1,77 @@ use structs::{ MapUpdate, GameEnded, TournamentEnded, SnakeDead, GameStarting, PlayerRegistered, InvalidPlayerName }; use maputil::{ Direction }; -use util::{ translate_positions }; +use util::{ translate_positions, translate_position, vector_diff }; const LOG_TARGET: &'static str = "snake"; pub struct Snake; impl Snake { - pub fn get_next_move(&self, msg: &MapUpdate) -> Direction { - debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick); + pub fn get_next_move(&self, msg: &MapUpdate) -> Direction { + debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick); - let map = &msg.map; - let snake = map.get_snake_by_id(&msg.receivingPlayerId).unwrap(); + let map = &msg.map; + let directions = vec![Direction::Down, Direction::Left, Direction::Right, Direction::Up]; - debug!(target: LOG_TARGET, "Food can be found at {:?}", translate_positions(&map.foodPositions, map.width)); - debug!(target: LOG_TARGET, "My snake positions are {:?}", translate_positions(&snake.positions, map.width)); - for &d in [Direction::Down, Direction::Left, Direction::Right, Direction::Up].into_iter() { - if map.can_snake_move_in_direction(snake, d) { - debug!(target: LOG_TARGET, "Snake will move in direction {:?}", d); - return d; - } - } - debug!(target: LOG_TARGET, "Snake cannot but will move down."); - return Direction::Down; - } + let snake = map.get_snake_by_id(&msg.receivingPlayerId).unwrap(); + let snakes = &map.snakeInfos; - pub fn on_game_ended(&self, msg: &GameEnded) { - debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); - } + let snake_positions = translate_positions(&snake.positions, map.width); + let foodPositions = translate_positions(&map.foodPositions, map.width); - pub fn on_tournament_ended(&self, msg: &TournamentEnded) { - debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); - } + let facing = match snake_positions.len() { + 0...1 => Direction::Right, + _ => Direction::from_movement_delta(vector_diff(snake_positions[0], snake_positions[1])) + }; + debug!(target: LOG_TARGET, "Food can be found at {:?}", foodPositions); + debug!(target: LOG_TARGET, "My snake positions are {:?}", snake_positions); - pub fn on_snake_dead(&self, msg: &SnakeDead) { - debug!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason); - } + for s in snakes { + if s.positions.len() == 0 || s.id == snake.id { + continue; + } - pub fn on_game_starting(&self, _: &GameStarting) { - debug!(target: LOG_TARGET, "All snakes are ready to rock. Game is starting."); - } + let tail_position = translate_position(*s.positions.last().unwrap(), map.width); + let tail_direction = Direction::from_movement_delta(vector_diff(tail_position, snake_positions[0])); - pub fn on_player_registered(&self, _: &PlayerRegistered) { - debug!(target: LOG_TARGET, "Player has been registered."); - } + if map.can_snake_move_in_direction(snake, tail_direction) { + debug!(target: LOG_TARGET, "Snake will hunt in direction {:?}", tail_direction); + return tail_direction; + } + } - pub fn on_invalid_playername(&self, _: &InvalidPlayerName) { - debug!(target: LOG_TARGET, "Player name invalid."); - } + for &d in directions.iter() { + if map.can_snake_move_in_direction(snake, d) { + debug!(target: LOG_TARGET, "Snake will move in direction {:?}", d); + return d; + } + } + debug!(target: LOG_TARGET, "Snake cannot but will move down."); + return Direction::Down; + } + + + pub fn on_game_ended(&self, msg: &GameEnded) { + debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); + } + + pub fn on_tournament_ended(&self, msg: &TournamentEnded) { + debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); + } + + pub fn on_snake_dead(&self, msg: &SnakeDead) { + debug!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason); + } + + pub fn on_game_starting(&self, _: &GameStarting) { + debug!(target: LOG_TARGET, "All snakes are ready to rock. Game is starting."); + } + + pub fn on_player_registered(&self, _: &PlayerRegistered) { + debug!(target: LOG_TARGET, "Player has been registered."); + } + + pub fn on_invalid_playername(&self, _: &InvalidPlayerName) { + debug!(target: LOG_TARGET, "Player name invalid."); + } } diff --git a/src/structs.rs b/src/structs.rs index c5bfdc8..c532575 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -2,141 +2,141 @@ #[derive(Serialize, Deserialize, Debug)] pub struct GameSettings { - pub maxNoofPlayers: u32, - pub startSnakeLength: u32, - pub timeInMsPerTick: u32, - pub obstaclesEnabled: bool, - pub foodEnabled: bool, - pub headToTailConsumes: bool, - pub tailConsumeGrows: bool, - pub addFoodLikelihood: u32, - pub removeFoodLikelihood: u32, - pub spontaneousGrowthEveryNWorldTick: u32, - pub trainingGame: bool, - pub pointsPerLength: u32, - pub pointsPerFood: u32, - pub pointsPerCausedDeath: u32, - pub pointsPerNibble: u32, - pub noofRoundsTailProtectedAfterNibble: u32, + pub maxNoofPlayers: u32, + pub startSnakeLength: u32, + pub timeInMsPerTick: u32, + pub obstaclesEnabled: bool, + pub foodEnabled: bool, + pub headToTailConsumes: bool, + pub tailConsumeGrows: bool, + pub addFoodLikelihood: u32, + pub removeFoodLikelihood: u32, + pub spontaneousGrowthEveryNWorldTick: u32, + pub trainingGame: bool, + pub pointsPerLength: u32, + pub pointsPerFood: u32, + pub pointsPerCausedDeath: u32, + pub pointsPerNibble: u32, + pub noofRoundsTailProtectedAfterNibble: u32, } impl Default for GameSettings { - fn default() -> GameSettings { - GameSettings { - maxNoofPlayers: 5, - startSnakeLength: 1, - timeInMsPerTick: 250, - obstaclesEnabled: true, - foodEnabled: true, - headToTailConsumes: true, - tailConsumeGrows: false, - addFoodLikelihood: 15, - removeFoodLikelihood: 5, - spontaneousGrowthEveryNWorldTick: 3, - trainingGame: false, - pointsPerLength: 1, - pointsPerFood: 2, - pointsPerCausedDeath: 5, - pointsPerNibble: 10, - noofRoundsTailProtectedAfterNibble: 3, - } - } + fn default() -> GameSettings { + GameSettings { + maxNoofPlayers: 5, + startSnakeLength: 1, + timeInMsPerTick: 250, + obstaclesEnabled: true, + foodEnabled: true, + headToTailConsumes: true, + tailConsumeGrows: false, + addFoodLikelihood: 15, + removeFoodLikelihood: 5, + spontaneousGrowthEveryNWorldTick: 3, + trainingGame: false, + pointsPerLength: 1, + pointsPerFood: 2, + pointsPerCausedDeath: 5, + pointsPerNibble: 10, + noofRoundsTailProtectedAfterNibble: 3, + } + } } #[derive(Serialize, Deserialize, Debug)] pub struct PlayerRegistered { - pub gameId: String, - pub gameMode: String, - pub receivingPlayerId: String, - pub name: String, - pub gameSettings: GameSettings + pub gameId: String, + pub gameMode: String, + pub receivingPlayerId: String, + pub name: String, + pub gameSettings: GameSettings } #[derive(Serialize, Deserialize, Debug)] pub struct MapUpdate { - pub receivingPlayerId: String, - pub gameId: String, - pub gameTick: u32, - pub map: Map, + pub receivingPlayerId: String, + pub gameId: String, + pub gameTick: u32, + pub map: Map, } #[derive(Serialize, Deserialize, Debug)] pub struct InvalidPlayerName { - pub reasonCode: u32, + pub reasonCode: u32, } #[derive(Serialize, Deserialize, Debug)] pub struct GameEnded { - pub receivingPlayerId: String, - pub playerWinnerId: String, - pub gameId: String, - pub gameTick: u32, - pub map: Map, + pub receivingPlayerId: String, + pub playerWinnerId: String, + pub gameId: String, + pub gameTick: u32, + pub map: Map, } #[derive(Serialize, Deserialize, Debug)] pub struct SnakeDead { - pub playerId: String, - pub x: u32, - pub y: u32, - pub gameId: String, - pub gameTick: u32, - pub deathReason: 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 { - pub receivingPlayerId: String, - pub gameId: String, - pub noofPlayers: u32, - pub width: u32, - pub height: u32, + pub receivingPlayerId: String, + pub gameId: String, + pub noofPlayers: u32, + pub width: u32, + pub height: u32, } #[derive(Serialize, Deserialize, Debug)] pub struct HeartBeatResponse { - pub receivingPlayerId: String + pub receivingPlayerId: String } #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct GameLink { - pub receivingPlayerId: String, - pub gameId: String, - pub url: String, + pub receivingPlayerId: String, + pub gameId: String, + pub url: String, } #[derive(Serialize, Deserialize, Debug)] pub struct TournamentEnded { - pub receivingPlayerId: String, - pub tournamentId: String, - pub tournamentName: String, - pub gameResult: Vec, - pub gameId: String, - pub playerWinnerId: String, + pub receivingPlayerId: String, + pub tournamentId: String, + pub tournamentName: String, + pub gameResult: Vec, + pub gameId: String, + pub playerWinnerId: String, } #[derive(Serialize, Deserialize, Debug)] pub struct GameResult { - pub points: i32, - pub playerId: String, - pub name: String + pub points: i32, + pub playerId: String, + pub name: String } #[derive(Serialize, Deserialize, Debug)] pub struct Map { - pub width: i32, - pub height: i32, - pub worldTick: u32, - pub snakeInfos: Vec, - pub foodPositions: Vec, - pub obstaclePositions: Vec + pub width: i32, + pub height: i32, + pub worldTick: u32, + pub snakeInfos: Vec, + pub foodPositions: Vec, + pub obstaclePositions: Vec } #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct SnakeInfo { - pub name: String, - pub points: i32, - pub positions: Vec, - pub tailProtectedForGameTicks: u32, - pub id: String + pub name: String, + pub points: i32, + pub positions: Vec, + pub tailProtectedForGameTicks: u32, + pub id: String } diff --git a/src/util.rs b/src/util.rs index 8b73fbb..6c22cdd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,53 +1,59 @@ +#[allow(dead_code)] +pub fn vector_diff(a: (i32, i32), b: (i32, i32)) -> (i32,i32) {( + a.0 - b.0, + a.1 - b.1, +)} + #[allow(dead_code)] pub fn translate_position(position: i32, map_width: i32) -> (i32,i32) { - let pos = position as f64; - let width = map_width as f64; + let pos = position as f64; + let width = map_width as f64; - let y = (pos / width).floor(); - let x = (pos - y * width).abs(); + let y = (pos / width).floor(); + let x = (pos - y * width).abs(); - (x as i32, y as i32) + (x as i32, y as i32) } #[allow(dead_code)] pub fn translate_positions(positions: &Vec, map_width: i32) -> Vec<(i32,i32)> { - positions.into_iter().map(|pos| translate_position(*pos, map_width)).collect() + positions.into_iter().map(|pos| translate_position(*pos, map_width)).collect() } #[allow(dead_code)] pub fn translate_coordinate(coordinates: (i32,i32), map_width: i32) -> i32 { - let (x,y) = coordinates; - x + y * map_width + let (x,y) = coordinates; + x + y * map_width } #[allow(dead_code)] pub fn get_manhattan_distance(start: (i32,i32), goal: (i32,i32)) -> i32 { - let (x1,y1) = start; - let (x2,y2) = goal; + let (x1,y1) = start; + let (x2,y2) = goal; - let x = ( x1 - x2 ).abs(); - let y = ( y1 - y2 ).abs(); + let x = ( x1 - x2 ).abs(); + let y = ( y1 - y2 ).abs(); - x+y + x+y } #[allow(dead_code)] pub fn get_euclidian_distance(start: (i32,i32), goal: (i32,i32)) -> f64 { - let (x1,y1) = start; - let (x2,y2) = goal; + let (x1,y1) = start; + let (x2,y2) = goal; - let x = ( x1 - x2 ).pow(2); - let y = ( y1 - y2 ).pow(2); - let d = ( x + y ) as f64; + let x = ( x1 - x2 ).pow(2); + let y = ( y1 - y2 ).pow(2); + let d = ( x + y ) as f64; - d.sqrt().floor() + d.sqrt().floor() } #[allow(dead_code)] pub fn is_within_square(coord: (i32,i32), ne_coord: (i32,i32), sw_coord: (i32,i32)) -> bool { - let (x,y) = coord; - let (ne_x, ne_y) = ne_coord; - let (sw_x, sw_y) = sw_coord; + let (x,y) = coord; + let (ne_x, ne_y) = ne_coord; + let (sw_x, sw_y) = sw_coord; - x < ne_x || x > sw_x || y < sw_y || y > ne_y + x < ne_x || x > sw_x || y < sw_y || y > ne_y }