new_snake_game 0.1.0

Snake game
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); // Snake moves every 150 ms

#[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;
        }

        // Wait until enough time has passed
        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,
        }

        // Collision with walls or itself
        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);

        // Eating food
        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);

        // Snake
        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());
        }

        // Food
        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());

        // Score
        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)
}