Last active
December 31, 2019 04:32
-
-
Save supergoat/c2d959cf119ce2c3ec47389d98fc664c 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> | |
<head> | |
<title>Snake Game</title> | |
<link href="https://fonts.googleapis.com/css?family=Antic+Slab" rel="stylesheet"> | |
</head> | |
<body> | |
<div id="score">0</div> | |
<canvas id="gameCanvas" width="300" height="300"></canvas> | |
<style> | |
#gameCanvas { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
} | |
#score { | |
text-align: center; | |
font-size: 140px; | |
font-family: 'Antic Slab', serif; | |
} | |
</style> | |
</body> | |
<script> | |
const GAME_SPEED = 100; | |
const CANVAS_BORDER_COLOUR = 'black'; | |
const CANVAS_BACKGROUND_COLOUR = "white"; | |
const SNAKE_COLOUR = 'lightgreen'; | |
const SNAKE_BORDER_COLOUR = 'darkgreen'; | |
const FOOD_COLOUR = 'red'; | |
const FOOD_BORDER_COLOUR = 'darkred'; | |
let snake = [ | |
{x: 150, y: 150}, | |
{x: 140, y: 150}, | |
{x: 130, y: 150}, | |
{x: 120, y: 150}, | |
{x: 110, y: 150} | |
] | |
// The user's score | |
let score = 0; | |
// When set to true the snake is changing direction | |
let changingDirection = false; | |
// Food x-coordinate | |
let foodX; | |
// Food y-coordinate | |
let foodY; | |
// Horizontal velocity | |
let dx = 10; | |
// Vertical velocity | |
let dy = 0; | |
// Get the canvas element | |
const gameCanvas = document.getElementById("gameCanvas"); | |
// Return a two dimensional drawing context | |
const ctx = gameCanvas.getContext("2d"); | |
// Start game | |
main(); | |
// Create the first food location | |
createFood(); | |
// Call changeDirection whenever a key is pressed | |
document.addEventListener("keydown", changeDirection); | |
/** | |
* Main function of the game | |
* called repeatedly to advance the game | |
*/ | |
function main() { | |
// If the game ended return early to stop game | |
if (didGameEnd()) return; | |
setTimeout(function onTick() { | |
changingDirection = false; | |
clearCanvas(); | |
drawFood(); | |
advanceSnake(); | |
drawSnake(); | |
// Call game again | |
main(); | |
}, GAME_SPEED) | |
} | |
/** | |
* Change the background colour of the canvas to CANVAS_BACKGROUND_COLOUR and | |
* draw a border around it | |
*/ | |
function clearCanvas() { | |
// Select the colour to fill the drawing | |
ctx.fillStyle = CANVAS_BACKGROUND_COLOUR; | |
// Select the colour for the border of the canvas | |
ctx.strokestyle = CANVAS_BORDER_COLOUR; | |
// Draw a "filled" rectangle to cover the entire canvas | |
ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); | |
// Draw a "border" around the entire canvas | |
ctx.strokeRect(0, 0, gameCanvas.width, gameCanvas.height); | |
} | |
/** | |
* Draw the food on the canvas | |
*/ | |
function drawFood() { | |
ctx.fillStyle = FOOD_COLOUR; | |
ctx.strokestyle = FOOD_BORDER_COLOUR; | |
ctx.fillRect(foodX, foodY, 10, 10); | |
ctx.strokeRect(foodX, foodY, 10, 10); | |
} | |
/** | |
* Advances the snake by changing the x-coordinates of its parts | |
* according to the horizontal velocity and the y-coordinates of its parts | |
* according to the vertical veolocity | |
*/ | |
function advanceSnake() { | |
// Create the new Snake's head | |
const head = {x: snake[0].x + dx, y: snake[0].y + dy}; | |
// Add the new head to the beginning of snake body | |
snake.unshift(head); | |
const didEatFood = snake[0].x === foodX && snake[0].y === foodY; | |
if (didEatFood) { | |
// Increase score | |
score += 10; | |
// Display score on screen | |
document.getElementById('score').innerHTML = score; | |
// Generate new food location | |
createFood(); | |
} else { | |
// Remove the last part of snake body | |
snake.pop(); | |
} | |
} | |
/** | |
* Returns true if the head of the snake touched another part of the game | |
* or any of the walls | |
*/ | |
function didGameEnd() { | |
for (let i = 4; i < snake.length; i++) { | |
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true | |
} | |
const hitLeftWall = snake[0].x < 0; | |
const hitRightWall = snake[0].x > gameCanvas.width - 10; | |
const hitToptWall = snake[0].y < 0; | |
const hitBottomWall = snake[0].y > gameCanvas.height - 10; | |
return hitLeftWall || hitRightWall || hitToptWall || hitBottomWall | |
} | |
/** | |
* Generates a random number that is a multiple of 10 given a minumum | |
* and a maximum number | |
* @param { number } min - The minimum number the random number can be | |
* @param { number } max - The maximum number the random number can be | |
*/ | |
function randomTen(min, max) { | |
return Math.round((Math.random() * (max-min) + min) / 10) * 10; | |
} | |
/** | |
* Creates random set of coordinates for the snake food. | |
*/ | |
function createFood() { | |
// Generate a random number the food x-coordinate | |
foodX = randomTen(0, gameCanvas.width - 10); | |
// Generate a random number for the food y-coordinate | |
foodY = randomTen(0, gameCanvas.height - 10); | |
// if the new food location is where the snake currently is, generate a new food location | |
snake.forEach(function isFoodOnSnake(part) { | |
const foodIsoNsnake = part.x == foodX && part.y == foodY; | |
if (foodIsoNsnake) createFood(); | |
}); | |
} | |
/** | |
* Draws the snake on the canvas | |
*/ | |
function drawSnake() { | |
// loop through the snake parts drawing each part on the canvas | |
snake.forEach(drawSnakePart) | |
} | |
/** | |
* Draws a part of the snake on the canvas | |
* @param { object } snakePart - The coordinates where the part should be drawn | |
*/ | |
function drawSnakePart(snakePart) { | |
// Set the colour of the snake part | |
ctx.fillStyle = SNAKE_COLOUR; | |
// Set the border colour of the snake part | |
ctx.strokestyle = SNAKE_BORDER_COLOUR; | |
// Draw a "filled" rectangle to represent the snake part at the coordinates | |
// the part is located | |
ctx.fillRect(snakePart.x, snakePart.y, 10, 10); | |
// Draw a border around the snake part | |
ctx.strokeRect(snakePart.x, snakePart.y, 10, 10); | |
} | |
/** | |
* Changes the vertical and horizontal velocity of the snake according to the | |
* key that was pressed. | |
* The direction cannot be switched to the opposite direction, to prevent the snake | |
* from reversing | |
* For example if the the direction is 'right' it cannot become 'left' | |
* @param { object } event - The keydown event | |
*/ | |
function changeDirection(event) { | |
const LEFT_KEY = 37; | |
const RIGHT_KEY = 39; | |
const UP_KEY = 38; | |
const DOWN_KEY = 40; | |
/** | |
* Prevent the snake from reversing | |
* Example scenario: | |
* Snake is moving to the right. User presses down and immediately left | |
* and the snake immediately changes direction without taking a step down first | |
*/ | |
if (changingDirection) return; | |
changingDirection = true; | |
const keyPressed = event.keyCode; | |
const goingUp = dy === -10; | |
const goingDown = dy === 10; | |
const goingRight = dx === 10; | |
const goingLeft = dx === -10; | |
if (keyPressed === LEFT_KEY && !goingRight) { | |
dx = -10; | |
dy = 0; | |
} | |
if (keyPressed === UP_KEY && !goingDown) { | |
dx = 0; | |
dy = -10; | |
} | |
if (keyPressed === RIGHT_KEY && !goingLeft) { | |
dx = 10; | |
dy = 0; | |
} | |
if (keyPressed === DOWN_KEY && !goingUp) { | |
dx = 0; | |
dy = 10; | |
} | |
} | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment