160 lines
4.3 KiB
Rust
160 lines
4.3 KiB
Rust
pub const GRID_WIDTH: u16 = 20;
|
|
pub const GRID_HEIGHT: u16 = 15;
|
|
const MAX_SNAKE_LEN: usize = 128;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct Position {
|
|
pub x: u16,
|
|
pub y: u16,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
impl Direction {
|
|
pub fn opposite(&self) -> Direction {
|
|
match self {
|
|
Direction::Up => Direction::Down,
|
|
Direction::Down => Direction::Up,
|
|
Direction::Left => Direction::Right,
|
|
Direction::Right => Direction::Left,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum GameState {
|
|
Running,
|
|
GameOver,
|
|
}
|
|
|
|
pub struct Snake {
|
|
body: [Position; MAX_SNAKE_LEN],
|
|
len: usize,
|
|
direction: Direction,
|
|
next_direction: Direction,
|
|
food: Position,
|
|
state: GameState,
|
|
score: u16,
|
|
rng_state: u32,
|
|
}
|
|
|
|
impl Snake {
|
|
pub fn new() -> Self {
|
|
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };
|
|
let mut body = [Position { x: 0, y: 0 }; MAX_SNAKE_LEN];
|
|
body[0] = start;
|
|
body[1] = Position { x: start.x - 1, y: start.y };
|
|
body[2] = Position { x: start.x - 2, y: start.y };
|
|
|
|
let mut game = Self {
|
|
body,
|
|
len: 3,
|
|
direction: Direction::Right,
|
|
next_direction: Direction::Right,
|
|
food: Position { x: 5, y: 5 },
|
|
state: GameState::Running,
|
|
score: 0,
|
|
rng_state: 42,
|
|
};
|
|
game.place_food();
|
|
game
|
|
}
|
|
|
|
pub fn set_direction(&mut self, dir: Direction) {
|
|
if dir != self.direction.opposite() {
|
|
self.next_direction = dir;
|
|
}
|
|
}
|
|
|
|
pub fn tick(&mut self) {
|
|
if self.state != GameState::Running {
|
|
return;
|
|
}
|
|
|
|
self.direction = self.next_direction;
|
|
|
|
let head = self.body[0];
|
|
let new_head = match self.direction {
|
|
Direction::Up => {
|
|
if head.y == 0 { self.state = GameState::GameOver; return; }
|
|
Position { x: head.x, y: head.y - 1 }
|
|
}
|
|
Direction::Down => {
|
|
if head.y >= GRID_HEIGHT - 1 { self.state = GameState::GameOver; return; }
|
|
Position { x: head.x, y: head.y + 1 }
|
|
}
|
|
Direction::Left => {
|
|
if head.x == 0 { self.state = GameState::GameOver; return; }
|
|
Position { x: head.x - 1, y: head.y }
|
|
}
|
|
Direction::Right => {
|
|
if head.x >= GRID_WIDTH - 1 { self.state = GameState::GameOver; return; }
|
|
Position { x: head.x + 1, y: head.y }
|
|
}
|
|
};
|
|
|
|
if self.hits_self(new_head) {
|
|
self.state = GameState::GameOver;
|
|
return;
|
|
}
|
|
|
|
let ate_food = new_head == self.food;
|
|
|
|
if !ate_food {
|
|
for i in (1..self.len).rev() {
|
|
self.body[i] = self.body[i - 1];
|
|
}
|
|
} else {
|
|
if self.len < MAX_SNAKE_LEN {
|
|
for i in (1..self.len + 1).rev() {
|
|
self.body[i] = self.body[i - 1];
|
|
}
|
|
self.len += 1;
|
|
}
|
|
self.score += 1;
|
|
self.place_food();
|
|
}
|
|
|
|
self.body[0] = new_head;
|
|
}
|
|
|
|
fn hits_self(&self, pos: Position) -> bool {
|
|
for i in 0..self.len {
|
|
if self.body[i] == pos {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn place_food(&mut self) {
|
|
loop {
|
|
self.rng_state ^= self.rng_state << 13;
|
|
self.rng_state ^= self.rng_state >> 17;
|
|
self.rng_state ^= self.rng_state << 5;
|
|
|
|
let x = (self.rng_state % GRID_WIDTH as u32) as u16;
|
|
let y = ((self.rng_state / GRID_WIDTH as u32) % GRID_HEIGHT as u32) as u16;
|
|
let pos = Position { x, y };
|
|
|
|
if !self.hits_self(pos) {
|
|
self.food = pos;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn head(&self) -> Position { self.body[0] }
|
|
pub fn body(&self) -> &[Position] { &self.body[..self.len] }
|
|
pub fn food(&self) -> Position { self.food }
|
|
pub fn state(&self) -> GameState { self.state }
|
|
pub fn score(&self) -> u16 { self.score }
|
|
pub fn direction(&self) -> Direction { self.direction }
|
|
}
|