135 lines
2.8 KiB
Rust
135 lines
2.8 KiB
Rust
use std::collections::VecDeque;
|
|
|
|
use rand::prelude::*;
|
|
|
|
pub type Position = (usize, usize);
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SnakeGame {
|
|
pub width: usize,
|
|
pub height: usize,
|
|
pub snake: VecDeque<Position>,
|
|
pub snake_direction: Direction,
|
|
pub food: Position,
|
|
pub game_over: bool,
|
|
}
|
|
|
|
fn rand_position(width: usize, height: usize) -> Position {
|
|
let mut rng = thread_rng();
|
|
(rng.gen_range(0..width), rng.gen_range(0..height))
|
|
}
|
|
|
|
fn get_opposite_direction(direction: &Direction) -> Direction {
|
|
match direction {
|
|
Direction::Up => Direction::Down,
|
|
Direction::Down => Direction::Up,
|
|
Direction::Right => Direction::Left,
|
|
Direction::Left => Direction::Right,
|
|
}
|
|
}
|
|
|
|
impl SnakeGame {
|
|
pub fn new(width: usize, height: usize) -> SnakeGame {
|
|
Self {
|
|
width,
|
|
height,
|
|
snake: VecDeque::from([(width / 2, height / 2)]),
|
|
snake_direction: Direction::Left,
|
|
food: rand_position(width, height),
|
|
game_over: false,
|
|
}
|
|
}
|
|
|
|
pub fn change_direction(&mut self, direction: Direction) {
|
|
// Because you can't start going in the opposite direction you are
|
|
// currently going
|
|
if get_opposite_direction(&self.snake_direction) == direction {
|
|
return;
|
|
}
|
|
|
|
self.snake_direction = direction
|
|
}
|
|
|
|
fn is_valid_move(&self, (x, y): Position, dx: i32, dy: i32) -> bool {
|
|
let new_x = x as i32 + dx;
|
|
let new_y = y as i32 + dy;
|
|
0 <= new_x
|
|
&& new_x < self.width as i32
|
|
&& 0 <= new_y
|
|
&& new_y < self.height as i32
|
|
}
|
|
|
|
fn get_unoccupied_position(&self) -> Option<Position> {
|
|
let mut rng = thread_rng();
|
|
(0..self.height)
|
|
.flat_map(|y| (0..self.width).map(move |x| (x, y)))
|
|
.filter(|pos| !self.snake.contains(pos))
|
|
.choose(&mut rng)
|
|
}
|
|
|
|
pub fn tick(&mut self) {
|
|
if self.game_over {
|
|
return;
|
|
}
|
|
|
|
let head = self.snake.front();
|
|
if head.is_none() {
|
|
return;
|
|
}
|
|
let head = *head.unwrap();
|
|
|
|
let (dx, dy): (i32, i32) = match &self.snake_direction {
|
|
Direction::Up => (0, -1),
|
|
Direction::Down => (0, 1),
|
|
Direction::Left => (-1, 0),
|
|
Direction::Right => (1, 0),
|
|
};
|
|
|
|
if !self.is_valid_move(head, dx, dy) {
|
|
self.game_over = true;
|
|
return;
|
|
}
|
|
|
|
let new_head =
|
|
((head.0 as i32 + dx) as usize, (head.1 as i32 + dy) as usize);
|
|
if self.snake.contains(&new_head) {
|
|
self.game_over = true;
|
|
return;
|
|
}
|
|
|
|
self.snake.push_front(new_head);
|
|
if new_head == self.food {
|
|
let new_food = self.get_unoccupied_position();
|
|
if let Some(new_food) = new_food {
|
|
self.food = new_food;
|
|
} else {
|
|
self.game_over = true;
|
|
}
|
|
} else {
|
|
self.snake.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn it_works() {
|
|
let mut game = SnakeGame::new(10, 10);
|
|
|
|
println!("{:?}", game);
|
|
game.change_direction(Direction::Up);
|
|
game.tick();
|
|
println!("{:?}", game);
|
|
}
|
|
}
|