Last active
September 30, 2017 08:11
-
-
Save doubleOrt/c9f83eded71463d3268ce69ae81c379d to your computer and use it in GitHub Desktop.
This file contains 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
<!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