This commit is contained in:
parent
9c76df1f69
commit
5e81a7952f
@ -9,7 +9,7 @@ use axum::{
|
||||
use log::{debug, info, warn};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::{net::TcpListener, time::Instant};
|
||||
use types::{
|
||||
simulation::Board,
|
||||
wire::{Request, Response},
|
||||
@ -65,6 +65,14 @@ async fn get_move(request: Json<Request>) -> response::Json<Response> {
|
||||
info!("got move request: {board}");
|
||||
let actions = board.valid_actions(0).collect::<Vec<_>>();
|
||||
info!("valid actions: {:?}", actions);
|
||||
let start = Instant::now();
|
||||
for _ in 0..100 {
|
||||
let mut board = board.clone();
|
||||
let score = board.simulate_random(|board| (board.num_snakes() <= 1).then_some(1));
|
||||
std::hint::black_box(score);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
debug!("simulated 100 random games in {elapsed:?}");
|
||||
let action = actions.choose(&mut thread_rng()).copied();
|
||||
if action.is_none() {
|
||||
warn!("unable to find a valid action");
|
||||
|
@ -19,6 +19,16 @@ impl Coord {
|
||||
Direction::Right => self.x.checked_add(1).map(|x| Self { x, y: self.y }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrapping_apply(mut self, direction: Direction) -> Self {
|
||||
match direction {
|
||||
Direction::Up => self.y = self.y.wrapping_add(1),
|
||||
Direction::Down => self.y = self.y.wrapping_sub(1),
|
||||
Direction::Left => self.x = self.x.wrapping_sub(1),
|
||||
Direction::Right => self.x = self.x.wrapping_add(1),
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Sequence)]
|
||||
|
@ -2,6 +2,7 @@ use std::{collections::VecDeque, fmt::Display};
|
||||
|
||||
use bitvec::prelude::*;
|
||||
use log::{error, warn};
|
||||
use rand::prelude::*;
|
||||
|
||||
use super::{wire::Request, Coord, Direction};
|
||||
|
||||
@ -17,6 +18,7 @@ pub struct Board {
|
||||
hazard: BitBox,
|
||||
free: BitBox,
|
||||
snakes: Vec<Snake>,
|
||||
constrictor: bool,
|
||||
}
|
||||
|
||||
impl From<&Request> for Board {
|
||||
@ -35,21 +37,26 @@ impl From<&Request> for Board {
|
||||
hazard: bitbox![0; fields],
|
||||
free: bitbox![1; fields],
|
||||
snakes: Vec::with_capacity(value.board.snakes.len()),
|
||||
constrictor: value.game.ruleset.name == "constrictor",
|
||||
};
|
||||
|
||||
for &food in &value.board.food {
|
||||
let index = usize::from(board.coord_to_linear(food));
|
||||
let index = 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));
|
||||
let index = 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));
|
||||
for &tile in snake
|
||||
.body
|
||||
.iter()
|
||||
.take(snake.body.len() - usize::from(!board.constrictor))
|
||||
{
|
||||
let index = board.coord_to_linear(tile);
|
||||
board.free.set(index, false);
|
||||
}
|
||||
let snake = Snake {
|
||||
@ -68,7 +75,8 @@ impl Display for Board {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{}x{} {}% ({}) {}dmg @ {}",
|
||||
"{} {}x{} {}% ({}) {}dmg @ {}",
|
||||
if self.constrictor { "constrictor" } else { "" },
|
||||
self.width,
|
||||
self.height,
|
||||
self.food_spawn_chance,
|
||||
@ -89,7 +97,7 @@ impl Display for Board {
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
let index = self.coord_to_linear(tile);
|
||||
if !self.free[index] {
|
||||
write!(f, "S")?;
|
||||
continue;
|
||||
@ -113,18 +121,25 @@ impl Display for Board {
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn num_snakes(&self) -> usize {
|
||||
self.snakes.len()
|
||||
}
|
||||
|
||||
pub fn is_food(&self, tile: Coord) -> bool {
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
let index = 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));
|
||||
let index = self.coord_to_linear(tile);
|
||||
self.hazard[index]
|
||||
}
|
||||
|
||||
pub fn is_free(&self, tile: Coord) -> bool {
|
||||
let index = usize::from(self.coord_to_linear(tile));
|
||||
if !(tile.x < self.width && tile.y < self.height) {
|
||||
return false;
|
||||
}
|
||||
let index = self.coord_to_linear(tile);
|
||||
self.free[index]
|
||||
}
|
||||
|
||||
@ -150,8 +165,191 @@ impl Board {
|
||||
.map(|(direction, _)| direction)
|
||||
}
|
||||
|
||||
fn coord_to_linear(&self, coord: Coord) -> u16 {
|
||||
u16::from(coord.x) + u16::from(coord.y) * u16::from(self.width)
|
||||
pub fn simulate_random<T>(&mut self, stop: impl Fn(&Self) -> Option<T>) -> T {
|
||||
loop {
|
||||
if let Some(score) = stop(self) {
|
||||
break score;
|
||||
}
|
||||
self.next_turn(&[]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_turn(&mut self, actions: &[(u8, Direction)]) {
|
||||
self.move_standard(actions);
|
||||
self.starvation_standard();
|
||||
self.hazard_damage_standard();
|
||||
self.feed_snakes_standard();
|
||||
self.eliminate_snake_standard();
|
||||
self.update_free_map();
|
||||
self.spawn_food();
|
||||
}
|
||||
|
||||
fn move_standard(&mut self, actions: &[(u8, Direction)]) {
|
||||
for i in 0..self.snakes.len() {
|
||||
let snake = &self.snakes[i];
|
||||
let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else(
|
||||
|| {
|
||||
self.valid_actions(snake.id)
|
||||
.choose(&mut thread_rng())
|
||||
.unwrap_or(Direction::Up)
|
||||
},
|
||||
|(_, action)| *action,
|
||||
);
|
||||
let new_head = snake.head().wrapping_apply(action);
|
||||
let snake = &mut self.snakes[i];
|
||||
snake.body.push_front(new_head);
|
||||
snake.body.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
fn starvation_standard(&mut self) {
|
||||
for snake in &mut self.snakes {
|
||||
snake.health = snake.health.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn hazard_damage_standard(&mut self) {
|
||||
let mut i = 0;
|
||||
while i < self.snakes.len() {
|
||||
let head = self.snakes[i].head();
|
||||
if self.is_in_bounds(head) {
|
||||
let head_index = self.coord_to_linear(head);
|
||||
if self.hazard[head_index] && !self.food[head_index] {
|
||||
let health = &mut self.snakes[i].health;
|
||||
*health = health.saturating_sub(1);
|
||||
if *health == 0 {
|
||||
let snake = self.snakes.remove(i);
|
||||
for tile in snake.body {
|
||||
let index = self.coord_to_linear(tile);
|
||||
self.free.set(index, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_snakes_standard(&mut self) {
|
||||
let mut eaten_food = vec![];
|
||||
for i in 0..self.snakes.len() {
|
||||
let head = self.snakes[i].head();
|
||||
if self.is_in_bounds(head) {
|
||||
let head_index = self.coord_to_linear(head);
|
||||
if self.food[head_index] {
|
||||
eaten_food.push(head_index);
|
||||
let snake = &mut self.snakes[i];
|
||||
snake.health = 100;
|
||||
let tail = snake.tail();
|
||||
snake.body.push_back(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
for food_index in eaten_food {
|
||||
self.food.set(food_index, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn eliminate_snake_standard(&mut self) {
|
||||
// eliminate out of health and out of bounds
|
||||
let mut i = 0;
|
||||
while i < self.snakes.len() {
|
||||
let snake = &self.snakes[i];
|
||||
if snake.health == 0 || !self.is_in_bounds(snake.head()) {
|
||||
let snake = self.snakes.remove(i);
|
||||
for tile in snake.body {
|
||||
if self.is_in_bounds(tile) {
|
||||
let index = self.coord_to_linear(tile);
|
||||
self.free.set(index, true);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// look for collisions
|
||||
let mut collisions = vec![];
|
||||
for snake in &self.snakes {
|
||||
let head = snake.head();
|
||||
let head_index = self.coord_to_linear(head);
|
||||
if !self.free[head_index] {
|
||||
collisions.push(snake.id);
|
||||
continue;
|
||||
}
|
||||
for snake2 in &self.snakes {
|
||||
if snake.id != snake2.id
|
||||
&& snake.head() == snake2.head()
|
||||
&& snake.body.len() <= snake2.body.len()
|
||||
{
|
||||
collisions.push(snake.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply collisions
|
||||
let mut i = 0;
|
||||
while i < self.snakes.len() {
|
||||
if collisions.contains(&self.snakes[i].id) {
|
||||
let snake = self.snakes.remove(i);
|
||||
for tile in snake.body {
|
||||
let index = self.coord_to_linear(tile);
|
||||
self.free.set(index, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_free_map(&mut self) {
|
||||
// free tails
|
||||
for snake in &self.snakes {
|
||||
let tail = snake.tail();
|
||||
let pre_tail = snake.body[snake.body.len() - 2];
|
||||
if tail != pre_tail {
|
||||
let tail_index = self.coord_to_linear(tail);
|
||||
self.free.set(tail_index, true);
|
||||
}
|
||||
}
|
||||
// block heads
|
||||
for snake in &self.snakes {
|
||||
let head = snake.head();
|
||||
let head_index = self.coord_to_linear(head);
|
||||
self.free.set(head_index, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_food(&mut self) {
|
||||
let num_food = self.food.count_ones();
|
||||
let needed_food = if num_food < usize::from(self.min_food) {
|
||||
usize::from(self.min_food) - num_food
|
||||
} else {
|
||||
usize::from(
|
||||
self.food_spawn_chance > 0
|
||||
&& thread_rng().gen_range(0..100) < self.food_spawn_chance,
|
||||
)
|
||||
};
|
||||
|
||||
let food_spots = self
|
||||
.free
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, free)| free.then_some(i))
|
||||
.choose_multiple(&mut thread_rng(), needed_food);
|
||||
for index in food_spots {
|
||||
self.food.set(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn coord_to_linear(&self, coord: Coord) -> usize {
|
||||
usize::from(coord.x) + usize::from(coord.y) * usize::from(self.width)
|
||||
}
|
||||
|
||||
const fn is_in_bounds(&self, coord: Coord) -> bool {
|
||||
coord.x < self.width && coord.y < self.height
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user