Edit 2: For anyone interested, you can play the game at buggysnake.com
Edit 1: I have typed up the code for making the body of the snake move. It's not perfect and there are small problems I need to fix. But I have updated the javascript code below to include that code.
Background: I recently started learning Javascript about 3 weeks ago. About a week ago I learnt about the Canvas API.
As for my first project, I decided to make a snake game using the Canvas API. As I am a beginner in both Javascript and the Canvas API, I am not sure if what I have typed up is good code. I would appreciate if someone could review it and provide some feedback regarding simplifying it and maybe generalising it to make the movements work with the entire body of the snake. As it is now, the only possible way I can think of moving the snake is by moving individual parts of it. There is still one piece missing in the code. That piece is the part where the body of the snake moves after the head has. I haven't been able to add that part in yet.
There are a total of three files. The HTML, CSS and Javascript files. I have typed them up in that order. Just in case if anyone wants to run the game on their own computer. But the code in review is the Javascript code.
index.html
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas</title>
<script src="script.js" defer></script>
<link href="style.css" rel="stylesheet">
</head>
<body>
<canvas class="myCanvas">
<p>Something for now</p>
</canvas>
</body>
</html>
style.css
body {
margin: 0;
overflow: hidden;
}
script.js
const canvas = document.querySelector(".myCanvas");
const html = document.querySelector("html");
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let scoreCounter = 0;
let generalInstanceNumber = 0;
let current_x;
let current_y;
//An array for our snake body.
const snakes = [];
//An array for the keys pressed.
const keysPressed = [];
//The constructor for our snake.
function Snake(x, y,) {
this.alreadyTurnedRight = true;
this.alreadyTurnedLeft = true;
this.alreadyTurnedUp = true;
this.alreadyTurnedDown = true;
this.x = x;
this.y = y;
this.velx = 0;
this.vely = 0;
this.position_x = 0;
this.position_y = 0;
this.instanceNumber;
this.draw = function () {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, 50, 50);
}
this.updateRight = function () {
if (this.x >= width) {
gameStarted = false;
gameOver();
}
else if (this.y === current_y) {
this.velx = 0;
this.velx += 10;
this.x += this.velx;
this.alreadyTurnedRight = false;
this.alreadyTurnedLeft = true;
this.alreadyTurnedUp = true;
this.alreadyTurnedDown = true;
}
else {
if (keysPressed[keysPressed.length - 2] === "ArrowUp" && this.alreadyTurnedRight === true) {
this.y -= 10;
}
if (keysPressed[keysPressed.length -2] === "ArrowDown" && this.alreadyTurnedRight === true) {
this.y += 10;
}
}
}
this.updateLeft = function () {
if (this.x <= 0) {
gameStarted = false;
gameOver();
}
else if (this.y === current_y) {
this.velx = 0;
this.velx -= 10;
this.x += this.velx;
this.alreadyTurnedRight = true;
this.alreadyTurnedLeft = false;
this.alreadyTurnedUp = true;
this.alreadyTurnedDown = true;
}
else {
if (keysPressed[keysPressed.length - 2] === "ArrowUp" && this.alreadyTurnedLeft === true) {
this.y -= 10;
}
if (keysPressed[keysPressed.length - 2] === "ArrowDown" && this.alreadyTurnedLeft === true) {
this.y += 10;
}
}
}
this.updateUp = function () {
if (this.y <= 0) {
gameStarted = false;
gameOver();
}
else if (this.x === current_x) {
this.vely = 0;
this.vely -= 10;
this.y += this.vely;
this.alreadyTurnedRight = true;
this.alreadyTurnedLeft = true;
this.alreadyTurnedUp = false;
this.alreadyTurnedDown = true;
}
else {
if (keysPressed[keysPressed.length - 2] === "ArrowRight" && this.alreadyTurnedUp === true) {
this.x += 10;
}
if (keysPressed[keysPressed.length - 2] === "ArrowLeft" && this.alreadyTurnedUp === true) {
this.x -= 10;
}
}
}
this.updateDown = function () {
if (this.y >= height) {
gameStarted = false;
gameOver();
}
else if (this.x === current_x) {
this.vely = 0;
this.vely += 10;
this.y += this.vely;
this.alreadyTurnedRight = true;
this.alreadyTurnedLeft = true;
this.alreadyTurnedUp = true;
this.alreadyTurnedDown = false;
}
else {
if (keysPressed[keysPressed.length - 2] === "ArrowRight" && this.alreadyTurnedDown === true) {
this.x += 10;
}
if (keysPressed[keysPressed.length - 2] === "ArrowLeft" && this.alreadyTurnedDown === true) {
this.x -= 10;
}
}
}
}
//Function for clearing the canvas for a new drawing.
function updateCanvas() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
}
//Function for Game Over.
function gameOver() {
rPressed = true;
updateCanvas();
ctx.fillStyle = "red";
ctx.font = "70px helvetica";
ctx.fillText("Game Over", width/2 - 200, height/2);
ctx.fillStyle = "yellow";
ctx.font = "45px helvetica";
ctx.fillText("Press r to restart the game", width/2 - 280, height/2 + 100);
}
//Function for drawing the score.
function score() {
ctx.fillStyle = "blue";
ctx.font = "45px helvetica";
ctx.fillText(`Score: ${scoreCounter}`, 10, 50);
}
//Code for setting up the starting screen.
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = "Green";
ctx.font = "70px helvetica";
ctx.fillText("Snake Game", width/2 - 220, height/2);
ctx.fillStyle = "yellow";
ctx.font = "45px helvetica";
ctx.fillText("Press s to start the game", width/2 - 260, height/2 + 100);
let x = width/2;
let y = height/2;
const snake = new Snake(x, y);
snake.instanceNumber = 0;
snakes.push(snake);
let apple_x = randomNumber(0, width);
let apple_y = randomNumber(0, height);
const apple = new Apple(apple_x, apple_y);
//Function for starting the game.
let gameStarted = false;
addEventListener("keydown", setUpScreen);
function setUpScreen(event) {
if (event.key === "s") {
updateCanvas();
snake.draw();
apple.draw();
score();
gameStarted = true;
removeEventListener("keydown", setUpScreen);
}
}
//Code for restarting the game.
let rPressed = false;
addEventListener("keydown", restartGame);
function restartGame(event) {
if (event.key === "r" && rPressed === true) {
location.reload();
}
}
//Code for performing animations based on user input.
let rightKeyPressed = true;
let leftKeyPressed = true;
let upKeyPressed = true;
let downKeyPressed = true;
addEventListener("keydown", moveSnake);
function moveSnake(event) {
if (event.key === "ArrowRight" && gameStarted === true && rightKeyPressed === true) {
keysPressed.push("ArrowRight");
current_x = snake.x;
current_y = snake.y;
rightKeyPressed = false;
leftKeyPressed = true;
upKeyPressed = true;
downKeyPressed = true;
cancelAnimationFrame(requestLoopLeft);
cancelAnimationFrame(requestLoopUp);
cancelAnimationFrame(requestLoopDown);
loopRight();
}
if (event.key === "ArrowLeft" && gameStarted === true && leftKeyPressed === true) {
keysPressed.push("ArrowLeft");
current_x = snake.x;
current_y = snake.y;
rightKeyPressed = true;
leftKeyPressed = false;
upKeyPressed = true;
downKeyPressed = true;
cancelAnimationFrame(requestLoopRight);
cancelAnimationFrame(requestLoopUp);
cancelAnimationFrame(requestLoopDown);
loopLeft();
}
if (event.key === "ArrowUp" && gameStarted === true && upKeyPressed === true) {
keysPressed.push("ArrowUp");
current_x = snake.x;
current_y = snake.y;
rightKeyPressed = true;
leftKeyPressed = true;
upKeyPressed = false;
downKeyPressed = true;
cancelAnimationFrame(requestLoopLeft);
cancelAnimationFrame(requestLoopRight);
cancelAnimationFrame(requestLoopDown);
loopUp();
}
if (event.key === "ArrowDown" && gameStarted === true && downKeyPressed === true) {
keysPressed.push("ArrowDown");
current_x = snake.x;
current_y = snake.y;
rightKeyPressed = true;
leftKeyPressed = true;
upKeyPressed = true;
downKeyPressed = false;
cancelAnimationFrame(requestLoopLeft);
cancelAnimationFrame(requestLoopRight);
cancelAnimationFrame(requestLoopUp);
loopDown();
}
}
//Functions for animating the direction of the snake.
let requestLoopRight;
function loopRight(number) {
updateCanvas();
score();
apple.draw();
for (const element of snakes) {
element.draw();
element.updateRight();
}
apple.update();
//console.log(`from loopRight ${snake.x}`);
requestLoopRight = requestAnimationFrame(loopRight);
}
let requestLoopLeft;
function loopLeft() {
updateCanvas();
score();
apple.draw();
for (const element of snakes) {
element.draw();
element.updateLeft();
}
apple.update();
//console.log(`From loopLeft ${snake.x}`);
requestLoopLeft = requestAnimationFrame(loopLeft);
}
let requestLoopUp;
function loopUp(name) {
updateCanvas();
score();
apple.draw();
for (const element of snakes) {
element.draw();
element.updateUp();
}
apple.update();
//console.log(`From loopUp ${snake.y}`);
requestLoopUp = requestAnimationFrame(loopUp);
}
let requestLoopDown;
function loopDown() {
updateCanvas();
score();
apple.draw();
for (const element of snakes) {
element.draw();
element.updateDown();
}
apple.update();
//console.log(`From loopDown ${snake.y}`);
requestLoopDown = requestAnimationFrame(loopDown);
}
//Function for generating a random integer.
function randomNumber(min, max) {
return Math.floor(min + Math.random()*(max - min));
}
//The constructor for our apple.
function Apple(x, y) {
this.x = x;
this.y = y;
this.draw = function () {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.x, this.y, 28, 0, 2*Math.PI, false);
ctx.fill();
}
//This function includes the code for collision detection and adding a square to the snakes body.
this.update = function () {
let x = 0;
let y = 0;
if (Math.sqrt(((snake.x + 25) - this.x)*((snake.x + 25) - this.x) + ((snake.y + 25) - this.y)*((snake.y + 25) - this.y)) <= 28) {
this.x = randomNumber(0, width);
this.y = randomNumber(0, height);
scoreCounter += 1;
generalInstanceNumber += 1;
if (rightKeyPressed === false) {
let arrayLength = snakes.length - 1;
x = snakes[arrayLength].x - 50;
y = snakes[arrayLength].y;
const newSnake = new Snake(x, y);
newSnake.instanceNumber = generalInstanceNumber;
snakes.push(newSnake);
}
if (leftKeyPressed === false) {
let arrayLength = snakes.length - 1;
x = snakes[arrayLength].x + 50;
y = snakes[arrayLength].y;
const newSnake = new Snake(x, y);
newSnake.instanceNumber = generalInstanceNumber;
snakes.push(newSnake);
}
if (upKeyPressed === false) {
let arrayLength = snakes.length - 1;
x = snakes[arrayLength].x;
y = snakes[arrayLength].y + 50;
const newSnake = new Snake(x, y);
newSnake.instanceNumber = generalInstanceNumber;
snakes.push(newSnake);
}
if (downKeyPressed === false) {
let arrayLength = snakes.length - 1;
x = snakes[arrayLength].x;
y = snakes[arrayLength].y - 50;
const newSnake = new Snake(x, y);
newSnake.instanceNumber = generalInstanceNumber;
snakes.push(newSnake);
}
}
}
}