create simple simulation datastructures
This commit is contained in:
parent
f3eba2ba75
commit
d9ee6ae2f7
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -172,12 +172,25 @@ name = "battlesnake"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bitvec",
|
||||
"env_logger",
|
||||
"log",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@ -330,6 +343,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@ -660,6 +679,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -870,6 +895,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.11"
|
||||
@ -1126,6 +1157,15 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
|
@ -16,8 +16,14 @@ pedantic = "warn"
|
||||
nursery = "warn"
|
||||
|
||||
[dependencies]
|
||||
# server
|
||||
tokio = { version = "1.43", features = ["net", "macros", "rt-multi-thread"] }
|
||||
axum = { version = "0.8", features = ["http2", "multipart", "ws"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
|
||||
# other
|
||||
bitvec = "1.0"
|
||||
|
@ -9,7 +9,11 @@ use axum::{
|
||||
use log::{debug, info};
|
||||
use serde::Serialize;
|
||||
use tokio::net::TcpListener;
|
||||
use types::wire::{Direction, Request, Response};
|
||||
use types::{
|
||||
simulation::Board,
|
||||
wire::{Request, Response},
|
||||
Direction,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@ -51,11 +55,13 @@ struct Info {
|
||||
}
|
||||
|
||||
async fn start(request: Json<Request>) {
|
||||
info!("got start request: {request:#?}");
|
||||
let board = Board::from(&*request);
|
||||
info!("got start request: {board}");
|
||||
}
|
||||
|
||||
async fn get_move(request: Json<Request>) -> response::Json<Response> {
|
||||
info!("got move request: {request:#?}");
|
||||
let board = Board::from(&*request);
|
||||
info!("got move request: {board}");
|
||||
response::Json(Response {
|
||||
direction: Direction::Up,
|
||||
shout: None,
|
||||
@ -63,5 +69,6 @@ async fn get_move(request: Json<Request>) -> response::Json<Response> {
|
||||
}
|
||||
|
||||
async fn end(request: Json<Request>) {
|
||||
info!("got end request: {request:#?}");
|
||||
let board = Board::from(&*request);
|
||||
info!("got end request: {board}");
|
||||
}
|
||||
|
@ -1 +1,22 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod simulation;
|
||||
pub mod wire;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)]
|
||||
pub struct Coord {
|
||||
x: u8,
|
||||
y: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
|
||||
pub enum Direction {
|
||||
/// Move in positive y direction
|
||||
Up,
|
||||
/// Move in negative y direction
|
||||
Down,
|
||||
/// Move in negative x direction
|
||||
Left,
|
||||
/// Move in positive x direction
|
||||
Right,
|
||||
}
|
||||
|
169
battlesnake/src/types/simulation.rs
Normal file
169
battlesnake/src/types/simulation.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use std::{collections::VecDeque, fmt::Display};
|
||||
|
||||
use bitvec::prelude::*;
|
||||
use log::error;
|
||||
|
||||
use super::{wire::Request, Coord};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Board {
|
||||
width: u8,
|
||||
height: u8,
|
||||
hazard_damage: u8,
|
||||
food_spawn_chance: u8,
|
||||
min_food: u16,
|
||||
turn: u32,
|
||||
food: BitBox,
|
||||
hazard: BitBox,
|
||||
free: BitBox,
|
||||
snakes: Vec<Snake>,
|
||||
}
|
||||
|
||||
impl From<&Request> for Board {
|
||||
fn from(value: &Request) -> Self {
|
||||
let width = value.board.width;
|
||||
let height = value.board.height;
|
||||
let fields = usize::from(width) * usize::from(height);
|
||||
let mut board = Self {
|
||||
width,
|
||||
height,
|
||||
hazard_damage: value.game.ruleset.settings.hazard_damage_per_turn,
|
||||
food_spawn_chance: value.game.ruleset.settings.food_spawn_chance,
|
||||
min_food: value.game.ruleset.settings.minimum_food,
|
||||
turn: value.turn,
|
||||
food: bitbox![0; fields],
|
||||
hazard: bitbox![0; fields],
|
||||
free: bitbox![1; fields],
|
||||
snakes: Vec::with_capacity(value.board.snakes.len()),
|
||||
};
|
||||
|
||||
for &food in &value.board.food {
|
||||
let index = usize::from(board.coord_to_linear(food));
|
||||
board.food.set(index, true);
|
||||
}
|
||||
|
||||
for &hazard in &value.board.hazards {
|
||||
let index = usize::from(board.coord_to_linear(hazard));
|
||||
board.hazard.set(index, true);
|
||||
}
|
||||
|
||||
for (id, snake) in value.board.snakes.iter().enumerate() {
|
||||
for &tile in &snake.body {
|
||||
let index = usize::from(board.coord_to_linear(tile));
|
||||
board.free.set(index, false);
|
||||
}
|
||||
let snake = Snake {
|
||||
body: snake.body.iter().copied().collect(),
|
||||
id: u8::try_from(id).unwrap_or(u8::MAX),
|
||||
health: snake.health,
|
||||
};
|
||||
board.snakes.push(snake);
|
||||
}
|
||||
|
||||
board
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Board {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{}x{} {}% ({}) {}dmg @ {}",
|
||||
self.width,
|
||||
self.height,
|
||||
self.food_spawn_chance,
|
||||
self.min_food,
|
||||
self.hazard_damage,
|
||||
self.turn
|
||||
)?;
|
||||
|
||||
for y in (0..self.height).rev() {
|
||||
for x in 0..self.width {
|
||||
let tile = Coord { x, y };
|
||||
if self.snakes.iter().any(|snake| snake.head() == tile) {
|
||||
write!(f, "H")?;
|
||||
continue;
|
||||
}
|
||||
if self.snakes.iter().any(|snake| snake.tail() == tile) {
|
||||
write!(f, "T")?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
if !self.free[index] {
|
||||
write!(f, "S")?;
|
||||
continue;
|
||||
}
|
||||
if self.food[index] {
|
||||
write!(f, "f")?;
|
||||
continue;
|
||||
}
|
||||
if self.hazard[index] {
|
||||
write!(f, "h")?;
|
||||
continue;
|
||||
}
|
||||
|
||||
write!(f, ".")?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn is_food(&self, tile: Coord) -> bool {
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
self.food[index]
|
||||
}
|
||||
|
||||
pub fn is_hazard(&self, tile: Coord) -> bool {
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
self.hazard[index]
|
||||
}
|
||||
|
||||
pub fn is_blocked(&self, tile: Coord) -> bool {
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
!self.free[index]
|
||||
}
|
||||
|
||||
fn coord_to_linear(&self, coord: Coord) -> u16 {
|
||||
u16::from(coord.x) + u16::from(coord.y) * u16::from(self.width)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct Snake {
|
||||
id: u8,
|
||||
health: u8,
|
||||
body: VecDeque<Coord>,
|
||||
}
|
||||
|
||||
impl PartialOrd for Snake {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Snake {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Snake {
|
||||
pub fn head(&self) -> Coord {
|
||||
self.body.front().copied().unwrap_or_else(|| {
|
||||
error!("Snake without a head: {self:?}");
|
||||
Coord { x: 0, y: 0 }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tail(&self) -> Coord {
|
||||
self.body.front().copied().unwrap_or_else(|| {
|
||||
error!("Snake without a tail: {self:?}");
|
||||
Coord { x: 0, y: 0 }
|
||||
})
|
||||
}
|
||||
}
|
@ -1,27 +1,29 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Coord, Direction};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||
pub struct Request {
|
||||
/// Game object describing the game being played.
|
||||
game: Game,
|
||||
pub game: Game,
|
||||
/// Turn number for this move.
|
||||
turn: u32,
|
||||
pub turn: u32,
|
||||
/// Board object describing the initial state of the game board.
|
||||
board: Board,
|
||||
pub board: Board,
|
||||
/// Battlesnake Object describing your Battlesnake.
|
||||
you: Battlesnake,
|
||||
pub you: Battlesnake,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||
pub struct Game {
|
||||
/// A unique identifier for this Game.
|
||||
id: String,
|
||||
pub id: String,
|
||||
/// Information about the ruleset being used to run this Game.
|
||||
ruleset: Ruleset,
|
||||
pub ruleset: Ruleset,
|
||||
/// The name of the map being played on.
|
||||
map: String,
|
||||
pub map: String,
|
||||
/// How much time your snake has to respond to requests for this Game.
|
||||
timeout: u16,
|
||||
pub timeout: u16,
|
||||
/// The source of this Game.
|
||||
/// One of:
|
||||
/// - tournament
|
||||
@ -29,87 +31,81 @@ pub struct Game {
|
||||
/// - arena
|
||||
/// - challenge
|
||||
/// - custom
|
||||
source: String,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||
pub struct Ruleset {
|
||||
/// Name of the ruleset being used to run this game.
|
||||
name: String,
|
||||
pub name: String,
|
||||
/// The release version of the Rules module used in this game.
|
||||
version: String,
|
||||
pub version: String,
|
||||
/// A collection of specific settings being used by the current game that control how the rules
|
||||
/// are applied.
|
||||
settings: Settings,
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Settings {
|
||||
/// Percentage chance of spawning a new food every round.
|
||||
food_spawn_chance: u8,
|
||||
pub food_spawn_chance: u8,
|
||||
/// Minimum food to keep on the board every turn.
|
||||
minimum_food: u16,
|
||||
pub minimum_food: u16,
|
||||
/// 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.
|
||||
hazard_damage_per_turn: u8,
|
||||
pub hazard_damage_per_turn: u8,
|
||||
/// Settings for the royale game mode
|
||||
royale: RoyaleSettings,
|
||||
pub royale: RoyaleSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RoyaleSettings {
|
||||
/// The number of turns between generating new hazards (shrinking the safe board space).
|
||||
shrink_every_n_turns: u8,
|
||||
pub shrink_every_n_turns: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||
pub struct Board {
|
||||
/// The number of rows in the y-axis of the game board.
|
||||
height: u16,
|
||||
pub height: u8,
|
||||
/// The number of rows in the x-axis of the game board.
|
||||
width: u16,
|
||||
pub width: u8,
|
||||
/// Array of coordinates representing food locations on the game board.
|
||||
food: Vec<Coord>,
|
||||
pub food: Vec<Coord>,
|
||||
/// Array of coordinates representing hazardous locations on the game board.
|
||||
hazards: Vec<Coord>,
|
||||
pub hazards: Vec<Coord>,
|
||||
/// Array of Battlesnake objects representing all Battlesnakes remaining on the game board
|
||||
/// (including yourself if you haven't been eliminated).
|
||||
snakes: Vec<Battlesnake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)]
|
||||
pub struct Coord {
|
||||
x: u16,
|
||||
y: u16,
|
||||
pub snakes: Vec<Battlesnake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||
pub struct Battlesnake {
|
||||
/// Unique identifier for this Battlesnake in the context of the current Game.
|
||||
id: String,
|
||||
pub id: String,
|
||||
/// Name given to this Battlesnake by its author
|
||||
name: String,
|
||||
pub name: String,
|
||||
/// Health value of this Battlesnake, between 0 and 100
|
||||
health: u8,
|
||||
pub health: u8,
|
||||
/// Array of coordinates representing the Battlesnake's location on the game board.
|
||||
/// This array is ordered from head to tail.
|
||||
body: Vec<Coord>,
|
||||
pub body: Vec<Coord>,
|
||||
/// 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,
|
||||
pub latency: String,
|
||||
/// Coordinates for this Battlesnake's head.
|
||||
/// Equivalent to the first element of the body array.
|
||||
head: Coord,
|
||||
pub head: Coord,
|
||||
/// Length of this Battlesnake from head to tail.
|
||||
/// Equivalent to the length of the body array.
|
||||
length: u32,
|
||||
pub length: u16,
|
||||
/// Message shouted by this Battlesnake on the previous turn
|
||||
shout: String,
|
||||
pub shout: String,
|
||||
/// The squad that the Battlesnake belongs to.
|
||||
/// Used to identify squad members in Squad Mode games.
|
||||
squad: String,
|
||||
pub squad: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
||||
@ -121,15 +117,3 @@ pub struct Response {
|
||||
/// Must be 256 characters or less.
|
||||
pub shout: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
|
||||
pub enum Direction {
|
||||
/// Move in positive y direction
|
||||
Up,
|
||||
/// Move in negative y direction
|
||||
Down,
|
||||
/// Move in negative x direction
|
||||
Left,
|
||||
/// Move in positive x direction
|
||||
Right,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user