Skip to content

Instantly share code, notes, and snippets.

@doubleOrt
Last active September 30, 2017 08:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doubleOrt/c9f83eded71463d3268ce69ae81c379d to your computer and use it in GitHub Desktop.
Save doubleOrt/c9f83eded71463d3268ce69ae81c379d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple Snake Game</title>
<!-- Basic styling, centering of the canvas. -->
<style>
canvas {
display: block;
position: absolute;
border: 1px solid #000;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
</style>
</head>
<body>
<script>
const
COLS = 20,
ROWS = 20,
CELL_SIZE = 30,
CANVAS_WIDTH = CELL_SIZE * COLS,
CANVAS_HEIGHT = CELL_SIZE * ROWS,
EMPTY = 0,
SNAKE = 1,
FRUIT = 2,
LEFT = 0,
UP = 1,
RIGHT = 2,
DOWN = 3,
KEY_LEFT = 37,
KEY_UP = 38,
KEY_RIGHT = 39,
KEY_DOWN = 40;
var
canvas, /* HTMLCanvas */
ctx, /* CanvasRenderingContext2d */
current_direction, /* Object, used for keyboard inputs */
frames, /* number, used for animation */
score; /* number, keep track of the player score */
/**
* Grid datastructor, usefull in games where the game world is
* confined in absolute sized chunks of data or information.
*
* @type {Object}
*/
var grid = {
width: null, /* number, the number of columns */
height: null, /* number, the number of rows */
_grid: null, /* Array<any>, data representation */
/**
* Initiate and fill a c x r grid with the value of d
* @param {any} d default value to fill with
* @param {number} c number of columns
* @param {number} r number of rows
*/
init: function(value, columns, rows) {
this.width = columns;
this.height = rows;
this._grid = [];
for (var x=0; x < columns; x++) {
this._grid.push([]);
for (var y=0; y < rows; y++) {
this._grid[x].push(value);
}
}
},
/**
* Set the value of the grid cell at (x, y)
*
* @param {any} val what to set
* @param {number} x the x-coordinate
* @param {number} y the y-coordinate
*/
set: function(val, x, y) {
this._grid[x][y] = val;
},
/**
* Get the value of the cell at (x, y)
*
* @param {number} x the x-coordinate
* @param {number} y the y-coordinate
* @return {any} the value at the cell
*/
get: function(x, y) {
return this._grid[x][y];
},
setFood: function() {
var empty = [];
// iterate through the grid and find all empty cells
for (var x=0; x < grid.width; x++) {
for (var y=0; y < grid.height; y++) {
if (grid.get(x, y) === EMPTY) {
empty.push({x:x, y:y});
}
}
}
// chooses a random cell
var randpos = empty[Math.round(Math.random()*(empty.length - 1))];
grid.set(FRUIT, randpos.x, randpos.y);
}
}
/**
* The snake, works as a queue (FIFO, first in first out) of data
* with all the current positions in the grid with the snake id
*
* @type {Object}
*/
var snake = {
direction: null, /* number, the direction */
get last() {
return this._queue[0];
}, /* Object, pointer to the last element in
the queue */
_queue: null, /* Array<number>, data representation*/
/**
* Clears the queue and sets the start position and direction
*
* @param {number} d start direction
* @param {number} x start x-coordinate
* @param {number} y start y-coordinate
*/
init: function(d, x, y) {
this.direction = d;
this._queue = [];
this.insert(x, y);
grid.set(SNAKE, x, y);
},
/**
* Adds an element to the queue
*
* @param {number} x x-coordinate
* @param {number} y y-coordinate
*/
insert: function(x, y) {
// unshift prepends an element to an array
this._queue.unshift({x:x, y:y});
// add a snake id at the new position and append it to
grid.set(SNAKE, x, y);
},
/**
* Removes and returns the first element in the queue.
*
* @return {Object} the first element
*/
remove: function() {
// pop returns the last element of an array
var tail = this._queue.pop();
grid.set(EMPTY, tail.x, tail.y);
}
};
/**
* Starts the game
*/
function main() {
// create and initiate the canvas element
canvas = document.createElement("canvas");
canvas.width = COLS * CELL_SIZE;
canvas.height = ROWS * CELL_SIZE;
ctx = canvas.getContext("2d");
// add the canvas element to the body of the document
document.body.appendChild(canvas);
// sets an base font for bigger score display
ctx.font = "16px Helvetica";
frames = 0;
keystate = {};
// keeps track of the keybourd input
document.addEventListener("keydown", function(evt) {
keystate[evt.keyCode] = true;
});
document.addEventListener("keyup", function(evt) {
delete keystate[evt.keyCode];
});
// intatiate game objects and starts the game loop
init();
loop();
}
/**
* Resets and inits game objects
*/
function init() {
score = 0;
grid.init(EMPTY, COLS, ROWS);
var sp = {x:Math.floor(COLS/2), y:ROWS-1};
snake.init(UP, sp.x, sp.y);
grid.setFood();
}
/**
* The game loop function, used for game updates and rendering
*/
function loop() {
update();
draw();
// When ready to redraw the canvas call the loop function
// first. Runs about 60 frames a second
window.requestAnimationFrame(loop, canvas);
}
/**
* Updates the game logic
*/
function update() {
frames++;
// changing direction of the snake depending on which keys
// that are pressed
if (keystate[KEY_LEFT] && snake.direction !== RIGHT) {
snake.direction = LEFT;
}
if (keystate[KEY_UP] && snake.direction !== DOWN) {
snake.direction = UP;
}
if (keystate[KEY_RIGHT] && snake.direction !== LEFT) {
snake.direction = RIGHT;
}
if (keystate[KEY_DOWN] && snake.direction !== UP) {
snake.direction = DOWN;
}
// each five frames update the game state.
if (frames%5 === 0) {
// pop the last element from the snake queue i.e. the
// head
var nx = snake.last.x;
var ny = snake.last.y;
// updates the position depending on the snake direction
switch (snake.direction) {
case LEFT:
nx--;
break;
case UP:
ny--;
break;
case RIGHT:
nx++;
break;
case DOWN:
ny++;
break;
}
// checks all gameover conditions
if (0 > nx || nx > grid.width-1 ||
0 > ny || ny > grid.height-1 ||
grid.get(nx, ny) === SNAKE
) {
return init();
}
// check wheter the new position are on the fruit item
if (grid.get(nx, ny) === FRUIT) {
// increment the score and sets a new fruit position
score++;
grid.setFood();
} else {
// take out the first item from the snake queue i.e
// the tail and remove id from grid
snake.remove();
}
// the snake queue
snake.insert(nx, ny);
}
}
/**
* Render the grid to the canvas.
*/
function draw() {
// iterate through the grid and draw all cells
for (var x=0; x < grid.width; x++) {
for (var y=0; y < grid.height; y++) {
// sets the fillstyle depending on the id of
// each cell
switch (grid.get(x, y)) {
case EMPTY:
ctx.fillStyle = "#fff";
break;
case SNAKE:
ctx.fillStyle = "#0ff";
break;
case FRUIT:
ctx.fillStyle = "#f00";
break;
}
ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}
}
ctx.fillStyle = "#000";
// message to the canvas
ctx.fillText("SCORE: " + score, 10, canvas.height-10);
}
// start and run the game
main();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment