Snake Game

Fri May 02 2025 15:06:07 GMT+0100 (British Summer Time) canvasgamedevtutorialjavascripthtmlarchived

Snake game concept and mechanics

Snake is a popular and simple game found in many variations across most platforms that support graphics. From the oldest mobiles capable of supporting games to any modern OS, and arcade machines that predate both, you'll be able to find this addicting game.

Play the Snake example

This example will give code with comments and descriptions of function, however, it assumes the reader has a reasonable knowledge of programming.

The premise to this game is simple:

  • The game takes place in an enclosed area
  • The snake is made of segments that follow one another; each segment will effectively take the same path as the head (front of the snake)
  • The snake is always moving - the player simply changes the direction of movement
  • Randomly placed "food" appears on the map
  • When the snake touches the food, it consumes it. The length of the snakes body increases, and the player gains a point
  • If the snake touches the outer perimeter of the map, or another part of its own body, the game ends

With this in mind, our game must complete the following basic process whilst the game is running:

while [not lost game]:
    if [user direction input]: change direction

    nextPos = [position of snake head] + [movement in current direction]

    if nextPos==[outside map bounds]: lost game
    if nextPos==[in snake body except last entry]: lost game

    [snake body].prepend( nextPos )

    if nextPos==foodPos:
        foodPos = [random map position, not on snake]
        increase score
    else:
        [snake body].pop()

With this in mind, lets look at how our snake game is put together.

The HTML Document

To begin with, we'll create a HTML document. I've called mine "snake.htm". This will be a simple bare-bones webpage that includes reference to an external Javascript (which I've called "snake-game.js"), and a Canvas element with the ID game, and a width and height of 300px and 400px respectively.


<!DOCTYPE html>
<html>
<head>

<script type="text/javascript" src="snake-game.js"></script>

</head>
<body>

<canvas id="game" width="300" height="400"></canvas>

</body>
</html>

This is it for our HTML - we'll continue the rest of the game in Javascript.

Globals and configuration

In our Javascript file, (snake-game.js), we'll begin by creating some global variables. The first of these will store a reference to our Canvas elements 2D drawing context:

var ctx = null;

Additionally, we'll have some variables for keeping track of the frame rate of the game (for informational purposes), the currently elapsed game time and exact time of the last frame, and a boolean flag that determines whether or not we'll show the frame rate on the screen.

var gameTime = 0, lastFrameTime = 0;
var currentSecond = 0, frameCount = 0, framesLastSecond = 0;
var showFramerate = false;

We also need some offset variables - we'll use these to calculate where to draw the game on our canvas:

var offsetX = 0;
var offsetY = 0;

We also want to store some information about the cursor. Our global mouseState object will store the last recorded x, y position of the cursor on the Canvas, and a click parameter - null if no new mouse click has been recorded, otherwise the position in the form of an array ([x, y]):

var mouseState = {
    x    : 0,
    y    : 0,
    click    : null
};

gameState

The current state of the game is stored as an object with a number of attributes:

  • screen - the current screen to draw
  • dir - the current snake direction (up:0, right:1, down:2, left:3)
  • moveDelay - the time, in milliseconds, to move 1 tile
  • lastMove - the game time, in milliseconds, the snake began moving from its current tile to the next tile
  • snake - an array of map coordinate, starting at the snakes head and working to its tail, containing all of the segments of the snake
  • newBlock - the coordinates of the next piece of food the snake can consume
  • mapW, mapH - the map dimensions, in tiles
  • tileW, tileH - the map tile dimensions, in pixels
  • score - the current score during a game
  • newBest - a boolean flag to determine if the score the player has achieved at the end of a game is higher than the current bestScore
  • bestScore - the best score the player has achieved this session in a game
var gameState = {
    screen        : 'menu',
    
    dir        : 0,
    moveDelay    : 300,
    lastMove    : 0,
    
    snake        : [],
    newBlock    : null,
    
    mapW        : 14,
    mapH        : 14,
    tileW        : 20,
    tileH        : 20,
    
    score        : 0,
    newBest        : false,
    bestScore    : 0
};

Game logic

We'll now look at the functions used for processing and logic during the game.

When we start a new game, we need to reset the game time counters and all of the gameState properties that do not persist between games to their starting values. We create a brand new, 1 block snake at the centre of the map, and place a random block of food somewhere:

function startGame()
{
    // Reset the game time
    gameTime    = 0;
    lastFrameTime    = 0;
    
    // Reset all of the gameState attributes to their
    // starting values (except those that need to persist
    // between games)
    gameState.snake.length        = 0;
    gameState.dir            = 0;
    gameState.score            = 0;
    gameState.lastMove        = 0;
    gameState.screen        = 'playing';
    gameState.newBest        = false;
    
    // Create the snake head at the centre of the map
    gameState.snake.push([
        Math.floor(gameState.mapW / 2),
        Math.floor(gameState.mapH / 2)
    ]);
    
    // Place a random "food" block on the map
    placeNewBlock();
}

We place the random food block on the map by finding a random coordinate on the map, and checking that coordinate isn't already occupied by the snakes body. If it is, we find somewhere else. If it isn't, we place the food here and the function is complete:

function placeNewBlock()
{
    do {
        // Choose a random coordinate somewhere on the map
        var x = Math.floor(Math.random() * gameState.mapW);
        var y = Math.floor(Math.random() * gameState.mapH);
        
        // Currently we don't think this block falls on the
        // snakes body
        var onSnake = false;
        
        // Loop through the snakes body segments
        for(var s in gameState.snake)
        {
            // If this is on the snakes body, change our
            // onSnake value to true
            if(gameState.snake[s][0]==x &&
                gameState.snake[s][1]==y)
            {
                onSnake = true;
                break;
            }
        }
        
        // If the coordinates are not on the snakes body,
        // place the newBlock here and exit out of the
        // loop and function
        if(!onSnake)
        {
            gameState.newBlock = [x, y];
            break;
        }
    
    // Keep trying until the block can be placed
    } while(true);
}

Our updateGame function, which is called every frame, either looks for a click on the "New Game" text if the current screen is the menu screen. Otherwise, it runs through the update logic we discussed on the first page of this article.

function updateGame()
{
    // Depending on the current screen being displayed, we
    // handle game updates differently
    
    if(gameState.screen=='menu')
    {
        if(mouseState.click!=null)
        {
            // If there's been a click on the screen and
            // it falls on the "New Game" line of text,
            // call the startGame function
            if(mouseState.click[1] >= 150 &&
                mouseState.click[1] <= 220)
            {
                startGame();
            }
        }
        
        // Clear the click state (we've done the processing
        // for it already)
        mouseState.click = null;
    }
    else if(gameState.screen=='playing')
    {
        // If enough time hasn't elapsed since the snake began
        // moving from the previous tile to the next, we don't
        // need to do anything yet, so leave the function
        if((gameTime - gameState.lastMove) < gameState.moveDelay)
        {
            return;
        }
        
        // Create a temporary variable storing the current
        // position of the snakes
head, and also the current
        // direction of the snake
        var tmp = gameState.snake[0];
        var head = [tmp[0], tmp[1]];
        var dir = gameState.dir;

        // If the next position of the snakes head falls outside
        // of the maps bounds, we've lost; call gameOver
        if(dir==0 && head[1]==0) { gameOver(); }
        else if(dir==2 && head[1]==(gameState.mapH-1)) { gameOver(); }
        else if(dir==3 && head[0]==0) { gameOver(); }
        else if(dir==1 && head[0]==(gameState.mapW-1)) { gameOver(); }
        
        // Modify our head variable to the next position the
        // snakes head will occupy
        if(dir==0) { head[1]-= 1; }
        else if(dir==2) { head[1]+= 1; }
        else if(dir==1) { head[0]+= 1; }
        else if(dir==3) { head[0]-= 1; }
        
        // Loop through the snake body segments
        for(var s in gameState.snake)
        {
            // If this is the end of the snake, ignore it
            if(s==(gameState.snake.length-1)) { break; }
            
            // If the next position of the snakes head falls
            // on part of the snakes body, gameOver
            if(gameState.snake[s][0]==head[0] &&
                gameState.snake[s][1]==head[1])
            {
                gameOver();
                break;
            }
        }
        
        // If gameOver has been called, it will have changed
        // the current screen.  In this case, exit the function
        if(gameState.screen=='menu') { return; }
        
        // Put the new head position on the start of the snake
        // body array and update the lastMove to the current gameTime
        gameState.snake.unshift(head);
        gameState.lastMove = gameTime;
        
        // If the new head position is the same as the food position,
        // increase the score and place a random new food block.
        // Otherwise, remove the tail of the snake.
        if(gameState.newBlock[0]==head[0] &&
            gameState.newBlock[1]==head[1])
        {
            gameState.score+= 1;
            placeNewBlock();
        }
        else { gameState.snake.pop(); }
    }
}

If the game is over, we change the currently displayed screen to the menu, and check the score the player has achieved. If it is more than the current high score, we flag this as a new high score and update the gameState bestScore property:

function gameOver()
{
    gameState.screen = 'menu';
    if(gameState.score > gameState.bestScore)
    {
        // If a new best score has been achieved,
        // update the bestScore and newBest flag
        // accordingly
        gameState.bestScore    = gameState.score;
        gameState.newBest    = true;
    }
}

Onload calculations and setup

When the page loads we need to update our pointer to the Canvas 2D drawing context (ctx), and calculate the offsetX, offsetY to position the map centrally on the Canvas when playing.

We'll also create event listeners for the mouse and the arrow keys, and the F key (for toggling frame rate visibility). Additionally, we'll let the window know that when it's ready to draw to the Canvas we'll handle it with the drawGame function.

window.onload = function()
{
    // Create a reference to the Canvas 2D drawing context
    ctx = document.getElementById('game').getContext('2d');

    // Calculate the offsets needed to centre our map on the
    // Canvas
    offsetX = Math.floor((document.getElementById('game').width -
        (gameState.mapW * gameState.tileW)) / 2);
    offsetY = Math.floor((document.getElementById('game').height -
        (gameState.mapH * gameState.tileH)) / 2);
    
    document.getElementById('game').addEventListener('click', function(e) {
        // When the Canvas is clicked on, find the position
        // of the mouse click and record a click event in the
        // mouseState object
        var pos = realPos(e.pageX, e.pageY);
        mouseState.click = pos;
    });
    document.getElementById('game').addEventListener('mousemove',
    function(e) {
        // When the mouse is moved on the Canvas, find the true
        // cursor position and update the mouseState x,y accordingly
        var pos = realPos(e.pageX, e.pageY);
        mouseState.x = pos[0];
        mouseState.y = pos[1];
    });
    
    window.addEventListener('keydown', function(e) {
        // If an arrow key is pressed, update the direction (dir)
        // property accordingly.  If the F key is pressed, toggle
        // the visibility of the frame rate counter
        if(e.keyCode==38) { gameState.dir = 0; }
        else if(e.keyCode==39) { gameState.dir = 1; }
        else if(e.keyCode==40) { gameState.dir = 2; }
        else if(e.keyCode==37) { gameState.dir = 3; }
        else if(e.keyCode==70) { showFramerate = !showFramerate; }
    });
    
    // When the Canvas is ready to draw, call our drawGame method    
    requestAnimationFrame(drawGame);
};

Our offsetX, offsetY values are calculated by subtracting the width/height of the map (in pixels) from the width/height of the Canvas (in pixels), and dividing the result by 2.

Screen drawing methods

We now add our drawing functions. We make use of a main function, that handles updates, clearing the canvas, and calling the current screen drawing function, and a seperate drawing function for each of the 2 game screens (menu and playing).

Firstly, our drawMenu function shows the "New Game" text, and current statistics, such as the best score, whether or not the player achieved a high score on their most recent game, and the score for the most recent game (if applicable).

function drawMenu()
{
    // Set the font for the main screen text
    ctx.textAlign    = "center";
    ctx.font    = "bold italic 20pt sans-serif";

    // Set the colour based on whether or not the moue cursor
    // is over the "New Game" text, then draw New Game
    ctx.fillStyle = ((mouseState.y>=150 && mouseState.y<=220) ?
        "#0000aa" : "#000000");
    
    ctx.fillText("New game", 150, 180);
    
    // Change the font and show the current best score
    ctx.font        = "italic 12pt sans-serif";
    ctx.fillText("Best score: " + gameState.bestScore, 150, 210);
    
    if(gameState.newBest)
    {
        // If the player achieved a new top score during their
        // last game, say so
        ctx.fillText("New top score!", 150, 240);
    }
    if(gameState.score>0)
    {
        // If the player has just finished a game and scored any
        // points, then show the last score
        ctx.fillText("Last score: " + gameState.score, 150, 260);
    }
}

Next, we have a method for drawing the game during play, drawPlaying. This shows the current score, the bounds of the map, the snakes body segments and the food.

function drawPlaying()
{
    // Set the stroke and fill colours
    ctx.strokeStyle = "#000000";
    ctx.fillStyle    = "#000000";
    
    // Draw the bouding area of the map
    ctx.strokeRect(offsetX, offsetY,
        (gameState.mapW * gameState.tileW),
        (gameState.mapH * gameState.tileH));
    
    for(var s in gameState.snake)
    {
        // Loop through the snake body segments and draw each of them
        ctx.fillRect(offsetX + (gameState.snake[s][0] * gameState.tileW),
            offsetY + (gameState.snake[s][1] * gameState.tileH),
            gameState.tileW, gameState.tileH);
    }
    
    // Set the font for the current score and show it
    ctx.font = "12pt sans-serif";
    ctx.textAlign = "right";
    ctx.fillText("Score: " + gameState.score, 290, 20);
    
    // Set the fill colour for the food, and draw it on the map
    ctx.fillStyle    = "#00cc00";
    ctx.fillRect(offsetX + (gameState.newBlock[0] * gameState.tileW),
        offsetY + (gameState.newBlock[1] * gameState.tileH),
        gameState.tileW, gameState.tileH);
}

Finally, we have the drawGame function. This not only calls the drawing method for the current screen and displays the frame rate if visibility is toggled on, but also calculates the current frame rate, the game time, and calls the updateGame function for managing the games logic:

function drawGame()
{
    if(ctx==null) { return; }
    
    // Frame & update related timing
    var currentFrameTime = Date.now();
    var timeElapsed = currentFrameTime - lastFrameTime;
    gameTime+= timeElapsed;
    
    // Update game
    updateGame();

    // Frame counting
    var sec = Math.floor(Date.now()/1000);
    if(sec!=currentSecond)
    {
        currentSecond = sec;
        framesLastSecond = frameCount;
        frameCount = 1;
    }
    else { frameCount++; }
    
    // Clear canvas
    ctx.fillStyle = "#ddddee";
    ctx.fillRect(0, 0, 300, 400);

    // Set font and show framerate (if toggled)
    if(showFramerate)
    {
        ctx.textAlign = "left";
        ctx.font = "10pt sans-serif";
        ctx.fillStyle = "#000000";
        ctx.fillText("Frames: " + framesLastSecond, 5, 15);
    }
    
    // Draw the current screen
    if(gameState.screen=='menu')        { drawMenu(); }
    else if(gameState.screen=='playing')    { drawPlaying(); }
    
    // Update the lastFrameTime
    lastFrameTime = currentFrameTime;
    
    // Wait for the next frame...
    requestAnimationFrame(drawGame);
}

Mouse position on Canvas element

We also have a helper method that converts the pageX, pageY values in the mouse event listeners of the window.onload method to the true position relative to the top left of the Canvas element.

This is required as positions are given relative to the top left of the document, so we need to find the offset of the Canvas relative to this position and subtract it to get the actual mouse position on the Canvas.

function realPos(x, y)
{
    var p = document.getElementById('game');
    
    do {
        x-= p.offsetLeft;
        y-= p.offsetTop;
        
        p = p.offsetParent;
    } while(p!=null);
    
    return [x, y];
}

Javascript source code

var ctx = null;

var gameTime = 0, lastFrameTime = 0;
var currentSecond = 0, frameCount = 0, framesLastSecond = 0;
var showFramerate = false;

var offsetX = 0;
var offsetY = 0;

var mouseState = {
    x    : 0,
    y    : 0,
    click    : null
};

var gameState = {
    screen        : 'menu',
    
    dir        : 0,
    moveDelay    : 300,
    lastMove    : 0,
    
    snake        : [],
    newBlock    : null,
    
    mapW        : 14,
    mapH        : 14,
    tileW        : 20,
    tileH        : 20,
    
    score        : 0,
    newBest        : false,
    bestScore    : 0
};

function startGame()
{
    // Reset the game time
    gameTime    = 0;
    lastFrameTime    = 0;
    
    // Reset all of the gameState attributes to their
    // starting values (except those that need to persist
    // between games)
    gameState.snake.length    = 0;
    gameState.dir        = 0;
    gameState.score        = 0;
    gameState.lastMove    = 0;
    gameState.screen    = 'playing';
    gameState.newBest    = false;
    
    // Create the snake head at the centre of the map
    gameState.snake.push([
        Math.floor(gameState.mapW / 2),
        Math.floor(gameState.mapH / 2)
    ]);
    
    // Place a random "food" block on the map
    placeNewBlock();
}

function placeNewBlock()
{
    do {
        // Choose a random coordinate somewhere on the map
        var x = Math.floor(Math.random() * gameState.mapW);
        var y = Math.floor(Math.random() * gameState.mapH);
        
        // Currently we don't think this block falls on the
        // snakes body
        var onSnake = false;
        
        // Loop through the snakes body segments
        for(var s in gameState.snake)
        {
            // If this is on the snakes body, change our
            // onSnake value to true
            if(gameState.snake[s][0]==x &&
                gameState.snake[s][1]==y)
            {
                onSnake = true;
                break;
            }
        }
        
        // If the coordinates are not on the snakes body,
        // place the newBlock here and exit out of the
        // loop and function
        if(!onSnake)
        {
            gameState.newBlock = [x, y];
            break;
        }
    
    // Keep trying until the block can be placed
    } while(true);
}

function updateGame()
{
    // Depending on the current screen being displayed, we
    // handle game updates differently
    
    if(gameState.screen=='menu')
    {
        if(mouseState.click!=null)
        {
            // If there's been a click on the screen and
            // it falls on the "New Game" line of text,
            // call the startGame function
            if(mouseState.click[1] >= 150 &&
                mouseState.click[1]<= 220)
            {
                startGame();
            }
        }
        
        // Clear the click state (we've done the processing
        // for it already)
        mouseState.click = null;
    }
    else if(gameState.screen=='playing')
    {
        // If enough time hasn't elapsed since the snake began
        // moving from the previous tile to the next, we don't
        // need to do anything yet, so leave the function
        if((gameTime - gameState.lastMove) < gameState.moveDelay)
        {
            return;
        }
        
        // Create a temporary variable storing the current
        // position of the snakes head, and also the current
        // direction of the snake
        var tmp = gameState.snake[0];
        var head = [tmp[0], tmp[1]];
        var dir = gameState.dir;

        // If the next position of the snakes head falls outside
        // of the maps bounds, we've lost; call gameOver
        if(dir==0 && head[1]==0) { gameOver(); }
        else if(dir==2 && head[1]==(gameState.mapH-1)) { gameOver(); }
        else if(dir==3 && head[0]==0) { gameOver(); }
        else if(dir==1 && head[0]==(gameState.mapW-1)) { gameOver(); }
        
        // Modify our head variable to the next position the
        // snakes head will occupy
        if(dir==0) { head[1]-= 1; }
        else if(dir==2) { head[1]+= 1; }
        else if(dir==1) { head[0]+= 1; }
        else if(dir==3) { head[0]-= 1; }
        
        // Loop through the snake body segments
        for(var s in gameState.snake)
        {
            // If this is the end of the snake, ignore it
            if(s==(gameState.snake.length-1)) { break; }
            
            // If the next position of the snakes head falls
            // on part of the snakes body, gameOver
            if(gameState.snake[s][0]==head[0] &&
                gameState.snake[s][1]==head[1])
            {
                gameOver();
                break;
            }
        }
        
        // If gameOver has been called, it will have changed
        // the current screen.  In this case, exit the function
        if(gameState.screen=='menu') { return; }
        
        // Put the new head position on the start of the snake
        // body array and update the lastMove to the current gameTime
        gameState.snake.unshift(head);
        gameState.lastMove = gameTime;
        
        // If the new head position is the same as the food position,
        // increase the score and place a random new food block.
        // Otherwise, remove the tail of the snake.
        if(gameState.newBlock[0]==head[0] &&
            gameState.newBlock[1]==head[1])
        {
            gameState.score+= 1;
            placeNewBlock();
        }
        else { gameState.snake.pop(); }
    }
}

function gameOver()
{
    gameState.screen = 'menu';
    if(gameState.score > gameState.bestScore)
    {
        // If a new best score has been achieved,
        // update the bestScore and newBest flag
        // accordingly
        gameState.bestScore    = gameState.score;
        gameState.newBest    = true;
    }
}

window.onload = function()
{
    // Create a reference to the Canvas 2D drawing context
    ctx = document.getElementById('game').getContext('2d');

    // Calculate the offsets needed to centre our map on the
    // Canvas
    offsetX = Math.floor((document.getElementById('game').width -
        (gameState.mapW * gameState.tileW)) / 2);
    offsetY = Math.floor((document.getElementById('game').height -
        (gameState.mapH * gameState.tileH)) / 2);
    
    document.getElementById('game').addEventListener('click', function(e) {
        // When the Canvas is clicked on, find the position
        // of the mouse click and record a click event in the
        // mouseState object
        var pos = realPos(e.pageX, e.pageY);
        mouseState.click = pos;
    });
    document.getElementById('game').addEventListener('mousemove',
    function(e) {
        // When the mouse is moved on the Canvas, find the true
        // cursor position and update the mouseState x,y accordingly
        var pos = realPos(e.pageX, e.pageY);
        mouseState.x = pos[0];
        mouseState.y = pos[1];
    });
    
    window.addEventListener('keydown', function(e) {
        // If an arrow key is pressed, update the direction (dir)
        // property accordingly.  If the F key is pressed, toggle
        // the visibility of the frame rate counter
        if(e.keyCode==38) { gameState.dir = 0; }
        else if(e.keyCode==39) { gameState.dir = 1; }
        else if(e.keyCode==40) { gameState.dir = 2; }
        else if(e.keyCode==37) { gameState.dir = 3; }
        else if(e.keyCode==70) { showFramerate = !showFramerate; }
    });
    
    // When the Canvas is ready to draw, call our drawGame method    
    requestAnimationFrame(drawGame);
};

function drawMenu()
{
    // Set the font for the main screen text
    ctx.textAlign    = "center";
    ctx.font    = "bold italic 20pt sans-serif";

    // Set the colour based on whether or not the moue cursor
    // is over the "New Game" text, then draw New Game
    ctx.fillStyle = ((mouseState.y>=150 && mouseState.y<=220) ?
        "#0000aa" : "#000000");
    
    ctx.fillText("New game", 150, 180);
    
    // Change the font and show the current best score
    ctx.font        = "italic 12pt sans-serif";
    ctx.fillText("Best score: " + gameState.bestScore, 150, 210);
    
    if(gameState.newBest)
    {
        // If the player achieved a new top score during their
        // last game, say so
        ctx.fillText("New top score!", 150, 240);
    }
    if(gameState.score>0)
    {
        // If the player has just finished a game and scored any
        // points, then show the last score
        ctx.fillText("Last score: " + gameState.score, 150, 260);
    }
}

function drawPlaying()
{
    // Set the stroke and fill colours
    ctx.strokeStyle = "#000000";
    ctx.fillStyle    = "#000000";
    
    // Draw the bouding area of the map
    ctx.strokeRect(offsetX, offsetY,
        (gameState.mapW * gameState.tileW),
        (gameState.mapH * gameState.tileH));
    
    for(var s in gameState.snake)
    {
        // Loop through the snake body segments and draw each of them
        ctx.fillRect(offsetX + (gameState.snake[s][0] * gameState.tileW),
            offsetY + (gameState.snake[s][1] * gameState.tileH),
            gameState.tileW, gameState.tileH);
    }
    
    // Set the font for the current score and show it
    ctx.font = "12pt sans-serif";
    ctx.textAlign = "right";
    ctx.fillText("Score: " + gameState.score, 290, 20);
    
    // Set the fill colour for the food, and draw it on the map
    ctx.fillStyle    = "#00cc00";
    ctx.fillRect(offsetX + (gameState.newBlock[0] * gameState.tileW),
        offsetY + (gameState.newBlock[1] * gameState.tileH),
        gameState.tileW, gameState.tileH);
}

function drawGame()
{
    if(ctx==null) { return; }
    
    // Frame & update related timing
    var currentFrameTime = Date.now();
    var timeElapsed = currentFrameTime - lastFrameTime;
    gameTime+= timeElapsed;
    
    // Update game
    updateGame();
n
    // Frame counting
    var sec = Math.floor(Date.now()/1000);
    if(sec!=currentSecond)
    {
        currentSecond = sec;
        framesLastSecond = frameCount;
        frameCount = 1;
    }
    else { frameCount++; }
    
    // Clear canvas
    ctx.fillStyle = "#ddddee";
    ctx.fillRect(0, 0, 300, 400);

    // Set font and show framerate (if toggled)
    if(showFramerate)
    {
        ctx.textAlign = "left";
        ctx.font = "10pt sans-serif";
        ctx.fillStyle = "#000000";
        ctx.fillText("Frames: " + framesLastSecond, 5, 15);
    }
    
    // Draw the current screen
    if(gameState.screen=='menu')        { drawMenu(); }
    else if(gameState.screen=='playing')    { drawPlaying(); }
    
    // Update the lastFrameTime
    lastFrameTime = currentFrameTime;
    
    // Wait for the next frame...
    requestAnimationFrame(drawGame);
}

function realPos(x, y)
{
    var p = document.getElementById('game');
    
    do {
        x-= p.offsetLeft;
        y-= p.offsetTop;
        
        p = p.offsetParent;
    } while(p!=null);
    
    return [x, y];
}