This commit is contained in:
parent
d858d88e76
commit
5b440bc7db
@ -9,10 +9,13 @@ use battlesnake::types::{
|
|||||||
wire::{Request, Response},
|
wire::{Request, Response},
|
||||||
Direction,
|
Direction,
|
||||||
};
|
};
|
||||||
use log::{debug, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::{net::TcpListener, time::Instant};
|
use tokio::{
|
||||||
|
net::TcpListener,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@ -59,20 +62,59 @@ async fn start(request: Json<Request>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_move(request: Json<Request>) -> response::Json<Response> {
|
async fn get_move(request: Json<Request>) -> response::Json<Response> {
|
||||||
let board = Board::from(&*request);
|
|
||||||
info!("got move request: {board}");
|
|
||||||
let actions = board.valid_actions(0).collect::<Vec<_>>();
|
|
||||||
info!("valid actions: {:?}", actions);
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for _ in 0..100 {
|
let board = Board::from(&*request);
|
||||||
let mut board = board.clone();
|
let id = board.get_id(&request.you.id).unwrap_or_else(|| {
|
||||||
let score = board.simulate_random(|board| (board.num_snakes() <= 1).then_some(1));
|
error!("My id is not in the simulation board");
|
||||||
std::hint::black_box(score);
|
0
|
||||||
|
});
|
||||||
|
info!("got move request: {board}");
|
||||||
|
let actions = board.valid_actions(id).collect::<Vec<_>>();
|
||||||
|
if actions.len() <= 1 {
|
||||||
|
info!(
|
||||||
|
"only one possible action. Fast forwarding {:?}",
|
||||||
|
actions.first()
|
||||||
|
);
|
||||||
|
return response::Json(Response {
|
||||||
|
direction: actions.first().copied().unwrap_or(Direction::Up),
|
||||||
|
shout: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let elapsed = start.elapsed();
|
info!("valid actions: {:?}", actions);
|
||||||
debug!("simulated 100 random games in {elapsed:?}");
|
|
||||||
let action = actions.choose(&mut thread_rng()).copied();
|
let mut action_data = [(0, 0); 4];
|
||||||
if action.is_none() {
|
let mut total_simulations = 0;
|
||||||
|
let base_turns = board.turn();
|
||||||
|
while start.elapsed() < Duration::from_millis(250) {
|
||||||
|
let mut board = board.clone();
|
||||||
|
let action = *actions.choose(&mut thread_rng()).unwrap_or(&Direction::Up);
|
||||||
|
board.next_turn(&[(id, action)]);
|
||||||
|
let turns = board.simulate_random(|board| {
|
||||||
|
if board.num_snakes() == 0
|
||||||
|
|| board.turn() > base_turns + u32::from(request.you.length) * 3
|
||||||
|
{
|
||||||
|
Some(board.turn())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let action_data = &mut action_data[usize::from(action)];
|
||||||
|
action_data.0 += turns - base_turns;
|
||||||
|
action_data.1 += 1;
|
||||||
|
total_simulations += 1;
|
||||||
|
}
|
||||||
|
debug!("action data: {action_data:?}");
|
||||||
|
|
||||||
|
let action = actions.into_iter().max_by_key(|action| {
|
||||||
|
let action_data = action_data[usize::from(*action)];
|
||||||
|
action_data.0 / action_data.1
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(action) = action {
|
||||||
|
let action_data = action_data[usize::from(action)];
|
||||||
|
let avg_turns = action_data.0 / action_data.1;
|
||||||
|
info!("found action {action:?} after {total_simulations} simulations with an average of {avg_turns} turns.");
|
||||||
|
} else {
|
||||||
warn!("unable to find a valid action");
|
warn!("unable to find a valid action");
|
||||||
}
|
}
|
||||||
info!("chose {action:?}");
|
info!("chose {action:?}");
|
||||||
|
@ -45,3 +45,14 @@ pub enum Direction {
|
|||||||
/// Move in positive x direction
|
/// Move in positive x direction
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Direction> for usize {
|
||||||
|
fn from(value: Direction) -> Self {
|
||||||
|
match value {
|
||||||
|
Direction::Up => 0,
|
||||||
|
Direction::Down => 1,
|
||||||
|
Direction::Left => 2,
|
||||||
|
Direction::Right => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -187,6 +187,14 @@ impl Display for Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_id(&self, str_id: &str) -> Option<u8> {
|
||||||
|
self.id_map
|
||||||
|
.iter()
|
||||||
|
.find(|(_, str_id2)| *str_id == **str_id2)
|
||||||
|
.map(|(id, _)| *id)
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn turn(&self) -> u32 {
|
pub const fn turn(&self) -> u32 {
|
||||||
self.turn
|
self.turn
|
||||||
@ -197,6 +205,11 @@ impl Board {
|
|||||||
self.snakes.len()
|
self.snakes.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn alive(&self, id: u8) -> bool {
|
||||||
|
self.id_to_index(id).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_food(&self, tile: Coord) -> bool {
|
pub fn is_food(&self, tile: Coord) -> bool {
|
||||||
let index = self.coord_to_linear(tile);
|
let index = self.coord_to_linear(tile);
|
||||||
@ -219,7 +232,7 @@ impl Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_actions(&self, id: u8) -> impl Iterator<Item = Direction> + use<'_> {
|
pub fn valid_actions(&self, id: u8) -> impl Iterator<Item = Direction> + use<'_> {
|
||||||
let index = self.snakes.binary_search_by_key(&id, |snake| snake.id).ok();
|
let index = self.id_to_index(id);
|
||||||
if index.is_none() {
|
if index.is_none() {
|
||||||
warn!("Asked for a snake that doesn't exist");
|
warn!("Asked for a snake that doesn't exist");
|
||||||
}
|
}
|
||||||
@ -228,10 +241,25 @@ impl Board {
|
|||||||
.flat_map(|index| self.valid_actions_index(index))
|
.flat_map(|index| self.valid_actions_index(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_actions(
|
#[must_use]
|
||||||
&self,
|
pub fn random_action(&self, id: u8) -> Direction {
|
||||||
) -> impl Iterator<Item = (u8, impl Iterator<Item = Direction> + use<'_>)> {
|
let Some(index) = self.id_to_index(id) else {
|
||||||
(0..self.snakes.len()).map(|index| (self.snakes[index].id, self.valid_actions_index(index)))
|
return Direction::Up;
|
||||||
|
};
|
||||||
|
self.valid_actions_index(index)
|
||||||
|
.choose(&mut thread_rng())
|
||||||
|
.unwrap_or(Direction::Up)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_actions(&self) -> impl Iterator<Item = (u8, Direction)> + use<'_> {
|
||||||
|
(0..self.snakes.len()).map(|index| {
|
||||||
|
(
|
||||||
|
self.snakes[index].id,
|
||||||
|
self.valid_actions_index(index)
|
||||||
|
.choose(&mut thread_rng())
|
||||||
|
.unwrap_or(Direction::Up),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate_random<T>(&mut self, stop: impl Fn(&Self) -> Option<T>) -> T {
|
pub fn simulate_random<T>(&mut self, stop: impl Fn(&Self) -> Option<T>) -> T {
|
||||||
@ -254,6 +282,10 @@ impl Board {
|
|||||||
self.turn += 1;
|
self.turn += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn id_to_index(&self, id: u8) -> Option<usize> {
|
||||||
|
self.snakes.binary_search_by_key(&id, |snake| snake.id).ok()
|
||||||
|
}
|
||||||
|
|
||||||
fn valid_actions_index(&self, index: usize) -> impl Iterator<Item = Direction> + use<'_> {
|
fn valid_actions_index(&self, index: usize) -> impl Iterator<Item = Direction> + use<'_> {
|
||||||
let head = self.snakes[index].head();
|
let head = self.snakes[index].head();
|
||||||
enum_iterator::all::<Direction>()
|
enum_iterator::all::<Direction>()
|
||||||
@ -338,9 +370,9 @@ impl Board {
|
|||||||
let snake = &self.snakes[i];
|
let snake = &self.snakes[i];
|
||||||
if snake.health == 0 || !self.is_in_bounds(snake.head()) {
|
if snake.health == 0 || !self.is_in_bounds(snake.head()) {
|
||||||
let snake = self.snakes.remove(i);
|
let snake = self.snakes.remove(i);
|
||||||
for tile in snake.body {
|
for tile in snake.body.iter().skip(1) {
|
||||||
if self.is_in_bounds(tile) {
|
if self.is_in_bounds(*tile) {
|
||||||
let index = self.coord_to_linear(tile);
|
let index = self.coord_to_linear(*tile);
|
||||||
self.free.set(index, true);
|
self.free.set(index, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,11 +445,21 @@ impl Board {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if needed_food == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let food_spots = self
|
let food_spots = self
|
||||||
.free
|
.free
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, free)| free.then_some(i))
|
.filter_map(|(i, free)| free.then_some(i))
|
||||||
|
.filter(|i| {
|
||||||
|
self.snakes
|
||||||
|
.iter()
|
||||||
|
.all(|snake| self.coord_to_linear(snake.tail()) != *i)
|
||||||
|
})
|
||||||
|
.filter(|i| !self.food[*i])
|
||||||
.choose_multiple(&mut thread_rng(), needed_food);
|
.choose_multiple(&mut thread_rng(), needed_food);
|
||||||
for index in food_spots {
|
for index in food_spots {
|
||||||
self.food.set(index, true);
|
self.food.set(index, true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user