Compare commits
No commits in common. "46f4e00e5044e220aa367d6b380764ceecd66951" and "e5b81a3ff9bca2c7c55b94b7b256d04111936c9a" have entirely different histories.
46f4e00e50
...
e5b81a3ff9
@ -14,71 +14,99 @@ use log::info;
|
|||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::{Battlesnake, Board, Direction, Game, Move};
|
use crate::{Battlesnake, Board, Coord, Direction, Game, Move};
|
||||||
|
|
||||||
const MAX_HEALTH: i32 = 100;
|
|
||||||
|
|
||||||
impl Battlesnake {
|
impl Battlesnake {
|
||||||
fn possible_moves_without_heads<'a>(
|
/// Check if moving the snake in `direction` is safe.
|
||||||
&'a self,
|
#[must_use]
|
||||||
game: &'a Game,
|
pub fn is_direction_safe(&self, direction: Direction, game: &Game, board: &Board) -> bool {
|
||||||
board: &'a Board,
|
if self.is_direction_death(direction, game, board) {
|
||||||
) -> impl Iterator<Item = Direction> + 'a {
|
return false;
|
||||||
enum_iterator::all::<Direction>()
|
}
|
||||||
.filter(|direction| {
|
|
||||||
// filter out directions that would go outside the field
|
let target = self.head.move_to(direction);
|
||||||
let target = self.head.move_to(*direction);
|
|
||||||
(0..board.width).contains(&target.x) && (0..board.height).contains(&target.y)
|
// check if a bigger snake could move to this square
|
||||||
})
|
if board
|
||||||
.filter(|direction| {
|
|
||||||
let target = self.head.move_to(*direction);
|
|
||||||
// don't collide with any snake
|
|
||||||
!board
|
|
||||||
.snakes
|
.snakes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|snake| {
|
.filter(|snake| snake.id != self.id && self.length <= snake.length)
|
||||||
// filter out snakes that are in our squad if body collisions are allowed
|
.flat_map(|snake| enum_iterator::all().map(|dir| snake.head.move_to(dir)))
|
||||||
!(game.ruleset.settings.squad.allow_body_collisions
|
.any(|coord| target == coord)
|
||||||
&& self.squad == snake.squad
|
{
|
||||||
&& self.id != snake.id)
|
return false;
|
||||||
})
|
}
|
||||||
.flat_map(|snake| {
|
|
||||||
// get all coordinates of the snake body. The tail of the body can be ignored
|
// check for hazards
|
||||||
// if the snake hasn't just eaten, as it will move out of the way
|
if board.hazards.iter().any(|&coord| target == coord) {
|
||||||
let has_eaten = snake.health == MAX_HEALTH;
|
return false;
|
||||||
snake.body[..snake.body.len() - usize::from(!has_eaten)].iter()
|
}
|
||||||
})
|
|
||||||
.any(|&coord| coord == target)
|
true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn possible_moves(&self, game: &Game, board: &Board) -> Vec<Direction> {
|
pub fn is_direction_death(&self, direction: Direction, game: &Game, board: &Board) -> bool {
|
||||||
self.possible_moves_without_heads(game, board)
|
let target = self.head.move_to(direction);
|
||||||
.filter(|direction| {
|
|
||||||
// don't go into spots where a bigger snake must go
|
// check if target is out of bounds
|
||||||
let target = self.head.move_to(*direction);
|
if !((0..board.width).contains(&target.x) && (0..board.height).contains(&target.y)) {
|
||||||
!board
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if target is inside a snake
|
||||||
|
if board
|
||||||
.snakes
|
.snakes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|snake| {
|
.filter(|snake| {
|
||||||
// The other snake must be bigger than we are
|
!(game.ruleset.settings.squad.allow_body_collisions && self.squad == snake.squad)
|
||||||
snake.length > self.length
|
|
||||||
})
|
})
|
||||||
.filter_map(|snake| {
|
.flat_map(|snake| snake.body.iter())
|
||||||
// get all snakes movement options
|
.any(|&coord| target == coord)
|
||||||
let moves = snake
|
{
|
||||||
.possible_moves_without_heads(game, board)
|
return true;
|
||||||
.collect::<Vec<_>>();
|
|
||||||
// only snakes that have a single option
|
|
||||||
match moves[..] {
|
|
||||||
[direction] => Some(snake.head.move_to(direction)),
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
})
|
})
|
||||||
.any(|coord| coord == target)
|
.flat_map(|snake| snake.body.iter())
|
||||||
})
|
.any(|&coord| tile == coord)
|
||||||
.collect()
|
{
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +139,19 @@ pub fn end(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) {
|
|||||||
// Valid moves are "up", "down", "left", or "right"
|
// Valid moves are "up", "down", "left", or "right"
|
||||||
// See https://docs.battlesnake.com/api/example-move for available data
|
// See https://docs.battlesnake.com/api/example-move for available data
|
||||||
pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Option<Move> {
|
pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Option<Move> {
|
||||||
let moves = you.possible_moves(game, board);
|
let moves = enum_iterator::all()
|
||||||
if moves.is_empty() {
|
.filter(|&direction| !you.is_direction_death(direction, game, board))
|
||||||
return None;
|
.collect::<Vec<_>>();
|
||||||
}
|
let safe_moves = moves
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|&direction| you.is_direction_safe(direction, game, board))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Choose a random move from the safe ones
|
// Choose a random move from the safe ones
|
||||||
let chosen = moves.choose(&mut rand::thread_rng())?;
|
let chosen = safe_moves
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.or_else(|| moves.choose(&mut rand::thread_rng()))?;
|
||||||
|
|
||||||
info!("MOVE {}: {:?}", turn, chosen);
|
info!("MOVE {}: {:?}", turn, chosen);
|
||||||
Some(Move {
|
Some(Move {
|
||||||
|
@ -30,9 +30,7 @@ pub enum Direction {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Move {
|
pub struct Move {
|
||||||
/// In which direction the snake should move
|
|
||||||
r#move: Direction,
|
r#move: Direction,
|
||||||
/// Say something to the other snakes
|
|
||||||
#[serde(default, skip_serializing_if = "is_default")]
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
shout: Option<String>,
|
shout: Option<String>,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user