LESSON 14 - April 17, 2013

HTML5 GAME DEVELOPMENT

Operation C.A.N.T (Canvas Ain't No Thang) LESSON # 14

Ben W. Savage

Wow! 2 full weeks of tutorials! Hope you've enjoyed them so far! Today we'll be continuing a habit we're going to be keeping every week, namely one or a series of review exercises aimed at integrating the techniques we've learned into real game dev. techniques. Last week we didn't have as many tools at our disposal, so we made some silly animations, but this week, though this is by no means a complete game or even a FUN game at that, it's much more of a step in the direction we want to take. Look back at what we've gone through so far or take a look at the contents of today's code and you'll see that what we're dealing with this week is much more complex and interesting.

So let's get started!

I'm gonna lay the code on ya right off, then we'll pick through it piece by piece. Ready?

It's a beaut:

<!doctype html>
<title>WHACK-A-MOLE!</title>
<canvas id = "canvas" width = "550" height = "400"></canvas>
<style>
canvas
{
background-color: brown;
}
</style>
<script>

var theCanvas = document.getElementById("canvas");
var context = theCanvas.getContext("2d");

var randomness = Math.random() * 3000;
var randomness2 = Math.random() * 2500;

context.fillStyle = "#000000";
context.arc(150,200,50,0,6.285,false);
context.arc(350,200,50,0,6.285,false);
context.fill();

context.fillStyle = "#000000";
context.fillRect(125,175,50,50);
context.fillRect(325,175,50,50);

context.shadowColor = "#000000";
context.shadowOffsetX = 3;
context.shadowOffsetY = 3;
context.shadowBlur =.5;

context.fillStyle = "#ffffff";
context.font = "25px Arial";
context.fillText("Whack-a-mole, er... rectangle!",20,30);

context.font = "16px Arial";
context.fillText("Press a corresponding key (1-2) when a mole appears.",80,380);

context.fillText("1",150,300);
context.fillText("2",350,300);

setInterval(mole1On,randomness);
setInterval(mole1Off, 1000 + randomness);

setInterval(mole2On,randomness2);
setInterval(mole2Off,1000 + randomness2);


function onKey1Down(event)
{
if (event.keyCode == 49)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",85,100);
window.setTimeout(eraseOuch,300);
}
}

function onKey2Down(event)
{
if (event.keyCode == 50)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",285,100);
window.setTimeout(eraseOuch2,300);
}
}

function eraseOuch()
{
context.clearRect(70,70,150,50);
}
function eraseOuch2()
{
context.clearRect(270,70,150,50);
}


function mole1On()
{

window.removeEventListener("keydown",onKey1Down,false);
context.fillStyle = "#000000";
context.fillRect(125,175,50,50);
randomness = Math.random() * 3000;
}
function mole1Off()
{

window.addEventListener("keydown",onKey1Down,false);
context.fillStyle = "#ff0000";
context.fillRect(125,175,50,50);
randomness = Math.random() * 3000;
}
function mole2On()
{
window.removeEventListener("keydown",onKey2Down,false);
context.fillStyle = "#000000";
context.fillRect(325,175,50,50);
randomness2 = Math.random() * 3000;
}
function mole2Off()
{

window.addEventListener("keydown",onKey2Down,false);
context.fillStyle = "#ff0000";
context.fillRect(325,175,50,50);
randomness2 = Math.random() * 3000;
}


</script>

Well HELLOOOO there! What's a code like you doin' in a place like this?

Run your eyes over this code a minute. Try to see what you recognize, then try to figure out how it works. Take a few minutes and do that. It's a good habit. See anything familiar? Any light bulbs flashing on? Anything perplex you? Anything irk you? See anything and think you could do it too?

Ask yourself: what makes this code run? Certainly there must be an engine of some sorts driving the actions. Is everything timer-based? If so, which timers? What is the role of each timer?

How about the listeners? What are we listening for? Keyboard events? Mouse events? Is the window being reloaded in some way?

Look at the variables now. What are these used for? What type of data are these? Where do these belong? Are they used only once or continuously?

And the shapes? The text? How big are they? Where are they being placed? Have we included a clearRect() anywhere to wipe out something? Is anything being rotated? Scaled?

Ask yourself a million questions and take the time to answer as many as possible.

Now I've only given our whack-a-mole game two holes because adding more would require some arrays and we're not quite ready to get into those just yet. But sit tight, we'll see everything in due time.

Also, I want to introduce the debugging console in Chrome. In Chrome, as I mentioned in the intro or lesson one, you need to go to the three horizontal lines on the top right of your browser window, then click on tools, finally on Javascript Console.

Now some words of wisdom: IF and I'm saying IF but I really mean WHEN you have problems with your code: when it doesn't run, when you see a blank screen, when everything's all messed up, you'll most likely find what the heck happened in the Javascript console tab. Let's open it now and I'll show you a sec. how it works:

But first we need to generate an error.

After you've copied or typed the code into your text editor, go ahead and make the following change to your code to set off an error:

Change this:

var randomness = Math.random() * 3000;

to

THIS:

van randomness = Math.random() * 3000:

See the miniscule little difference?? Now save it and try to run it. No go? That's because your browser is giving you a hard time over a STUPID LETTER!! Unbelievable, right? Coding is a frustrating activity, people. Run while there's still time. Run far... bef.....

....

So, I just wanted you to throw that little error so we can have a chance to use our Javascript console. It's like the time you ride your bike wthout handlebars for the first time.. So exciting, yet dangerous! Will you crash? Yes! Will you crash and burn sometimes? Absolutely! Will you get so lost in your code that you'll need to give up? Certainly! Will you look for an error a zillion times, then find the answer was right in front of your eyes? Yup!

Get used to faiiling and failing miserably, but NEVER let yourself get discouraged! Coding is a damned hard art to learn. It takes years and a lot of time in front of a little box. You need to experiment, learn, then go back to experimentation again repeatedly. You need to set aside time to challenge yourself to learn the stuff you don't know yet or find boring. But, like I said, NEVER give up. You'll make it sooner or later and be VERY glad you did!

Now about that Javascript console thingy....

Go ahead and save the code (the erroneous code, that is) and try to run it. You'll only see the canvas and nothing else. Why? Because our Javascript has failed to run correctly. When you open the Javascript console, you'll see the red mark down in the bottom-right corner. In Chrome, at least, I'm not sure about the other browsers, you'll see it.

Google, man.

When you click on said red circle, you'll see the following message pop up in your console:

Uncaught SyntaxError: Unexpected identifier

Ouch. That sounds really important! Who knows what we've gotten ourselves into. Who knows what laws we've broken!

Fortunately, it's just a simple error. Translation: you've misspelled "var".

When your code doesn't collaborate, check your console and learn to understand these cryptic messages. They're your helpers even if they bring your game to a cashing halt. The REAL meanies are the runtime errors. These guys come out WHILE your code is running and without warning. You may think you have the most pristine code the world has yet seen, but have overlooked something or other causing your game to go haywire for no apparent reason. So try to keep your code clean, folks!

Now, back to that big old code!

At the top we assign our variables:

And there aren't that many of them:

var theCanvas = document.getElementById("canvas");
var context = theCanvas.getContext("2d");

var randomness = Math.random() * 3000;
var randomness2 = Math.random() * 2500;

The first two, of course, we know and love.

The second two assign a degree of randomness to a random number between 0 and 3000 and 0 and 2500. This was done by multiplying Math.random() (as we've already seen) by a number in order to obtain a random number between 0 and that number.

So, in other words, we want two random values: one between 0 and 3000 and the other between 0 and 2500.

Moving on we have:

context.fillStyle = "#000000";
context.arc(150,200,50,0,6.285,false);
context.arc(350,200,50,0,6.285,false);
context.fill();

context.fillStyle = "#000000";
context.fillRect(125,175,50,50);
context.fillRect(325,175,50,50);

These are our two holes and rectangles. Both are given a fill value of black because we're going to be starting out the game with empty holes.

Then we have our text features:

context.shadowColor = "#000000";
context.shadowOffsetX = 3;
context.shadowOffsetY = 3;
context.shadowBlur =.5;

context.fillStyle = "#ffffff";
context.font = "25px Arial";
context.fillText("Whack-a-mole, er... rectangle!",20,30);

context.font = "16px Arial";
context.fillText("Press a corresponding key (1-2) when a mole appears.",80,380);

context.fillText("1",150,300);
context.fillText("2",350,300);

Here pretty self-explanatory as well. We assign a drop shadow to our text and give it a 50% blur.
Then we draw a white line of text at 25px. This will serve as our title. It is placed at near the top of the screen at (20,30).
On the bottom of the screen we write some smaller text which will serve as our instructions.
And lastly we write the (albeit obvious) numbers for each hole.

Following this we have 4 setInterval timers.


setInterval(mole1On,randomness);
setInterval(mole1Off, 1000 + randomness);

setInterval(mole2On,randomness2);
setInterval(mole2Off,1000 + randomness2);

And, as you can see, this is where we plug in our randomness variable from the top as the time between timer sequences. We assigned a function to turn off the mole and one to turn it on for each mole.

Next we have our two event methods, but we'll look at them in a minute...

function onKey1Down(event)
{
if (event.keyCode == 49)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",85,100);
window.setTimeout(eraseOuch,300);
}
}

function onKey2Down(event)
{
if (event.keyCode == 50)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",285,100);
window.setTimeout(eraseOuch2,300);
}
}

Let's skip the eraseOuch functions a minute as well...


function eraseOuch()
{
context.clearRect(70,70,150,50);
}
function eraseOuch2()
{
context.clearRect(270,70,150,50);
}

...and go straight to the heart of our code.


function mole1On()
{

window.removeEventListener("keydown",onKey1Down,false);
context.fillStyle = "#000000";
context.fillRect(125,175,50,50);
randomness = Math.random() * 3000;
}
function mole1Off()
{

window.addEventListener("keydown",onKey1Down,false);
context.fillStyle = "#ff0000";
context.fillRect(125,175,50,50);
randomness = Math.random() * 3000;
}
function mole2On()
{
window.removeEventListener("keydown",onKey2Down,false);
context.fillStyle = "#000000";
context.fillRect(325,175,50,50);
randomness2 = Math.random() * 3000;
}
function mole2Off()
{

window.addEventListener("keydown",onKey2Down,false);
context.fillStyle = "#ff0000";
context.fillRect(325,175,50,50);
randomness2 = Math.random() * 3000;
}

It may look complicated, but it's the same thing repeated once again for the second two functions. So really, if we can understand mole1On and mole1Off, we can understand the other two as well since they're set up the exact same way.

Let's take a closer look:

Here we remove the event listener that we ADD to the mole1Off function. These were our keyboard listeners and basically we're saying:

Hey! When the mole is OFF, we don't want these clicks to be registering, so go ahead and shut 'em off!

Then, when the red rectangle appears, we say the opposite:

Hey! When the mole is ON I want to be able to click on it, so attach my keyboard event, please!

In the mole1On function we have a red rectangle (our faux mole), while in the mole1Off function we "turn off" our red rectangle by turning it black. Sorta hacky, but it sure works! At least we didn't move it off the screen or something! Then, lastly, both the mole1On and the mole1Off are reassgined a random number between 0 and 3000. This keeps things truly random, because otherwise we'd only have the same intervals repeating over and over! Not too random!

So after we've written the two functions for the first hole, we just need to do the same for the second. And that's exactly what the last two functions are!

So now let's return up to our two event handlers:

function onKey1Down(event)
{
if (event.keyCode == 49)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",85,100);
window.setTimeout(eraseOuch,300);
}
}

function onKey2Down(event)
{
if (event.keyCode == 50)
{
context.font = "40px Arial";
context.strokeStyle = "#ffffff";
context.strokeText("OUCH!",285,100);
window.setTimeout(eraseOuch2,300);
}
}

What do THESE guys do? Well, they are the functions which react to our key presses. You remember these, right?

First we ask the computer is the number 1 is being pressed (keyCode 49) and if so, it displays the text "OUCH" above the first hole. Same with the second function which listens for the number 2 (keyCode 50), then displays "OUCH" above the second hole.

The last two lines of each are the parts that erase the ouch. They each call a setTimeout timer which intervenes in 300 milliseconds (quickly), then calll a the functions eraseOuch and eraseOuch2.

Where were they again? Oh, here they are:

function eraseOuch()
{
context.clearRect(70,70,150,50);
}
function eraseOuch2()
{
context.clearRect(270,70,150,50);
}

Here, very simply, we call a clearRect over each "OUCH" text and erase it. That's it!

And the whole cycle begins again at the next interval/keypress.

Fun, huh?

Now, having analyzed this code, I'd like you to go in, get your hands dirty and MESS WITH IT! You've always got the full code here in case you mess up, so go crazy, be fearless and try to apply everything you can to these lines! The further you try to bend this code, the more you'll learn, so don't be afraid. Use your imagination and have fun!

And that will conclude today's revision lesson. Hope you enjoyed!

So stay tuned and I hope you enjoyed! As always, thanks for following along! Code didn't work for you? Hate me? Are you my illegitimate child? Send input anytime!

Until tomorrow!

-Ben
@benwhi
Onward to Lesson Fifteen!
Back to Lesson Thirteen!
Back to Index