Tuesday, January 29, 2013

A Size for All Windows

One of the problems web app developers face is all the differences between browsers and monitors that we are trying to write for. We want our app to look good on everyone's screen. And we want it to look good on everyone's favorite browser.

Click this link to our Dog Race project for this post. This version has been updated to adapt to your browser.

Re-size your browser window and refresh the page. You should see the size of the canvas change. The finish line moves and the dogs still end at the right hand edge.

The good news is that there are several values that the browser will reveal to us. We can use JavaScript to gather information about the current size of the window. Look at the following code:

alert(window.innerWidth + " x " + window.innerHeight);

You can find all the details here at: W3Schools.

I know it is a bit annoying, but I have left the alert in the code so that you can try different window sizes and see the values on your own screen.

I have put the height of the window in there for you to see, but this page doesn't use it anywhere. Instead we will make horizontal adjustments only. We are not checking for changes to the size while the app is running, only when it is refreshed.

To make all this work, we set up a few global variables:
var currentWidth = window.innerWidth;
var canvasLeft = currentWidth * 0.05;
var canvasWidth = currentWidth * 0.88;
We have been using canvasLeft all along, but now we are going to set it to 5% of the window size. We are adding canvasWidth which will be 88% of the window size. That number came about from experimenting with different values.

It only requires a few changes to the original script to place things in the right spot no matter what the size of the window. First we will set the size of our game board:

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,canvasWidth,400);
       ctx.fill();
 }

We also need to make sure the finish line moves:

        //draw the finish line
        ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + canvasWidth - 30,15);
        ctx.lineTo(canvasLeft + canvasWidth - 30,380);
        ctx.closePath();
        ctx.stroke();


We will use the same variable to adjust the spot on the canvas that where we want the dogs to stop:
var leaderNose = canvasLeft + canvasWidth - 90;
When it came time to print the finishing order next to each dog at the end of the race, I decided to switch the alignment from left to right so that I didn't have to worry about the width of the character:

ctx.textAlign="right";
ctx.fillText(place,canvasLeft+canvasWidth,dog*60 + 60);

Sometimes the fanciest effects are the easiest to code. If you want to copy and paste the entire script into your own editor, here it is below. Don't forget to give Drop Your Anchor a try. C U Next Time!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    /* Demo Script for 'Writing Games with Canvas in HTML5'
    © Copyright 2013, Budd Churchward
    */
   
   
   
  
   
    var myRed = "#FE0000";         //red - first value is 254
    var myBlue = "#0200DD";        //blue - first value is 2
    var myYellow = "#FCFF00";      //yellow - first value is 252
    var myMagenta = "#660066";     //magenta - first value is 253
    var myOrange = "#FF6600";      //orange is - first value is 255
    var myGreen = "#06FF00";       //green is - first value is 6
    var myBrown = "#993300";       //brown is - first value is 153
    var myBackground = "#007777";  //background -- first value is 0
   
    var currentWidth = window.innerWidth;
    var canvasLeft = currentWidth * 0.05;
    var canvasWidth = currentWidth * 0.88;
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
        alert(window.innerWidth + " x " + window.innerHeight);
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);
        //draw the finish line
        ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + canvasWidth - 30,15);
        ctx.lineTo(canvasLeft + canvasWidth - 30,380);
        ctx.closePath();
        ctx.stroke();
       
       
        // set start point for each dog and image array
        for (var x = 0; x < 6; x++){
            hisSpot[x] = canvasLeft + 2;
            dogImage[x] = 0;
            }
        dogCatcher();
        delayLoop=self.setInterval(function(){scooter(15)},20);
    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }


 var delayLoop = 0;
 var runningDogX = canvasLeft;
 var testSpot = 0;

 var hisSpot = new Array();
 var dogImage = new Array();


 var thisDog = new Array();
 thisDog[0] = "Arlo";
 thisDog[1] = "O'Yeller";
 thisDog[2] = "Brownie";
 thisDog[3] = "Red Rover";
 thisDog[4] = "Bo";
 thisDog[5] = "Max";

var placeText = new Array();
placeText[0] = "1st Place: ";
placeText[1] = "2nd Place: ";
placeText[2] = "3rd Place: ";

 function showWinner(dog){
     ctx.fillStyle='White';
    ctx.font="24px Arial";
    ctx.textAlign="left";
    ctx.fillText(placeText[place] + thisDog[dog],canvasLeft + 20,40*place+30);
    }
   
 function goAgain(){
     location.reload();
     }

 function dogCatcher(){
     for(var dog = 0; dog < 6; dog++){
         dogImage[dog] = ctx.getImageData(hisSpot[dog], dog * 60 + 15,70 ,55);
        //ctx.putImageData(dogImage[dog],0,dog * 60);
        }
     }
var leaderNose = canvasLeft + canvasWidth - 90;
var place = 0;

function scooter(){
    var step = 1;
    var keepGoing = 0;
    for(var dog = 0; dog < 6; dog++){
         if(hisSpot[dog]>0){
             step = Math.floor(Math.random() * 8)+1;
             hisSpot[dog] = hisSpot[dog] + step;
             ctx.putImageData(dogImage[dog],hisSpot[dog],dog * 60 + 15);
             keepGoing++;
             if(hisSpot[dog]>leaderNose){
                 hisSpot[dog]=0;
                 if(place<3){showWinner(dog); }
                 place++;
                 ctx.textAlign="right";
                 ctx.fillText(place,canvasLeft+canvasWidth,dog*60 + 60);
                 }
             }
         }
         if(keepGoing==0){clearInterval(delayLoop); }
    }
   
function myDogSpot(x,y){
    ctx.putImageData(dogImage,x, y);
    }

 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

      }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,canvasWidth,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="1440" height="400" ;">  

    </canvas>

</body>
</html>

Sunday, January 27, 2013

Let's Start with 'Game Over'

If you have been following this blog you will know that we are looking at a simple page that shows six dogs racing across the screen. You can find our sample page by clicking this link.
In this post we will focus on the end of the game and review how we move the images across the canvas. In a earlier version we were picking dogs at random and nudging them along one at a time. This version still has to move them one at a time, but we move each dog one after the other. We make it a race by picking a random distance for each move.

Testing the end of the race is simple enough. Our array, hisSpot[], keeps track of the location of each dog. We pick a random number and store it in step. Then use that to bump up the value in the array. Once that has been done we put a new copy of the image down at the new location. The left side of our image blots out any part of the dog that might have been visible in the old location. Here is the code that does that.

step=Math.floor(Math.random()*8)+1;
hisSpot[dog]=hisSpot[dog]+step;
ctx.putImageData(dogImage[dog],hisSpot[dog],dog * 60 + 15);

The code above picks a number between 1 and 8. Math.random()*8 actually picks a number between 0 and 7 so we just add 1 to that value. You can experiment with different values, I didn't want zero to ever come up which would mean that the dog didn't move.

So how do we get a dog to stop moving when it reaches the finish line. Here is a slick trick! Rather than set up yet one more array to keep track of which of the six dogs has finished, we will use hisSpot[] for two purposes. When we initiallized the array we set each value at canvasLeft, which currently is 120. So after we have moved the dog the last time, we change that value to zero. Later on we are going to cover the topic of user screen size and adjust the size of our canvas to look well on different devices. To be ready for this in the future I have set up a variable called leaderNose and given it a value that we can experiment with. By subtracting canvasLeft from this value we leave the door open for shifting things left and right on the page. We can move the board around and not have to hunt for a new value to put in here.
var leaderNose = 1230-canvasLeft;
With that in place, all we need to do is move our dog and then test the location:
if(hisSpot[dog]>leaderNose){
    hisSpot[dog]=0;
 
Do you see where this takes us? Knowing that, we only move dogs that have a value greater than zero in hisSpot[]. So we wrap an if structure around the whole thing.


if(hisSpot[dog]>0){
    .
    .
    .
That leaves us with one more problem to solve. We have started a loop running that keeps calling this function over and over. We need to stop that loop when the dogs are all done running. Here is another slick trick. Each time the function is called we set up a simiple variable:  var keepGoing = 0;

Every time we move one of the dogs we increment that variable: keepGoing++;

So if we try to move all six dogs and every one of them has already finished, keepGoing will still be zero which tells us it is time to quit:
if(keepGoing==0){clearInterval(delayLoop); }
How cool is that?

Finally, let's display the first three finishers and put a place number next to each dog when he finishes the race. We need another global variable: var place = 0; and the following code:

if(place<3){showWinner(dog); }
place++;
ctx.fillText(place,1180,dog*60 + 60);
Let's look at the last two lines first. place++; will increment our counter. That means that it will be 1 when the first dog crosses the line. The third line simply puts the value of place on the screen. We are using an absolute value, 1180, here. Which means we will have to change it later when we want to support different screen sizes. Oh, well.

Now let's go back to the first line. Notice that we only call the function showWinner() when place is 0, 1 or 2. This is a rewite of the routine:

var placeText = new Array();
placeText[0] = "1st Place: ";
placeText[1] = "2nd Place: ";
placeText[2] = "3rd Place: ";


function showWinner(dog){
   ctx.fillStyle='White';
   ctx.font="24px Arial";
   ctx.textAlign="left";
   ctx.fillText(placeText[place] + thisDog[dog],canvasLeft + 20,40*place+30);
   }


It is important that we call this function before we print the places next to the dogs for two reasons. First, We are using a small array to hold text for the placing and the first item in an array is zero. Second, the code here sets up the font color and size for the numbers we will print next to the dogs at the finish line.


You can find the complete code ready for you to copy and paste at the bottom of the previous post. If you would like see a project that uses all of these techniques and more, check out my web app: Drop Your Anchor.


..

Look At Them Run


In this post we will look at a second way to move our graphics across the canvas. Click this link here to see the page we will be coding. I had to make some changes to the project we have been working on because this version moves so much faster than the first. We will talk about why later.

I also changed the size of the canvas to make the game board much wider. This gives us a chance to actually see which dogs are pulling ahead and which ones are falling behind.

We will let every dog finish the race rather than stopping when the first one crosses the line. And we will print the names of the first, second and third place winners.

On the left side of the web page you will see images of the six dogs. I put them there while I was writing the code to see exactly what the image was that I was capturing at the beginning of the game. You will recall from the earlier posts that we have made our canvas wider than the dark area that you see on the screen. The actual game board is smaller and is shifted to the right from the edge of the window. We use the variable canvasLeft to determine this gap. This makes the page look more visually pleasing but it also gives us a little bit of works space to test things on. It is important that it is a different background color so that we can easily see where the edges are on the images that we manipulate in the game.  I have left them here so that you can see how I use this space during game development. This is even more clear if you step through the script using Firebug.

In the previous post our puppies appear to move because we a capturing a strip of the image with the dog somewhere in the middle and then placing it back on the screen in a new location shifted only two pixels right of the original. We do not know where the dog is in the final image, so we have to stop and check for a color other than the background in a key location at the finish line. We repeatedly recapture and reposition images as the dogs race across the screen.

In this version we must capture six images, one for each dog, and then keep track of where we are putting them on the screen. The images are smaller and once they are captured they do not change so we only need to do that once, when we are first setting up the game. We also need to keep track of the location of each dog. We will know where each trotter is, so we don't have to test the screen for the winner. Instead we can check a value.

To do these two things we will declare two arrays:


var hisSpot = new Array();
var dogImage = new Array();
Remember that an array is a list of values. So hisSpot[] will be a list of six numbers representing the X coordinate of the dog's image. dogImage[] will be a set of six images stored in computer memory. These values need to be global so that they can be used and manipulated by several functions in our script. You simply place the declarations anywhere in your script as long as it is not inside of one of your functions. Sometimes you might want to put all your declarations in the same place at the beginning of the script. As your code gets longer, it is sometimes more convenient to locate them near the functions that will use them so that debugging and editing is easier.

You will find this code in the first function that gets called when the page is loaded. We only need to do this once.
       
        // set start point for each dog and image array
   for (var x = 0; x < 6; x++){
       hisSpot[x] = canvasLeft;
       dogImage[x] = 0;
       }

   dogCatcher();
We use a simple for loop to load hisSpot[] for each dog and give or dogImage[] an initial value. After these are initialized we call the function dogCatcher() to capture an image of each of the six puppies. Here it is. 

function dogCatcher(){ 
    for(var dog = 0; dog < 6; dog++){ 
        dogImage[dog]=ctx.getImageData(hisSpot[dog],dog*60+15,70 ,55); 
        ctx.putImageData(dogImage[dog],0,dog*60); 
        } 
    }
We have to be sure that two things are done first. One: the dogs have to be already drawn on the canvas. Two: our array, hisSpot[], has to be filled with their locations. You will see that we are still multiplying our loop value, dog, times 60. That is the vertical spacing between the dogs. The +15 how far down the first dog is from the top of the canvas. The 70 is the width of the image that we need to capture, and the 55 is the height. The final line: ctx.putImageData(dogImage[dog],0,dog * 60); is for debugging. It puts a copy of the image that we just captured in the scratch space on the left side of the screen. If you experiment by changing the last two values in the line above it, you will see how handy this is to get just the image you need.

It is important that the dog itself is offset in the image so that there is more space behind his tail than there is infront of his nose. We need this background space to erase the tail on the canvas as the dog moves forward. That is why we set the original value at simply canvasLeft. You can experiment by adding and subtracting numbers in the getImageData line. For example:
hisSpot[dog] + 15
hisSpot[dog] - 15
Play around with this a little and it wll be very clear how that trailing background color makes the annimation work.

In the next few posts we will look at other parts of this program and see how they each work. If you need it, here is the full script that you can copy and paste into your own editor.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    /* Demo Script for 'Writing Games with Canvas in HTML5'
    © Copyright 2013, Budd Churchward
    */
   
   
   
    var canvasLeft = 120;
   
    var myRed = "#FE0000";         //red - first value is 254
    var myBlue = "#0200DD";        //blue - first value is 2
    var myYellow = "#FCFF00";      //yellow - first value is 252
    var myMagenta = "#660066";     //magenta - first value is 253
    var myOrange = "#FF6600";      //orange is - first value is 255
    var myGreen = "#06FF00";       //green is - first value is 6
    var myBrown = "#993300";       //brown is - first value is 153
    var myBackground = "#007777";  //background -- first value is 0
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);
        //draw the finish line
        ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + 1180-canvasLeft,15);
        ctx.lineTo(canvasLeft + 1180-canvasLeft,380);
        ctx.closePath();
        ctx.stroke();
       
       
        // set start point for each dog and image array
        for (var x = 0; x < 6; x++){
            hisSpot[x] = canvasLeft;
            dogImage[x] = 0;
            }
        dogCatcher();
        delayLoop=self.setInterval(function(){scooter(15)},20);
    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }


 var delayLoop = 0;
 var runningDogX = canvasLeft;
 var testSpot = 0;

 var hisSpot = new Array();
 var dogImage = new Array();


 var thisDog = new Array();
 thisDog[0] = "Arlo";
 thisDog[1] = "O'Yeller";
 thisDog[2] = "Brownie";
 thisDog[3] = "Red Rover";
 thisDog[4] = "Bo";
 thisDog[5] = "Max";

 function showWinner(dog){
     ctx.fillStyle='White';
    ctx.font="24px Arial";
    ctx.textAlign="left";
    ctx.fillText(placeText[place] + thisDog[dog],canvasLeft + 20,40*place+30);
    }
   
 function goAgain(){
     location.reload();
     }

 function dogCatcher(){
     for(var dog = 0; dog < 6; dog++){
         dogImage[dog] = ctx.getImageData(hisSpot[dog], dog * 60 + 15,70 ,55);
        ctx.putImageData(dogImage[dog],0,dog * 60);
        }
     }
var leaderNose = 1230-canvasLeft;
var place = 0;
var placeText = new Array();
placeText[0] = "1st Place: ";
placeText[1] = "2nd Place: ";
placeText[2] = "3rd Place: ";

function scooter(){
    var step = 1;
    var keepGoing = 0;
    for(var dog = 0; dog < 6; dog++){
         if(hisSpot[dog]>0){
             step = Math.floor(Math.random() * 8)+1;
             hisSpot[dog] = hisSpot[dog] + step;
             ctx.putImageData(dogImage[dog],hisSpot[dog],dog * 60 + 15);
             keepGoing++;
             if(hisSpot[dog]>leaderNose){
                 //clearInterval(delayLoop);
                 hisSpot[dog]=0;
                 //leaderNose = leaderNose - 5;
                 if(place<3){showWinner(dog); }
                 place++;
               
                 ctx.fillText(place,1180,dog*60 + 60);
                 }
             }
         }
         if(keepGoing==0){clearInterval(delayLoop); }
    }
   
function myDogSpot(x,y){
    ctx.putImageData(dogImage,x, y);
    }

 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

      }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,1100,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="1200" height="400" ;">  

    </canvas>

</body>
</html>






Saturday, January 26, 2013

And the Winner IS... building an Array


In the last posting, we discovered that, using random numbers, we could shift part of our image to the right and make it appear that our dogs were trotting across the screen towards a finish line. 

Today, let's ask our JavaScript to actually tell us who the winner is.
We need a way to store the names of our six dogs and then print the correct name on the screen when the first one reaches the finish line. 


If you already know about arrays, skip down to the horizontal bar. The rest of us will take a minute to go over this topic quickly.

Coding languages give us a way to store values that we can call up later and manipulate when needed. These are called variables. Sometimes they contain numbers, and sometimes they store text. When they contain text we call them strings because we can think of them as a string of characters. Take a look at this line:



var thisDog = "Spot";

 
This creates a variable or a container that stores the characters S, p, o, and t in memory. That is simple enough if we had only one dog. 



An Array is a way of storing a collection of strings that share a common variable. We can create a list of names like this:

var thisDog = new Array();

thisDog[0] = "Arlo";
thisDog[1] = "O'Yeller";
thisDog[2] = "Brownie";
thisDog[3] = "Red Rover";
thisDog[4] = "Bo";
thisDog[5] = "Max";

Each of these values is the name of a different dog, but they all share the same variable: thisDog followed by a value in the square brackets which is an index to each item in our list. The cool thing about this is that our index can be a variable itself! That means we can write code like this: thisDog[dog]. It is no coincidence that I put the variable dog in the brackets. That's the variable that we used in our random number. It will be the one that just reached the finish line.
Let's write a new function to display the winner's name.

function showWinner(dog){
   ctx.fillStyle='White';
   ctx.font="24px Arial";
   ctx.textAlign="left";
   ctx.fillText(thisDog[dog] + " is the Winner!",canvasLeft + 20,150);
    }
 

We will also need to add a line to scooter() that will call this function at the end of the race. It is highlighted below:

     if (testSpot.data[0] > 0){
         clearInterval(delayLoop);
         showWinner(dog);
         }

Many of the lines in the new function will look familiar. They are like ones that we used to draw our dogs earlier. ctx.fillStyle= 'White'; sets the color of our text. This time we are using one of the HTML5 colors that all browsers will recognize. 

ctx.font="24px Arial"; sets both the size and font of the text. Notice that sometimes I have used 'single quotes' around my code and other times I have used "double quotes." It doesn't matter which ones you use, they both do the same thing. But if you want to include either of those characters in your string, make sure you package it between two of the other. We did this when we named or yellow dog: thisDog[1] = "O'Yeller";

ctx.fillText( ... ); is the line that puts the text on the canvas. It needs three values. The first is the text that you want to print. And the next two are the X,Y coordinates of a location on the canvas where you want the text to appear. These last two values can be expressions like: canvasLeft + 20 or absolute numbers like: 150. But look at how we have set up the text: thisDog[dog] + " is the Winner!" 

We are going to pull the name of the winner our of our array using the variable dog that we passed into this function. And then we use the + to add the string " is the Winner!" after it.

I skipped one line: ctx.textAlign="left"; This tells your computer how to place the text in relation to the X,Y coordinates in ctx.fillText(); Other choices are: right and center.

In the next post we will explore an alternate way of moving the dogs. In our first version the dogs stand still while they wait for their random number to come up and then scoot forward. Let's see if we can smooth this out by having each dog move in turn but make their step forward a random distance.

As always, you can copy the full script from this session below and paste it into your own editor. Experiment with it, and make it your own. C U next time.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    var canvasLeft = 120;
   
    var myRed = "#FE0000";         //red - first value is 254
    var myBlue = "#0200DD";        //blue - first value is 2
    var myYellow = "#FCFF00";      //yellow - first value is 252
    var myMagenta = "#660066";     //magenta - first value is 253
    var myOrange = "#FF6600";      //orange is - first value is 255
    var myGreen = "#06FF00";       //green is - first value is 6
    var myBrown = "#993300";       //brown is - first value is 153
    var myBackground = "#007777";  //background -- first value is 0
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);

        ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + 580,15);
        ctx.lineTo(canvasLeft + 580,380);
        ctx.closePath();
        ctx.stroke();
        delayLoop=self.setInterval(function(){scooter(15)},2);

    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }

 var dogImage = 0;
 var delayLoop = 0;
 var runningDogX = canvasLeft;
 var testSpot = 0;


 function scooter(){
     var dog = Math.floor(Math.random() * 6);
     dogCatcher(dog);
     myDogSpot(canvasLeft+2, dog * 60 + 15);
   
     testSpot = ctx.getImageData(canvasLeft+575,dog* 60 + 31,10,10);
     ctx.putImageData(testSpot,20,dog * 60 + 30);
     if (testSpot.data[0] > 0){
         clearInterval(delayLoop);
         showWinner(dog);
         }
     }
   
 var thisDog = new Array();
 thisDog[0] = "Arlo";
 thisDog[1] = "O'Yeller";
 thisDog[2] = "Brownie";
 thisDog[3] = "Red Rover";
 thisDog[4] = "Bo";
 thisDog[5] = "Max";

 function showWinner(dog){
     ctx.fillStyle='White';
    ctx.font="24px Arial";
    ctx.textAlign="left";
    ctx.fillText(thisDog[dog] + " is the Winner!",canvasLeft + 20,150);
    }
   
 function goAgain(){
     location.reload();
     }

 function dogCatcher(dog){
     dogImage = ctx.getImageData(canvasLeft, dog * 60 + 15, 577, 55);
     }
   
function myDogSpot(x,y){
    ctx.putImageData(dogImage,x, y);
    }

 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

 }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,600,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="840" height="400" ;">  

    </canvas>

</body>
</html>

Sunday, December 16, 2012

Moving Multiple Objects on our HTML5 Canvas

Let's look at how we can move one of our dogs closer to the finish line. Our example project is relatively simple because the objects in our game never collide with each other. They move along in an orderly manner on their own 'track' until they reach a finish line. We will simply capture a strip of the canvas that contains one of the dogs. Then we will put it back but shifted over 2 pixels, so that the dog will appear to be in a new location.

The image you see above is the actual image that we are capturing. Many captures earlier, the dog was on the far left. Now it is nearly to the finish line. You do not see the finish line in our image, because we don't want it to appear to be moving. We capture this section just a few pixels short of the finish line so when it is put back on the screen, the line is not disturbed.

We should be clear about this. We are not taking anything off of the canvas, we are simply copying a section of it and storing it in memory. Then we put it into the canvas image in a new location. That covers up the original puppy so you don't see any trailing bits of dog dribbled across the screen.

You might think of this as a Copy and Paste function.

Almost any game you create will require an element of unpredictability. We need to chose a dog at random, move him a bit, then chose another and do the same. We repeat this over and over until one of the dog reaches the finish line. We do this with a random number.

Ah! But the image is always the same size and we always put it back in the same place, not far from where we got it. So how do we know when one of the dog reaches the end of the race? This is where our coding gets clever. We are going to capture another image, a small one. It will come from just in front of the finish line. We will store it in a variable testSpot and then check the very first byte in the data to see if it is not our background. REMEMBER the first byte of our background is Hex 00. So if the first byte of this test image is not zero, it has to be the color of one of our dogs. GAME OVER!

Let's start with the random numbers. We have six dogs. Like all things computer, we number them 0 through 5. Look at this code:

        var dog = Math.floor(Math.random() * 6);

We establish a variable called dog and then give it a random value from 0 to 5. This variable will then be used to mathematically determine the Y axis for all the image manipulation. In effect, it identifies which of the six dogs were are going to move at this moment. The X axis of the image location is the same for every dog, so that never changes. The line: dogCatcher(dog); calls the following function which will capture our image:

 function dogCatcher(dog){
     dogImage = ctx.getImageData(canvasLeft, dog * 60 + 15, 577, 55);
     }


We have our usual players in here. The ever present canvasLeft references the edge of our game board. dog is the random number we sent to this function. First we multiply it by 60, which is the distance between two dogs: tail to tail, nose to nose. It represents the Y axis of our graphic. We add 15 to the value because our first dog is dropped a little more than 15 pixels down from the top of the canvas. The 577 is the width of the image we want to capture and the 55 is the height.

Notice that for the first dog, we multiply 0 x 60 = 0 and then 0 + 15 = 15. The second dog will be 1 x 60 = 60 and 60 + 15 = 75. This technique allows us to capture a strip of the canvas that is 557 pixels wide and 55 pixels high that will contain one of our dogs somewhere in the middle. Just like the one you see above.

We are grabbing an image every 60 pixels up and down the screen that is only 55 pixels high. That leaves a 5 pixel gap between them that is undisturbed. We could use that to draw in lines, if we wanted, separating the tracks the dogs run in.

Once we have stored the image we can write it back to the canvas with this line:

        myDogSpot(canvasLeft + 2, dog * 60 + 15);

Of course this line doesn't put the image on the screen itself, it calls a function that we have used before that does that for us. Notice that the math here is nearly the same. dog * 60 + 15 is the Y axis and it is identical to what we captured. But this time we have increased the X axis a very small amount: canvasLeft + 2. As you experiment with these numbers, play around with the + 2 and you can see that it makes a big difference on how fast your puppies trot. But it is really the distance that you are changing.

Now let's talk about the finish line.

We can use the same random value, dog, to pick a spot of the screen to test. In our simple project we don't need to worry about any of the other dogs only the one we just moved. So we will get a bit of the image again:

    testSpot = ctx.getImageData(canvasLeft+575,dog * 60 + 31,10,10);

testSpot is a variable that we declared earlier. It is a place to park the data so we can peek into it and check for the winning pouch. You will see canvasLeft in there again. This time we adding 575 to it because the finish line is at the other edge of the canvas. We still multiply dog * 60 for the same reason we did above. This time we are adding 31 to that value. I figured that out by trial and error. It should pickup the color of the dog's snout right behind it's black nose. The size here, 10 by 10, could be anything, 4 by 4 or 100 by 100. It doesn't matter because we only care about the very first byte, which is the 'red' value of the pixel in the upper left corner of the rectangle. It will be: testSpot.data[0]. Here is how we use it:

        if (testSpot.data[0] > 0){
             clearInterval(delayLoop);
       
            }


We just keep checking that first byte and the first time it isn't zero, GAME OVER, we have a winner. So we clear the interval and everything stops.

Find the line that reads:

        ctx.putImageData(testSpot,20,dog * 60 + 30);

I put that in there just for debugging purposes. Notice that there is no canvasLeft in it. No, this time our Y axis is simply 20. This allows us to park a tiny image on the scratch space to the left of the game and actually see for our self if part of the dog is in there or not. I used it for my trial and error to find the right place on the board to catch the first dog as her nose reaches the finish line. We will want to get rid of this line when the project is completed.

You will be able to copy the complete code below. As you experiment with it on your own computer, add a break point in Firebug so you can watch the test image yourself.  Also, you might find it interesting to actually select testSpot.data[0]and watch it's value. Firebug will let you see all the bytes in the image data which should help you learn even more about how a pixel image is stored in the computer.

CU Next Time ...

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    var canvasLeft = 120;
   
    var myRed = "#FE0000";         //red - first value is 254
    var myBlue = "#0200DD";        //blue - first value is 2
    var myYellow = "#FCFF00";      //yellow - first value is 252
    var myMagenta = "#660066";     //magenta - first value is 253
    var myOrange = "#FF6600";      //orange is - first value is 255
    var myGreen = "#06FF00";       //green is - first value is 6
    var myBrown = "#993300";       //brown is - first value is 153
    var myBackground = "#007777";  //background -- first value is 0
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);

        

ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + 580,15);
        ctx.lineTo(canvasLeft + 580,380);
        ctx.closePath();
        ctx.stroke();
        delayLoop=self.setInterval(function(){scooter(15)},2);

    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }

 var dogImage = 0;
 var delayLoop = 0;
 var runningDogX = canvasLeft;
 var testSpot = 0;

 function scooter(){
     var dog = Math.floor(Math.random() * 6);
     dogCatcher(dog);
     myDogSpot(canvasLeft + 2, dog * 60 + 15);
   
     testSpot = ctx.getImageData(canvasLeft+575,dog* 60 + 31,10,10);
     ctx.putImageData(testSpot,20,dog * 60 + 30);
     if (testSpot.data[0] > 0){
         clearInterval(delayLoop);
       
         }
     }
 function goAgain(){
     location.reload();
     }

 function dogCatcher(dog){
     dogImage = ctx.getImageData(canvasLeft, dog * 60 + 15, 577, 55);
     }
   
function myDogSpot(x,y){
    ctx.putImageData(dogImage,x, y);
    }

 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

 }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,600,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="840" height="400" ;">  

    </canvas>

</body>
</html>

Wednesday, December 12, 2012

On your mark, get set, GO!

OK, if you have been following this blog, I hope you have copied and pasted the code from the earlier post and seen the dog's all stacked up and ready to race across the screen. Let's talk now about how we are going to move them.

You might think that we could somehow erase the image that is there and then draw it again in a new spot. We could do that, but it wouldn't be very efficient and might look pretty jerky on some screens.

The method that I prefer is to actually capture a small rectangle of the canvas and then put it back in a new location. To do that we will use getImageData() and putImageData().

We will need to declare a variable that will hold the image data. And we need to tell Javascript the location and the size of the image we want to capture. It goes like this:

var dogImage = ctx.getImageData(x, y, w, h);

x and y are the coordinates of the top left corner of the image we want to capture. w and h are the width and height.

To put the image back on the canvas we do this:

ctx.putImageData(dogImage, newX, newY);

So the image on the left shows you how we can use this work space that we set up next to our game background. The code you find at the bottom captured a section of the canvas using the following function. You can play with the numbers and make it capture only one whole dog.


  function dogCatcher(){
     dogImage = ctx.getImageData(canvasLeft, 15, 80, 80);
     }


And the following function is used to put a copy of it in our scratch space. We are using different background colors so it is easy to see how much of the image we are grabbing.

function myDogSpot(){
    ctx.putImageData(dogImage,0, 0);
    }

These two lines are added to the first function at the beginning of the script. Here is the whole thing. Copy and paste it into your editor and give it a try. I will let you do some trial and error to get the image you want.

Try for something like the one you see here. There is a reason I have more background on the left of the dog than on the right. We need that so we can overlap the previous image and erase the tail end of the dog as we move it forward across the screen. We will do that in the next  post. I have highlighted the new lines of code below, so you can simply add them to the code you copied last time if you prefer.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    var canvasLeft = 120;
   
    var myRed = "#FE0000";           //red - first value is 254
    var myBlue = "#0200DD";          //blue - first value is 2
    var myYellow = "#FCFF00";        //yellow - first value is 252
    var myMagenta = "#660066";       //magenta - first value is 253
    var myOrange = "#FF6600";        //orange is - first value is 255
    var myGreen = "#06FF00";         //green is - first value is 6
    var myBrown = "#993300";         //brown is - first value is 153
    var myBackground = "#007777";    //background -- first value is 0
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);

        dogCatcher();
        myDogSpot();

    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }

 var dogImage = 0;

 function dogCatcher(){
     dogImage = ctx.getImageData(canvasLeft, 15, 80, 80);
     }
   
 function myDogSpot(){
    ctx.putImageData(dogImage,0, 0);
    }


 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

 }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,600,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="840" height="400" ;">  

    </canvas>

</body>
</html>