Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
1 contributor

Users who have contributed to this file

361 lines (318 sloc) 11.8 KB
extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;
extern crate rand;
use piston::event_loop::*;
use piston::input::*;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{GlGraphics};
use opengl_graphics::glyph_cache::GlyphCache;
use graphics::types::Rectangle;
use rand::Rng;
/// Contains colors that can be used in the game
pub mod game_colors {
pub const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
pub const BLACK: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
pub const BLUE: [f32; 4] = [0.0, 0.0, 1.0, 1.0];
pub const LIGHTBLUE: [f32; 4] = [0.0, 1.0, 1.0, 1.0];
pub const ORANGE: [f32; 4] = [1.0, 0.5, 0.0, 1.0];
pub const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
pub const PINK: [f32; 4] = [1.0, 0.0, 1.0, 1.0];
pub const ANGEL: [f32; 4 ] = [0.5,0.5,1.0,0.5];
pub const GREEN: [f32; 4 ] = [0.0,0.5,0.0,1.0];
}
// Most of the game is described by a block
// (The Fruit,the Snakehead, Snakebody ...)
#[derive(Debug,Clone)]
struct Block{
pos_x : i32,
pos_y : i32,
}
// The Game struct
#[derive(Clone)]
pub struct SnakeGame{
velocity: f64,
game_over: bool,
restart: bool,
dimensions: [u32; 2],
snake_head: Block,
snake_body: Vec<Block>,
direction: Direction,
update_time: f64,
is_growing: bool,
fruit : Block,
score : u32,
}
// We want to know which kind of collision occured
enum Collision{
WithFruit(Block),
WithSnake,
WithBorder,
NoCollision,
}
#[derive(Debug,Clone,PartialEq,Eq)]
enum Direction{
Up,
Down,
Left,
Right
}
// In order to prohibit moving the snakehead back in the direction it came from
// we check if the direction is allowed in the function snake_set_direction()
fn opposite_direction(dir : &Direction) -> Direction{
match *dir {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
impl SnakeGame{
pub fn new(width: u32, height: u32) -> Self{
let center_x = ((width as f64) * 0.5) as i32;
let center_y = ((height as f64) * 0.5) as i32;
SnakeGame{
velocity : 10.0,
game_over: false,
restart: false,
dimensions: [width, height],
snake_head: Block {pos_x: center_x, pos_y: center_y },
snake_body: vec![Block {pos_x: center_x + 1, pos_y: center_y},
Block {pos_x: center_x + 2, pos_y: center_y}],
direction: Direction::Left,
update_time: 0.0,
is_growing: false,
fruit: Block{ pos_x: rand::thread_rng().gen_range(1, (width - 1) as i32),
pos_y: rand::thread_rng().gen_range(1, (height - 1) as i32) },
score: 0,
}
}
fn change_pos_fruit(&mut self){
// simply pick a random location, might also be on the snake
self.fruit.pos_x = rand::thread_rng().gen_range(1, (self.dimensions[0] - 1) as i32);
self.fruit.pos_y = rand::thread_rng().gen_range(1, (self.dimensions[1] - 1) as i32);
}
fn is_collision(&mut self) -> Collision{
// is the snakehead colliding with the body?
for block in self.snake_body.iter(){
if self.snake_head.pos_x == block.pos_x && self.snake_head.pos_y == block.pos_y{
return Collision::WithSnake;
}
}
// is the snakehead colliding with the border?
if self.snake_head.pos_x <= 0 || self.snake_head.pos_x >= self.dimensions[0] as i32
|| self.snake_head.pos_y <= 0 || self.snake_head.pos_y >= self.dimensions[1] as i32{
return Collision::WithBorder;
}
// is the snakehead colliding with the fruit?
if self.snake_head.pos_x == self.fruit.pos_x && self.snake_head.pos_y == self.fruit.pos_y{
return Collision::WithFruit(self.fruit.clone());
}
Collision::NoCollision
}
// The games update loop, process the propagated changes
fn on_update(&mut self, upd: &UpdateArgs){
// Look for collision
match self.is_collision(){
Collision::WithFruit(fruit) =>{
self.snake_grow();
self.change_pos_fruit();
}
Collision::NoCollision =>{
}
_ => {
// Collision WithBorder / WithSnake
self.game_over = true;
}
}
// Accumulate time to next update
self.update_time += upd.dt;
// We update the game logic in fixed intervalls
if self.update_time >= (1.0 / self.velocity){
let (x,y) = match self.direction{
Direction::Up => (0,-1),
Direction::Down => (0,1),
Direction::Right => (1,0),
Direction::Left => (-1,0),
};
if self.restart{
let pristine = SnakeGame::new(self.dimensions[0],self.dimensions[1]);
self.game_over = pristine.game_over;
self.restart = pristine.restart;
self.direction = pristine.direction;
self.velocity = pristine.velocity;
self.snake_body = pristine.snake_body.clone();
self.snake_head = pristine.snake_head.clone();
self.update_time = pristine.update_time;
self.is_growing = pristine.is_growing;
self.fruit = pristine.fruit.clone();
self.score = pristine.score;
return;
}
if self.game_over{
return;
}
// Clone current headposition, will be part of the body
let mut oldblock = self.snake_head.clone();
// Update position of snake head
self.snake_head.pos_x = self.snake_head.pos_x + x;
self.snake_head.pos_y = self.snake_head.pos_y + y;
// If growing flag is set, add a new block to the body
if self.is_growing{
let block = Block{pos_x : 0, pos_y: 0};
self.snake_body.push(block);
self.is_growing = false;
}
// "Move" the snake by pushing current blocks of snakebody to new vector
let mut blocks = Vec::new();
for block in self.snake_body.iter_mut().rev(){
blocks.push(oldblock);
oldblock = block.clone();
}
blocks.reverse();
// Assign new body
self.snake_body = blocks;
self.update_time = 0.0;
}
}
fn snake_grow(&mut self){
self.is_growing = true;
self.score += 1;
self.velocity += 0.01;
}
fn snake_set_direction(&mut self, direction: Direction){
if opposite_direction(&direction) == self.direction {
return;
}
self.direction = direction;
}
fn on_keypress(&mut self, key : Key){
let dir = match key {
Key::Up => {
Direction::Up
}
Key::Down => {
Direction::Down
}
Key::Left => {
Direction::Left
}
Key::Right => {
Direction::Right
}
Key::Space => {
self.restart = true;
return;
}
Key::Escape => {
println!("Quit!");
self.game_over = true;
return;
}
_ => {
return;
}
};
self.snake_set_direction(dir);
}
fn renderable_rect(&self,pos_x : i32, pos_y : i32, args: &RenderArgs) -> Rectangle{
let block_size_x = (args.width as f64) / (self.dimensions[0] as f64);
let block_size_y = (args.height as f64) / (self.dimensions[1] as f64);
let snake_pos_x = (pos_x as f64) * block_size_x;
let snake_pos_y = (pos_y as f64) * block_size_y;
let rect = graphics::rectangle::rectangle_by_corners(snake_pos_x - block_size_x * 0.5, snake_pos_y - block_size_y * 0.5,
snake_pos_x + block_size_x * 0.5, snake_pos_y + block_size_y * 0.5);
rect
}
fn on_game_render(&self, args: &RenderArgs, gl: &mut GlGraphics, glyph_cache: &mut GlyphCache){
use graphics::*;
// Only draw the "game over" screen
if self.game_over{
self.on_you_lost_window_render(args,gl,glyph_cache);
return;
}
// draw viewport
gl.draw(args.viewport(), |c, gl| {
// clear the screen
clear(game_colors::BLACK, gl);
// draw snakes head
rectangle(game_colors::BLUE, self.renderable_rect(self.snake_head.pos_x,self.snake_head.pos_y,args), c.transform, gl);
// draw fruit
rectangle(game_colors::RED, self.renderable_rect(self.fruit.pos_x,self.fruit.pos_y,args), c.transform, gl);
// draw borders of the game
let line_width = (args.width as f64) / (self.dimensions[0] as f64) * 0.5;
let line_height = (args.height as f64) / (self.dimensions[1] as f64) * 0.5;
line(game_colors::WHITE,
line_width,
[0.0, 0.0, 0.0, args.height as f64],
c.transform,
gl);
line(game_colors::WHITE,
line_width,
[args.width as f64, 0.0, args.width as f64, args.height as f64],
c.transform,
gl);
line(game_colors::WHITE,
line_height,
[0.0, 0.0, args.width as f64, 0.0],
c.transform,
gl);
line(game_colors::WHITE,
line_height,
[0.0, args.height as f64, args.width as f64, args.height as f64],
c.transform,
gl);
// draw snakes body
for block in self.snake_body.iter(){
rectangle(color::WHITE, self.renderable_rect(block.pos_x,block.pos_y,args), c.transform, gl);
}
});
}
fn on_you_lost_window_render(&self, args: &RenderArgs, gl: &mut GlGraphics, glyph_cache: &mut GlyphCache){
use graphics::*;
// draw viewport
gl.draw(args.viewport(), |c, gl| {
// clear the screen
clear(game_colors::BLACK, gl);
if self.game_over{
// display Game over and score
text(color::WHITE,
10,
format!("Game over! Press Space to restart, Escape to quit!")
.as_str(),
glyph_cache,
c.transform.trans(10.0, 10.0),
gl);
text(color::WHITE,
10,
format!("Your score is {}",self.score)
.as_str(),
glyph_cache,
c.transform.trans(10.0, 20.0),
gl);
return;
}
});
}
pub fn exec(&mut self,
mut window: &mut Window,
mut gl: &mut GlGraphics,
mut e: &mut Events,
mut glyph_cache: &mut GlyphCache){
while let Some(e) = e.next(window) {
if let Some(r) = e.update_args() {
self.on_update(&r);
}
if let Some(button) = e.press_args() {
match button {
Button::Keyboard(key) => self.on_keypress(key),
_ => {}
}
}
if let Some(r) = e.render_args() {
self.on_game_render(&r,gl,glyph_cache);
}
}
}
}
You can’t perform that action at this time.