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,9 +31,10 @@ 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)]
@ -74,9 +76,8 @@ struct Client {
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));
match inbound_msg {
match try!(handle_inbound_msg(str_msg)) {
Inbound::GameEnded(msg) => {
snake.on_game_ended(&msg);
if client.config.venue == "training" {
@ -88,10 +89,13 @@ fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> {
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));
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);
@ -104,9 +108,9 @@ fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> {
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));
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");
@ -119,9 +123,12 @@ fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> {
Inbound::HeartBeatResponse(_) => {
// do nothing
},
Inbound::GameLinkEvent(msg) => {
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);
}
@ -130,28 +137,17 @@ fn route_msg(client: &mut Client, str_msg: &String) -> Result<(), ClientError> {
Ok(())
}
impl ws::Handler for Client {
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
debug!(target: LOG_TARGET, "Connection to Websocket opened");
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));
}
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)
}
let m = render_outbound_message(Outbound::ClientInfo);
info!(target: LOG_TARGET, "Sending client info to server: {:?}", m);
try!(self.out.send(m));
let msg = render_outbound_message(Outbound::RegisterPlayer {
playerName: self.config.snake_name.clone(),
gameSettings: Default::default() });
info!(target: LOG_TARGET, "Registering player with message: {:?}", msg);
self.out.send(msg)
}
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
@ -251,17 +247,10 @@ fn do_heart_beat(id: String, out: Arc<ws::Sender>, done_receiver: mpsc::Receiver
}
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);
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);
}
} else {
error!(target: LOG_TARGET, "Unable to parse heart beat message {:?}", parsed_msg);
}
}
}

View File

@ -1,5 +1,7 @@
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> {
@ -11,7 +13,7 @@ pub enum Tile<'a> {
SnakeBody { coordinate: (i32,i32), snake: &'a SnakeInfo }
}
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub enum Direction {
Down,
Up,
@ -19,23 +21,39 @@ pub enum Direction {
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))
}
}
}
}
@ -82,11 +100,25 @@ impl Map {
}
}
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
}
}
pub fn can_snake_move_in_direction(&self, snake: &SnakeInfo, direction: Direction) -> bool {
let (xd,yd) = direction_as_movement_delta(&direction);
let (xd,yd) = direction.as_movement_delta();
let (x,y) = util::translate_position(snake.positions[0], self.width);
self.is_tile_available_for_movement((x+xd,y+yd))
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)]
@ -94,6 +126,47 @@ impl Map {
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)]

View File

@ -1,40 +1,12 @@
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),
@ -44,97 +16,72 @@ pub enum Inbound {
PlayerRegistered(structs::PlayerRegistered),
InvalidPlayerName(structs::InvalidPlayerName),
HeartBeatResponse(structs::HeartBeatResponse),
GameLinkEvent(structs::GameLink),
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,6 +1,6 @@
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";
@ -10,30 +10,47 @@ impl Snake {
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);
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);
debug!(target: LOG_TARGET, "Snake will move in direction {:?}", direction);
direction
for s in snakes {
if s.positions.len() == 0 || s.id == snake.id {
continue;
}
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]));
if map.can_snake_move_in_direction(snake, tail_direction) {
debug!(target: LOG_TARGET, "Snake will hunt in direction {:?}", tail_direction);
return tail_direction;
}
}
for &d in directions.iter() {
if map.can_snake_move_in_direction(snake, d) {
debug!(target: LOG_TARGET, "Snake will move in direction {:?}", d);
return d;
}
}
debug!(target: LOG_TARGET, "Snake cannot but will move down.");
return Direction::Down;
}
pub fn on_game_ended(&self, msg: &GameEnded) {
debug!(target: LOG_TARGET, "Game ended, the winner is: {:?}", msg.playerWinnerId);
}
@ -47,14 +64,14 @@ impl Snake {
}
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

@ -20,52 +20,31 @@ pub struct GameSettings {
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,
@ -75,8 +54,6 @@ pub struct PlayerRegistered {
#[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub gameId: String,
pub gameTick: u32,
@ -85,15 +62,11 @@ pub struct MapUpdate {
#[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName {
#[serde(rename="type")]
pub type_: String,
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,
@ -103,8 +76,6 @@ pub struct GameEnded {
#[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead {
#[serde(rename="type")]
pub type_: String,
pub playerId: String,
pub x: u32,
pub y: u32,
@ -115,8 +86,6 @@ pub struct SnakeDead {
#[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting {
#[serde(rename="type")]
pub type_: String,
pub receivingPlayerId: String,
pub gameId: String,
pub noofPlayers: u32,
@ -126,15 +95,11 @@ pub struct GameStarting {
#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse {
#[serde(rename="type")]
pub type_: 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,
@ -142,18 +107,16 @@ pub struct GameLink {
#[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 gameResult: Vec<GameResult>,
pub gameId: String,
pub playerWinnerId: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GameResultSnake {
pub struct GameResult {
pub points: i32,
pub playerId: String,
pub name: String

View File

@ -1,3 +1,11 @@
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;
@ -9,6 +17,19 @@ pub fn translate_position(position: i32, map_width: i32) -> (i32,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()