diff --git a/Cargo.lock b/Cargo.lock index d9708d0..fa29fe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,7 @@ dependencies = [ name = "battlesnake" version = "1.0.0" dependencies = [ + "enum-iterator", "env_logger", "log", "rand", @@ -275,6 +276,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-iterator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_filter" version = "0.1.2" diff --git a/battlesnake/Cargo.toml b/battlesnake/Cargo.toml index adde98a..91c69ba 100644 --- a/battlesnake/Cargo.toml +++ b/battlesnake/Cargo.toml @@ -22,3 +22,4 @@ serde_json = "1.0.59" log = "0.4.0" env_logger = "0.11.5" rand = "0.8.4" +enum-iterator = "2.1" diff --git a/battlesnake/src/logic.rs b/battlesnake/src/logic.rs index 6353022..09cb675 100644 --- a/battlesnake/src/logic.rs +++ b/battlesnake/src/logic.rs @@ -13,9 +13,52 @@ use log::info; use rand::seq::SliceRandom; use serde_json::{json, Value}; -use std::collections::HashMap; -use crate::{Battlesnake, Board, Coord, Direction, Game, Move}; +use crate::{Battlesnake, Board, Direction, Game, Move}; + +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 { + 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 false; + } + + // check if target is inside a snake + 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| target == coord) + { + return false; + } + + // 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 + } +} // info is called when you create your Battlesnake on play.battlesnake.com // and controls your Battlesnake's appearance @@ -45,87 +88,15 @@ pub fn end(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) { // move is called on every turn and returns your next move // 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) -> Move { - use Direction::{Down, Left, Right, Up}; - - let mut is_move_safe: HashMap<_, _> = - vec![(Up, true), (Down, true), (Left, true), (Right, true)] - .into_iter() - .collect(); - - // We've included code to prevent your Battlesnake from moving backwards - let my_head = &you.body[0]; // Coordinates of your head - let my_neck = &you.body[1]; // Coordinates of your "neck" - - if my_neck.x < my_head.x { - // Neck is left of head, don't move left - is_move_safe.insert(Left, false); - } else if my_neck.x > my_head.x { - // Neck is right of head, don't move right - is_move_safe.insert(Right, false); - } else if my_neck.y < my_head.y { - // Neck is below head, don't move down - is_move_safe.insert(Down, false); - } else if my_neck.y > my_head.y { - // Neck is above head, don't move up - is_move_safe.insert(Up, false); - } - - // TODO: Step 1 - Prevent your Battlesnake from moving out of bounds - if my_head.x == 0 { - is_move_safe.insert(Left, false); - } - if my_head.x == board.width - 1 { - is_move_safe.insert(Right, false); - } - if my_head.y == 0 { - is_move_safe.insert(Down, false); - } - if my_head.y == board.height - 1 { - is_move_safe.insert(Up, false); - } - - // TODO: Step 2 - Prevent your Battlesnake from colliding with itself - if you.body.contains(&Coord { - x: my_head.x - 1, - y: my_head.y, - }) { - is_move_safe.insert(Left, false); - } - if you.body.contains(&Coord { - x: my_head.x + 1, - y: my_head.y, - }) { - is_move_safe.insert(Right, false); - } - if you.body.contains(&Coord { - x: my_head.x, - y: my_head.y - 1, - }) { - is_move_safe.insert(Down, false); - } - if you.body.contains(&Coord { - x: my_head.x, - y: my_head.y + 1, - }) { - is_move_safe.insert(Up, false); - } - - // TODO: Step 3 - Prevent your Battlesnake from colliding with other Battlesnakes - // let opponents = &board.snakes; - - // Are there any safe moves left? - let safe_moves = is_move_safe - .into_iter() - .filter(|&(_, v)| v) - .map(|(k, _)| k) +pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Move { + let safe_moves = enum_iterator::all() + .filter(|&direction| you.is_direction_safe(direction, game, board)) .collect::>(); // Choose a random move from the safe ones - let chosen = safe_moves.choose(&mut rand::thread_rng()).unwrap_or(&Up); - - // TODO: Step 4 - Move towards food instead of random, to regain health and survive longer - // let food = &board.food; + let chosen = safe_moves + .choose(&mut rand::thread_rng()) + .unwrap_or(&Direction::Up); info!("MOVE {}: {:?}", turn, chosen); Move { diff --git a/battlesnake/src/main.rs b/battlesnake/src/main.rs index 3b5ae45..e700c13 100644 --- a/battlesnake/src/main.rs +++ b/battlesnake/src/main.rs @@ -1,5 +1,6 @@ #![allow(clippy::needless_pass_by_value)] +use enum_iterator::Sequence; use log::info; use rocket::fairing::AdHoc; use rocket::http::Status; @@ -14,7 +15,7 @@ mod logic; // API and Response Objects // See https://docs.battlesnake.com/api -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize, Sequence)] pub enum Direction { /// Move left (-x) #[serde(rename = "left")] @@ -165,6 +166,18 @@ pub struct Coord { y: i32, } +impl Coord { + const fn move_to(mut self, direction: Direction) -> Self { + match direction { + Direction::Left => self.x -= 1, + Direction::Up => self.y += 1, + Direction::Right => self.x += 1, + Direction::Down => self.y -= 1, + } + self + } +} + #[derive(Deserialize, Serialize, Debug)] pub struct GameState { game: Game,