extern crate good_web_game as ggez;
use quad_rand as qrand;
use ggez::event::{KeyCode, KeyMods};
use ggez::{event, graphics, miniquad, timer, Context, GameResult};
use std::collections::LinkedList;
const GRID_SIZE: (i16, i16) = (25, 19);
const GRID_CELL_SIZE: (i16, i16) = (32, 32);
const SCREEN_SIZE: (i32, i32) = (
(GRID_SIZE.0 * GRID_CELL_SIZE.0) as i32,
(GRID_SIZE.1 * GRID_CELL_SIZE.1) as i32,
);
const DESIRED_FPS: u32 = 8;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct GridPosition {
x: i16,
y: i16,
}
impl GridPosition {
pub fn new(x: i16, y: i16) -> Self {
GridPosition { x, y }
}
pub fn random(max_x: i16, max_y: i16) -> Self {
(qrand::gen_range(0, max_x), qrand::gen_range(0, max_y)).into()
}
pub fn new_from_move(pos: GridPosition, dir: Direction) -> Self {
match dir {
Direction::Up => GridPosition::new(pos.x, (pos.y - 1).rem_euclid(GRID_SIZE.1)),
Direction::Down => GridPosition::new(pos.x, (pos.y + 1).rem_euclid(GRID_SIZE.1)),
Direction::Left => GridPosition::new((pos.x - 1).rem_euclid(GRID_SIZE.0), pos.y),
Direction::Right => GridPosition::new((pos.x + 1).rem_euclid(GRID_SIZE.0), pos.y),
}
}
}
impl From<GridPosition> for graphics::Rect {
fn from(pos: GridPosition) -> Self {
graphics::Rect::new_i32(
pos.x as i32 * GRID_CELL_SIZE.0 as i32,
pos.y as i32 * GRID_CELL_SIZE.1 as i32,
GRID_CELL_SIZE.0 as i32,
GRID_CELL_SIZE.1 as i32,
)
}
}
impl From<(i16, i16)> for GridPosition {
fn from(pos: (i16, i16)) -> Self {
GridPosition { x: pos.0, y: pos.1 }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
pub fn inverse(&self) -> Self {
match *self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
pub fn from_keycode(key: KeyCode) -> Option<Direction> {
match key {
KeyCode::Up => Some(Direction::Up),
KeyCode::Down => Some(Direction::Down),
KeyCode::Left => Some(Direction::Left),
KeyCode::Right => Some(Direction::Right),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug)]
struct Segment {
pos: GridPosition,
}
impl Segment {
pub fn new(pos: GridPosition) -> Self {
Segment { pos }
}
}
struct Food {
pos: GridPosition,
}
impl Food {
pub fn new(pos: GridPosition) -> Self {
Food { pos }
}
fn draw(&self, ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult {
let color = [0.0, 0.0, 1.0, 1.0].into();
let rectangle = graphics::Mesh::new_rectangle(
ctx,
quad_ctx,
graphics::DrawMode::fill(),
self.pos.into(),
color,
)?;
graphics::draw(
ctx,
quad_ctx,
&rectangle,
(mint::Point2 { x: 0.0, y: 0.0 },),
)
}
}
#[derive(Clone, Copy, Debug)]
enum Ate {
Itself,
Food,
}
struct Snake {
head: Segment,
dir: Direction,
body: LinkedList<Segment>,
ate: Option<Ate>,
last_update_dir: Direction,
next_dir: Option<Direction>,
}
impl Snake {
pub fn new(pos: GridPosition) -> Self {
let mut body = LinkedList::new();
body.push_back(Segment::new((pos.x - 1, pos.y).into()));
Snake {
head: Segment::new(pos),
dir: Direction::Right,
last_update_dir: Direction::Right,
body,
ate: None,
next_dir: None,
}
}
fn eats(&self, food: &Food) -> bool {
self.head.pos == food.pos
}
fn eats_self(&self) -> bool {
for seg in self.body.iter() {
if self.head.pos == seg.pos {
return true;
}
}
false
}
fn update(&mut self, food: &Food) {
if self.last_update_dir == self.dir && self.next_dir.is_some() {
self.dir = self.next_dir.unwrap();
self.next_dir = None;
}
let new_head_pos = GridPosition::new_from_move(self.head.pos, self.dir);
let new_head = Segment::new(new_head_pos);
self.body.push_front(self.head);
self.head = new_head;
if self.eats_self() {
self.ate = Some(Ate::Itself);
} else if self.eats(food) {
self.ate = Some(Ate::Food);
} else {
self.ate = None
}
if self.ate.is_none() {
self.body.pop_back();
}
self.last_update_dir = self.dir;
}
fn draw(&self, ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult {
for seg in self.body.iter() {
let rectangle = graphics::Mesh::new_rectangle(
ctx,
quad_ctx,
graphics::DrawMode::fill(),
seg.pos.into(),
[0.3, 0.3, 0.0, 1.0].into(),
)?;
graphics::draw(
ctx,
quad_ctx,
&rectangle,
(mint::Point2 { x: 0.0, y: 0.0 },),
)?;
}
let rectangle = graphics::Mesh::new_rectangle(
ctx,
quad_ctx,
graphics::DrawMode::fill(),
self.head.pos.into(),
[1.0, 0.5, 0.0, 1.0].into(),
)?;
graphics::draw(
ctx,
quad_ctx,
&rectangle,
(mint::Point2 { x: 0.0, y: 0.0 },),
)?;
Ok(())
}
}
struct GameState {
snake: Snake,
food: Food,
gameover: bool,
}
impl GameState {
pub fn new() -> Self {
let snake_pos = (GRID_SIZE.0 / 4, GRID_SIZE.1 / 2).into();
qrand::srand(12345);
let food_pos = GridPosition::random(GRID_SIZE.0, GRID_SIZE.1);
GameState {
snake: Snake::new(snake_pos),
food: Food::new(food_pos),
gameover: false,
}
}
}
impl event::EventHandler for GameState {
fn update(
&mut self,
ctx: &mut Context,
_quad_ctx: &mut miniquad::GraphicsContext,
) -> GameResult {
while timer::check_update_time(ctx, DESIRED_FPS) {
if !self.gameover {
self.snake.update(&self.food);
if let Some(ate) = self.snake.ate {
match ate {
Ate::Food => {
let new_food_pos = GridPosition::random(GRID_SIZE.0, GRID_SIZE.1);
self.food.pos = new_food_pos;
}
Ate::Itself => {
self.gameover = true;
}
}
}
}
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult {
graphics::clear(ctx, quad_ctx, [0.0, 1.0, 0.0, 1.0].into());
self.snake.draw(ctx, quad_ctx)?;
self.food.draw(ctx, quad_ctx)?;
graphics::present(ctx, quad_ctx)?;
Ok(())
}
fn key_down_event(
&mut self,
_ctx: &mut Context,
_quad_ctx: &mut miniquad::GraphicsContext,
keycode: KeyCode,
_keymod: KeyMods,
_repeat: bool,
) {
if let Some(dir) = Direction::from_keycode(keycode) {
if self.snake.dir != self.snake.last_update_dir && dir.inverse() != self.snake.dir {
self.snake.next_dir = Some(dir);
} else if dir.inverse() != self.snake.last_update_dir {
self.snake.dir = dir;
}
}
}
}
fn main() -> GameResult {
let state = GameState::new();
let conf = ggez::conf::Conf::default()
.cache(Some(include_bytes!("resources.tar")))
.window_title("Snake!".to_string())
.window_width(SCREEN_SIZE.0)
.window_height(SCREEN_SIZE.1);
ggez::start(conf, |_context, _quad_ctx| Box::new(state))
}