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] [package]
name = "snakebot_rust" name = "snakebot_rust"
version = "1.1.0" 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" license-file = "LICENSE"
[dependencies] [dependencies]

View File

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

View File

@ -4,7 +4,7 @@
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[macro_use] extern crate serde_json; #[macro_use] extern crate serde_json;
extern crate clap; extern crate clap;
extern crate config; //extern crate config;
extern crate log4rs; extern crate log4rs;
extern crate rustc_version; extern crate rustc_version;
extern crate serde; extern crate serde;
@ -31,275 +31,276 @@ const LOG_TARGET: &'static str = "client";
const HEART_BEAT_S: u64 = 20; const HEART_BEAT_S: u64 = 20;
const CONFIG_FILE: &'static str = "snake.conf"; const CONFIG_FILE: &'static str = "snake.conf";
const DEFAULT_HOST: &'static str = "snake.cygni.se"; //const DEFAULT_HOST: &'static str = "snake.cygni.se";
const DEFAULT_PORT: &'static str = "80"; const DEFAULT_HOST: &'static str = "localhost";
const DEFAULT_SNAKE_NAME: &'static str = "default-rust-snake-name"; const DEFAULT_PORT: &'static str = "8080";
const DEFAULT_SNAKE_NAME: &'static str = "ten_points_to_slytherin";
const DEFAULT_VENUE: &'static str = "training"; const DEFAULT_VENUE: &'static str = "training";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Config { struct Config {
host: String, host: String,
port: i32, port: i32,
snake_name: String, snake_name: String,
venue: String venue: String
} }
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum ClientError { pub enum ClientError {
Message(err: serde_json::Error) { Message(err: serde_json::Error) {
from() from()
} }
Websocket(err: ws::Error) { Websocket(err: ws::Error) {
from() from()
} }
StringChannel(err: mpsc::SendError<String>) { StringChannel(err: mpsc::SendError<String>) {
from() from()
} }
WebsocketChannel(err: mpsc::SendError<Arc<ws::Sender>>) { WebsocketChannel(err: mpsc::SendError<Arc<ws::Sender>>) {
from() from()
} }
} }
} }
struct Client { struct Client {
out: Arc<ws::Sender>, out: Arc<ws::Sender>,
snake: Snake, snake: Snake,
config: Config, config: Config,
out_sender: mpsc::Sender<Arc<ws::Sender>>, out_sender: mpsc::Sender<Arc<ws::Sender>>,
id_sender: mpsc::Sender<String> id_sender: mpsc::Sender<String>
} }
fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> { 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)) { match try!(handle_inbound_msg(str_msg)) {
Inbound::GameEnded(msg) => { Inbound::GameEnded(msg) => {
snake.on_game_ended(&msg); snake.on_game_ended(&msg);
if client.config.venue == "training" { if client.config.venue == "training" {
try!(client.out.close(ws::CloseCode::Normal)); try!(client.out.close(ws::CloseCode::Normal));
} }
}, },
Inbound::TournamentEnded(msg) => { Inbound::TournamentEnded(msg) => {
snake.on_tournament_ended(&msg); snake.on_tournament_ended(&msg);
try!(client.out.close(ws::CloseCode::Normal)); try!(client.out.close(ws::CloseCode::Normal));
}, },
Inbound::MapUpdate(msg) => { Inbound::MapUpdate(msg) => {
let m = render_outbound_message(Outbound::RegisterMove { let m = render_outbound_message(Outbound::RegisterMove {
direction: snake.get_next_move(&msg), direction: snake.get_next_move(&msg),
gameTick: msg.gameTick, gameTick: msg.gameTick,
receivingPlayerId: msg.receivingPlayerId, receivingPlayerId: msg.receivingPlayerId,
gameId: msg.gameId }); gameId: msg.gameId });
debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", m); debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", m);
try!(client.out.send(m)); try!(client.out.send(m));
}, },
Inbound::SnakeDead(msg) => { Inbound::SnakeDead(msg) => {
snake.on_snake_dead(&msg); snake.on_snake_dead(&msg);
}, },
Inbound::GameStarting(msg) => { Inbound::GameStarting(msg) => {
snake.on_game_starting(&msg); snake.on_game_starting(&msg);
}, },
Inbound::PlayerRegistered(msg) => { Inbound::PlayerRegistered(msg) => {
info!(target: LOG_TARGET, "Successfully registered player"); info!(target: LOG_TARGET, "Successfully registered player");
snake.on_player_registered(&msg); snake.on_player_registered(&msg);
if msg.gameMode == "TRAINING" { if msg.gameMode == "TRAINING" {
let m = render_outbound_message(Outbound::StartGame); let m = render_outbound_message(Outbound::StartGame);
debug!(target: LOG_TARGET, "Requesting a game start {:?}", m); debug!(target: LOG_TARGET, "Requesting a game start {:?}", m);
try!(client.out.send(m)); try!(client.out.send(m));
}; };
info!(target: LOG_TARGET, "Starting heart beat"); info!(target: LOG_TARGET, "Starting heart beat");
try!(client.out_sender.send(client.out.clone())); try!(client.out_sender.send(client.out.clone()));
try!(client.id_sender.send(msg.receivingPlayerId)); try!(client.id_sender.send(msg.receivingPlayerId));
}, },
Inbound::InvalidPlayerName(msg) => { Inbound::InvalidPlayerName(msg) => {
snake.on_invalid_playername(&msg); snake.on_invalid_playername(&msg);
}, },
Inbound::HeartBeatResponse(_) => { Inbound::HeartBeatResponse(_) => {
// do nothing // do nothing
}, },
Inbound::GameLink(msg) => { Inbound::GameLink(msg) => {
info!(target: LOG_TARGET, "Watch game at {}", msg.url); info!(target: LOG_TARGET, "Watch game at {}", msg.url);
}, },
Inbound::GameResult(msg) => { Inbound::GameResult(msg) => {
info!(target: LOG_TARGET, "We got some game result! {:?}", msg); info!(target: LOG_TARGET, "We got some game result! {:?}", msg);
}, },
Inbound::UnrecognizedMessage => { Inbound::UnrecognizedMessage => {
error!(target: LOG_TARGET, "Received unrecognized message {:?}", str_msg); error!(target: LOG_TARGET, "Received unrecognized message {:?}", str_msg);
} }
}; };
Ok(()) Ok(())
} }
impl ws::Handler for Client { impl ws::Handler for Client {
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
debug!(target: LOG_TARGET, "Connection to Websocket opened"); debug!(target: LOG_TARGET, "Connection to Websocket opened");
let m = render_outbound_message(Outbound::ClientInfo); let m = render_outbound_message(Outbound::ClientInfo);
info!(target: LOG_TARGET, "Sending client info to server: {:?}", m); info!(target: LOG_TARGET, "Sending client info to server: {:?}", m);
try!(self.out.send(m)); try!(self.out.send(m));
let msg = render_outbound_message(Outbound::RegisterPlayer { let msg = render_outbound_message(Outbound::RegisterPlayer {
playerName: self.config.snake_name.clone(), playerName: self.config.snake_name.clone(),
gameSettings: Default::default() }); gameSettings: Default::default() });
info!(target: LOG_TARGET, "Registering player with message: {:?}", msg); info!(target: LOG_TARGET, "Registering player with message: {:?}", msg);
self.out.send(msg) self.out.send(msg)
} }
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
if let ws::Message::Text(text) = msg { if let ws::Message::Text(text) = msg {
let route_result = route_msg(self, &text); let route_result = route_msg(self, &text);
match route_result { match route_result {
Err(e) => error!(target: LOG_TARGET, "Got error \'{:?}\' when routing message: {}", e, text), Err(e) => error!(target: LOG_TARGET, "Got error \'{:?}\' when routing message: {}", e, text),
Ok(_) => debug!(target: LOG_TARGET, "Succeeded in routing message {}", text) Ok(_) => debug!(target: LOG_TARGET, "Succeeded in routing message {}", text)
} }
} else { } else {
warn!(target: LOG_TARGET, "Unexpectedly received non-string message: {:?}", msg) warn!(target: LOG_TARGET, "Unexpectedly received non-string message: {:?}", msg)
} }
Ok(()) Ok(())
} }
} }
fn read_conf_file() -> Config { fn read_conf_file() -> Config {
let config_path = Path::new(CONFIG_FILE); let config_path = Path::new(CONFIG_FILE);
info!(target: LOG_TARGET, "Reading config from file at {:?}", config_path.canonicalize()); info!(target: LOG_TARGET, "Reading config from file at {:?}", config_path.canonicalize());
let matches = App::new("Rust snake client") let matches = App::new("Rust snake client")
.version("1.1.0") .version("1.1.0")
.author("Martin Barksten <martin.barksten@cygni.se>") .author("Martin Barksten <martin.barksten@cygni.se>")
.about("A snake client in the least friendly language.") .about("A snake client in the least friendly language.")
.arg(Arg::with_name("host") .arg(Arg::with_name("host")
.short("h") .short("h")
.long("host") .long("host")
.help("The host to connect to") .help("The host to connect to")
.takes_value(true) .takes_value(true)
.default_value(DEFAULT_HOST)) .default_value(DEFAULT_HOST))
.arg(Arg::with_name("port") .arg(Arg::with_name("port")
.short("p") .short("p")
.long("port") .long("port")
.help("The port to connect to") .help("The port to connect to")
.takes_value(true) .takes_value(true)
.default_value(DEFAULT_PORT)) .default_value(DEFAULT_PORT))
.arg(Arg::with_name("venue") .arg(Arg::with_name("venue")
.short("v") .short("v")
.long("venue") .long("venue")
.help("The venue (tournament or training)") .help("The venue (tournament or training)")
.takes_value(true) .takes_value(true)
.default_value(DEFAULT_VENUE) .default_value(DEFAULT_VENUE)
.possible_values(&["tournament", "training"])) .possible_values(&["tournament", "training"]))
.arg(Arg::with_name("snake-name") .arg(Arg::with_name("snake-name")
.short("n") .short("n")
.long("snake-name") .long("snake-name")
.help("The name of the snake") .help("The name of the snake")
.takes_value(true) .takes_value(true)
.default_value(DEFAULT_SNAKE_NAME)) .default_value(DEFAULT_SNAKE_NAME))
.get_matches(); .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 { Config {
host: String::from(matches.value_of("host").unwrap_or(DEFAULT_HOST)), host: String::from(matches.value_of("host").unwrap_or(DEFAULT_HOST)),
port: port, port: port,
snake_name: String::from(matches.value_of("snake-name").unwrap_or(DEFAULT_SNAKE_NAME)), 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)) venue: String::from(matches.value_of("venue").unwrap_or(DEFAULT_VENUE))
} }
} }
fn start_websocket_thread(id_sender: mpsc::Sender<String>, fn start_websocket_thread(id_sender: mpsc::Sender<String>,
out_sender: mpsc::Sender<Arc<ws::Sender>>) -> thread::JoinHandle<()> { out_sender: mpsc::Sender<Arc<ws::Sender>>) -> thread::JoinHandle<()> {
thread::spawn(move || { thread::spawn(move || {
let config = read_conf_file(); let config = read_conf_file();
let connection_url = format!("ws://{}:{}/{}", config.host, config.port, config.venue); let connection_url = format!("ws://{}:{}/{}", config.host, config.port, config.venue);
info!(target: LOG_TARGET, "Connecting to {:?}", connection_url); info!(target: LOG_TARGET, "Connecting to {:?}", connection_url);
let result = ws::connect(connection_url, |out| { let result = ws::connect(connection_url, |out| {
Client { Client {
out: Arc::from(out), out: Arc::from(out),
snake: snake::Snake, snake: snake::Snake,
config: config.clone(), config: config.clone(),
out_sender: out_sender.clone(), out_sender: out_sender.clone(),
id_sender: id_sender.clone() id_sender: id_sender.clone()
} }
}); });
debug!(target: LOG_TARGET, "Websocket is done, result {:?}", result); debug!(target: LOG_TARGET, "Websocket is done, result {:?}", result);
}) })
} }
fn do_heart_beat(id: String, out: Arc<ws::Sender>, done_receiver: mpsc::Receiver<()>) { fn do_heart_beat(id: String, out: Arc<ws::Sender>, done_receiver: mpsc::Receiver<()>) {
loop { loop {
thread::sleep(Duration::from_secs(HEART_BEAT_S)); thread::sleep(Duration::from_secs(HEART_BEAT_S));
let rec = done_receiver.try_recv(); let rec = done_receiver.try_recv();
// if the channel is disconnected or a done message is sent, break the loop // if the channel is disconnected or a done message is sent, break the loop
if let Err(e) = rec { if let Err(e) = rec {
if e == mpsc::TryRecvError::Disconnected { if e == mpsc::TryRecvError::Disconnected {
debug!(target: LOG_TARGET, "Stopping heartbeat due to channel disconnecting"); debug!(target: LOG_TARGET, "Stopping heartbeat due to channel disconnecting");
break; break;
} }
} else { } else {
debug!(target: LOG_TARGET, "Stopping heartbeat due to finished execution"); debug!(target: LOG_TARGET, "Stopping heartbeat due to finished execution");
break; break;
} }
debug!(target: LOG_TARGET, "Sending heartbeat request"); debug!(target: LOG_TARGET, "Sending heartbeat request");
let send_result = out.send(render_outbound_message(Outbound::HeartBeat { receivingPlayerId: id.clone() })); let send_result = out.send(render_outbound_message(Outbound::HeartBeat { receivingPlayerId: id.clone() }));
if let Err(e) = send_result { if let Err(e) = send_result {
error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e); error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e);
} }
} }
} }
pub fn recv_channels(id_receiver: mpsc::Receiver<String>, pub fn recv_channels(id_receiver: mpsc::Receiver<String>,
out_receiver: mpsc::Receiver<Arc<ws::Sender>>) out_receiver: mpsc::Receiver<Arc<ws::Sender>>)
-> Result<(String, Arc<ws::Sender>), mpsc::RecvError> { -> Result<(String, Arc<ws::Sender>), mpsc::RecvError> {
let id = try!(id_receiver.recv()); let id = try!(id_receiver.recv());
let out = try!(out_receiver.recv()); let out = try!(out_receiver.recv());
Ok((id, out)) Ok((id, out))
} }
fn start_heart_beat_thread(id_receiver: mpsc::Receiver<String>, fn start_heart_beat_thread(id_receiver: mpsc::Receiver<String>,
out_receiver: mpsc::Receiver<Arc<ws::Sender>>, out_receiver: mpsc::Receiver<Arc<ws::Sender>>,
done_receiver: mpsc::Receiver<()>) -> thread::JoinHandle<()> { done_receiver: mpsc::Receiver<()>) -> thread::JoinHandle<()> {
thread::spawn(move || { thread::spawn(move || {
let res = recv_channels(id_receiver, out_receiver); let res = recv_channels(id_receiver, out_receiver);
if let Ok((id, out)) = res { if let Ok((id, out)) = res {
debug!(target: LOG_TARGET, "Starting heartbeat"); debug!(target: LOG_TARGET, "Starting heartbeat");
do_heart_beat(id, out, done_receiver); do_heart_beat(id, out, done_receiver);
} else { } else {
error!(target: LOG_TARGET, "Unable to start heart beat, the channel has been closed."); error!(target: LOG_TARGET, "Unable to start heart beat, the channel has been closed.");
}; };
}) })
} }
fn start_client() { fn start_client() {
let (id_sender,id_receiver) = mpsc::channel(); let (id_sender,id_receiver) = mpsc::channel();
let (out_sender,out_receiver) = mpsc::channel(); let (out_sender,out_receiver) = mpsc::channel();
let (done_sender,done_receiver) = mpsc::channel(); let (done_sender,done_receiver) = mpsc::channel();
let websocket = start_websocket_thread(id_sender, out_sender); let websocket = start_websocket_thread(id_sender, out_sender);
let heartbeat = start_heart_beat_thread(id_receiver, out_receiver, done_receiver); let heartbeat = start_heart_beat_thread(id_receiver, out_receiver, done_receiver);
let websocket_res = websocket.join(); let websocket_res = websocket.join();
debug!(target: LOG_TARGET, "Joining Websocket thread gave result {:?}", websocket_res); debug!(target: LOG_TARGET, "Joining Websocket thread gave result {:?}", websocket_res);
let send_res = done_sender.send(()); let send_res = done_sender.send(());
if let Err(e) = send_res { if let Err(e) = send_res {
error!(target: LOG_TARGET, "Unable to send done message, got error {:?}", e); error!(target: LOG_TARGET, "Unable to send done message, got error {:?}", e);
} }
let heartbeat_res = heartbeat.join(); let heartbeat_res = heartbeat.join();
debug!(target: LOG_TARGET, "Joining heartbeat thread gave result {:?}", heartbeat_res); debug!(target: LOG_TARGET, "Joining heartbeat thread gave result {:?}", heartbeat_res);
} }
fn main() { fn main() {
if let Err(_) = log4rs::init_file("log4rs.toml", Default::default()) { if let Err(_) = log4rs::init_file("log4rs.toml", Default::default()) {
log4rs::init_file("../log4rs.toml", Default::default()).unwrap(); log4rs::init_file("../log4rs.toml", Default::default()).unwrap();
} }
start_client(); start_client();
} }

View File

@ -1,217 +1,234 @@
use structs::{ Map, SnakeInfo }; use structs::{ Map, SnakeInfo };
use util; use util;
use serde::ser::{ Serialize, Serializer }; use serde::ser::{ Serialize, Serializer };
use util::{ translate_position };
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Tile<'a> { pub enum Tile<'a> {
Wall, Wall,
Food { coordinate: (i32,i32) }, Food { coordinate: (i32,i32) },
Obstacle { coordinate: (i32,i32) }, Obstacle { coordinate: (i32,i32) },
Empty { coordinate: (i32,i32) }, Empty { coordinate: (i32,i32) },
SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo }, SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo },
SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo } SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo }
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Direction { pub enum Direction {
Down, Down,
Up, Up,
Left, Left,
Right Right
} }
impl Serialize for Direction { impl Serialize for Direction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer where S: Serializer
{ {
serializer.serialize_str(match *self { serializer.serialize_str(match *self {
Direction::Down => "DOWN", Direction::Down => "DOWN",
Direction::Up => "UP", Direction::Up => "UP",
Direction::Left => "LEFT", Direction::Left => "LEFT",
Direction::Right => "RIGHT", Direction::Right => "RIGHT",
}) })
} }
} }
impl Direction { impl Direction {
pub fn as_movement_delta(&self) -> (i32,i32) { pub fn as_movement_delta(&self) -> (i32,i32) {
match *self { match *self {
Direction::Down => ( 0, 1), Direction::Down => ( 0, 1),
Direction::Up => ( 0, -1), Direction::Up => ( 0, -1),
Direction::Left => (-1, 0), Direction::Left => (-1, 0),
Direction::Right => ( 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 { impl Map {
pub fn inside_map(&self, coordinate: (i32, i32)) -> bool { pub fn inside_map(&self, coordinate: (i32, i32)) -> bool {
let (x,y) = coordinate; let (x,y) = coordinate;
let inside_x = x >= 0 && x < self.width; let inside_x = x >= 0 && x < self.width;
let inside_y = y >= 0 && y < self.height; let inside_y = y >= 0 && y < self.height;
inside_x && inside_y inside_x && inside_y
} }
pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> { pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> {
self.snakeInfos.iter().find(|s| &s.id == id) self.snakeInfos.iter().find(|s| &s.id == id)
} }
pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile { pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile {
let position = util::translate_coordinate(coordinate, self.width); let position = util::translate_coordinate(coordinate, self.width);
let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position)); let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position));
if self.obstaclePositions.contains(&position) { if self.obstaclePositions.contains(&position) {
Tile::Obstacle { coordinate: coordinate } Tile::Obstacle { coordinate: coordinate }
} else if self.foodPositions.contains(&position) { } else if self.foodPositions.contains(&position) {
Tile::Food { coordinate: coordinate } Tile::Food { coordinate: coordinate }
} else if snake_at_tile.is_some() { } else if snake_at_tile.is_some() {
let s = snake_at_tile.unwrap(); let s = snake_at_tile.unwrap();
if s.positions[0] == position { if s.positions[0] == position {
Tile::SnakeHead { coordinate: coordinate, snake: s } Tile::SnakeHead { coordinate: coordinate, snake: s }
} else { } else {
Tile::SnakeBody { coordinate: coordinate, snake: s } Tile::SnakeBody { coordinate: coordinate, snake: s }
} }
} else if !self.inside_map(coordinate) { } else if !self.inside_map(coordinate) {
Tile::Wall Tile::Wall
} else { } else {
Tile::Empty { coordinate: coordinate } Tile::Empty { coordinate: coordinate }
} }
} }
pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool { pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool {
let tile = self.get_tile_at(coordinate); let tile = self.get_tile_at(coordinate);
match tile { match tile {
Tile::Empty { coordinate: _ } => true, Tile::Empty { coordinate: _ } => true,
Tile::Food { coordinate: _ } => true, Tile::Food { coordinate: _ } => true,
_ => false 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 { pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool {
let (xd,yd) = direction.as_movement_delta(); let (xd,yd) = direction.as_movement_delta();
let (x,y) = util::translate_position(snake.positions[0], self.width); 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)] #[allow(dead_code)]
pub fn is_coordinate_out_of_bounds(&self, coordinate: (i32,i32)) -> bool { pub fn is_coordinate_out_of_bounds(&self, coordinate: (i32,i32)) -> bool {
let (x,y) = coordinate; let (x,y) = coordinate;
x < 0 || x >= self.width || y < 0 || y >= self.height x < 0 || x >= self.width || y < 0 || y >= self.height
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use util::{ translate_coordinate }; use util::{ translate_coordinate };
use maputil::{ Direction, Tile }; use maputil::{ Direction, Tile };
use structs::{ Map, SnakeInfo }; use structs::{ Map, SnakeInfo };
const MAP_WIDTH: i32 = 3; const MAP_WIDTH: i32 = 3;
fn get_snake_one() -> SnakeInfo { fn get_snake_one() -> SnakeInfo {
SnakeInfo { SnakeInfo {
name: String::from("1"), name: String::from("1"),
points: 0, points: 0,
tailProtectedForGameTicks: 0, tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,1), MAP_WIDTH), positions: vec![translate_coordinate((1,1), MAP_WIDTH),
translate_coordinate((0,1), MAP_WIDTH)], translate_coordinate((0,1), MAP_WIDTH)],
id: String::from("1") id: String::from("1")
} }
} }
fn get_snake_two() -> SnakeInfo { fn get_snake_two() -> SnakeInfo {
SnakeInfo { SnakeInfo {
name: String::from("2"), name: String::from("2"),
points: 0, points: 0,
tailProtectedForGameTicks: 0, tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,2), MAP_WIDTH)], positions: vec![translate_coordinate((1,2), MAP_WIDTH)],
id: String::from("2") id: String::from("2")
} }
} }
// The map used for testing, 1 and 2 represents the snakes // The map used for testing, 1 and 2 represents the snakes
//yx012 //yx012
//0 F //0 F
//1 11# //1 11#
//2 2 //2 2
fn get_test_map() -> Map { fn get_test_map() -> Map {
Map { Map {
width: MAP_WIDTH, width: MAP_WIDTH,
height: MAP_WIDTH, height: MAP_WIDTH,
worldTick: 0, worldTick: 0,
snakeInfos: vec![get_snake_one(), get_snake_two()], snakeInfos: vec![get_snake_one(), get_snake_two()],
foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)], foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)],
obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)] obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)]
} }
} }
#[test] #[test]
fn snake_can_be_found_by_id() { fn snake_can_be_found_by_id() {
let map = get_test_map(); let map = get_test_map();
let id = &get_snake_one().id; let id = &get_snake_one().id;
let s = map.get_snake_by_id(id); let s = map.get_snake_by_id(id);
let found_id = &s.unwrap().id; let found_id = &s.unwrap().id;
assert_eq!(id, found_id); assert_eq!(id, found_id);
} }
#[test] #[test]
fn tile_is_correctly_found() { fn tile_is_correctly_found() {
let map = get_test_map(); let map = get_test_map();
let snake_one = get_snake_one(); let snake_one = get_snake_one();
let snake_two = get_snake_two(); let snake_two = get_snake_two();
let tiles = let tiles =
vec![ vec![
vec![Tile::Empty{ coordinate: (0,0) }, vec![Tile::Empty{ coordinate: (0,0) },
Tile::Food{ coordinate: (1,0) }, Tile::Food{ coordinate: (1,0) },
Tile::Empty{ coordinate: (2,0) }], Tile::Empty{ coordinate: (2,0) }],
vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one }, vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one },
Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one }, Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one },
Tile::Obstacle{ coordinate: (2,1)}], Tile::Obstacle{ coordinate: (2,1)}],
vec![Tile::Empty{ coordinate: (0,2) }, vec![Tile::Empty{ coordinate: (0,2) },
Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two }, Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two },
Tile::Empty{ coordinate:(2,2) }]]; Tile::Empty{ coordinate:(2,2) }]];
for y in 0..map.width { for y in 0..map.width {
for x in 0..map.height { for x in 0..map.height {
assert_eq!(tiles[y as usize][x as usize], assert_eq!(tiles[y as usize][x as usize],
map.get_tile_at((x,y))); map.get_tile_at((x,y)));
} }
} }
} }
#[test] #[test]
fn tile_is_correctly_marked_as_movable() { fn tile_is_correctly_marked_as_movable() {
let map = get_test_map(); let map = get_test_map();
let tiles = vec![vec![true, true, true], let tiles = vec![vec![true, true, true],
vec![false, false, false], vec![false, false, false],
vec![true, false, true]]; vec![true, false, true]];
for y in 0..map.height { for y in 0..map.height {
for x in 0..map.width { for x in 0..map.width {
assert_eq!(tiles[y as usize][x as usize], assert_eq!(tiles[y as usize][x as usize],
map.is_tile_available_for_movement((x,y))); map.is_tile_available_for_movement((x,y)));
} }
} }
} }
#[test] #[test]
fn can_snake_move_identifies_correctly() { fn can_snake_move_identifies_correctly() {
let map = get_test_map(); let map = get_test_map();
let id = &get_snake_one().id; let id = &get_snake_one().id;
let snake = map.get_snake_by_id(id).unwrap(); let snake = map.get_snake_by_id(id).unwrap();
assert_eq!(true, map.can_snake_move_in_direction(&snake, Direction::Up)); 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::Down));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Left)); 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!(false, map.can_snake_move_in_direction(&snake, Direction::Right));
} }
#[test] #[test]
fn can_not_move_to_walls() { fn can_not_move_to_walls() {
let map = get_test_map(); let map = get_test_map();
let id = &get_snake_two().id; let id = &get_snake_two().id;
let snake = map.get_snake_by_id(id).unwrap(); 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)] #[derive(Serialize, Deserialize, Debug)]
pub enum Inbound { pub enum Inbound {
GameEnded(structs::GameEnded), GameEnded(structs::GameEnded),
TournamentEnded(structs::TournamentEnded), TournamentEnded(structs::TournamentEnded),
MapUpdate(structs::MapUpdate), MapUpdate(structs::MapUpdate),
SnakeDead(structs::SnakeDead), SnakeDead(structs::SnakeDead),
GameStarting(structs::GameStarting), GameStarting(structs::GameStarting),
PlayerRegistered(structs::PlayerRegistered), PlayerRegistered(structs::PlayerRegistered),
InvalidPlayerName(structs::InvalidPlayerName), InvalidPlayerName(structs::InvalidPlayerName),
HeartBeatResponse(structs::HeartBeatResponse), HeartBeatResponse(structs::HeartBeatResponse),
GameLink(structs::GameLink), GameLink(structs::GameLink),
GameResult(structs::GameResult), GameResult(structs::GameResult),
UnrecognizedMessage UnrecognizedMessage
} }
/// We turn the string into `Inbound` by converting the string into a /// 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>} /// Like this: {GameResult: <Some JSON data>}
/// ///
pub fn handle_inbound_msg(s: &str) -> Result<Inbound, Error> { pub fn handle_inbound_msg(s: &str) -> Result<Inbound, Error> {
let mut json_value = from_str::<Value>(s) let mut json_value = from_str::<Value>(s)
.expect(&format!("Couldn't parse string into JSON: {:?}", s)); .expect(&format!("Couldn't parse string into JSON: {:?}", s));
let mut map = json_value.as_object_mut() let map = json_value.as_object_mut()
.expect(&format!("Couldn't parse string into JSON object: {:?}", s)); .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_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 type_str = type_value.as_str().expect(&format!("Couldn't turn JSON Value into string: {:?}", &map));
let typ = type_str.rsplit('.').next() let typ = type_str.rsplit('.').next()
.expect(&format!("The type parser needs a dot-separated string, this string lacks dots: {:?}", type_str)) .expect(&format!("The type parser needs a dot-separated string, this string lacks dots: {:?}", type_str))
.replace("Event", ""); .replace("Event", "");
from_value(Value::Object(Map::from_iter(vec![(typ, Value::Object(map.clone()))]))) from_value(Value::Object(Map::from_iter(vec![(typ, Value::Object(map.clone()))])))
} }
pub enum Outbound { pub enum Outbound {
RegisterPlayer{playerName: String, gameSettings: GameSettings}, RegisterPlayer{playerName: String, gameSettings: GameSettings},
StartGame, StartGame,
RegisterMove{direction: Direction, gameTick: u32, receivingPlayerId: String, gameId: String}, RegisterMove{direction: Direction, gameTick: u32, receivingPlayerId: String, gameId: String},
HeartBeat{receivingPlayerId: String}, HeartBeat{receivingPlayerId: String},
ClientInfo, ClientInfo,
} }
pub fn render_outbound_message(msg: Outbound) -> String { pub fn render_outbound_message(msg: Outbound) -> String {
(match msg { (match msg {
Outbound::RegisterPlayer {playerName, gameSettings} => json!({ Outbound::RegisterPlayer {playerName, gameSettings} => json!({
"type": "se.cygni.snake.api.request.RegisterPlayer", "type": "se.cygni.snake.api.request.RegisterPlayer",
"playerName": playerName, "playerName": playerName,
"gameSettings": gameSettings "gameSettings": gameSettings
}), }),
Outbound::StartGame => json!({ Outbound::StartGame => json!({
"type": "se.cygni.snake.api.request.StartGame", "type": "se.cygni.snake.api.request.StartGame",
}), }),
Outbound::RegisterMove {direction, gameTick, receivingPlayerId, gameId} => json!({ Outbound::RegisterMove {direction, gameTick, receivingPlayerId, gameId} => json!({
"type": "se.cygni.snake.api.request.RegisterMove", "type": "se.cygni.snake.api.request.RegisterMove",
"direction": direction, "direction": direction,
"gameTick": gameTick, "gameTick": gameTick,
"receivingPlayerId": receivingPlayerId, "receivingPlayerId": receivingPlayerId,
"gameId": gameId, "gameId": gameId,
}), }),
Outbound::HeartBeat {receivingPlayerId} => json!({ Outbound::HeartBeat {receivingPlayerId} => json!({
"type": "se.cygni.snake.api.request.HeartBeatRequest", "type": "se.cygni.snake.api.request.HeartBeatRequest",
"receivingPlayerId": receivingPlayerId, "receivingPlayerId": receivingPlayerId,
}), }),
Outbound::ClientInfo => json!({ Outbound::ClientInfo => json!({
"type": "se.cygni.snake.api.request.ClientInfo", "type": "se.cygni.snake.api.request.ClientInfo",
"language": "Rust", "language": "Rust",
"languageVersion": version().unwrap().to_string(), "languageVersion": version().unwrap().to_string(),
"operatingSystem": Target::os(), "operatingSystem": Target::os(),
"operatingSystemVersion": "???", "operatingSystemVersion": "???",
"clientVersion": option_env!("CARGO_PKG_VERSION").unwrap_or("0.1337"), "clientVersion": option_env!("CARGO_PKG_VERSION").unwrap_or("0.1337"),
}), }),
}).to_string() }).to_string()
} }

View File

@ -1,51 +1,77 @@
use structs::{ MapUpdate, GameEnded, TournamentEnded, SnakeDead, GameStarting, PlayerRegistered, InvalidPlayerName }; use structs::{ MapUpdate, GameEnded, TournamentEnded, SnakeDead, GameStarting, PlayerRegistered, InvalidPlayerName };
use maputil::{ Direction }; use maputil::{ Direction };
use util::{ translate_positions }; use util::{ translate_positions, translate_position, vector_diff };
const LOG_TARGET: &'static str = "snake"; const LOG_TARGET: &'static str = "snake";
pub struct Snake; pub struct Snake;
impl Snake { impl Snake {
pub fn get_next_move(&self, msg: &MapUpdate) -> Direction { pub fn get_next_move(&self, msg: &MapUpdate) -> Direction {
debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick); debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick);
let map = &msg.map; let map = &msg.map;
let snake = map.get_snake_by_id(&msg.receivingPlayerId).unwrap(); 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)); let snake = map.get_snake_by_id(&msg.receivingPlayerId).unwrap();
debug!(target: LOG_TARGET, "My snake positions are {:?}", translate_positions(&snake.positions, map.width)); let snakes = &map.snakeInfos;
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;
}
pub fn on_game_ended(&self, msg: &GameEnded) { let snake_positions = translate_positions(&snake.positions, map.width);
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); let foodPositions = translate_positions(&map.foodPositions, map.width);
}
pub fn on_tournament_ended(&self, msg: &TournamentEnded) { let facing = match snake_positions.len() {
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); 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) { for s in snakes {
debug!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason); if s.positions.len() == 0 || s.id == snake.id {
} continue;
}
pub fn on_game_starting(&self, _: &GameStarting) { let tail_position = translate_position(*s.positions.last().unwrap(), map.width);
debug!(target: LOG_TARGET, "All snakes are ready to rock. Game is starting."); let tail_direction = Direction::from_movement_delta(vector_diff(tail_position, snake_positions[0]));
}
pub fn on_player_registered(&self, _: &PlayerRegistered) { if map.can_snake_move_in_direction(snake, tail_direction) {
debug!(target: LOG_TARGET, "Player has been registered."); debug!(target: LOG_TARGET, "Snake will hunt in direction {:?}", tail_direction);
} return tail_direction;
}
}
pub fn on_invalid_playername(&self, _: &InvalidPlayerName) { for &d in directions.iter() {
debug!(target: LOG_TARGET, "Player name invalid."); 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)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameSettings { pub struct GameSettings {
pub maxNoofPlayers: u32, pub maxNoofPlayers: u32,
pub startSnakeLength: u32, pub startSnakeLength: u32,
pub timeInMsPerTick: u32, pub timeInMsPerTick: u32,
pub obstaclesEnabled: bool, pub obstaclesEnabled: bool,
pub foodEnabled: bool, pub foodEnabled: bool,
pub headToTailConsumes: bool, pub headToTailConsumes: bool,
pub tailConsumeGrows: bool, pub tailConsumeGrows: bool,
pub addFoodLikelihood: u32, pub addFoodLikelihood: u32,
pub removeFoodLikelihood: u32, pub removeFoodLikelihood: u32,
pub spontaneousGrowthEveryNWorldTick: u32, pub spontaneousGrowthEveryNWorldTick: u32,
pub trainingGame: bool, pub trainingGame: bool,
pub pointsPerLength: u32, pub pointsPerLength: u32,
pub pointsPerFood: u32, pub pointsPerFood: u32,
pub pointsPerCausedDeath: u32, pub pointsPerCausedDeath: u32,
pub pointsPerNibble: u32, pub pointsPerNibble: u32,
pub noofRoundsTailProtectedAfterNibble: u32, pub noofRoundsTailProtectedAfterNibble: u32,
} }
impl Default for GameSettings { impl Default for GameSettings {
fn default() -> GameSettings { fn default() -> GameSettings {
GameSettings { GameSettings {
maxNoofPlayers: 5, maxNoofPlayers: 5,
startSnakeLength: 1, startSnakeLength: 1,
timeInMsPerTick: 250, timeInMsPerTick: 250,
obstaclesEnabled: true, obstaclesEnabled: true,
foodEnabled: true, foodEnabled: true,
headToTailConsumes: true, headToTailConsumes: true,
tailConsumeGrows: false, tailConsumeGrows: false,
addFoodLikelihood: 15, addFoodLikelihood: 15,
removeFoodLikelihood: 5, removeFoodLikelihood: 5,
spontaneousGrowthEveryNWorldTick: 3, spontaneousGrowthEveryNWorldTick: 3,
trainingGame: false, trainingGame: false,
pointsPerLength: 1, pointsPerLength: 1,
pointsPerFood: 2, pointsPerFood: 2,
pointsPerCausedDeath: 5, pointsPerCausedDeath: 5,
pointsPerNibble: 10, pointsPerNibble: 10,
noofRoundsTailProtectedAfterNibble: 3, noofRoundsTailProtectedAfterNibble: 3,
} }
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct PlayerRegistered { pub struct PlayerRegistered {
pub gameId: String, pub gameId: String,
pub gameMode: String, pub gameMode: String,
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub name: String, pub name: String,
pub gameSettings: GameSettings pub gameSettings: GameSettings
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate { pub struct MapUpdate {
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub gameId: String, pub gameId: String,
pub gameTick: u32, pub gameTick: u32,
pub map: Map, pub map: Map,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName { pub struct InvalidPlayerName {
pub reasonCode: u32, pub reasonCode: u32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameEnded { pub struct GameEnded {
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub playerWinnerId: String, pub playerWinnerId: String,
pub gameId: String, pub gameId: String,
pub gameTick: u32, pub gameTick: u32,
pub map: Map, pub map: Map,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead { pub struct SnakeDead {
pub playerId: String, pub playerId: String,
pub x: u32, pub x: u32,
pub y: u32, pub y: u32,
pub gameId: String, pub gameId: String,
pub gameTick: u32, pub gameTick: u32,
pub deathReason: String, pub deathReason: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting { pub struct GameStarting {
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub gameId: String, pub gameId: String,
pub noofPlayers: u32, pub noofPlayers: u32,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse { pub struct HeartBeatResponse {
pub receivingPlayerId: String pub receivingPlayerId: String
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GameLink { pub struct GameLink {
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub gameId: String, pub gameId: String,
pub url: String, pub url: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct TournamentEnded { pub struct TournamentEnded {
pub receivingPlayerId: String, pub receivingPlayerId: String,
pub tournamentId: String, pub tournamentId: String,
pub tournamentName: String, pub tournamentName: String,
pub gameResult: Vec<GameResult>, pub gameResult: Vec<GameResult>,
pub gameId: String, pub gameId: String,
pub playerWinnerId: String, pub playerWinnerId: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameResult { pub struct GameResult {
pub points: i32, pub points: i32,
pub playerId: String, pub playerId: String,
pub name: String pub name: String
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Map { pub struct Map {
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
pub worldTick: u32, pub worldTick: u32,
pub snakeInfos: Vec<SnakeInfo>, pub snakeInfos: Vec<SnakeInfo>,
pub foodPositions: Vec<i32>, pub foodPositions: Vec<i32>,
pub obstaclePositions: Vec<i32> pub obstaclePositions: Vec<i32>
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SnakeInfo { pub struct SnakeInfo {
pub name: String, pub name: String,
pub points: i32, pub points: i32,
pub positions: Vec<i32>, pub positions: Vec<i32>,
pub tailProtectedForGameTicks: u32, pub tailProtectedForGameTicks: u32,
pub id: String 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)] #[allow(dead_code)]
pub fn translate_position(position: i32, map_width: i32) -> (i32,i32) { pub fn translate_position(position: i32, map_width: i32) -> (i32,i32) {
let pos = position as f64; let pos = position as f64;
let width = map_width as f64; let width = map_width as f64;
let y = (pos / width).floor(); let y = (pos / width).floor();
let x = (pos - y * width).abs(); let x = (pos - y * width).abs();
(x as i32, y as i32) (x as i32, y as i32)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn translate_positions(positions: &Vec<i32>, map_width: i32) -> Vec<(i32,i32)> { 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)] #[allow(dead_code)]
pub fn translate_coordinate(coordinates: (i32,i32), map_width: i32) -> i32 { pub fn translate_coordinate(coordinates: (i32,i32), map_width: i32) -> i32 {
let (x,y) = coordinates; let (x,y) = coordinates;
x + y * map_width x + y * map_width
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn get_manhattan_distance(start: (i32,i32), goal: (i32,i32)) -> i32 { pub fn get_manhattan_distance(start: (i32,i32), goal: (i32,i32)) -> i32 {
let (x1,y1) = start; let (x1,y1) = start;
let (x2,y2) = goal; let (x2,y2) = goal;
let x = ( x1 - x2 ).abs(); let x = ( x1 - x2 ).abs();
let y = ( y1 - y2 ).abs(); let y = ( y1 - y2 ).abs();
x+y x+y
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn get_euclidian_distance(start: (i32,i32), goal: (i32,i32)) -> f64 { pub fn get_euclidian_distance(start: (i32,i32), goal: (i32,i32)) -> f64 {
let (x1,y1) = start; let (x1,y1) = start;
let (x2,y2) = goal; let (x2,y2) = goal;
let x = ( x1 - x2 ).pow(2); let x = ( x1 - x2 ).pow(2);
let y = ( y1 - y2 ).pow(2); let y = ( y1 - y2 ).pow(2);
let d = ( x + y ) as f64; let d = ( x + y ) as f64;
d.sqrt().floor() d.sqrt().floor()
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn is_within_square(coord: (i32,i32), ne_coord: (i32,i32), sw_coord: (i32,i32)) -> bool { pub fn is_within_square(coord: (i32,i32), ne_coord: (i32,i32), sw_coord: (i32,i32)) -> bool {
let (x,y) = coord; let (x,y) = coord;
let (ne_x, ne_y) = ne_coord; let (ne_x, ne_y) = ne_coord;
let (sw_x, sw_y) = sw_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
} }