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]
name = "snakebot_rust"
version = "1.1.0"
authors = ["Martin Barksten <martin.barksten@cygni.com>"]
authors = [
"Martin Barksten <martin.barksten@cygni.com>",
"Joakim Hulthe <joakim@hulthe.net>",
]
license-file = "LICENSE"
[dependencies]

View File

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

View File

@ -1,7 +1,5 @@
# 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 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?
@ -9,14 +7,14 @@ Then here is the ultimate snake client for you, written for the beautiful langua
## 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)
## 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`;

View File

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

View File

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

View File

@ -1,212 +1,285 @@
use structs::{ Map, SnakeInfo };
use util;
use serde::ser::{ Serialize, Serializer };
use util::{ translate_position, step_in_direction };
#[derive(PartialEq, Debug)]
pub enum Tile<'a> {
Wall,
Food { coordinate: (i32,i32) },
Obstacle { coordinate: (i32,i32) },
Empty { coordinate: (i32,i32) },
SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo },
SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo }
Wall,
Food { coordinate: (i32,i32) },
Obstacle { coordinate: (i32,i32) },
Empty { coordinate: (i32,i32) },
SnakeHead { coordinate: (i32,i32), snake: &'a SnakeInfo },
SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo }
}
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub enum Direction {
Down,
Up,
Left,
Right
Down,
Up,
Left,
Right
}
pub fn direction_as_string(direction: &Direction) -> String {
let s = match direction {
&Direction::Down => "DOWN",
&Direction::Up => "UP",
&Direction::Left => "LEFT",
&Direction::Right => "RIGHT"
};
String::from(s)
impl Serialize for Direction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
Direction::Down => "DOWN",
Direction::Up => "UP",
Direction::Left => "LEFT",
Direction::Right => "RIGHT",
})
}
}
pub fn direction_as_movement_delta(direction: &Direction) -> (i32,i32) {
match direction {
&Direction::Down => (0, 1),
&Direction::Up => (0, -1),
&Direction::Left => (-1, 0),
&Direction::Right => (1, 0)
}
impl Direction {
pub fn as_movement_delta(&self) -> (i32,i32) {
match *self {
Direction::Down => ( 0, 1),
Direction::Up => ( 0, -1),
Direction::Left => (-1, 0),
Direction::Right => ( 1, 0)
}
}
pub fn from_movement_delta(delta: (i32,i32)) -> Direction {
match delta {
(x, y) if y <= -x.abs() => Direction::Up,
(x, y) if y >= x.abs() => Direction::Down,
(x, y) if x <= -y.abs() => Direction::Left,
(x, y) if x >= y.abs() => Direction::Right,
( _, _) => {
panic!(format!("({}, {}) is not a valid movement delta.", delta.0, delta.1))
}
}
}
}
impl Map {
pub fn inside_map(&self, coordinate: (i32, i32)) -> bool {
let (x,y) = coordinate;
let inside_x = x >= 0 && x < self.width;
let inside_y = y >= 0 && y < self.height;
inside_x && inside_y
}
pub fn inside_map(&self, coordinate: (i32, i32)) -> bool {
let (x,y) = coordinate;
let inside_x = x >= 0 && x < self.width;
let inside_y = y >= 0 && y < self.height;
inside_x && inside_y
}
pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> {
self.snakeInfos.iter().find(|s| &s.id == id)
}
pub fn get_snake_by_id<'a>(&'a self, id: &String) -> Option<&'a SnakeInfo> {
self.snakeInfos.iter().find(|s| &s.id == id)
}
pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile {
let position = util::translate_coordinate(coordinate, self.width);
let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position));
pub fn get_tile_at(&self, coordinate: (i32,i32)) -> Tile {
let position = util::translate_coordinate(coordinate, self.width);
let snake_at_tile = self.snakeInfos.iter().find(|s| s.positions.contains(&position));
if self.obstaclePositions.contains(&position) {
Tile::Obstacle { coordinate: coordinate }
} else if self.foodPositions.contains(&position) {
Tile::Food { coordinate: coordinate }
} else if snake_at_tile.is_some() {
let s = snake_at_tile.unwrap();
if s.positions[0] == position {
Tile::SnakeHead { coordinate: coordinate, snake: s }
} else {
Tile::SnakeBody { coordinate: coordinate, snake: s }
}
} else if !self.inside_map(coordinate) {
Tile::Wall
} else {
Tile::Empty { coordinate: coordinate }
}
}
if self.obstaclePositions.contains(&position) {
Tile::Obstacle { coordinate: coordinate }
} else if self.foodPositions.contains(&position) {
Tile::Food { coordinate: coordinate }
} else if snake_at_tile.is_some() {
let s = snake_at_tile.unwrap();
if s.positions[0] == position {
Tile::SnakeHead { coordinate: coordinate, snake: s }
} else {
Tile::SnakeBody { coordinate: coordinate, snake: s }
}
} else if !self.inside_map(coordinate) {
Tile::Wall
} else {
Tile::Empty { coordinate: coordinate }
}
}
pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool {
let tile = self.get_tile_at(coordinate);
match tile {
Tile::Empty { coordinate: _ } => true,
Tile::Food { coordinate: _ } => true,
_ => false
}
}
pub fn is_tile_available_for_movement(&self, coordinate: (i32,i32)) -> bool {
let tile = self.get_tile_at(coordinate);
match tile {
Tile::Empty { coordinate: _ } => true,
Tile::Food { coordinate: _ } => true,
_ => false
}
}
pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool {
let (xd,yd) = direction_as_movement_delta(&direction);
let (x,y) = util::translate_position(snake.positions[0], self.width);
pub fn tile_is_nibbleable_snake(&self, nibbler: &SnakeInfo, coordinate: (i32,i32)) -> bool {
let tile = self.get_tile_at(coordinate);
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)]
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
}
let tile_coord = (x+xd,y+yd);
self.is_tile_available_for_movement(tile_coord) ||
self.tile_is_nibbleable_snake(snake, (tile_coord))
}
#[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)]
mod test {
use util::{ translate_coordinate };
use maputil::{ Direction, Tile };
use structs::{ Map, SnakeInfo };
use util::{ translate_coordinate };
use maputil::{ Direction, Tile };
use structs::{ Map, SnakeInfo };
const MAP_WIDTH: i32 = 3;
const MAP_WIDTH: i32 = 3;
fn get_snake_one() -> SnakeInfo {
SnakeInfo {
name: String::from("1"),
points: 0,
tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,1), MAP_WIDTH),
translate_coordinate((0,1), MAP_WIDTH)],
id: String::from("1")
}
}
fn get_snake_one() -> SnakeInfo {
SnakeInfo {
name: String::from("1"),
points: 0,
tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,1), MAP_WIDTH),
translate_coordinate((0,1), MAP_WIDTH)],
id: String::from("1")
}
}
fn get_snake_two() -> SnakeInfo {
SnakeInfo {
name: String::from("2"),
points: 0,
tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,2), MAP_WIDTH)],
id: String::from("2")
}
}
fn get_snake_two() -> SnakeInfo {
SnakeInfo {
name: String::from("2"),
points: 0,
tailProtectedForGameTicks: 0,
positions: vec![translate_coordinate((1,2), MAP_WIDTH)],
id: String::from("2")
}
}
// The map used for testing, 1 and 2 represents the snakes
//yx012
//0 F
//1 11#
//2 2
fn get_test_map() -> Map {
Map {
width: MAP_WIDTH,
height: MAP_WIDTH,
worldTick: 0,
snakeInfos: vec![get_snake_one(), get_snake_two()],
foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)],
obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)]
}
}
// The map used for testing, 1 and 2 represents the snakes
//yx012
//0 F
//1 11#
//2 2
fn get_test_map() -> Map {
Map {
width: MAP_WIDTH,
height: MAP_WIDTH,
worldTick: 0,
snakeInfos: vec![get_snake_one(), get_snake_two()],
foodPositions: vec![translate_coordinate((1,0), MAP_WIDTH)],
obstaclePositions: vec![translate_coordinate((2,1), MAP_WIDTH)]
}
}
#[test]
fn snake_can_be_found_by_id() {
let map = get_test_map();
let id = &get_snake_one().id;
let s = map.get_snake_by_id(id);
let found_id = &s.unwrap().id;
assert_eq!(id, found_id);
}
#[test]
fn snake_can_be_found_by_id() {
let map = get_test_map();
let id = &get_snake_one().id;
let s = map.get_snake_by_id(id);
let found_id = &s.unwrap().id;
assert_eq!(id, found_id);
}
#[test]
fn tile_is_correctly_found() {
let map = get_test_map();
let snake_one = get_snake_one();
let snake_two = get_snake_two();
let tiles =
vec![
vec![Tile::Empty{ coordinate: (0,0) },
Tile::Food{ coordinate: (1,0) },
Tile::Empty{ coordinate: (2,0) }],
vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one },
Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one },
Tile::Obstacle{ coordinate: (2,1)}],
vec![Tile::Empty{ coordinate: (0,2) },
Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two },
Tile::Empty{ coordinate:(2,2) }]];
for y in 0..map.width {
for x in 0..map.height {
assert_eq!(tiles[y as usize][x as usize],
map.get_tile_at((x,y)));
}
}
}
#[test]
fn tile_is_correctly_found() {
let map = get_test_map();
let snake_one = get_snake_one();
let snake_two = get_snake_two();
let tiles =
vec![
vec![Tile::Empty{ coordinate: (0,0) },
Tile::Food{ coordinate: (1,0) },
Tile::Empty{ coordinate: (2,0) }],
vec![Tile::SnakeBody{ coordinate: (0,1), snake: &snake_one },
Tile::SnakeHead{ coordinate: (1,1), snake: &snake_one },
Tile::Obstacle{ coordinate: (2,1)}],
vec![Tile::Empty{ coordinate: (0,2) },
Tile::SnakeHead{ coordinate: (1,2), snake: &snake_two },
Tile::Empty{ coordinate:(2,2) }]];
for y in 0..map.width {
for x in 0..map.height {
assert_eq!(tiles[y as usize][x as usize],
map.get_tile_at((x,y)));
}
}
}
#[test]
fn tile_is_correctly_marked_as_movable() {
let map = get_test_map();
let tiles = vec![vec![true, true, true],
vec![false, false, false],
vec![true, false, true]];
#[test]
fn tile_is_correctly_marked_as_movable() {
let map = get_test_map();
let tiles = vec![vec![true, true, true],
vec![false, false, false],
vec![true, false, true]];
for y in 0..map.height {
for x in 0..map.width {
assert_eq!(tiles[y as usize][x as usize],
map.is_tile_available_for_movement((x,y)));
}
}
}
for y in 0..map.height {
for x in 0..map.width {
assert_eq!(tiles[y as usize][x as usize],
map.is_tile_available_for_movement((x,y)));
}
}
}
#[test]
fn can_snake_move_identifies_correctly() {
let map = get_test_map();
let id = &get_snake_one().id;
let snake = map.get_snake_by_id(id).unwrap();
#[test]
fn can_snake_move_identifies_correctly() {
let map = get_test_map();
let id = &get_snake_one().id;
let snake = map.get_snake_by_id(id).unwrap();
assert_eq!(true, map.can_snake_move_in_direction(&snake, Direction::Up));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Left));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Right));
}
assert_eq!(true, map.can_snake_move_in_direction(&snake, Direction::Up));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Left));
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Right));
}
#[test]
fn can_not_move_to_walls() {
let map = get_test_map();
let id = &get_snake_two().id;
let snake = map.get_snake_by_id(id).unwrap();
#[test]
fn can_not_move_to_walls() {
let map = get_test_map();
let id = &get_snake_two().id;
let snake = map.get_snake_by_id(id).unwrap();
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down));
}
assert_eq!(false, map.can_snake_move_in_direction(&snake, Direction::Down));
}
}

View File

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

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 util::{ translate_positions };
use util::{ translate_positions, translate_position, vector_diff };
const LOG_TARGET: &'static str = "snake";
pub struct Snake;
impl Snake {
pub fn get_next_move(&self, msg: &MapUpdate) -> Direction {
debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick);
pub fn get_next_move(&self, msg: &MapUpdate) -> Direction {
debug!(target: LOG_TARGET, "Game map updated, tick: {}", msg.gameTick);
let ref map = msg.map;
let player_id = &msg.receivingPlayerId;
let snake = map.get_snake_by_id(player_id).unwrap();
let map = &msg.map;
let directions = vec![Direction::Down, Direction::Left, Direction::Right, Direction::Up];
debug!(target: LOG_TARGET, "Food can be found at {:?}", translate_positions(&map.foodPositions, map.width));
debug!(target: LOG_TARGET, "My snake positions are {:?}", translate_positions(&snake.positions, map.width));
let snake = map.get_snake_by_id(&msg.receivingPlayerId).unwrap();
let snakes = &map.snakeInfos;
let direction = if map.can_snake_move_in_direction(snake, Direction::Down) {
Direction::Down
} 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
};
let snake_positions = translate_positions(&snake.positions, map.width);
let foodPositions = translate_positions(&map.foodPositions, map.width);
debug!(target: LOG_TARGET, "Snake will move in direction {:?}", direction);
direction
}
let facing = match snake_positions.len() {
0...1 => Direction::Right,
_ => Direction::from_movement_delta(vector_diff(snake_positions[0], snake_positions[1]))
};
debug!(target: LOG_TARGET, "Food can be found at {:?}", foodPositions);
debug!(target: LOG_TARGET, "My snake positions are {:?}", snake_positions);
pub fn on_game_ended(&self, msg: &GameEnded) {
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId);
}
for s in snakes {
if s.positions.len() == 0 || s.id == snake.id {
continue;
}
pub fn on_tournament_ended(&self, msg: &TournamentEnded) {
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId);
}
let tail_position = translate_position(*s.positions.last().unwrap(), map.width);
let tail_direction = Direction::from_movement_delta(vector_diff(tail_position, snake_positions[0]));
pub fn on_snake_dead(&self, msg: &SnakeDead) {
debug!(target: LOG_TARGET, "The snake died, reason was: {:?}", msg.deathReason);
}
if map.can_snake_move_in_direction(snake, tail_direction) {
debug!(target: LOG_TARGET, "Snake will hunt in direction {:?}", tail_direction);
return tail_direction;
}
}
pub fn on_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)]
pub struct GameSettings {
pub maxNoofPlayers: u32,
pub startSnakeLength: u32,
pub timeInMsPerTick: u32,
pub obstaclesEnabled: bool,
pub foodEnabled: bool,
pub headToTailConsumes: bool,
pub tailConsumeGrows: bool,
pub addFoodLikelihood: u32,
pub removeFoodLikelihood: u32,
pub spontaneousGrowthEveryNWorldTick: u32,
pub trainingGame: bool,
pub pointsPerLength: u32,
pub pointsPerFood: u32,
pub pointsPerCausedDeath: u32,
pub pointsPerNibble: u32,
pub noofRoundsTailProtectedAfterNibble: u32,
pub maxNoofPlayers: u32,
pub startSnakeLength: u32,
pub timeInMsPerTick: u32,
pub obstaclesEnabled: bool,
pub foodEnabled: bool,
pub headToTailConsumes: bool,
pub tailConsumeGrows: bool,
pub addFoodLikelihood: u32,
pub removeFoodLikelihood: u32,
pub spontaneousGrowthEveryNWorldTick: u32,
pub trainingGame: bool,
pub pointsPerLength: u32,
pub pointsPerFood: u32,
pub pointsPerCausedDeath: u32,
pub pointsPerNibble: u32,
pub noofRoundsTailProtectedAfterNibble: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PlayRegistration {
#[serde(rename="type")]
pub type_: String,
pub playerName: String,
pub gameSettings: GameSettings,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ClientInfo {
#[serde(rename="type")]
pub type_: String,
pub language: String,
pub languageVersion: String,
pub operatingSystem: String,
pub operatingSystemVersion: String,
pub clientVersion: String
}
#[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
impl Default for GameSettings {
fn default() -> GameSettings {
GameSettings {
maxNoofPlayers: 5,
startSnakeLength: 1,
timeInMsPerTick: 250,
obstaclesEnabled: true,
foodEnabled: true,
headToTailConsumes: true,
tailConsumeGrows: false,
addFoodLikelihood: 15,
removeFoodLikelihood: 5,
spontaneousGrowthEveryNWorldTick: 3,
trainingGame: false,
pointsPerLength: 1,
pointsPerFood: 2,
pointsPerCausedDeath: 5,
pointsPerNibble: 10,
noofRoundsTailProtectedAfterNibble: 3,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PlayerRegistered {
#[serde(rename="type")]
pub type_: String,
pub gameId: String,
pub gameMode: String,
pub receivingPlayerId: String,
pub name: String,
pub gameSettings: GameSettings
pub gameId: String,
pub gameMode: String,
pub receivingPlayerId: String,
pub name: String,
pub gameSettings: GameSettings
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub gameId: String,
pub gameTick: u32,
pub map: Map,
pub receivingPlayerId: String,
pub gameId: String,
pub gameTick: u32,
pub map: Map,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName {
#[serde(rename="type")]
pub type_: String,
pub reasonCode: u32,
pub reasonCode: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GameEnded {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub playerWinnerId: String,
pub gameId: String,
pub gameTick: u32,
pub map: Map,
pub receivingPlayerId: String,
pub playerWinnerId: String,
pub gameId: String,
pub gameTick: u32,
pub map: Map,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead {
#[serde(rename="type")]
pub type_: String,
pub playerId: String,
pub x: u32,
pub y: u32,
pub gameId: String,
pub gameTick: u32,
pub deathReason: String,
pub playerId: String,
pub x: u32,
pub y: u32,
pub gameId: String,
pub gameTick: u32,
pub deathReason: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub gameId: String,
pub noofPlayers: u32,
pub width: u32,
pub height: u32,
pub receivingPlayerId: String,
pub gameId: String,
pub noofPlayers: u32,
pub width: u32,
pub height: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String
pub receivingPlayerId: String
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GameLink {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub gameId: String,
pub url: String,
pub receivingPlayerId: String,
pub gameId: String,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TournamentEnded {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub tournamentId: String,
pub tournamentName: String,
pub gameResult: Vec<GameResultSnake>,
pub gameId: String,
pub playerWinnerId: String,
pub receivingPlayerId: String,
pub tournamentId: String,
pub tournamentName: String,
pub gameResult: Vec<GameResult>,
pub gameId: String,
pub playerWinnerId: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GameResultSnake {
pub points: i32,
pub playerId: String,
pub name: String
pub struct GameResult {
pub points: i32,
pub playerId: String,
pub name: String
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Map {
pub width: i32,
pub height: i32,
pub worldTick: u32,
pub snakeInfos: Vec<SnakeInfo>,
pub foodPositions: Vec<i32>,
pub obstaclePositions: Vec<i32>
pub width: i32,
pub height: i32,
pub worldTick: u32,
pub snakeInfos: Vec<SnakeInfo>,
pub foodPositions: Vec<i32>,
pub obstaclePositions: Vec<i32>
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SnakeInfo {
pub name: String,
pub points: i32,
pub positions: Vec<i32>,
pub tailProtectedForGameTicks: u32,
pub id: String
pub name: String,
pub points: i32,
pub positions: Vec<i32>,
pub tailProtectedForGameTicks: u32,
pub id: String
}

View File

@ -1,53 +1,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)]
pub fn translate_position(position: i32, map_width: i32) -> (i32,i32) {
let pos = position as f64;
let width = map_width as f64;
let pos = position as f64;
let width = map_width as f64;
let y = (pos / width).floor();
let x = (pos - y * width).abs();
let y = (pos / width).floor();
let x = (pos - y * width).abs();
(x as i32, y as i32)
(x as i32, y as i32)
}
#[allow(dead_code)]
pub fn 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)]
pub fn translate_positions(positions: &Vec<i32>, map_width: i32) -> Vec<(i32,i32)> {
positions.into_iter().map(|pos| translate_position(*pos, map_width)).collect()
positions.into_iter().map(|pos| translate_position(*pos, map_width)).collect()
}
#[allow(dead_code)]
pub fn translate_coordinate(coordinates: (i32,i32), map_width: i32) -> i32 {
let (x,y) = coordinates;
x + y * map_width
let (x,y) = coordinates;
x + y * map_width
}
#[allow(dead_code)]
pub fn get_manhattan_distance(start: (i32,i32), goal: (i32,i32)) -> i32 {
let (x1,y1) = start;
let (x2,y2) = goal;
let (x1,y1) = start;
let (x2,y2) = goal;
let x = ( x1 - x2 ).abs();
let y = ( y1 - y2 ).abs();
let x = ( x1 - x2 ).abs();
let y = ( y1 - y2 ).abs();
x+y
x+y
}
#[allow(dead_code)]
pub fn get_euclidian_distance(start: (i32,i32), goal: (i32,i32)) -> f64 {
let (x1,y1) = start;
let (x2,y2) = goal;
let (x1,y1) = start;
let (x2,y2) = goal;
let x = ( x1 - x2 ).pow(2);
let y = ( y1 - y2 ).pow(2);
let d = ( x + y ) as f64;
let x = ( x1 - x2 ).pow(2);
let y = ( y1 - y2 ).pow(2);
let d = ( x + y ) as f64;
d.sqrt().floor()
d.sqrt().floor()
}
#[allow(dead_code)]
pub fn is_within_square(coord: (i32,i32), ne_coord: (i32,i32), sw_coord: (i32,i32)) -> bool {
let (x,y) = coord;
let (ne_x, ne_y) = ne_coord;
let (sw_x, sw_y) = sw_coord;
let (x,y) = coord;
let (ne_x, ne_y) = ne_coord;
let (sw_x, sw_y) = sw_coord;
x < ne_x || x > sw_x || y < sw_y || y > ne_y
x < ne_x || x > sw_x || y < sw_y || y > ne_y
}