In my last blog, I had posted the game of Snake that I developed as my first attempt in HTML5 programming and all I can say is... it was fun!
In this tutorial, I will show you how to use the basic features of HTML5 and get the simple game of Snake up and ready within a couple of hours, even if you are a beginner. All you require is some basic logic and any experience with programming is a huge plus. However, I must also admit here that this is more of a JavaScript work than HTML5, as it just uses the canvas and a couple more elements.
Okay, so let's begin!
First things first - Create an HTML file. Open your favorite editor, copy the snippet below and save it with a name as per your liking. I'll stick with "index.html".There is no different file extension for an HTML5 document. It simply uses one subtle and welcoming change in the "DOCTYPE
" declaration (welcoming as it's easy to remember compared to that from HTML4) and the browser will understand that it is HTML5.
Code Snippet
<!DOCTYPEHTML>
<html>
<head>
</head>
<body>
<divid="wrapper">
<h1>Snake</h1>
<divid="score">Score:0 Level:1</div>
<canvaswidth="300"height="400"id="canvas">
</canvas>
<divid="control">Controls: W = Up; A = Left; S = Down; D = Right</div>
</div>
</body>
</html>
Let's now look at what we have written here:
<!Doctype HTML> - As I mentioned above, a very simple and neat way to tell your HTML5 enabled browser that it will be accessing a HTML5 document. Then, there is a regular <head>
tag followed by the <body>
tag.
In the body of the page, we first define a div
named "wrapper
" which essentially will include everything that we are about to display. Note that this is not required in our example. Then we create a div
to display the game score and the level. As you'll find out later, we will also use this to inform the player if the game is over.
Then comes the main component, "Canvas
", the most essential HTML5 component for that can be used for rendering graphs, game graphics, or other visual images on the fly. HTML5 defines a set of functions for drawing various shapes, creating paths, shades and gradients with the help of JavaScript to display them within the Canvas
. For the purpose of this tutorial, I have defined canvas
with width as 300, height as 400 and id = "canvas"
.
Lastly, we define another div
to help user with the controls or any other message that we may want to display to user related to controls during gameplay or afterwards.
Now the fun part, the JavaScript for the game. We'll start with defining all the global variables that we'll require during our game. The script goes into the <head>
tag of the HTML file.
Code Snippet
var context;
var width = 300;
var height = 400;
var snakeLength = 3;
var level = 1;
var sqSize = 10;
var bodyX = new Array(150, 150-sqSize, 150-2*sqSize);
var bodyY = new Array(200, 200, 200);
var vX = new Array(1, 1, 1);
var vY = new Array(0, 0, 0);
var rX;
var rY;
var score = 0;
var scoreDiv;
var eaten = true;
var gameOver = false;
var controlsDiv;
</script>
Comments accompanying the variables are self explanatory, for those having any confusion, hold on for few minutes till you reach the code where they are used and it should get clear to you. Still have any doubt, ping it to me.
Another neat feature of HTML5, in case you haven't noticed till now is <script>
tag, which no longer requires to be specified with type
attribute.
Now let's have a look at a couple of drawing APIs for HTML5. Below, we define 3 functions:
- To draw the canvas boundary in order to mark our playing area
- To draw the points (squares) for the snake body
- Draw our snake
Code Snippet
function drawCanvasBoundary()
{
context.fillStyle="#FFF";
context.fillRect(0,0,width,height);
context.fill();
context.strokeStyle="#000";
context.strokeRect(0,0,width,height)
}
function drawPoint(x,y)
{
context.fillStyle = "#000";
context.fillRect(x,y,sqSize, sqSize);
context.fill();
context.strokeStyle="#FFFFFF";
context.strokeRect(x,y,sqSize, sqSize);
}
function drawSnake()
{
for(var i=0; i < snakeLength; i++)
drawPoint(bodyX[i],bodyY[i]);
}
Here, we essentially use 2 functions from the HTML5 APIs - strokeRect
and fillRect
and customize them using the corresponding style attributes, viz., strokeStyle
and fillStyle
. But what are these functions for? Well as the name suggests, strokeRect
is to draw a rectangle and fillRect
first draws the rectangle and then fills it with the color specified in its attributes. Does this remind you of anything familiar?? MSPaint??
So over here, we have defined that we need a white background for our playarea and snake has to be displayed in black color squares with white borders to give the impression of the digital displays of our older mobiles.
Now that we have written the drawing functions, let us display our canvas
and snake
.
Code Snippet
function init()
{
context = document.getElementById("canvas").getContext("2d");
drawCanvasBoundary();
drawSnake();
}
window.addEventListener("load", init, true);
The init
function first gets the "game context", 2D in this case (Canvas
supports 3D as well!). This context is required to tell our browser, where all the commands need to be executed. Then, we make function calls for the 2 draw
functions we previously defined. Lastly, we tell the browser to execute our init
function whenever the page gets loaded with the help of addEventListener
command. Now reload your page and you should see the following output:
Till now, we are just displaying a static output. Now it's time to add interactivity and move our snake. Let's define the functions to move snake and the controllers with their behaviors.
Code Snippet
function moveSnake()
{
for(var i=0; i < snakeLength; i++)
{
bodyX[i] += (vX[i]*sqSize);
bodyY[i] += (vY[i]*sqSize);
}
for(var i=snakeLength-1; i>0; i--)
{
vX[i] = vX[i-1];
vY[i] = vY[i-1];
}
}
function keydown(e)
{
if(e.keyCode == 65 && vX[0] != 1)
{
vX[0] = -1;
vY[0] = 0;
}
else if (e.keyCode == 87 && vY[0] != 1)
{
vY[0] = -1;
vX[0] = 0;
}
else if (e.keyCode == 68 && vX[0] != -1)
{
vX[0] = 1;
vY[0] = 0;
}
else if (e.keyCode == 83 && vY[0] != -1)
{
vY[0] = 1;
vX[0] = 0;
}
else if (e.keyCode == 13 && gameOver == true)
{
gameOver = false;
}
}
function gameProcess()
{
intervalId = setTimeout(gameProcess, 1000/(6*level));
clear();
drawCanvasBoundary();
moveSnake();
drawSnake();
}
function clear()
{
context.clearRect(0,0,width,height);
}
Okay, so we defined a lot of things here... let's take it one by one:
clear()
- As the comments say, this function simply clears the entire canvas which is then redrawn. keydown(e)
- Function to handle the key strokes for guiding our snake through the game, currently it only listens to "W,A,S,D" for directions and "Enter" to restart the game after it gets over.
Note: I had to change controls from arrow keys to WASD and include a restart key from what I learnt from the last blog post - 1. arrow keys were moving the page up n down; and 2. to restart the game, the user had to reload the entire page. moveSnake()
- Unlike any other object, different parts of Snake's body can move in directions different than its head which forces us to keep direction coordinates for each and every body part in the Vx and Vy arrays. The motion is however constrained by that of preceding body part. So the direction vector gets simply copied downwards. gameProcess()
or the "Game Loop", every game has this which basically performs - (update-draw), (update-draw),.... repeatedly. The function sets the time for next refresh, clean the canvas, calls moveSnake
to calculate the updated coordinates (the update
function of game) and finally draws snake
(the draw
function).
Now, let's refresh the page again... what happened? there is something wrong here... we forgot to initialize the game loop in our init
function, so let's do it now.
Code Snippet
intervalId = setTimeout(gameProcess, 1000/6);
scoreDiv = document.getElementById("score");
controlDiv = document.getElementById("control");
window.onkeydown = keydown;
Add these 4 commands in the init
functions and reload the page. Yippiee!!! Our snake is now moving. So where is the RAT?? Let's start putting the rats in the game and make our snake eat them.
Code Snippet
function placeRat()
{
if(eaten)
{
rX = Math.floor(width*Math.random()/sqSize)*sqSize;
rY = Math.floor(height*Math.random()/sqSize)*sqSize;
if(checkFoodCollision(rX,rY))
placeRat();
else
eaten = false;
}
drawPoint(rX, rY);
};
function checkFoodCollision(x, y)
{
for (var i = 0;i<snakeLength; i++)
if(x == bodyX[i]&& y == bodyY[i])
{
return true;
}
return false;
}
function eatRat()
{
if(bodyX[0] == rX && bodyY[0] == rY)
{
eaten = true;
var newX = bodyX[snakeLength-1]-vX[snakeLength-1]*sqSize;
var newY = bodyY[snakeLength-1]-vY[snakeLength-1]*sqSize;
bodyX.push(newX);
bodyY.push(newY);
vX.push(vX[snakeLength-1]);
vY.push(vY[snakeLength-1]);
snakeLength++;
score += 10;
if((score%100) == 0)
level++;
scoreDiv.innerHTML = "Score: "
+score+" Level: "+level;
}
}
Again, let's look at each function one-by-one: placeRat()
- This function uses the math library to generate a random position (x,y) coordinate on the canvas. It then checks if Snake's body is not hiding the new Rat
position, if it does - it calls for generating fresh set of coordinates otherwise, it sets eaten Rat
parameter to be false
and draws the new Rat
. checkFoodCollision()
- Function called by placeRat()
to check if the new rat position is not hidden under snake's body eatRat()
- Checks if Snake
's head is at the location of Rat
. If yes, it raise the rat is eaten flag to tell our program to generate new rat position. Then it increments snake length and add new body part at the end. Finally, it increments score and determine if the level needs to be increased as well, and then display both of them.
Also unhide the call to "eatRat()
" from moveSnake()
function and Add
, the call to placeRat()
to gameProcess()
function.
Our game of snake is almost ready, but for one last bit of work. Currently, if you try to move the snake past the boundary, it will go and disappear from the skin. Also try folding snake on itself, it will do because we haven't worked on the collision/termination conditions. Let's do it now!
Code Snippet
function checkCollision()
{
if(bodyX[0] >= width || bodyX[0] < 0 || bodyY[0] < 0 || bodyY[0] >= height)
{
scoreDiv.innerHTML = "Score: "
+score+" Level: "
+level+" <b>Game Over</b>";
controlDiv.innerHTML = "Press \"Enter\" to restart";
gameOver = true;
clearTimeout(intervalId);
}
else if(snakeLength > 4)
{
if(checkSelfCollision(bodyX[0],bodyY[0]))
{
scoreDiv.innerHTML = "Score: "
+score+" Level: "
+level+" <b>Game Over</b>";
controlDiv.innerHTML = "Press \"Enter\" to restart";
gameOver = true;
clearTimeout(intervalId);
}
}
}
function checkSelfCollision(x, y)
{
for (var i = 4; i < snakeLength; i++)
if(x == bodyX[i] && y == bodyY[i])
{
return true;
}
return false;
}
We need to check for 2 different types of collisions, one is snake colliding with the boundary walls and the other being, self collision.
checkSelfCollision()
- This function iterates over the body parts starting from 5 (why?) and checks whether the snake's head has not collided with the body part or not. checkCollision()
- Checks if the snake has collided with the boundary, if not, calls for checking selfCollision
Now, just add a call to checkCollision
before we drawSnake()
in the gameProcess()
function, so that the final gameProcess
function looks like this.
Code Snippet
function gameProcess()
{
intervalId = setTimeout(gameProcess, 1000/(6*level));
clear();
drawCanvasBoundary();
placeRat();
moveSnake();
checkCollision();
drawSnake();
}
Lastly, let's enable the control to restart the game when it gets over. Unhide the call to restart()
function from keydown()
function (look at the end where we handle case for keycode 13, i.e., "Enter Key") and then add then define the restart function as below.
Code Snippet
function restart()
{
bodyX = new Array(150, 150-sqSize, 150-2*sqSize);
bodyY = new Array(200, 200, 200);
vX = new Array(1, 1, 1);
vY = new Array(0, 0, 0);
snakeLength = 3;
score = 0;
level = 1;
eaten = true;
scoreDiv.innerHTML = "Score: " +score+"Level: "+level;
controlDiv.innerHTML = "Controls: W = Up; A = Left; S = Down; D = Right";
intervalId = setTimeout(gameProcess, 1000/6);
}
And we are done!!! The complete source code can be obtained from here. Enjoy the game here and do write your suggestions/comments.