271 lines
7.8 KiB
Rust
271 lines
7.8 KiB
Rust
#![allow(clippy::needless_pass_by_value)]
|
|
|
|
use enum_iterator::Sequence;
|
|
use log::info;
|
|
use rocket::fairing::AdHoc;
|
|
use rocket::http::Status;
|
|
use rocket::serde::{json::Json, Deserialize};
|
|
use rocket::{get, launch, post, routes};
|
|
use serde::Serialize;
|
|
use serde_json::Value;
|
|
use std::env;
|
|
|
|
mod logic;
|
|
mod simulation;
|
|
|
|
const MAX_HEALTH: i32 = 100;
|
|
|
|
// API and Response Objects
|
|
// See https://docs.battlesnake.com/api
|
|
|
|
#[derive(
|
|
Debug,
|
|
PartialEq,
|
|
Eq,
|
|
PartialOrd,
|
|
Ord,
|
|
Hash,
|
|
Clone,
|
|
Copy,
|
|
Deserialize,
|
|
Serialize,
|
|
Sequence,
|
|
Default,
|
|
)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Direction {
|
|
/// Move left (-x)
|
|
#[default]
|
|
Left,
|
|
/// Move up (+y)
|
|
Up,
|
|
/// Move right (+x)
|
|
Right,
|
|
/// Move down (-y)
|
|
Down,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Action {
|
|
/// In which direction the snake should move
|
|
r#move: Direction,
|
|
/// Say something to the other snakes
|
|
#[serde(default, skip_serializing_if = "is_default")]
|
|
shout: Option<String>,
|
|
}
|
|
|
|
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
|
|
*value == T::default()
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
pub struct Game {
|
|
/// A unique identifier for this Game
|
|
id: String,
|
|
/// Information about the ruleset being used to run this game
|
|
ruleset: Ruleset,
|
|
/// The name of the map being played on.
|
|
map: String,
|
|
/// How much time your snake has to respond to requests for this Game
|
|
timeout: u32,
|
|
/// The source of this game.
|
|
///
|
|
/// One of:
|
|
/// - "tournament"
|
|
/// - "league"
|
|
/// - "arena"
|
|
/// - "challenge"
|
|
/// - "custom"
|
|
///
|
|
/// The values may change.
|
|
source: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Ruleset {
|
|
/// Name of the ruleset being used to run this game.
|
|
name: String,
|
|
/// The release version of the [Rules](https://github.com/BattlesnakeOfficial/rules) module used in this game.
|
|
version: String,
|
|
/// A collection of specific settings being used by the current game that control how the rules
|
|
/// are applied.
|
|
settings: RulesetSettings,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct RulesetSettings {
|
|
/// Percentage chance of spawning a new food every round.
|
|
#[serde(rename = "foodSpawnChance")]
|
|
food_spawn_chance: u8,
|
|
/// Minimum food to keep on the board every turn.
|
|
#[serde(rename = "minimumFood")]
|
|
minimum_food: u8,
|
|
/// Health damage a snake will take when ending its turn in a hazard. This stacks on top of the
|
|
/// regular 1 damage a snake takes per turn.
|
|
#[serde(rename = "hazardDamagePerTurn")]
|
|
hazard_damage_per_turn: u8,
|
|
/// rules for the royale mode
|
|
royale: RulesetRoyale,
|
|
/// rules for the squad mode
|
|
squad: RulesetSquad,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct RulesetRoyale {
|
|
/// The number of turns between generating new hazards (shrinking the safe board space).
|
|
#[serde(rename = "shrinkEveryNTurns")]
|
|
shrink_every_n_turns: i32,
|
|
}
|
|
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct RulesetSquad {
|
|
/// Allow members of the same squad to move over each other without dying.
|
|
#[serde(rename = "allowBodyCollisions")]
|
|
allow_body_collisions: bool,
|
|
/// All squad members are eliminated when one is eliminated.
|
|
#[serde(rename = "sharedElimination")]
|
|
shared_elimination: bool,
|
|
/// All squad members share health.
|
|
#[serde(rename = "sharedHealth")]
|
|
shared_health: bool,
|
|
/// All squad members share length.
|
|
#[serde(rename = "sharedLength")]
|
|
shared_length: bool,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
pub struct Board {
|
|
/// The number of rows in the y-axis of the game board.
|
|
height: i32,
|
|
/// The number of columns in the x-axis of the game board.
|
|
width: i32,
|
|
/// Array of coordinates representing food locations on the game board.
|
|
food: Vec<Coord>,
|
|
/// Array of Battlesnakes representing all Battlesnakes remaining on the game board (including
|
|
/// yourself if you haven't been eliminated).
|
|
snakes: Vec<Battlesnake>,
|
|
/// Array of coordinates representing hazardous locations on the game board.
|
|
hazards: Vec<Coord>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
pub struct Battlesnake {
|
|
/// Unique identifier for this Battlesnake in the context of the current Game
|
|
id: String,
|
|
/// Name given to this Battlesnake by its author
|
|
name: String,
|
|
/// Health value of this Battlesnake, between 0 and 100
|
|
health: i32,
|
|
/// Array of coordinates representing this Battlesnake's location on the game board. This array
|
|
/// is ordered from head to tail
|
|
body: Vec<Coord>,
|
|
/// Coordinates for this Battlesnake's head. Equivalent to the first element of the body array.
|
|
head: Coord,
|
|
/// Length of this Battlesnake from head to tail. Equivalent to the length of the body array.
|
|
length: i32,
|
|
/// The previous response time of this Battlesnake, in milliseconds. If the Battlesnake timed
|
|
/// out and failed to respond, the game timeout will be returned.
|
|
latency: String,
|
|
/// Message shouted by this Battlesnake on the previous turn.
|
|
shout: Option<String>,
|
|
/// The squad that the Battlesnake belongs to. Used to identify squad members in Squad Mode
|
|
/// games.
|
|
squad: String,
|
|
// /// The collection of customizations that control how this Battlesnake is displayed.
|
|
// customizations: {color, head, tail}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deserialize, Serialize)]
|
|
pub struct Coord {
|
|
x: i32,
|
|
y: i32,
|
|
}
|
|
|
|
impl Coord {
|
|
const fn move_to(mut self, direction: Direction) -> Self {
|
|
match direction {
|
|
Direction::Left => self.x -= 1,
|
|
Direction::Up => self.y += 1,
|
|
Direction::Right => self.x += 1,
|
|
Direction::Down => self.y -= 1,
|
|
}
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
pub struct GameState {
|
|
game: Game,
|
|
turn: i32,
|
|
board: Board,
|
|
you: Battlesnake,
|
|
}
|
|
|
|
#[get("/")]
|
|
fn handle_index() -> Json<Value> {
|
|
Json(logic::info())
|
|
}
|
|
|
|
#[post("/start", format = "json", data = "<start_req>")]
|
|
fn handle_start(start_req: Json<GameState>) -> Status {
|
|
logic::start(
|
|
&start_req.game,
|
|
start_req.turn,
|
|
&start_req.board,
|
|
&start_req.you,
|
|
);
|
|
|
|
Status::Ok
|
|
}
|
|
|
|
#[post("/move", format = "json", data = "<move_req>")]
|
|
fn handle_move(move_req: Json<GameState>) -> Option<Json<Action>> {
|
|
let response = logic::get_move(
|
|
&move_req.game,
|
|
move_req.turn,
|
|
&move_req.board,
|
|
&move_req.you,
|
|
)?;
|
|
|
|
Some(Json(response))
|
|
}
|
|
|
|
#[post("/end", format = "json", data = "<end_req>")]
|
|
fn handle_end(end_req: Json<GameState>) -> Status {
|
|
logic::end(&end_req.game, end_req.turn, &end_req.board, &end_req.you);
|
|
|
|
Status::Ok
|
|
}
|
|
|
|
#[launch]
|
|
fn rocket() -> _ {
|
|
// Lots of web hosting services expect you to bind to the port specified by the `PORT`
|
|
// environment variable. However, Rocket looks at the `ROCKET_PORT` environment variable.
|
|
// If we find a value for `PORT`, we set `ROCKET_PORT` to that value.
|
|
if let Ok(port) = env::var("PORT") {
|
|
env::set_var("ROCKET_PORT", &port);
|
|
}
|
|
|
|
// We default to 'info' level logging. But if the `RUST_LOG` environment variable is set,
|
|
// we keep that value instead.
|
|
if env::var("RUST_LOG").is_err() {
|
|
env::set_var("RUST_LOG", "info");
|
|
}
|
|
|
|
env_logger::init();
|
|
|
|
info!("Starting Battlesnake Server...");
|
|
|
|
rocket::build()
|
|
.attach(AdHoc::on_response("Server ID Middleware", |_, res| {
|
|
Box::pin(async move {
|
|
res.set_raw_header("Server", "battlesnake/github/starter-snake-rust");
|
|
})
|
|
}))
|
|
.mount(
|
|
"/",
|
|
routes![handle_index, handle_start, handle_move, handle_end],
|
|
)
|
|
}
|