use ggez::event::EventHandler;
use ggez::graphics::{self, Color, DrawParam, Mesh, Rect, Text, Canvas};
use ggez::input::keyboard::{self, KeyCode, KeyInput};
use ggez::{Context, ContextBuilder, GameResult};
use glam::Vec2;
use std::collections::VecDeque;
use std::time::{Duration, Instant};
const GRID_SIZE: (i32, i32) = (20, 20);
const CELL_SIZE: f32 = 20.0;
const UPDATE_DELAY: Duration = Duration::from_millis(150);
#[derive(Clone, Copy, PartialEq)]
enum Direction {
Up,
Down,
Left,
Right,
}
struct SnakeGame {
snake: VecDeque<(i32, i32)>,
dir: Direction,
food: (i32, i32),
score: i32,
game_over: bool,
last_update: Instant,
}
impl SnakeGame {
fn new() -> Self {
let mut snake = VecDeque::new();
snake.push_back((10, 10));
Self {
snake,
dir: Direction::Right,
food: (5, 5),
score: 0,
game_over: false,
last_update: Instant::now(),
}
}
fn reset(&mut self) {
self.snake.clear();
self.snake.push_back((10, 10));
self.dir = Direction::Right;
self.food = (5, 5);
self.score = 0;
self.game_over = false;
self.last_update = Instant::now();
}
fn update_snake(&mut self) {
if self.game_over {
return;
}
if self.last_update.elapsed() < UPDATE_DELAY {
return;
}
self.last_update = Instant::now();
let mut new_head = *self.snake.front().unwrap();
match self.dir {
Direction::Up => new_head.1 -= 1,
Direction::Down => new_head.1 += 1,
Direction::Left => new_head.0 -= 1,
Direction::Right => new_head.0 += 1,
}
if new_head.0 < 0
|| new_head.1 < 0
|| new_head.0 >= GRID_SIZE.0
|| new_head.1 >= GRID_SIZE.1
|| self.snake.contains(&new_head)
{
self.game_over = true;
return;
}
self.snake.push_front(new_head);
if new_head == self.food {
self.score += 1;
self.food = (
(rand::random::<u8>() % GRID_SIZE.0 as u8) as i32,
(rand::random::<u8>() % GRID_SIZE.1 as u8) as i32,
);
} else {
self.snake.pop_back();
}
}
}
impl EventHandler for SnakeGame {
fn update(&mut self, _ctx: &mut Context) -> GameResult {
self.update_snake();
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
let mut canvas = Canvas::from_frame(ctx, Color::BLACK);
for &(x, y) in &self.snake {
let rect = Mesh::new_rectangle(
ctx,
graphics::DrawMode::fill(),
Rect::new_i32(x * CELL_SIZE as i32, y * CELL_SIZE as i32, CELL_SIZE as i32, CELL_SIZE as i32),
Color::GREEN,
)?;
canvas.draw(&rect, DrawParam::default());
}
let food_rect = Mesh::new_rectangle(
ctx,
graphics::DrawMode::fill(),
Rect::new_i32(self.food.0 * CELL_SIZE as i32, self.food.1 * CELL_SIZE as i32, CELL_SIZE as i32, CELL_SIZE as i32),
Color::RED,
)?;
canvas.draw(&food_rect, DrawParam::default());
let score_text = Text::new(format!("Score: {}", self.score));
canvas.draw(&score_text, DrawParam::default().dest(Vec2::new(5.0, 5.0)));
if self.game_over {
let go_text = Text::new("GAME OVER! Press R to restart");
canvas.draw(&go_text, DrawParam::default().dest(Vec2::new(50.0, 200.0)));
}
canvas.finish(ctx)?;
Ok(())
}
fn key_down_event(&mut self, _ctx: &mut Context, input: KeyInput, _repeat: bool) -> GameResult {
if let Some(keycode) = input.keycode {
match keycode {
KeyCode::Up if self.dir != Direction::Down => self.dir = Direction::Up,
KeyCode::Down if self.dir != Direction::Up => self.dir = Direction::Down,
KeyCode::Left if self.dir != Direction::Right => self.dir = Direction::Left,
KeyCode::Right if self.dir != Direction::Left => self.dir = Direction::Right,
KeyCode::R if self.game_over => self.reset(),
_ => {}
}
}
Ok(())
}
}
fn main() -> GameResult {
let (mut ctx, event_loop) = ContextBuilder::new("snake", "YourName")
.window_setup(ggez::conf::WindowSetup::default().title("Snake Game"))
.window_mode(ggez::conf::WindowMode::default().dimensions(
(GRID_SIZE.0 as f32 * CELL_SIZE),
(GRID_SIZE.1 as f32 * CELL_SIZE),
))
.build()?;
let game = SnakeGame::new();
ggez::event::run(ctx, event_loop, game)
}