diff --git a/battlesnake/src/logic.rs b/battlesnake/src/logic.rs index 89a73e0..5db1008 100644 --- a/battlesnake/src/logic.rs +++ b/battlesnake/src/logic.rs @@ -48,6 +48,7 @@ struct GameInfo { calculation_time: Arc>, token_mapping: Arc>, my_token: SnakeToken, + tree: Arc>>, } static GAME_INFOS: LazyLock>> = @@ -70,6 +71,7 @@ pub fn start(game: &Game, _turn: i32, board: &Board, you: &Battlesnake) { ))), token_mapping, my_token, + tree: Arc::new(Mutex::new(None)), }, ); } @@ -114,6 +116,7 @@ pub fn get_move( ))), token_mapping, my_token, + tree: Arc::new(Mutex::new(None)), } }); @@ -126,12 +129,9 @@ pub fn get_move( game.ruleset.name == "constrictor", ); let possible_actions = board.possible_actions().get(&game_info.my_token).cloned()?; - if possible_actions.len() <= 1 { - info!("Only one movement option exists in turn {turn}. Skipping Tree evaluation"); - return possible_actions.first().map(|direction| Action { - r#move: *direction, - shout: None, - }); + if possible_actions.is_empty() { + info!("No movement options in turn {turn}"); + return None; } // do some latency compensation @@ -153,7 +153,45 @@ pub fn get_move( }, ); - let mut tree = Node::default(); + let mut tree_guard = game_info.tree.lock(); + let tree = match tree_guard { + Err(ref e) => { + error!("unable to lock tree: {e}"); + None + } + Ok(ref mut guard) => guard.as_mut(), + }; + let mut tree = tree + .and_then(|node| { + let snake_length_direction: BTreeMap<_, _> = board + .snakes() + .map(|snake| { + let length = board.snake_length(snake).unwrap_or_default(); + let action = board.last_action(snake).unwrap_or(Direction::Up); + (snake, (action, length)) + }) + .collect(); + let node_key = node + .childs + .keys() + .find(|child| { + child.iter().all(|(snake, (direction, length))| { + snake_length_direction + .get(snake) + .copied() + .unwrap_or((*direction, 0)) + == (*direction, *length) + }) + })? + .clone(); + let node = node.childs.remove(&node_key)?; + info!( + "using previous node with {} simulations", + node.statistic.played + ); + Some(node) + }) + .unwrap_or_default(); while Instant::now() < deadline { let mut board = board.clone(); @@ -175,6 +213,11 @@ pub fn get_move( .map(|(direction, _)| *direction) .or_else(|| possible_actions.iter().choose(&mut thread_rng()).copied())?; + if let Ok(ref mut guard) = tree_guard { + **guard = Some(tree); + } + std::mem::drop(tree_guard); + info!( "DIRECTION {turn}: {chosen:?} after {}ms ({})", start.elapsed().as_millis(), diff --git a/battlesnake/src/simulation.rs b/battlesnake/src/simulation.rs index 85ddb4a..4556111 100644 --- a/battlesnake/src/simulation.rs +++ b/battlesnake/src/simulation.rs @@ -118,6 +118,14 @@ impl Board { self.snakes.get(&token).map(|snake| snake.body.len()) } + #[must_use] + pub fn last_action(&self, token: SnakeToken) -> Option { + self.snakes.get(&token).and_then(|snake| { + enum_iterator::all::() + .find(|direction| snake.body[1].move_to(*direction) == *snake.head()) + }) + } + pub fn simulate_actions( &mut self, actions: &BTreeMap, @@ -313,7 +321,6 @@ impl Battlesnake { #[must_use] pub fn head(&self) -> &Coord { - debug_assert!(!self.body.is_empty()); - self.body.front().unwrap_or_else(|| unreachable!()) + &self.body[0] } }