Compare commits
5 Commits
435bd355d4
...
6b81b6bc40
| Author | SHA1 | Date | |
|---|---|---|---|
|
6b81b6bc40
|
|||
|
2137fbd565
|
|||
|
366d39c0a7
|
|||
|
e274bd37e7
|
|||
| 061da11d43 |
@ -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]
|
||||||
|
|||||||
@ -23,5 +23,5 @@ additive = false
|
|||||||
|
|
||||||
[loggers.snake]
|
[loggers.snake]
|
||||||
level = "debug"
|
level = "debug"
|
||||||
appenders = [ "snake" ]
|
appenders = [ "console" ]
|
||||||
additive = false
|
additive = false
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
# SNAKE CLIENT
|
# SNAKE CLIENT
|
||||||
|
|
||||||
[](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`;
|
||||||
|
|
||||||
|
|||||||
@ -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";
|
||||||
|
|||||||
447
src/main.rs
447
src/main.rs
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
419
src/maputil.rs
419
src/maputil.rs
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
205
src/messages.rs
205
src/messages.rs
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
95
src/snake.rs
95
src/snake.rs
@ -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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
213
src/structs.rs
213
src/structs.rs
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/util.rs
67
src/util.rs
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user