1. Game Development
  2. Game Design

How to Create an HTML5 Hangman Game With Canvas: Bells and Whistles

Read Time:24 minsLanguages:

Welcome to the second part of this HTML5 Hangman game tutorial. Now that the basic gameplay is completed, we'll add extra bells and whistles: a localStorage score system, animation and sound, and keyboard control.

Also available in this series:

  1. How to Create an HTML5 Hangman Game With Canvas: The Basic Gameplay
  2. How to Create an HTML5 Hangman Game With Canvas: Bells and Whistles

Step 25: drawText()

At the end of the first part of this tutorial, we were showing the word only when the player lost a game. Let's remove that code and show the word at the beginning again, to help with debugging.

Remove the line alert("The correct word was\n "+hangman.theWord) from within the drawCanvas() function, and then within the createGuessWord() function add the following.

We need to draw the text "YOU LOSE!!" when the player loses a game. We are already drawing the guess word, so we are starting to repeat ourselves - and any time you do something more than once in your programs you should encapsulate the code into a reusable function.

We will make a generic drawText() function that handles drawing text on the canvas. Enter the following code above the drawCanvas function.

All we are doing here is encapsulating the drawing code. We pass in a word to draw, the x and y positions, a font, and the color. We will put this function to use in the next step.

Step 26: Drawing the 'Lose' Text

Add the following within the drawCanvas() function.

We are calling our drawText() function and 35px red text to the screen with the words "You Lose!!". If you test now and lose a game the word should appear.

Step 27: Updating doWin()

In this step we will code the animation that plays when the player guesses the word correctly. This will be accomplished by using a sprite sheet.

We need a couple of new variables, so add the following to your hangman object.

Make sure you add a comma after gameOver: false. The imageCounter is used to keep track of which "frame" we are on in the sprite sheet, and the imageInterval is used to repeat a block of code continuosly by calling setInterval().

Modify the doWin function to the following.

Here we reset the imageCounter property of the hangman object to 0, and set imageInterval to a new Interval that calls a function named drawWinScreen every 120 milliseconds. We will code the drawWinscreen() in the next steps, but first we need to load our image.

Here is a link to setInterval() on the site, with some examples to help you better understand it.

Step 28: Loading the Sprite Sheet

In this step we will load the sprite sheet that is used for the hangman animation. We need a new variable for our sprite sheet image, so add the following to the hangman object.

Make sure you add a comma after the imageInterval: null. Then add the following code below the hangman object.

Here we add an event listener to the image, of type "load". This gets fired when the image has loaded and calls the incrementAssetsLoaded function. We then set the image's src to "spriteSheet.png", which you can find in the download files.

Now update the incrementAssetsLoaded() to the following.

All we are doing is checking whether assetsLoaded is now equal to 2 (i.e. whether both files have loaded).

Step 29: drawWinScreen()

Now that our sprite sheet image is loading, we can continue and write the drawWinScreen() function.

Add the following code below the doWin() function:

The first thing we do is clear the canvas. We then check if imageCounter is greater than or equal to 85 (this is the number of frames in the sprite sheet I'm using). If it is, we clear the interval we set up in the doWin() function and use setTimeOut() to call the function clearAndRestartGame after three seconds. We then use the drawImage() method of the canvas to draw our image, which will be one section of the spritesheet (a frame of the animation).

We draw some text to the screen using our drawText() function and then we increment the imageCounter property of the hangman object.

The canvas has three different drawImage() image methods, and I have listed them below with their signatures.

  • drawImage(image, x, y)
  • drawImage(image, x, y, width, height)
  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

The first two should be self explanatory; the only difference between the two of them is in the second one you can specify the width and height. The third one takes a whopping nine arguments, and isn't too easy to understand by looking a the signature. This method allows you to copy a portion of an image to the canvas. The sx and sy are the source image's x and y, the sWidth and sHeight are the source images width and height, and the others are the destination x, y, width, and height on the canvas.

The images in the sprite sheet are 164*264, so you should now be able to piece together how ctx.drawImage(hangman.hangmanSheet, 164*hangman.imageCounter, 0, 164, 264, 136, 58, 164, 264) works. We use 164*hangman.imageCounter to pick each image from the sheet in turn, since it is made up of several 164x264px images side by side. The rest should be self explanatory.

While we are here, let's go ahead and code the clearAndRestartGame() function. Add the following below the drawWinScreen() function.

If you test the game now and guess the right word, you should see the hangman animation play with the words "YOU WIN!!". We will wire up the sound in the next steps.

Here is a link to clearInterval() on the

Step 30: Showing the Word After a Loss

Right now we are alerting the word at the start of each game, to help with debugging. But in the finished game we want to show the word only when the player loses a game. Modify the drawCanvas() function as follows:

Here along with drawing the the text "You Lose" we also show the original word if the player loses a round. Otherwise we draw the newGuessWord (the one with the question marks). Notice we removed the original code we had drawing the word at the beginning of the function as well.

Test now and lose a game; you should see the original word being drawn.

Step 31: HTML5 Audio

Out of the entire HTML5 specification, I would say that HTML5 audio is in the worst shape of all right now. Each browser vendor chooses what to support - some support MP3, some support OGG, some support PCM (wav), and some support a combination of these. There are other types of audio available, but they are less common. It could change any time as well: an update could drop support for one or the other. This does present a slight problem in that you cannot just grab an MP3 file and expect all browsers to play it. But luckily the HTML5 Audio specification specifies that the browser should provide a way to detect the type of files it can play. We will be writing some code to detect whether to play an MP3 or an OGG file a little later.

I tested this app in Internet Explorer 9, Chrome 17, and Firefox 4. Below is a chart showing what formats each browser supports.

In case you're interested I used to figure this out; this is a great site to see what HTML5 features a particular browser has implemented.

Step 32: Loading an Audio File

In this step we will see how to load an audio file. In the download files you'll find an MP3 and an OGG file. We will just work with the MP3 file for now, adding support for OGG in the coming steps.

We need a variable for our audio file, so add the following to your hangman object.

Here we set a variable danceMusic equal to a new Audio() object. Make sure you add a comma after hangmanSheet: new Image()

Now add the following code beneath the line hangman.hangmanSheet.src = "spriteSheet.png";.

Make sure you added the MP3 to your project folder. All we are doing here is setting the danceMusic src equal to "danceMusic.mp3", adding an event listener of type canplaythrough , and calling load() which load the audio file.

When I was first building this app, I added an event listener of type load instead of canplaythrough.... and it never fired off. I found out that HTML5 audio does not have a load event listener; instead canplaythrough should be used. This gets fired off when the browser has determined that it has downloaded enough of the audio that, should you start playing it, it would play all the way through without any interruption.

If you go to and look at the HTML5 audio specification you can learn more about the type of events the Audio Elements has. Press CTRL-F and type "canplaythrough" to learn about the event we added.

Now change the incrementAssetsLoaded() function as follows:

Here we are now checking that assetsLoaded is equal to 3 instead of 2, since we added a third asset. If you test now the game should start (but make sure you test in a browser that supports MP3!), provided you added the MP3 to your source folder. If not you will get a blank canvas, so we know the code is working.

Step 33: Playing the Audio File

In this step we will play the audio file whenever a user wins the game, along with the animation. Add the following code to drawWinScreen():

Here we just tell call If you have a keen eye you might realize we are calling the play() method every time we run drawWinScreen() which is currently every 120 milliseconds. Why does it not play over the top of itself, or start over each time? Well, I must admit I don't know myself; it must just be the way HTML5 audio works. The added bonus however, is that as soon as it ends, it does start playing again which is what I wanted it to do.

You will notice that it keeps playing even after a new game starts. We will fix that in the next step.

Step 34: Stopping the Audio

When I was writing this tutorial I tried the following: hangman.danceMusic.stop(). Well, it didn't work. Once again I turned to the HTML5 audio specification and found out that HTML5 Audio does not contain a stop() method. It does, however, have a pause() method. So add the following to the clearAndRestartGame() function:

Now play the game and win two times. What? The Audio starts where we paused it! We want it to stop and play from the begining. Well, seeing as HTML5 Audio does not have a stop() method we need to come up with something else.

It turns out we have a currentTime property, where we can set the track's current time (or position, if you will). All we need to do is reset it to 0, and we have mimicked a stop method. How did something so simple as a stop() method get left out of the HTML5 Audio Specs? Your guess is as good as mine, but at least we have a solution.

Add the following to the doWin() function:

Now if you test again the audio will stop when a new game begins, but will also replay from the beginning when you win again.

Step 35: Cross Browser Compatible Audio

We want the music to play no matter what browser the user happens to be using. In this step we will write some detection code to see whether we should play an MP3 or an OGG file. This should cover all browsers to date.

The HTML5 Audio specification states that the browser manufacturer should implement a canPlayType() method for the media elements, which includes audio. The canPlayType() method returns an empty string (a negative response), "maybe", or "probably" based on how confident the user agent is that it can play media resources of the given type. In our code we will test for the "maybe" and "probably" strings and assume everything is okay if we get one or the other.

The parameter you pass to canPlayType is the MIME type of the extension you are testing for. Add the following code beneath the checkGuess() function:

Here we set a temporary variable "extension" to an empty string. We are passing in a param called audio, which will be an Audio Element. In the first if statement we check whether it can play a MIME type of "audio/ogg", and if it passes we set extension equal to ".ogg".

In the second we test the MIME type "audio/mp3" and return ".mp3" if it passes.

Lastly, we return the extension. If browser supports both .ogg and .mp3, the the OGG file would be used because the else if branch would not get run.

Step 36: Playing the Audio

Now that we have our handy little getAudioExtension() function ready, let's make this app play the the audio no matter what browser the user happens to be using!

Add the following beneath the line var ctx = hangman.theCanvas.getContext("2d");

Here we set a variable danceMusicAudio equal to the string "danceMusic". We then we tack on the extenison by calling getAudioExtension(hangman.danceMusic) remember getAudioExtension() returns either ".ogg" or ".mp3" so we are making a string equal to "danceMusic.ogg" or "danceMusic.mp3". Also, notice how we passed in the Audio Element hangman.danceMusic which we set equal to new Audio() earlier in the tutorial.

Now we that we have the proper file we just need to update the audio's src, so modify the relevant line as follows:

Make sure you add the "danceMusic.ogg" file from the source download to your project and test it in Firefox. It should work as in the previous steps. (Test it here.)

By the way, if you a looking for a good free app to convert audio files, you should consider Audacity. It is open source as well, so if you fancy looking through code to see what makes it tick, it is there for the taking.

Step 37: Examining Local Storage

Local Storage is an exciting part of HTML5 that allows you to do browser side storage that is persistent, meaning it lasts between browser sessions. It only disappears when the user clears their browser cache.

It is a very easy API to use, but can be used in a few different ways. One way is to use localStorage.setItem('key','value'), and localStorage.getItem('key'). Another way is to use Object Notation: localStorage[key] = value to set a value, and theValue = localStorage[key] to retrive it. And, if that wasn't enough, there is yet a third way - Dot Notation: localStorage.key = value to set it, and theValue = localStorage.key to retrieve it.

I am opting for the third way in this tutorial, but if you prefer one of the other ways you can modify the code and it should work just fine. Local Storage does have a few other methods, but these are the only two methods we need: one to set a value and one to retrieve that value.

Step 38: setWinScore()

In this step we will code the setWinScore() function that gets run whenever the user starts the game. Add the following code beneath the getAudioExtension() function you created in the step above.

This code checks whether there is already a localStorage key with the value of numWins, and creates one if there isn't.

Now call this function directly underneath where you created it:

Step 39: updateWinScore

In this step we code the updateWinScore which adds one to localStorage.numWins when the player wins a game.

Add the following code beneath where you called the setWinScore function above.

Here we cast localStorage.numWins to a Number and add one to it.

Step 40: getWinScore()

We need a way to retrieve localStorage.numWins.

Add the following beneath the updateWinScore you coded in the step above:

Here we are making sure localStorage.numWins exists and then simply returning it.

Step 41: Local Storage for Wins

Now that we have a system setup to set and retrieve the number of times a user has won the game, let's implement it.

Add the following code within the doWin() function:

Here we call just updateWinScore() each time the player wins the game. Now add the following code within the drawCanvas() function:

Here we are using our drawText() function to draw the word "Games Won" and the number of times the user has won. Since getWinScore() returns the number as a string, we just concatenate it onto the end of "Games Won".

If you test now you should see "Games Won 0", and if you keep winning games it should increment. Close the browser and test again, and your score should still be there.

Step 42: setLoseScore()

Add the following code beneath the getWinScore() function you created in the step above.

This is the same code as the setWinScore(), only we are setting localStorage.numLost. Make sure you call the function as well.

Step 43: updateLostScore()

In this step we update the lost score. Same deal as before, only we are using localStorage.numLost.

Step 44: getLostScore()

Add the following beneath the updateLostScore() function you added in the step above:

Same again, I am sure you get the point! Moving on.

Step 45: Local Storage for Losses

In this step we will implement the Local Storage for when the player loses a game.

Add the following code to the drawCanvas() function:

Here we update localStorage.numLost and draw the number of times the player has lost.

Test the page now and lose a few games, close the browser and come back; you should see the last number of games lost is still showing.

Step 46: addKeyListener()

Some players may prefer to use the keyboard, instead of clicking the buttons. In this step we will begin adding the keyboard interaction. Add the following code beneath the enableButtons() function you created earlier in the tutorial.

Here we create a function addKeyListener() that uses jQuery's on() method to add a keyup event to the document. Now add it inside startGame().

We wait till the game starts to add the listener, because as with the buttons, if the user starts pressing keys before the assets have loaded it would mess the game up.

Now test the page and you should be able to press any keys on the keyboard and get an alert("Key Pressed") pop up.

Here is a link to jQuery's on() method so you can learn more about it.

Step 47: removeKeyListener()

In this step we will add the code to remove the key listener. We need to remove the key listener when the user wins or loses a game, just like how we disable the buttons.

Add the following below the addKeyListener() function you created in the step above.

Here we call jQuery's off() method and pass in "keyup"; this turns off all listeners of type keyup.

Now add the following within the drawCanvas() function:

Here we call removeKeyListener() when the player loses a game. We also need to do this when the player wins a game, so add the following within the checkGuess() function@

Now test the page and press the a key on the keyboard you should get an alert, win or lose a game and a press a key on the keyboard, you should not get an alert. But, after a new game start then the alerts should be working again.

Here is link to jQuery's off() method so you can learn more about it.

Step 48: Finishing the Keyboard Interaction

Now that we have the key presses working at the correct times in the game, we will write the code that uses whichever letter the user has pressed in the checkGuess() function.

First, change the addKeyListener() function as follows:

Here we removed the alert("Key Pressed") and replaced it with a call to the checkGuess() function. We are passing the event and true along as parameters. If you remember, the buttons use this same checkGuess() function; we are passing true because this is a keypress. (With the buttons we passed false.)

Now modify the checkGuess function as follows - there is a lot of new code here but I will explain it step by step:

Here we create a new variable name RegEx that will be used as a regular expression test. We also moved the correctGuess up top with all the other variables.

We check if (isKeyPress == true); if it's false, we skip to the else part of the code, which should be familiar because it is the same code as before.

If it's true, then we set currentButton equal to "btn_"+String.fromCharCode(event.keyCode). This equates it to something like "btn_A","btn_G" etc. We are using the String.fromCharCode() method to turn the event.keyCode of the keyboard button into a letter.

Add an alert(event.keyCode) above the line currentButton = "btn_"+String.fromCharCode(event.keyCode) and test the page. When you press on different letters you should get a different number each time. Here is a chart showing the key codes for the keyboard, and a link to the String.fromCharCode() to help you better understand its usage.

Next we set theLetter equal to $("#"+currentButton).text().toLowerCase(). Here we are using a jQuery selector to get the correctButton. $("#"+currentButton) would equate to ("#btn_A"), ("#btn_G") etc - and remember, our clickable buttons have the IDs "btn_A", "btn_G", and so on. We next use the same selection technique to add a disabled attribute to the corresponding button, since it's already been guessed.

Finally we run the regular expression test against theLetter; if it does not pass we return from the function, so none of the other code gets run. The reason we are doing this is because we want to make sure they pressed a letter, specifically. If we did not do this, and they pressed, for instance, the Shift key or the number 7, then it would be counted as a wrong answer. The /[a-zA-Z]/ reqular expression matches any letter a-z whether lowercase or uppercase.

(Jeffrey Way recently released a course on regular expressions, so if this area is new to you be sure to check it out!)

If you play the game now you should be able to guess letters using the keyboard. But, there is still one bug we need to work out - can you find it? Read the next step to see what it is.

Step 49: Fixing the Keyboard Bug

Did you find it? It so happens, that if you press the same key twice it gets counted twice. You could essentially lose a game just by pressing the wrong key six times in a row! Let's fix this up.

Add the following to the hangman object:

Make sure you add a comma after the line danceMusic: new Audio(). Here we added a guessedLetters array. We will use this array to keep track of which letters the player has already guessed. Add the following code within the checkGuess() function:

Here we check whether the letter is already in the array by using the Array method indexOf(); this method returns the position of what you are searching for in the array. It returns -1 if it does not find it, otherwise it returns the index of the item within the array.

We don't really care about the letter's actual position, but if it is greater than or equal to zero we know that it is already in the array and has thus been guessed, so we return from the function, and none of the other code runs.

If the letter was not in the array then we push() it onto the end of the array.

Here are links to the indexOf() and push() methods of the array with some example code so you can learn more about them.

We need to reset the guessedLetters array when a new game begins, so add the following code within the doGameOver() method:

You can test now, and pressing the same key twice will have no ill effects.


By going through this tutorial you have learned quite a few HTML5 specific techniques, and have learned how to program a fun and interesting hangman game. I hope you have learned something useful, and thanks for reading!

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.