Sitemap / Advertise

Introduction

This particular game is an iteration of the well-known Snake in JavaScript, including a scoreboard and ranks up to five high scores if you are a subscriber of TheAmplituhedron.


Tags

Share

JavaScript Game: Snake with ScoreBoard

Advertisement:


read_later

Read Later



read_later

Read Later

Introduction

This particular game is an iteration of the well-known Snake in JavaScript, including a scoreboard and ranks up to five high scores if you are a subscriber of TheAmplituhedron.

Tags

Share





Advertisement

Advertisement





Introduction

I am new to developing and designing a game using JavaScript. In this project series named JavaScript Game, I will share games I develop from basic to advanced. This particular game is an iteration of the well-known Snake in JavaScript, including a scoreboard and ranks up to five high scores if you are a subscriber of TheAmplituhedron.

It is a competitive game: when you exceed the highest scores defined as ranks, your username will be registered as the new highest rank scorer on the database.

Click 'Preview' to Play.

For more information about the code files, please visit the Source Code section below.

Step 1: Features

- Move the snake using arrow keys or the mobile keyboard to eat the fruit.

project-image
Figure - 33.1

- Pause and resume the game using the escape key or the P mobile key.

project-image
Figure - 33.2

- There are three different levels in this game:

- In Level 1, each strawberry increases the sum of the score by 10 points.

project-image
Figure - 33.3

- In Level 2, each kiwi increases the sum of the score by 20 points.

project-image
Figure - 33.4

- In Level 3, each meat increases the sum of the score by 30 points, and also the border restriction is activated.

project-image
Figure - 33.5

- When you hit the snake's tail or the border, only in Level 3, with the snake's head, the game is over.

project-image
Figure - 33.6

- Note: You have to sign in to join to the scoreboard on TheAmplituhedron.

project-image
Figure - 33.7

- If you hit a new highest record defined as rank, you will be the new highest rank scorer in the scoreboard :)

project-image
Figure - 33.8

Step 2: Index

- Import the Game class.

- Get the canvas element.

- Create the 2d context to draw.

- Define the game width and the game height.

- Define the game object.

- Get the highest scores from the score.php file as the response by making a get request in jQuery / AJAX.

- Create the loop function to update and draw the game using requestAnimationFrame(loop);.

project-image
Figure - 33.9

Step 3: Game

- Import Snake, InputHandler, Fruit, and Collision classes.

- Define game states.

- Create and define game objects. And, attach the class itself to the created game objects.

- Update each game object if the game state is not paused or gameover.

- Print the game score and the current game level.

- Proceed after drawing each object unless there is not a change in the game state.

- Define pause() and gameOver() functions.

- In the gameOver() function, send the new score to the score.php file and get the notification messages after interpretation as the response.

project-image
Figure - 33.10


project-image
Figure - 33.11


project-image
Figure - 33.12

Step 4: Snake

- Get the information including the game width and the game height from the Game class.

- Define the snake's properties - width, height, speed, accelerate, position, score, and level.

- Define the tail array and the initial tail size.

- Define moveRight(), moveLeft(), moveUp(), and moveDown() functions.

- Draw the background of the canvas.

- If the tail size is greater than nought, draw each tail unit in the tail array recursively.

- Draw the snake's head.

- If the current tail size is greater than the length of the tail array, add a new tail unit to the tail array using the push() function.

- Otherwise, update the tail units in the tail array using shift() and push() functions.

- Update the snake position.

- If the current game level is 3, activate the border restriction.

project-image
Figure - 33.13


project-image
Figure - 33.14


project-image
Figure - 33.15

Step 5: Fruit

- Get the information including the game width and the game height from the Game class.

- Define the fruit's properties - width, height, and position.

- In the nextFruit() function, define the position of the next fruit using Math.floor() and Math.random() functions.

- Depending on the current game level, draw the next fruit - strawberry, kiwi, or meat.

project-image
Figure - 33.16

Step 6: Collision

- Get objects and variables from the Game class.

- In the detect_fruit_collision(snake, fruit) function, detect whether the snake eats the fruit or not.

- If it is true, create the new fruit and increase the tail size by 1.

- If the current tail size equals 51, instigate level 2.

- If the current tail size equals 101, instigate level 3.

- Define fruit points per level.

- In the detect_tail_collision(snake, tail) function, detect whether the snake hits its tail or not.

- Update the detect_tail_collision(snake, tail) function for each tail unit.

project-image
Figure - 33.17


project-image
Figure - 33.18

Step 7: InputHandler

- Get objects and variables from the Game class.

- If the right arrow is pressed or the mobile right key is clicked, execute the moveRight() function.

- If the left arrow is pressed or the mobile left key is clicked, execute the moveLeft() function.

- If the up arrow is pressed or the mobile up key is clicked, execute the moveUp() function.

- If the down arrow is pressed or the mobile down key is clicked, execute the moveDown() function.

- To avoid the page navigation while pressing arrows, use event.preventDefault();

- If the escape key is pressed or the mobile P key is clicked, pause or resume the game.

- If the space key is pressed or the mobile P key is clicked, restart the game in the GAMEOVER state.

project-image
Figure - 33.19

Step 8: Score

- Define the database information to connect to the database table.

- Select ranks from the database table.

- If the scores variable is set, print all ranks and usernames.

- If the new score variable is set, add each rank to the scores array using the array_push() function.

- If the new score is higher than any ranks, update the database and print a congratulations notification message as the response.

- Otherwise, print a try again notification message as the response.

project-image
Figure - 33.20


project-image
Figure - 33.21

Source Code

Please sign in to download source code files and the game, including index.css, index.html, and assets, in a zipped folder to play and try the game on your computer.

Zip Folder

Download


Index

Download



import Game from './game.js';

let canvas = document.getElementById("game");
let ctx = canvas.getContext("2d");

const GAME_WIDTH = 300;
const GAME_HEIGHT = 500;

let game = new Game(GAME_WIDTH, GAME_HEIGHT);

// GET SCORES

  $.ajax({		 
    url: 'score.php?scores=true', 
	type: 'GET',
	async:true,
	success: (result) => {if(result) $(".scoreBoard").append(result); }
  });

function loop(timestamp){
	// Define if needed.
	let deltaTime;
	
	ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
    game.update(deltaTime);
	game.draw(ctx);
	
	requestAnimationFrame(loop);
}

requestAnimationFrame(loop);


Game

Download



import Snake from './snake.js';
import InputHandler from './keyInput.js';
import Fruit from './fruit.js';
import Collision from './collision.js';

export default class Game{
	constructor(gameWidth, gameHeight){
		this.gameWidth = gameWidth;
		this.gameHeight = gameHeight;
		
		this.STATE = {
			PAUSED: 0,
			RUNNING: 1,
			GAMEOVER: 2
		};
		
	    this.snake = new Snake(this);
        this.fruit = new Fruit(this);
		this.collision = new Collision(this);
		new InputHandler(this);
		this.objects = [this.snake, this.fruit, this.collision];

	}
	
	update(deltaTime){
		if(this.gameState == this.STATE.PAUSED || this.gameState == this.STATE.GAMEOVER) return;
		this.objects.forEach((object) => object.update(deltaTime));
	}
	
	draw(ctx){
		this.objects.forEach((object) => object.draw(ctx));
		
		ctx.font = "bold 20px Times";
		ctx.fillStyle = "#eb2e00";
		ctx.fillText("SCORE: " + this.snake.properties.score, 10, 25);
		ctx.fillText("LEVEL: " + this.snake.properties.LEVEL, 10, this.gameHeight - 25);
		
		if(this.gameState == this.STATE.PAUSED){
			ctx.fillStyle = "rgba(78, 88, 81, 0.6)";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("PAUSED", this.gameWidth / 2, this.gameHeight / 3);
			ctx.textAlign = "left";
		}
		
		if(this.gameState == this.STATE.GAMEOVER){
			ctx.fillStyle = "#1F2020";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("GAME OVER !!!", this.gameWidth / 2, this.gameHeight / 2);
		}

	}
	
	pause(){
		if(this.gameState == this.STATE.PAUSED){
			this.gameState = this.STATE.RUNNING
		}else{
			this.gameState = this.STATE.PAUSED;
		}
	}
	
	gameOver(){
		this.gameState = this.STATE.GAMEOVER;
		
		// SEND NEW SCORE TO THE DATABASE
		$.ajax({
			url: 'score.php?new_score=' + this.snake.properties.score,
			type: 'GET',
			async:true,
			success: (result) => { alert(result); }
		});
	}
}


Snake

Download



export default class Snake{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			width: 10,
			height: 10,
			speed: {x: 0, y: 0},
			accelerate: 5,
			position: {x : 15, y: 50},
			score: 0,
			LEVEL: 1
		};
		
		this.tail = [];
		this.tailSize = 0;
	}
	
	moveRight(){
		this.properties.speed.x = -this.properties.accelerate;
		this.properties.speed.y = 0;
	}
	
	moveLeft(){
		this.properties.speed.x = this.properties.accelerate;
		this.properties.speed.y = 0;
	}
	
	moveUp(){
		this.properties.speed.x = 0;
		this.properties.speed.y = -this.properties.accelerate;
	}
	
	moveDown(){
		this.properties.speed.x = 0;
		this.properties.speed.y = this.properties.accelerate;
	}
	
	draw(ctx){
		ctx.drawImage(document.getElementById("background"), 0, 0, this.gameWidth, this.gameHeight);
		ctx.fillStyle = "red";
		ctx.strokeStyle = "white";
        if(this.tailSize > 0) this.tail.forEach((object) => {
			ctx.fillRect(object.position.x, object.position.y, this.properties.width, this.properties.height);
			ctx.strokeRect(object.position.x, object.position.y, this.properties.width, this.properties.height);
		});
		ctx.fillStyle = "black";
		ctx.fillRect(this.properties.position.x, this.properties.position.y, this.properties.width, this.properties.height);
		ctx.strokeRect(this.properties.position.x, this.properties.position.y, this.properties.width, this.properties.height);
	}
	
	update(deltaTime){
		if(this.tailSize > this.tail.length){
	        this.tail.push({position: {x: this.properties.position.x, y: this.properties.position.y}});
		}else{
			this.tail.shift();
	        this.tail.push({position: {x: this.properties.position.x, y: this.properties.position.y}});
		}
		
		this.properties.position.x += this.properties.speed.x;
		this.properties.position.y += this.properties.speed.y;
		
		if(this.properties.LEVEL == 3){
			$("canvas").css("border", "10px solid black");
			if(this.properties.position.x > this.gameWidth || this.properties.position.x < 0 || this.properties.position.y > this.gameHeight || this.properties.position.y < 0) this.game.gameOver();
		}else{
			if(this.properties.position.x > this.gameWidth) this.properties.position.x = 0;
		    if(this.properties.position.x < 0) this.properties.position.x = this.gameWidth;
	        if(this.properties.position.y > this.gameHeight) this.properties.position.y = 0;
	        if(this.properties.position.y < 0) this.properties.position.y = this.gameHeight;
		}
		

	}
}


Fruit

Download



export default class Fruit{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			width: 20,
			height: 20,
			position: {x: Math.floor(Math.random() * 14) * 20, y: Math.floor(Math.random() * 25) * 20}
		};
		
	}
	
	nextFruit(){
		this.properties.position.x = Math.floor(Math.random() * 14) * this.properties.width;
		this.properties.position.y = Math.floor(Math.random() * 25) * this.properties.height;
	}
	
	draw(ctx){
		let Image;
		if(this.game.snake.properties.LEVEL == 1) Image = document.getElementById("fruit");
		if(this.game.snake.properties.LEVEL == 2) Image = document.getElementById("fruit_2");
		if(this.game.snake.properties.LEVEL == 3) Image = document.getElementById("fruit_3");
		
		ctx.drawImage(Image, this.properties.position.x, this.properties.position.y, this.properties.width, this.properties.height);
	}
		
	update(deltaTime){
		
	}
}


Collision

Download



export default class Collision{
	constructor(game){
        // GET ALL INFO FROM THE GAME CLASS
		this.game = game;
	}
	
	detect_fruit_collision(snake, fruit){
		let bottomOfFruit = fruit.properties.position.y + fruit.properties.height;
        let leftOfFruit = fruit.properties.position.x;
        let rightOfFruit = fruit.properties.position.x + fruit.properties.width;
        let topOfFruit = fruit.properties.position.y;
        
        let bottomOfSnake = snake.properties.position.y + snake.properties.height;
        let leftOfSnake = snake.properties.position.x;		
        let rightOfSnake = snake.properties.position.x + snake.properties.width;		
        let topOfSnake = snake.properties.position.y;
        
        if(leftOfSnake < rightOfFruit && rightOfSnake > leftOfFruit && topOfSnake < bottomOfFruit && bottomOfSnake > topOfFruit){
			fruit.nextFruit();
			snake.tailSize++;
			
			if(snake.tailSize == 51) snake.properties.LEVEL = 2;
			if(snake.tailSize == 101) snake.properties.LEVEL = 3;
			
		    if(snake.properties.LEVEL == 1){snake.properties.score += 10;}
		    else if(snake.properties.LEVEL == 2){snake.properties.score += 20;}
		    else if(snake.properties.LEVEL == 3){snake.properties.score += 30;}
		}
	}
	
    detect_tail_collision(snake, tail){	
		if(snake.properties.position.x === tail.position.x && snake.properties.position.y === tail.position.y && snake.tailSize > 0){
			this.game.gameOver();
		}
	
    }
	
	draw(ctx){
		
	}
	
	update(deltaTime){
		this.detect_fruit_collision(this.game.snake, this.game.fruit);
		this.game.snake.tail.forEach((object) => {this.detect_tail_collision(this.game.snake, object)});
	}
}


InputHandler

Download



export default class InputHandler{
	constructor(game){
	    // GET ALL INFO FROM THE GAME CLASS
		this.game = game;
		
		let direction;
		document.addEventListener("keydown", (event) => {
			let key = event.keyCode || event.which;
			
			if(key == 37 && direction != "LEFT"){direction = "RIGHT"; this.game.snake.moveRight();}
			if(key == 38 && direction != "DOWN"){event.preventDefault(); direction = "UP"; this.game.snake.moveUp();}
			if(key == 39 && direction != "RIGHT"){direction = "LEFT"; this.game.snake.moveLeft();}
			if(key == 40 && direction != "UP"){event.preventDefault(); direction = "DOWN"; this.game.snake.moveDown();}
			if(key == 27 && this.game.gameState != this.game.STATE.GAMEOVER){this.game.pause();}
			if(key == 32 && this.game.gameState == this.game.STATE.GAMEOVER){event.preventDefault(); location.reload();}

		});
		
		// MOBILE KEYBOARD
		
		document.getElementById("RIGHT").addEventListener("click", () => {
			if(direction != "LEFT"){direction = "RIGHT"; this.game.snake.moveRight();}
		});
		
		document.getElementById("UP").addEventListener("click", () => {
			if(direction != "DOWN"){direction = "UP"; this.game.snake.moveUp();}
		});
		
		document.getElementById("LEFT").addEventListener("click", () => {
			if(direction != "RIGHT"){direction = "LEFT"; this.game.snake.moveLeft();}
		});
		
		document.getElementById("DOWN").addEventListener("click", () => {
			if(direction != "UP"){direction = "DOWN"; this.game.snake.moveDown();}
		});
		
		document.getElementById("SPACE").addEventListener("click", () => {
			if(this.game.gameState != this.game.STATE.GAMEOVER){this.game.pause();}
			else{location.reload();}
		});
		
	}
}


Score

Download



<?php

$DBServerName = "[SERVER_NAME]";
$DBUserName = "[USER_NAME]";
$DBPassword = "[PASSWORD]";
$DBName = "[DATABASE_NAME]";

$conn = mysqli_connect($DBServerName, $DBUserName, $DBPassword, $DBName);

// CREATE A TABLE ON YOUR SERVER, INCLUDING USERNAME, SCORE, AND RANK VARIABLES
$sql = "SELECT * FROM `[TABLE_NAME]`";
$result = mysqli_query($conn, $sql);

if(isset($_GET['scores'])){
	while($row = mysqli_fetch_assoc($result)){
		echo '<p>'.$row["username"].': '.$row["score"].'</p>';
	}
	exit();
}

if(isset($_GET['new_score'])){
	  $new_score = $_GET['new_score'];
	  $username = "[USER_NAME]";
	  $scores = [];
	  while($row = mysqli_fetch_assoc($result)){
	  	  array_push($scores, $row['score']);
	  }
	
	  if($new_score > $scores[0]){
		  $sql = "UPDATE `snake` SET `username` = '$username', `score` = '$new_score' WHERE rank=1";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_1 exceeded :)';
		  exit();
	  }else if($new_score > $scores[1]){
		  $sql = "UPDATE `snake` SET `username` = '$username', `score` = '$new_score' WHERE rank=2";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_2 exceeded :)';
		  exit();
	  }else if($new_score > $scores[2]){
		  $sql = "UPDATE `snake` SET `username` = '$username', `score` = '$new_score' WHERE rank=3";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_3 exceeded :)';
		  exit();
	  }else if($new_score > $scores[3]){
		  $sql = "UPDATE `snake` SET `username` = '$username', `score` = '$new_score' WHERE rank=4";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_4 exceeded :)';
		  exit();
	  }else if($new_score > $scores[4]){
		  $sql = "UPDATE `snake` SET `username` = '$username', `score` = '$new_score' WHERE rank=5";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_5 exceeded :)';
		  exit();
	  }else{
		  echo 'Try again to hit a new record :)';
		  exit();
	  }
  }

 ?>