From 46f4e00e5044e220aa367d6b380764ceecd66951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=A4nner?= Date: Mon, 30 Sep 2024 19:34:09 +0200 Subject: [PATCH] start over. Move randomly in a valid direction --- battlesnake/src/logic.rs | 162 ++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 98 deletions(-) diff --git a/battlesnake/src/logic.rs b/battlesnake/src/logic.rs index 9f0ee07..5d4dcc4 100644 --- a/battlesnake/src/logic.rs +++ b/battlesnake/src/logic.rs @@ -14,99 +14,71 @@ use log::info; use rand::seq::SliceRandom; use serde_json::{json, Value}; -use crate::{Battlesnake, Board, Coord, Direction, Game, Move}; +use crate::{Battlesnake, Board, Direction, Game, Move}; + +const MAX_HEALTH: i32 = 100; impl Battlesnake { - /// Check if moving the snake in `direction` is safe. - #[must_use] - pub fn is_direction_safe(&self, direction: Direction, game: &Game, board: &Board) -> bool { - if self.is_direction_death(direction, game, board) { - return false; - } - - let target = self.head.move_to(direction); - - // check if a bigger snake could move to this square - if board - .snakes - .iter() - .filter(|snake| snake.id != self.id && self.length <= snake.length) - .flat_map(|snake| enum_iterator::all().map(|dir| snake.head.move_to(dir))) - .any(|coord| target == coord) - { - return false; - } - - // check for hazards - if board.hazards.iter().any(|&coord| target == coord) { - return false; - } - - true + fn possible_moves_without_heads<'a>( + &'a self, + game: &'a Game, + board: &'a Board, + ) -> impl Iterator + 'a { + enum_iterator::all::() + .filter(|direction| { + // filter out directions that would go outside the field + let target = self.head.move_to(*direction); + (0..board.width).contains(&target.x) && (0..board.height).contains(&target.y) + }) + .filter(|direction| { + let target = self.head.move_to(*direction); + // don't collide with any snake + !board + .snakes + .iter() + .filter(|snake| { + // filter out snakes that are in our squad if body collisions are allowed + !(game.ruleset.settings.squad.allow_body_collisions + && self.squad == snake.squad + && self.id != snake.id) + }) + .flat_map(|snake| { + // get all coordinates of the snake body. The tail of the body can be ignored + // if the snake hasn't just eaten, as it will move out of the way + let has_eaten = snake.health == MAX_HEALTH; + snake.body[..snake.body.len() - usize::from(!has_eaten)].iter() + }) + .any(|&coord| coord == target) + }) } #[must_use] - pub fn is_direction_death(&self, direction: Direction, game: &Game, board: &Board) -> bool { - let target = self.head.move_to(direction); - - // check if target is out of bounds - if !((0..board.width).contains(&target.x) && (0..board.height).contains(&target.y)) { - return true; - } - - // check if target is inside a snake - if board - .snakes - .iter() - .filter(|snake| { - !(game.ruleset.settings.squad.allow_body_collisions && self.squad == snake.squad) + pub fn possible_moves(&self, game: &Game, board: &Board) -> Vec { + self.possible_moves_without_heads(game, board) + .filter(|direction| { + // don't go into spots where a bigger snake must go + let target = self.head.move_to(*direction); + !board + .snakes + .iter() + .filter(|snake| { + // The other snake must be bigger than we are + snake.length > self.length + }) + .filter_map(|snake| { + // get all snakes movement options + let moves = snake + .possible_moves_without_heads(game, board) + .collect::>(); + // only snakes that have a single option + match moves[..] { + [direction] => Some(snake.head.move_to(direction)), + _ => None, + } + }) + .any(|coord| coord == target) }) - .flat_map(|snake| snake.body.iter()) - .any(|&coord| target == coord) - { - return true; - } - false - } - - #[must_use] - pub fn health_cost(&self, tile: Coord, game: &Game, board: &Board) -> i32 { - // we can't leave the board - if !((0..board.width).contains(&tile.x) && (0..board.height).contains(&tile.y)) { - return self.health; - } - - // we can't move into other snakes - if board - .snakes - .iter() - .filter(|snake| { - !(game.ruleset.settings.squad.allow_body_collisions && self.squad == snake.squad) - }) - .flat_map(|snake| snake.body.iter()) - .any(|&coord| tile == coord) - { - return self.health; - } - - // every step costs us one health point - let mut cost = 1; - - // hazards increase the health loss - if board.hazards.iter().any(|&hazard| tile == hazard) { - cost += game.ruleset.settings.hazard_damage_per_turn; - } - - // check for bigger snakes that can move into this square - for snake in board - .snakes - .iter() - .filter(|snake| snake.id != self.id && self.length <= snake.length) - { - let moves = enum_iterator::all().map(|dir| snake.head.move_to(dir)); - } - - cost + .collect() } } @@ -139,19 +111,13 @@ pub fn end(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) { // Valid moves are "up", "down", "left", or "right" // See https://docs.battlesnake.com/api/example-move for available data pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Option { - let moves = enum_iterator::all() - .filter(|&direction| !you.is_direction_death(direction, game, board)) - .collect::>(); - let safe_moves = moves - .iter() - .copied() - .filter(|&direction| you.is_direction_safe(direction, game, board)) - .collect::>(); + let moves = you.possible_moves(game, board); + if moves.is_empty() { + return None; + } // Choose a random move from the safe ones - let chosen = safe_moves - .choose(&mut rand::thread_rng()) - .or_else(|| moves.choose(&mut rand::thread_rng()))?; + let chosen = moves.choose(&mut rand::thread_rng())?; info!("MOVE {}: {:?}", turn, chosen); Some(Move {