Skip to content

Instantly share code, notes, and snippets.

@justinledwards
Created May 2, 2023 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinledwards/dae3b9e6dea6c3fd4e1bd659d3b082fb to your computer and use it in GitHub Desktop.
Save justinledwards/dae3b9e6dea6c3fd4e1bd659d3b082fb to your computer and use it in GitHub Desktop.
Rust Snake GUI
[package]
name = "snake"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
piston_window = "0.128.0"
rand = "0.8.5"
tinyfiledialogs = "3.9.1"
use std::env;
extern crate piston_window;
extern crate rand;
extern crate tinyfiledialogs;
use piston_window::*;
use rand::{ Rng };
use std::collections::VecDeque;
use tinyfiledialogs::{ message_box_ok, MessageBoxIcon };
fn main() {
let args: Vec<String> = env::args().collect();
let default_speed = 60000.0;
let speed = if args.len() > 1 {
args[1].parse::<f64>().unwrap_or(default_speed)
} else {
default_speed
};
let mut accumulated_time = 0.0;
let (w, h) = (20, 20);
let mut window: PistonWindow = WindowSettings::new("Snake", [w * 20, h * 20])
.exit_on_esc(true)
.build()
.unwrap();
let mut game = Game::new(w, h, speed);
while let Some(e) = window.next() {
if let Some(Button::Keyboard(key)) = e.press_args() {
game.key_pressed(key);
}
window.draw_2d(&e, |c, g, _device| {
clear([1.0; 4], g);
game.draw(&c, g);
});
if let Some(u) = e.update_args() {
accumulated_time += u.dt;
let update_interval = 1.0 / (speed / (20.0 * 60.0));
if accumulated_time >= update_interval {
game.update(&mut window);
accumulated_time -= update_interval;
}
}
}
}
struct Game {
snake: Snake,
food: Food,
w: u32,
h: u32,
key_buffer: VecDeque<Direction>,
speed: f64,
game_over: bool,
}
impl Game {
fn new(w: u32, h: u32, speed: f64) -> Game {
Game {
snake: Snake::new(),
food: Food::new(w, h),
w,
h,
key_buffer: VecDeque::new(),
speed,
game_over: false,
}
}
fn key_pressed(&mut self, key: Key) {
let d = match key {
Key::Up => Direction::Up,
Key::Down => Direction::Down,
Key::Left => Direction::Left,
Key::Right => Direction::Right,
_ => {
return;
}
};
self.key_buffer.push_back(d);
}
fn update(&mut self, window: &mut PistonWindow) {
if self.game_over {
return;
}
if let Some(dir) = self.key_buffer.pop_front() {
self.snake.turn(dir);
}
self.snake.update();
if self.snake.collides_with_food(&self.food) {
self.snake.grow();
self.food = Food::new(self.w, self.h);
}
if self.snake.collides_with_self() {
// Handle game over
self.game_over = true;
let score = self.snake.body.len() - 1;
let message = format!("Game over! Score: {} Speed: {:.2}", score, self.speed);
println!("{}", message);
message_box_ok("Game Over", &message, MessageBoxIcon::Error);
// Reset game
self.snake = Snake::new();
self.food = Food::new(self.w, self.h);
self.key_buffer.clear();
self.game_over = false;
}
let score = self.snake.body.len() - 1;
let title = format!("Snake - Score: {} - Speed: {:.2}", score, self.speed);
window.set_title(title);
}
fn draw(&self, c: &Context, g: &mut G2d) {
self.snake.draw(c, g);
self.food.draw(c, g);
}
}
struct Food {
x: f64,
y: f64,
}
impl Food {
fn new(w: u32, h: u32) -> Food {
let mut rng = rand::thread_rng();
Food {
x: (rng.gen_range(0..w) as f64) * 20.0,
y: (rng.gen_range(0..h) as f64) * 20.0,
}
}
fn draw(&self, c: &Context, g: &mut G2d) {
rectangle([1.0, 0.0, 0.0, 1.0], [self.x, self.y, 20.0, 20.0], c.transform, g);
}
}
#[derive(PartialEq, Copy, Clone)]
enum Direction {
Up,
Down,
Left,
Right,
}
struct Snake {
body: Vec<(f64, f64)>,
dir: Direction,
next_dir: Direction,
}
impl Snake {
fn new() -> Snake {
Snake {
body: vec![(0.0, 0.0)],
dir: Direction::Right,
next_dir: Direction::Right,
}
}
fn turn(&mut self, d: Direction) {
let opposite = match self.dir {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
};
if d != opposite {
self.next_dir = d;
}
}
fn update(&mut self) {
self.dir = self.next_dir;
let (dx, dy) = match self.next_dir {
Direction::Up => (0.0, -20.0),
Direction::Down => (0.0, 20.0),
Direction::Left => (-20.0, 0.0),
Direction::Right => (20.0, 0.0),
};
let new_head = (
(self.body[0].0 + dx + 400.0) % 400.0,
(self.body[0].1 + dy + 400.0) % 400.0,
);
self.body.insert(0, new_head);
self.body.pop();
}
fn grow(&mut self) {
self.body.push((0.0, 0.0));
}
fn collides_with_food(&self, food: &Food) -> bool {
self.body[0].0 == food.x && self.body[0].1 == food.y
}
fn collides_with_self(&self) -> bool {
let head = self.body.first().unwrap();
self.body
.iter()
.skip(1)
.any(|&segment| segment == *head)
}
fn draw(&self, c: &Context, g: &mut G2d) {
let body_len = self.body.len() as f32;
for (i, &(x, y)) in self.body.iter().enumerate() {
let green = 0.2 + 0.8 * ((i as f32) / body_len);
let color = if i == 0 {
[0.0, 1.0, 0.0, 1.0] // Light green for the head
} else {
[0.0, green, 0.0, 1.0] // Gradually darker green for the body
};
rectangle(color, [x, y, 20.0, 20.0], c.transform, g);
if i == 0 {
// Draw eyes for the snake head
let eye_color = [1.0, 1.0, 0.0, 1.0];
let pupil_color = [0.0, 0.0, 0.0, 1.0];
let (eye_x_offset, eye_y_offset) = match self.dir {
Direction::Up => (4.0, 4.0),
Direction::Down => (4.0, 12.0),
Direction::Left => (4.0, 4.0),
Direction::Right => (12.0, 4.0),
};
ellipse(eye_color, [x + eye_x_offset, y + eye_y_offset, 4.0, 4.0], c.transform, g);
ellipse(
eye_color,
[x + 20.0 - eye_x_offset - 4.0, y + eye_y_offset, 4.0, 4.0],
c.transform,
g
);
ellipse(
pupil_color,
[x + eye_x_offset + 1.0, y + eye_y_offset + 1.0, 2.0, 2.0],
c.transform,
g
);
ellipse(
pupil_color,
[x + 20.0 - eye_x_offset - 3.0, y + eye_y_offset + 1.0, 2.0, 2.0],
c.transform,
g
);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment