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