Initial Commit for snake ten_points_to_slytherin

This commit is contained in:
2017-10-20 21:38:03 +02:00
parent 061da11d43
commit e274bd37e7
8 changed files with 641 additions and 589 deletions

View File

@ -1,7 +1,10 @@
[package]
name = "snakebot_rust"
version = "1.1.0"
authors = ["Martin Barksten <martin.barksten@cygni.com>"]
authors = [
"Martin Barksten <martin.barksten@cygni.com>",
"Joakim Hulthe <joakim@hulthe.net>",
]
license-file = "LICENSE"
[dependencies]

View File

@ -1,5 +1,5 @@
host = "localhost";
port = 8080;
snake_name = "rusty-snake";
snake_name = "ten_points_to_slytherin";
venue = "training";

View File

@ -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<String>) {
from()
}
StringChannel(err: mpsc::SendError<String>) {
from()
}
WebsocketChannel(err: mpsc::SendError<Arc<ws::Sender>>) {
from()
}
}
WebsocketChannel(err: mpsc::SendError<Arc<ws::Sender>>) {
from()
}
}
}
struct Client {
out: Arc<ws::Sender>,
snake: Snake,
config: Config,
out_sender: mpsc::Sender<Arc<ws::Sender>>,
id_sender: mpsc::Sender<String>
out: Arc<ws::Sender>,
snake: Snake,
config: Config,
out_sender: mpsc::Sender<Arc<ws::Sender>>,
id_sender: mpsc::Sender<String>
}
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 <martin.barksten@cygni.se>")
.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 <martin.barksten@cygni.se>")
.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::<i32>().unwrap();
let port = matches.value_of("port").unwrap_or(DEFAULT_PORT).parse::<i32>().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<String>,
out_sender: mpsc::Sender<Arc<ws::Sender>>) -> thread::JoinHandle<()> {
thread::spawn(move || {
let config = read_conf_file();
out_sender: mpsc::Sender<Arc<ws::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<ws::Sender>, 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<String>,
out_receiver: mpsc::Receiver<Arc<ws::Sender>>)
-> Result<(String, Arc<ws::Sender>), mpsc::RecvError> {
let id = try!(id_receiver.recv());
let out = try!(out_receiver.recv());
Ok((id, out))
out_receiver: mpsc::Receiver<Arc<ws::Sender>>)
-> Result<(String, Arc<ws::Sender>), 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<String>,
out_receiver: mpsc::Receiver<Arc<ws::Sender>>,
done_receiver: mpsc::Receiver<()>) -> thread::JoinHandle<()> {
thread::spawn(move || {
let res = recv_channels(id_receiver, out_receiver);
out_receiver: mpsc::Receiver<Arc<ws::Sender>>,
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();
}

View File

@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
Direction::Down => "DOWN",
Direction::Up => "UP",
Direction::Left => "LEFT",
Direction::Right => "RIGHT",
})
}
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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));
}
}

View File

@ -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: <Some JSON data>}
///
pub fn handle_inbound_msg(s: &str) -> Result<Inbound, Error> {
let mut json_value = from_str::<Value>(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::<Value>(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()
}

View File

@ -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.");
}
}

View File

@ -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<GameResult>,
pub gameId: String,
pub playerWinnerId: String,
pub receivingPlayerId: String,
pub tournamentId: String,
pub tournamentName: String,
pub gameResult: Vec<GameResult>,
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<SnakeInfo>,
pub foodPositions: Vec<i32>,
pub obstaclePositions: Vec<i32>
pub width: i32,
pub height: i32,
pub worldTick: u32,
pub snakeInfos: Vec<SnakeInfo>,
pub foodPositions: Vec<i32>,
pub obstaclePositions: Vec<i32>
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SnakeInfo {
pub name: String,
pub points: i32,
pub positions: Vec<i32>,
pub tailProtectedForGameTicks: u32,
pub id: String
pub name: String,
pub points: i32,
pub positions: Vec<i32>,
pub tailProtectedForGameTicks: u32,
pub id: String
}

View File

@ -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<i32>, 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
}