use std::collections::VecDeque;
use super::types::{Direction, Position};
#[derive(Debug, Clone)]
pub struct Snake {
pub id: usize,
pub head: Position,
pub body: VecDeque<Position>,
pub direction: Direction,
pub length: usize,
pub alive: bool,
}
impl Snake {
pub fn new(id: usize, start_pos: Position, start_direction: Direction) -> Self {
let mut body = VecDeque::new();
body.push_back(start_pos);
Self { id, head: start_pos, body, direction: start_direction, length: 1, alive: true }
}
pub fn move_forward(&mut self) {
let new_head = self.head.add_direction(self.direction);
self.body.push_front(new_head);
self.head = new_head;
if self.body.len() > self.length {
self.body.pop_back();
}
}
pub fn move_forward_wrap(&mut self, width: i32, height: i32) {
let new_head = self.head.add_direction(self.direction).wrap(width, height);
self.body.push_front(new_head);
self.head = new_head;
if self.body.len() > self.length {
self.body.pop_back();
}
}
pub fn change_direction(&mut self, new_direction: Direction) {
if new_direction != self.direction.opposite() {
self.direction = new_direction;
}
}
pub fn grow(&mut self) {
self.length += 1;
}
pub fn collides_with_wall(&self, width: i32, height: i32) -> bool {
!self.head.in_bounds(width, height)
}
pub fn collides_with_self(&self) -> bool {
self.body.iter().skip(1).any(|&pos| pos == self.head)
}
pub fn collides_with_other(&self, other: &Snake) -> bool {
other.body.iter().any(|&pos| pos == self.head)
}
pub fn eats_food(&self, food_pos: &Position) -> bool {
self.head == *food_pos
}
pub fn get_all_positions(&self) -> Vec<Position> {
self.body.iter().cloned().collect()
}
pub fn len(&self) -> usize {
self.length
}
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn is_alive(&self) -> bool {
self.alive
}
}
#[derive(Debug, Clone)]
pub struct Food {
pub position: Position,
}
impl Food {
pub fn new(position: Position) -> Self {
Self { position }
}
pub fn generate_random(
width: i32,
height: i32,
snakes: &[Snake],
rng: &mut impl rand::Rng,
) -> Self {
loop {
let x = rng.random_range(0..width);
let y = rng.random_range(0..height);
let pos = Position::new(x, y);
let occupied = snakes.iter().any(|snake| snake.get_all_positions().contains(&pos));
if !occupied {
return Self::new(pos);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_snake_creation() {
let pos = Position::new(5, 5);
let snake = Snake::new(0, pos, Direction::Right);
assert_eq!(snake.id, 0);
assert_eq!(snake.head, pos);
assert_eq!(snake.direction, Direction::Right);
assert_eq!(snake.length, 1);
assert_eq!(snake.body.len(), 1);
assert!(snake.is_alive());
}
#[test]
fn test_snake_movement() {
let start_pos = Position::new(5, 5);
let mut snake = Snake::new(0, start_pos, Direction::Right);
snake.move_forward();
assert_eq!(snake.head, Position::new(6, 5));
assert_eq!(snake.body.len(), 1);
snake.grow();
snake.move_forward();
assert_eq!(snake.head, Position::new(7, 5));
assert_eq!(snake.body.len(), 2); assert_eq!(snake.length, 2);
}
#[test]
fn test_snake_direction_change() {
let mut snake = Snake::new(0, Position::new(5, 5), Direction::Right);
snake.change_direction(Direction::Down);
assert_eq!(snake.direction, Direction::Down);
snake.change_direction(Direction::Up);
assert_eq!(snake.direction, Direction::Down); }
#[test]
fn test_snake_collision_detection() {
let mut snake = Snake::new(0, Position::new(1, 1), Direction::Right);
snake.grow();
snake.move_forward(); assert!(!snake.collides_with_wall(3, 3), "(2, 1) is in-bounds on 3x3");
snake.move_forward(); assert!(snake.collides_with_wall(3, 3), "(3, 1) is out-of-bounds on 3x3");
let mut snake2 = Snake::new(0, Position::new(5, 5), Direction::Right);
snake2.grow();
snake2.grow();
snake2.grow();
snake2.grow(); snake2.move_forward(); snake2.change_direction(Direction::Down);
snake2.move_forward(); snake2.change_direction(Direction::Left);
snake2.move_forward(); snake2.change_direction(Direction::Up);
snake2.move_forward(); assert!(snake2.collides_with_self());
}
#[test]
fn test_snake_food_eating() {
let snake_pos = Position::new(5, 5);
let food_pos = Position::new(5, 5);
let snake = Snake::new(0, snake_pos, Direction::Right);
assert!(snake.eats_food(&food_pos));
let food_pos2 = Position::new(6, 5);
assert!(!snake.eats_food(&food_pos2));
}
#[test]
fn test_food_generation() {
let mut rng = rand::rng();
let snakes = vec![
Snake::new(0, Position::new(0, 0), Direction::Right),
Snake::new(1, Position::new(2, 2), Direction::Right),
];
let food = Food::generate_random(5, 5, &snakes, &mut rng);
let food_pos = food.position;
assert!(food_pos.in_bounds(5, 5));
for snake in &snakes {
assert!(!snake.get_all_positions().contains(&food_pos));
}
}
}