This commit is contained in:
		| @@ -9,10 +9,13 @@ use battlesnake::types::{ | ||||
|     wire::{Request, Response}, | ||||
|     Direction, | ||||
| }; | ||||
| use log::{debug, info, warn}; | ||||
| use log::{debug, error, info, warn}; | ||||
| use rand::prelude::*; | ||||
| use serde::Serialize; | ||||
| use tokio::{net::TcpListener, time::Instant}; | ||||
| use tokio::{ | ||||
|     net::TcpListener, | ||||
|     time::{Duration, Instant}, | ||||
| }; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| @@ -59,20 +62,59 @@ async fn start(request: Json<Request>) { | ||||
| } | ||||
|  | ||||
| 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(); | ||||
|     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 board = Board::from(&*request); | ||||
|     let id = board.get_id(&request.you.id).unwrap_or_else(|| { | ||||
|         error!("My id is not in the simulation board"); | ||||
|         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(); | ||||
|     debug!("simulated 100 random games in {elapsed:?}"); | ||||
|     let action = actions.choose(&mut thread_rng()).copied(); | ||||
|     if action.is_none() { | ||||
|     info!("valid actions: {:?}", actions); | ||||
|  | ||||
|     let mut action_data = [(0, 0); 4]; | ||||
|     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"); | ||||
|     } | ||||
|     info!("chose {action:?}"); | ||||
|   | ||||
| @@ -45,3 +45,14 @@ pub enum Direction { | ||||
|     /// Move in positive x direction | ||||
|     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 { | ||||
|     #[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] | ||||
|     pub const fn turn(&self) -> u32 { | ||||
|         self.turn | ||||
| @@ -197,6 +205,11 @@ impl Board { | ||||
|         self.snakes.len() | ||||
|     } | ||||
|  | ||||
|     #[must_use] | ||||
|     pub fn alive(&self, id: u8) -> bool { | ||||
|         self.id_to_index(id).is_some() | ||||
|     } | ||||
|  | ||||
|     #[must_use] | ||||
|     pub fn is_food(&self, tile: Coord) -> bool { | ||||
|         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<'_> { | ||||
|         let index = self.snakes.binary_search_by_key(&id, |snake| snake.id).ok(); | ||||
|         let index = self.id_to_index(id); | ||||
|         if index.is_none() { | ||||
|             warn!("Asked for a snake that doesn't exist"); | ||||
|         } | ||||
| @@ -228,10 +241,25 @@ impl Board { | ||||
|             .flat_map(|index| self.valid_actions_index(index)) | ||||
|     } | ||||
|  | ||||
|     pub fn random_actions( | ||||
|         &self, | ||||
|     ) -> impl Iterator<Item = (u8, impl Iterator<Item = Direction> + use<'_>)> { | ||||
|         (0..self.snakes.len()).map(|index| (self.snakes[index].id, self.valid_actions_index(index))) | ||||
|     #[must_use] | ||||
|     pub fn random_action(&self, id: u8) -> Direction { | ||||
|         let Some(index) = self.id_to_index(id) else { | ||||
|             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 { | ||||
| @@ -254,6 +282,10 @@ impl Board { | ||||
|         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<'_> { | ||||
|         let head = self.snakes[index].head(); | ||||
|         enum_iterator::all::<Direction>() | ||||
| @@ -338,9 +370,9 @@ impl Board { | ||||
|             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); | ||||
|                 for tile in snake.body.iter().skip(1) { | ||||
|                     if self.is_in_bounds(*tile) { | ||||
|                         let index = self.coord_to_linear(*tile); | ||||
|                         self.free.set(index, true); | ||||
|                     } | ||||
|                 } | ||||
| @@ -413,11 +445,21 @@ impl Board { | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         if needed_food == 0 { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let food_spots = self | ||||
|             .free | ||||
|             .iter() | ||||
|             .enumerate() | ||||
|             .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); | ||||
|         for index in food_spots { | ||||
|             self.food.set(index, true); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user