-
-
Save AndrewJakubowicz/9972b5d46be474c186a2dc3a71326de4 to your computer and use it in GitHub Desktop.
[package] | |
name = "snake_game" | |
version = "0.1.0" | |
authors = ["youcodethings <[email protected]>"] | |
[dependencies] | |
piston = "0.36.0" | |
piston2d-graphics = "0.26.0" | |
pistoncore-glutin_window = "0.45.0" | |
piston2d-opengl_graphics = "0.52.0" | |
rand = "0.4" |
//! 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. | |
extern crate glutin_window; | |
extern crate graphics; | |
extern crate opengl_graphics; | |
extern crate piston; | |
extern crate rand; | |
use piston::window::WindowSettings; | |
use piston::event_loop::*; | |
use piston::input::*; | |
use glutin_window::GlutinWindow; | |
use opengl_graphics::{GlGraphics, OpenGL}; | |
use std::collections::LinkedList; | |
use std::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) { | |
use graphics; | |
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 { | |
use rand::Rng; | |
use rand::thread_rng; | |
// try my luck | |
let mut r = 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<Snake_Piece>, | |
width: u32, | |
d: Direction, | |
} | |
#[derive(Clone)] | |
pub struct Snake_Piece(u32, u32); | |
impl Snake { | |
pub fn render(&mut self, args: &RenderArgs) { | |
use graphics; | |
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; | |
let squares: Vec<graphics::types::Rectangle> = self.snake_parts | |
.iter() | |
.map(|p| Snake_Piece(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: Snake_Piece = | |
(*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) { | |
use graphics; | |
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; | |
let WIDTH = COLS * SQUARE_WIDTH; | |
let HEIGHT = ROWS * SQUARE_WIDTH; | |
let mut window: GlutinWindow = WindowSettings::new("Snake Game", [WIDTH, HEIGHT]) | |
.opengl(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![Snake_Piece(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); | |
} |
Thanks for pointing this out @JeanAbayo. I didn't test on mac so don't know why that error is occurring.
Are you using the most up to date Rust, and/or nightly Rust?
Are you able to get the basic code working? I fear there is an error in the winit library.
maybe just in windows ?
tried it on Windows but it just shows the screen for a few seconds then dies with "Congratulations" message
it just flashes and terminates by popping out the congratulations message on ubuntu.
Unsure what is going on. But I now recommend checking out GGEZ and their snake example: https://github.com/ggez/ggez/blob/master/examples/04_snake.rs
There is also amethyst: https://github.com/amethyst/amethyst
I don't know how to push commit to GitHub Gist, so I just put link for tweaked version here: https://gist.github.com/640f17e2e92b5d7db5002b84b662deed.git
Although there is a LOT of room for improvement, I decided to fix the very minimum so that the code does not differ much from the one shown in the video. What I did:
- updated dependencies;
- removed redundant imports;
- removed extern(s) (not needed after 2015 edition, I believe);
- a couple of naming and formatting fixes;
- fixes in a couple of places with changed API.
If @AndrewJakubowicz "merge" it here, I'll delete my gist.
please tell me the dependencies. It doesn't work for me
Hi @SpyR1014 is there a specific reason this doesn't run on a mac. Here is the error I've been getting.
Screenshot
![]()
Hi, (I'm only a beginner in Rust programming, but i got intermediate knowledge in Rust). You said that the code isn't running, and you've gotten that error, As i can see in your terminal the code panicked at window.rs at line number 13 in main.rs "extern crate opengl_graphics;" which i think is the cause, so maybe opengl. I don't know the exact cause of the problem but that's worth to find out.
Hi @SpyR1014 is there a specific reason this doesn't run on a mac.
Here is the error I've been getting.
Screenshot