Last active
June 7, 2023 13:25
-
-
Save AndrewJakubowicz/9972b5d46be474c186a2dc3a71326de4 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" | |
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" |
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. | |
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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Suprisingly enough, it runs on mine, and i'm running this Rust compiler version(rustc version) rustc 1.70.0 (90c541806 2023-05-31). Not important, but I run Endeavour OS cassini nova, Based on Arch
