1
0
rust-wasm-snake/src/snake.rs

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);
}
}