-
-
Save lufog/640f17e2e92b5d7db5002b84b662deed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "snake_game" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
piston = "0.53" | |
piston2d-graphics = "0.41" | |
pistoncore-glutin_window = "0.69" | |
piston2d-opengl_graphics = "0.79" | |
rand = "0.8" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Snake game | |
//! | |
//! Example of 2d graphics in Rust. | |
//! | |
//! Completed code for https://youtu.be/HCwMb0KslX8 | |
//! Author: @youcodethings | |
//! | |
//! All additional changes are in logic only. | |
//! No new rendering or library stuff. | |
use glutin_window::GlutinWindow; | |
use opengl_graphics::{GlGraphics, OpenGL}; | |
use piston::{event_loop::*, input::*, window::WindowSettings}; | |
use rand::Rng; | |
use std::{collections::LinkedList, iter::FromIterator}; | |
pub struct Game { | |
gl: GlGraphics, | |
rows: u32, | |
cols: u32, | |
snake: Snake, | |
just_eaten: bool, | |
square_width: u32, | |
food: Food, | |
score: u32, | |
} | |
impl Game { | |
fn render(&mut self, args: &RenderArgs) { | |
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0]; | |
self.gl.draw(args.viewport(), |_c, gl| { | |
graphics::clear(GREEN, gl); | |
}); | |
self.snake.render(args); | |
self.food.render(&mut self.gl, args, self.square_width); | |
} | |
fn update(&mut self, _args: &UpdateArgs) -> bool { | |
if !self.snake.update(self.just_eaten, self.cols, self.rows) { | |
return false; | |
} | |
if self.just_eaten { | |
self.score += 1; | |
self.just_eaten = false; | |
} | |
self.just_eaten = self.food.update(&self.snake); | |
if self.just_eaten { | |
// try my luck | |
let mut r = rand::thread_rng(); | |
loop { | |
let new_x = r.gen_range(0..self.cols); | |
let new_y = r.gen_range(0..self.rows); | |
if !self.snake.is_collide(new_x, new_y) { | |
self.food = Food { x: new_x, y: new_y }; | |
break; | |
} | |
} | |
} | |
true | |
} | |
fn pressed(&mut self, btn: &Button) { | |
let last_direction = self.snake.d.clone(); | |
self.snake.d = match btn { | |
&Button::Keyboard(Key::Up) if last_direction != Direction::DOWN => Direction::UP, | |
&Button::Keyboard(Key::Down) if last_direction != Direction::UP => Direction::DOWN, | |
&Button::Keyboard(Key::Left) if last_direction != Direction::RIGHT => Direction::LEFT, | |
&Button::Keyboard(Key::Right) if last_direction != Direction::LEFT => Direction::RIGHT, | |
_ => last_direction, | |
}; | |
} | |
} | |
/// The direction the snake moves in. | |
#[derive(Clone, PartialEq)] | |
enum Direction { | |
UP, | |
DOWN, | |
LEFT, | |
RIGHT, | |
} | |
pub struct Snake { | |
gl: GlGraphics, | |
snake_parts: LinkedList<SnakePiece>, | |
width: u32, | |
d: Direction, | |
} | |
#[derive(Clone)] | |
pub struct SnakePiece(u32, u32); | |
impl Snake { | |
pub fn render(&mut self, args: &RenderArgs) { | |
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; | |
let squares: Vec<graphics::types::Rectangle> = self | |
.snake_parts | |
.iter() | |
.map(|p| SnakePiece(p.0 * self.width, p.1 * self.width)) | |
.map(|p| graphics::rectangle::square(p.0 as f64, p.1 as f64, self.width as f64)) | |
.collect(); | |
self.gl.draw(args.viewport(), |c, gl| { | |
let transform = c.transform; | |
squares | |
.into_iter() | |
.for_each(|square| graphics::rectangle(RED, square, transform, gl)); | |
}) | |
} | |
/// Move the snake if valid, otherwise returns false. | |
pub fn update(&mut self, just_eaten: bool, cols: u32, rows: u32) -> bool { | |
let mut new_front: SnakePiece = | |
(*self.snake_parts.front().expect("No front of snake found.")).clone(); | |
if (self.d == Direction::UP && new_front.1 == 0) | |
|| (self.d == Direction::LEFT && new_front.0 == 0) | |
|| (self.d == Direction::DOWN && new_front.1 == rows - 1) | |
|| (self.d == Direction::RIGHT && new_front.0 == cols - 1) | |
{ | |
return false; | |
} | |
match self.d { | |
Direction::UP => new_front.1 -= 1, | |
Direction::DOWN => new_front.1 += 1, | |
Direction::LEFT => new_front.0 -= 1, | |
Direction::RIGHT => new_front.0 += 1, | |
} | |
if !just_eaten { | |
self.snake_parts.pop_back(); | |
} | |
// Checks self collision. | |
if self.is_collide(new_front.0, new_front.1) { | |
return false; | |
} | |
self.snake_parts.push_front(new_front); | |
true | |
} | |
fn is_collide(&self, x: u32, y: u32) -> bool { | |
self.snake_parts.iter().any(|p| x == p.0 && y == p.1) | |
} | |
} | |
pub struct Food { | |
x: u32, | |
y: u32, | |
} | |
impl Food { | |
// Return true if snake ate food this update | |
fn update(&mut self, s: &Snake) -> bool { | |
let front = s.snake_parts.front().unwrap(); | |
if front.0 == self.x && front.1 == self.y { | |
true | |
} else { | |
false | |
} | |
} | |
fn render(&mut self, gl: &mut GlGraphics, args: &RenderArgs, width: u32) { | |
const BLACK: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; | |
let x = self.x * width; | |
let y = self.y * width; | |
let square = graphics::rectangle::square(x as f64, y as f64, width as f64); | |
gl.draw(args.viewport(), |c, gl| { | |
let transform = c.transform; | |
graphics::rectangle(BLACK, square, transform, gl) | |
}); | |
} | |
} | |
fn main() { | |
// Change this to OpenGL::V2_1 if this fails. | |
let opengl = OpenGL::V3_2; | |
const COLS: u32 = 30; | |
const ROWS: u32 = 20; | |
const SQUARE_WIDTH: u32 = 20; | |
const WIDTH: u32 = COLS * SQUARE_WIDTH; | |
const HEIGHT: u32 = ROWS * SQUARE_WIDTH; | |
let mut window: GlutinWindow = WindowSettings::new("Snake Game", [WIDTH, HEIGHT]) | |
.graphics_api(opengl) | |
.exit_on_esc(true) | |
.build() | |
.unwrap(); | |
let mut game = Game { | |
gl: GlGraphics::new(opengl), | |
rows: ROWS, | |
cols: COLS, | |
square_width: SQUARE_WIDTH, | |
just_eaten: false, | |
food: Food { x: 1, y: 1 }, | |
score: 0, | |
snake: Snake { | |
gl: GlGraphics::new(opengl), | |
snake_parts: LinkedList::from_iter((vec![SnakePiece(COLS / 2, ROWS / 2)]).into_iter()), | |
width: SQUARE_WIDTH, | |
d: Direction::DOWN, | |
}, | |
}; | |
let mut events = Events::new(EventSettings::new()).ups(10); | |
while let Some(e) = events.next(&mut window) { | |
if let Some(r) = e.render_args() { | |
game.render(&r); | |
} | |
if let Some(u) = e.update_args() { | |
if !game.update(&u) { | |
break; | |
} | |
} | |
if let Some(k) = e.button_args() { | |
if k.state == ButtonState::Press { | |
game.pressed(&k.button); | |
} | |
} | |
} | |
println!("Congratulations, your score was: {}", game.score); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment