Compare commits

...

5 Commits

Author SHA1 Message Date
6b81b6bc40 Implement Map tick method 2017-10-31 19:57:45 +01:00
2137fbd565 Fix self-nibbling 2017-10-31 19:01:20 +01:00
366d39c0a7 Actualize readme.md 2017-10-20 21:44:20 +02:00
e274bd37e7 Initial Commit for snake ten_points_to_slytherin 2017-10-20 21:38:35 +02:00
061da11d43 Refactored the Rust Snakebot codebase. (#7)
* Normal logging now logs to stdout;
  * Renamed Inbound::GameLinkEvent to Inbound::Gamelink;
  * Renamed the struct GameResultSnake to GameResult;
  * Added Inbound::GameResult;
* Rewrote the message handling to become more succinct;
* Turned the default_gamesettings function into a Default impl;
* Made the snake smaller and prettier;
* Replaced some direction checking code with less code;
* Added logging messages to all callbacks;
* Added as_movement_delta to the Direction impl;
* Added nice error messages to the message parsing;
* Moved some code in maputil to increase the DRY-factor.
2017-04-21 15:49:42 +02:00
10 changed files with 737 additions and 726 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

@ -23,5 +23,5 @@ additive = false
[loggers.snake] [loggers.snake]
level = "debug" level = "debug"
appenders = [ "snake" ] appenders = [ "console" ]
additive = false additive = false

View File

@ -1,7 +1,5 @@
# SNAKE CLIENT # SNAKE CLIENT
[![Build Status](http://jenkins.snake.cygni.se/buildStatus/icon?job=snake client rust)](http://jenkins.snake.cygni.se/job/snake%20client%20rust/)
Do you want the most annoying compiler ever? Do you want the most annoying compiler ever?
Do you want to constantly think of what is owning what variable? Do you want to constantly think of what is owning what variable?
Do you want to stare angrily at the screen and wonder what the hell it means that some dumb value can't be moved? Do you want to stare angrily at the screen and wonder what the hell it means that some dumb value can't be moved?
@ -9,14 +7,14 @@ Then here is the ultimate snake client for you, written for the beautiful langua
## Requirements ## Requirements
* Rust (which should be installed via [rustup](https://github.com/rust-lang-nursery/rustup.rs)) * Rust (which should be installed via [rustup](https://rustup.rs/))
* Snake server (local or remote) * Snake server (local or remote)
## Setup ## Setup
A. Clone the repository: `git clone https://github.com/cygni/snakebot-client-rust.git`; A. Clone the repository: `git clone https://git.nubo.sh/hulthe/snakebot.git`;
B. Open the repo: `cd snakebot-client-rust`; B. Open the repo: `cd snakebot`;
C. Build the snake: `cargo build`; C. Build the snake: `cargo build`;

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

@ -1,12 +1,13 @@
#![allow(non_snake_case)]
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate quick_error; #[macro_use] extern crate quick_error;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[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;
extern crate serde_json;
extern crate target_info; extern crate target_info;
extern crate ws; extern crate ws;
@ -17,7 +18,7 @@ mod structs;
mod util; mod util;
use clap::{ Arg, App }; use clap::{ Arg, App };
use messages::{ Inbound }; use messages::{ Inbound, Outbound, handle_inbound_msg, render_outbound_message };
use snake::{ Snake }; use snake::{ Snake };
use std::path::Path; use std::path::Path;
use std::string::{ String }; use std::string::{ String };
@ -30,288 +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;
let inbound_msg = try!(messages::parse_inbound_msg(str_msg));
match inbound_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 direction = maputil::direction_as_string(&snake.get_next_move(&msg)); let m = render_outbound_message(Outbound::RegisterMove {
let response = try!(messages::create_register_move_msg(direction, msg)); direction: snake.get_next_move(&msg),
debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", response); gameTick: msg.gameTick,
try!(client.out.send(response)); receivingPlayerId: msg.receivingPlayerId,
}, gameId: msg.gameId });
Inbound::SnakeDead(msg) => { debug!(target: LOG_TARGET, "Responding with RegisterMove {:?}", m);
snake.on_snake_dead(&msg); try!(client.out.send(m));
}, },
Inbound::GameStarting(msg) => { Inbound::SnakeDead(msg) => {
snake.on_game_starting(&msg); snake.on_snake_dead(&msg);
}, },
Inbound::PlayerRegistered(msg) => { Inbound::GameStarting(msg) => {
info!(target: LOG_TARGET, "Successfully registered player"); snake.on_game_starting(&msg);
snake.on_player_registered(&msg); },
Inbound::PlayerRegistered(msg) => {
info!(target: LOG_TARGET, "Successfully registered player");
snake.on_player_registered(&msg);
if msg.gameMode == "TRAINING" { if msg.gameMode == "TRAINING" {
let response = try!(messages::create_start_game_msg()); let m = render_outbound_message(Outbound::StartGame);
debug!(target: LOG_TARGET, "Requesting a game start {:?}", response); debug!(target: LOG_TARGET, "Requesting a game start {:?}", m);
try!(client.out.send(response)); 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::GameLinkEvent(msg) => { Inbound::GameLink(msg) => {
info!(target: LOG_TARGET, "Watch game at {}", msg.url); info!(target: LOG_TARGET, "Watch game at {}", msg.url);
}, },
Inbound::UnrecognizedMessage => { Inbound::GameResult(msg) => {
error!(target: LOG_TARGET, "Received unrecognized message {:?}", str_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 { 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);
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)
}
let client_info = messages::create_client_info_msg(); fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
if let Ok(message) = client_info { if let ws::Message::Text(text) = msg {
info!(target: LOG_TARGET, "Sending client info to server: {:?}", message); let route_result = route_msg(self, &text);
try!(self.out.send(message)); match route_result {
} else { Err(e) => error!(target: LOG_TARGET, "Got error \'{:?}\' when routing message: {}", e, text),
error!(target: LOG_TARGET, "Unable to create client info message {:?}", client_info); Ok(_) => debug!(target: LOG_TARGET, "Succeeded in routing message {}", text)
try!(self.out.close(ws::CloseCode::Error)); }
} } else {
warn!(target: LOG_TARGET, "Unexpectedly received non-string message: {:?}", msg)
}
let parse_msg = messages::create_play_registration_msg(self.config.snake_name.clone()); Ok(())
if let Ok(response) = parse_msg { }
info!(target: LOG_TARGET, "Registering player with message: {:?}", response);
self.out.send(response)
} else {
error!(target: LOG_TARGET, "Unable to create play registration message {:?}", parse_msg);
self.out.close(ws::CloseCode::Error)
}
}
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
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(())
}
} }
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 id = id.clone(); if let Err(e) = send_result {
let parsed_msg = messages::create_heart_beat_msg(id); error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e);
if let Ok(heart_beat) = parsed_msg { }
let send_result = out.send(heart_beat); }
if let Err(e) = send_result {
error!(target: LOG_TARGET, "Unable to send heartbeat, got error {:?}", e);
}
} else {
error!(target: LOG_TARGET, "Unable to parse heart beat message {:?}", parsed_msg);
}
}
} }
pub fn recv_channels(id_receiver: mpsc::Receiver<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,212 +1,285 @@
use structs::{ Map, SnakeInfo }; use structs::{ Map, SnakeInfo };
use util; use util;
use serde::ser::{ Serialize, Serializer };
use util::{ translate_position, step_in_direction };
#[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(Debug)] #[derive(Clone, Copy, Debug)]
pub enum Direction { pub enum Direction {
Down, Down,
Up, Up,
Left, Left,
Right Right
} }
pub fn direction_as_string(direction: &Direction) -> String { impl Serialize for Direction {
let s = match direction { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
&Direction::Down => "DOWN", where S: Serializer
&Direction::Up => "UP", {
&Direction::Left => "LEFT", serializer.serialize_str(match *self {
&Direction::Right => "RIGHT" Direction::Down => "DOWN",
}; Direction::Up => "UP",
Direction::Left => "LEFT",
String::from(s) Direction::Right => "RIGHT",
})
}
} }
pub fn direction_as_movement_delta(direction: &Direction) -> (i32,i32) { impl Direction {
match direction { pub fn as_movement_delta(&self) -> (i32,i32) {
&Direction::Down => (0, 1), match *self {
&Direction::Up => (0, -1), Direction::Down => ( 0, 1),
&Direction::Left => (-1, 0), Direction::Up => ( 0, -1),
&Direction::Right => (1, 0) 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 { 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 _ => false
} }
} }
pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool { pub fn tile_is_nibbleable_snake(&self, nibbler: &SnakeInfo, coordinate: (i32,i32)) -> bool {
let (xd,yd) = direction_as_movement_delta(&direction); let tile = self.get_tile_at(coordinate);
let (x,y) = util::translate_position(snake.positions[0], self.width); match tile {
Tile::SnakeBody { coordinate: _, snake } => {
snake.id != nibbler.id &&
snake.tailProtectedForGameTicks == 0 &&
coordinate == translate_position(*snake.positions.last().unwrap(), self.width)
},
_ => false
}
}
self.is_tile_available_for_movement((x+xd,y+yd)) 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);
#[allow(dead_code)] let tile_coord = (x+xd,y+yd);
pub fn is_coordinate_out_of_bounds(&self, coordinate: (i32,i32)) -> bool { self.is_tile_available_for_movement(tile_coord) ||
let (x,y) = coordinate; self.tile_is_nibbleable_snake(snake, (tile_coord))
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
}
pub fn tick(&self, decisions: Vec<Direction>) -> Map {
if decisions.len() < self.snakeInfos.len() {
panic!("All snakes have to make a decision");
}
let worldTick = self.worldTick + 1;
let mut newSnakeInfos: Vec<SnakeInfo> = vec![];
let grow_snakes: bool = worldTick % 3 == 1;
if !grow_snakes {
for i in 0..newSnakeInfos.len() {
newSnakeInfos[i].positions.pop();
}
}
for i in 0..self.snakeInfos.len() {
let snake: &mut SnakeInfo = &mut newSnakeInfos[i];
if(self.can_snake_move_in_direction(snake, decisions[i])) {
let head_position = snake.positions[0].clone();
snake.positions.insert(0,
step_in_direction(
head_position,
decisions[i],
self.width
),
);
}
}
Map {
width: self.width.clone(),
height: self.height.clone(),
worldTick: worldTick,
snakeInfos: newSnakeInfos,
foodPositions: self.foodPositions.clone(),
obstaclePositions: self.obstaclePositions.clone(),
}
}
} }
#[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

@ -1,140 +1,87 @@
use structs; use structs;
use serde_json::{ from_str, to_string, Error };
use target_info::Target; use target_info::Target;
use rustc_version; use rustc_version::{version};
use maputil::{Direction};
// Inbound use structs::{GameSettings};
pub const GAME_ENDED: &'static str = use serde_json::{ from_str, from_value, Error, Map, Value };
"se.cygni.snake.api.event.GameEndedEvent"; use std::iter::FromIterator;
pub const TOURNAMENT_ENDED: &'static str =
"se.cygni.snake.api.event.TournamentEndedEvent";
pub const MAP_UPDATE: &'static str =
"se.cygni.snake.api.event.MapUpdateEvent";
pub const SNAKE_DEAD: &'static str =
"se.cygni.snake.api.event.SnakeDeadEvent";
pub const GAME_STARTING: &'static str =
"se.cygni.snake.api.event.GameStartingEvent";
pub const PLAYER_REGISTERED: &'static str =
"se.cygni.snake.api.response.PlayerRegistered";
pub const INVALID_PLAYER_NAME: &'static str =
"se.cygni.snake.api.exception.InvalidPlayerName";
pub const HEART_BEAT_RESPONSE: &'static str =
"se.cygni.snake.api.response.HeartBeatResponse";
pub const GAME_LINK_EVENT: &'static str =
"se.cygni.snake.api.event.GameLinkEvent";
// Outbound
const REGISTER_PLAYER_MESSAGE_TYPE: &'static str =
"se.cygni.snake.api.request.RegisterPlayer";
const START_GAME: &'static str =
"se.cygni.snake.api.request.StartGame";
const REGISTER_MOVE: &'static str =
"se.cygni.snake.api.request.RegisterMove";
const HEART_BEAT_REQUEST: &'static str =
"se.cygni.snake.api.request.HeartBeatRequest";
const CLIENT_INFO: &'static str =
"se.cygni.snake.api.request.ClientInfo";
#[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),
GameLinkEvent(structs::GameLink), GameLink(structs::GameLink),
UnrecognizedMessage GameResult(structs::GameResult),
UnrecognizedMessage
} }
pub fn parse_inbound_msg(msg: &String) -> Result<Inbound, Error> { /// We turn the string into `Inbound` by converting the string into a
let msg: Inbound = /// JSON object, extracting the type-field from the object, and using the
if msg.contains(GAME_ENDED) { /// last part of the type-field to get the correct constructor in `Inbound`.
Inbound::GameEnded(try!(from_str(msg))) /// Then we let Serde do its magic and deserialize a constructed JSON object
} else if msg.contains(TOURNAMENT_ENDED) { /// with the constructor name as type. If the type has a Event suffix it is
Inbound::TournamentEnded(try!(from_str(msg))) /// removed since almost all `Inbound` messages are events.
} else if msg.contains(MAP_UPDATE) { ///
Inbound::MapUpdate(try!(from_str(msg))) /// Example:
} else if msg.contains(SNAKE_DEAD) { /// { type: "foo.bar.baz.GameResult", <Some JSON data> }
Inbound::SnakeDead(try!(from_str(msg))) /// ---------- This is the part we extract and hand to serde.
} else if msg.contains(GAME_STARTING) { /// Like this: {GameResult: <Some JSON data>}
Inbound::GameStarting(try!(from_str(msg))) ///
} else if msg.contains(PLAYER_REGISTERED) { pub fn handle_inbound_msg(s: &str) -> Result<Inbound, Error> {
Inbound::PlayerRegistered(try!(from_str(msg))) let mut json_value = from_str::<Value>(s)
} else if msg.contains(INVALID_PLAYER_NAME) { .expect(&format!("Couldn't parse string into JSON: {:?}", s));
Inbound::InvalidPlayerName(try!(from_str(msg))) let map = json_value.as_object_mut()
} else if msg.contains(HEART_BEAT_RESPONSE) { .expect(&format!("Couldn't parse string into JSON object: {:?}", s));
Inbound::HeartBeatResponse(try!(from_str(msg))) let type_value = map.remove("type").expect(&format!("Couldn't find key `type` in: {:?}", &map));
} else if msg.contains(GAME_LINK_EVENT) { let type_str = type_value.as_str().expect(&format!("Couldn't turn JSON Value into string: {:?}", &map));
Inbound::GameLinkEvent(try!(from_str(msg))) let typ = type_str.rsplit('.').next()
} else { .expect(&format!("The type parser needs a dot-separated string, this string lacks dots: {:?}", type_str))
Inbound::UnrecognizedMessage .replace("Event", "");
}; from_value(Value::Object(Map::from_iter(vec![(typ, Value::Object(map.clone()))])))
Ok(msg)
} }
pub enum Outbound {
pub fn create_play_registration_msg(name: String) -> Result<String, Error> { RegisterPlayer{playerName: String, gameSettings: GameSettings},
to_string(&structs::PlayRegistration { StartGame,
type_: String::from(REGISTER_PLAYER_MESSAGE_TYPE), RegisterMove{direction: Direction, gameTick: u32, receivingPlayerId: String, gameId: String},
playerName: name, HeartBeat{receivingPlayerId: String},
gameSettings: default_gamesettings() ClientInfo,
})
} }
pub fn create_start_game_msg() -> Result<String, Error> { pub fn render_outbound_message(msg: Outbound) -> String {
to_string(&structs::StartGame { (match msg {
type_: String::from(START_GAME) Outbound::RegisterPlayer {playerName, gameSettings} => json!({
}) "type": "se.cygni.snake.api.request.RegisterPlayer",
} "playerName": playerName,
"gameSettings": gameSettings
pub fn create_register_move_msg(direction: String, request: structs::MapUpdate) -> Result<String, Error> { }),
to_string(&structs::RegisterMove { Outbound::StartGame => json!({
type_: String::from(REGISTER_MOVE), "type": "se.cygni.snake.api.request.StartGame",
direction: direction, }),
gameTick: request.gameTick, Outbound::RegisterMove {direction, gameTick, receivingPlayerId, gameId} => json!({
receivingPlayerId: request.receivingPlayerId, "type": "se.cygni.snake.api.request.RegisterMove",
gameId: request.gameId "direction": direction,
}) "gameTick": gameTick,
} "receivingPlayerId": receivingPlayerId,
"gameId": gameId,
pub fn create_heart_beat_msg(id: String) -> Result<String, Error> { }),
to_string(&structs::HeartBeatRequest { Outbound::HeartBeat {receivingPlayerId} => json!({
type_: String::from( HEART_BEAT_REQUEST ), "type": "se.cygni.snake.api.request.HeartBeatRequest",
receivingPlayerId: id "receivingPlayerId": receivingPlayerId,
}) }),
} Outbound::ClientInfo => json!({
"type": "se.cygni.snake.api.request.ClientInfo",
pub fn create_client_info_msg() -> Result<String, Error> { "language": "Rust",
to_string(&structs::ClientInfo { "languageVersion": version().unwrap().to_string(),
type_: String::from(CLIENT_INFO), "operatingSystem": Target::os(),
language: String::from("rust"), "operatingSystemVersion": "???",
languageVersion: format!("{}", rustc_version::version().unwrap()), "clientVersion": option_env!("CARGO_PKG_VERSION").unwrap_or("0.1337"),
operatingSystem: String::from(Target::os()), }),
operatingSystemVersion: String::from(""), }).to_string()
clientVersion: String::from(option_env!("CARGO_PKG_VERSION").unwrap_or(""))
})
}
pub fn default_gamesettings() -> structs::GameSettings {
structs::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,
}
} }

View File

@ -1,60 +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 ref map = msg.map; let map = &msg.map;
let player_id = &msg.receivingPlayerId; let directions = vec![Direction::Down, Direction::Left, Direction::Right, Direction::Up];
let snake = map.get_snake_by_id(player_id).unwrap();
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;
let direction = if map.can_snake_move_in_direction(snake, Direction::Down) { let snake_positions = translate_positions(&snake.positions, map.width);
Direction::Down let foodPositions = translate_positions(&map.foodPositions, map.width);
} else if map.can_snake_move_in_direction(snake, Direction::Left) {
Direction::Left
} else if map.can_snake_move_in_direction(snake, Direction::Right) {
Direction::Right
} else if map.can_snake_move_in_direction(snake, Direction::Up) {
Direction::Up
} else {
// this is bad
Direction::Down
};
debug!(target: LOG_TARGET, "Snake will move in direction {:?}", direction); let facing = match snake_positions.len() {
direction 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_game_ended(&self, msg: &GameEnded) { for s in snakes {
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); if s.positions.len() == 0 || s.id == snake.id {
} continue;
}
pub fn on_tournament_ended(&self, msg: &TournamentEnded) { let tail_position = translate_position(*s.positions.last().unwrap(), map.width);
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId); let tail_direction = Direction::from_movement_delta(vector_diff(tail_position, snake_positions[0]));
}
pub fn on_snake_dead(&self, msg: &SnakeDead) { if map.can_snake_move_in_direction(snake, tail_direction) {
debug!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason); debug!(target: LOG_TARGET, "Snake will hunt in direction {:?}", tail_direction);
} return tail_direction;
}
}
pub fn on_game_starting(&self, _: &GameStarting) { 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_player_registered(&self, _: &PlayerRegistered) { 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_invalid_playername(&self, _: &InvalidPlayerName) { 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,178 +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,
} }
#[derive(Serialize, Deserialize, Debug)] impl Default for GameSettings {
pub struct PlayRegistration { fn default() -> GameSettings {
#[serde(rename="type")] GameSettings {
pub type_: String, maxNoofPlayers: 5,
pub playerName: String, startSnakeLength: 1,
pub gameSettings: GameSettings, timeInMsPerTick: 250,
} obstaclesEnabled: true,
foodEnabled: true,
#[derive(Serialize, Deserialize, Debug)] headToTailConsumes: true,
pub struct ClientInfo { tailConsumeGrows: false,
#[serde(rename="type")] addFoodLikelihood: 15,
pub type_: String, removeFoodLikelihood: 5,
pub language: String, spontaneousGrowthEveryNWorldTick: 3,
pub languageVersion: String, trainingGame: false,
pub operatingSystem: String, pointsPerLength: 1,
pub operatingSystemVersion: String, pointsPerFood: 2,
pub clientVersion: String pointsPerCausedDeath: 5,
} pointsPerNibble: 10,
noofRoundsTailProtectedAfterNibble: 3,
#[derive(Serialize, Deserialize, Debug)] }
pub struct RegisterMove { }
#[serde(rename="type")]
pub type_: String,
pub direction: String,
pub gameTick: u32,
pub receivingPlayerId: String,
pub gameId: String
}
#[derive(Serialize, Deserialize, Debug)]
pub struct StartGame {
#[serde(rename="type")]
pub type_: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatRequest {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct PlayerRegistered { pub struct PlayerRegistered {
#[serde(rename="type")] pub gameId: String,
pub type_: String, pub gameMode: String,
pub gameId: String, pub receivingPlayerId: String,
pub gameMode: String, pub name: String,
pub receivingPlayerId: String, pub gameSettings: GameSettings
pub name: String,
pub gameSettings: GameSettings
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate { pub struct MapUpdate {
#[serde(rename="type")] pub receivingPlayerId: String,
pub type_: String, pub gameId: String,
pub receivingPlayerId: String, pub gameTick: u32,
pub gameId: String, pub map: Map,
pub gameTick: u32,
pub map: Map,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName { pub struct InvalidPlayerName {
#[serde(rename="type")] pub reasonCode: u32,
pub type_: String,
pub reasonCode: u32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameEnded { pub struct GameEnded {
#[serde(rename="type")] pub receivingPlayerId: String,
pub type_: String, pub playerWinnerId: String,
pub receivingPlayerId: String, pub gameId: String,
pub playerWinnerId: String, pub gameTick: u32,
pub gameId: String, pub map: Map,
pub gameTick: u32,
pub map: Map,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead { pub struct SnakeDead {
#[serde(rename="type")] pub playerId: String,
pub type_: String, pub x: u32,
pub playerId: String, pub y: u32,
pub x: u32, pub gameId: String,
pub y: u32, pub gameTick: u32,
pub gameId: String, pub deathReason: String,
pub gameTick: u32,
pub deathReason: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting { pub struct GameStarting {
#[serde(rename="type")] pub receivingPlayerId: String,
pub type_: String, pub gameId: String,
pub receivingPlayerId: String, pub noofPlayers: u32,
pub gameId: String, pub width: u32,
pub noofPlayers: u32, pub height: u32,
pub width: u32,
pub height: u32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse { pub struct HeartBeatResponse {
#[serde(rename="type")] pub receivingPlayerId: String
pub type_: String,
pub receivingPlayerId: String
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GameLink { pub struct GameLink {
#[serde(rename="type")] pub receivingPlayerId: String,
pub type_: String, pub gameId: String,
pub receivingPlayerId: String, pub url: String,
pub gameId: String,
pub url: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct TournamentEnded { pub struct TournamentEnded {
#[serde(rename="type")] pub receivingPlayerId: String,
pub type_: String, pub tournamentId: String,
pub receivingPlayerId: String, pub tournamentName: String,
pub tournamentId: String, pub gameResult: Vec<GameResult>,
pub tournamentName: String, pub gameId: String,
pub gameResult: Vec<GameResultSnake>, pub playerWinnerId: String,
pub gameId: String,
pub playerWinnerId: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GameResultSnake { 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,74 @@
use maputil::{ Direction };
#[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)]
pub fn step_in_direction(position: i32, direction: Direction, map_width: i32) -> i32 {
let xy = translate_position(position, map_width);
let dxy = match direction {
Direction::Down => (xy.0, xy.1 + 1),
Direction::Up => (xy.0, xy.1 - 1),
Direction::Left => (xy.0 - 1, xy.1),
Direction::Right => (xy.0 + 1, xy.1),
};
translate_coordinate(dxy, map_width)
} }
#[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
} }