Jump to content

How to Use the Canvas and Draw Elements in HTML5

+ 1
  chco's Photo
Posted Aug 17 2010 10:23 AM

In this excerpt from HTML5: Up and Running the author shows you how to use the new HTML5 elements <canvas> and <draw>.
Halma is a centuries-old board game. Many variations exist. In this example, I've created a solitaire version of Halma with nine pieces on a 9 x 9 board. In the beginning of the game, the pieces form a 3 x 3 square in the bottom-left corner of the board. The object of the game is to move all the pieces so they form a 3 x 3 square in the upper-right corner of the board, in the least possible number of moves.

There are two types of legal moves in Halma:

  • Take a piece and move it to any adjacent empty square. An “empty” square is one that does not currently have a piece in it. An “adjacent” square is immediately north, south, east, west, northwest, northeast, southwest, or southeast of the piece’s current position. (The board does not wrap around from one side to the other. If a piece is in the leftmost column, it cannot move west, northwest, or southwest. If a piece is in the bottommost row, it cannot move south, southeast, or southwest.)
  • Take a piece and hop over an adjacent piece, and possibly repeat. That is, if you hop over an adjacent piece, then hop over another piece adjacent to your new position, that counts as a single move. In fact, any number of hops still counts as a single move. (Since the goal is to minimize the total number of moves, doing well in Halma involves constructing, and then using, long chains of staggered pieces so that other pieces can hop over them in long sequences.)


Figure 4-16 is a screenshot of the game itself; you can also play it online if you want to poke at it with your browser’s developer tools.

So how does it work? I’m so glad you asked. I won’t show all the code here (you can see it at http://diveintohtml5...amples/halma.js). I’ll skip over most of the gameplay code itself, but I want to highlight a few parts of the code that deal with actually drawing on the canvas and responding to mouse clicks on the <canvas> element.

Figure 4-16. Starting position of a Halma game

Attached Image


During page load, we initialize the game by setting the dimensions of the <canvas> itself and storing a reference to its drawing context:

gCanvasElement.width = kPixelWidth;
gCanvasElement.height = kPixelHeight;
gDrawingContext = gCanvasElement.getContext("2d");


Then we do something you haven’t seen yet—we add an event listener to the <canvas> element to listen for click events:

gCanvasElement.addEventListener("click", halmaOnClick, false);


The halmaOnClick() function gets called when the user clicks anywhere within the canvas. Its argument is a MouseEvent object that contains information about where the user clicked:

function halmaOnClick(e) {
    var cell = getCursorPosition(e);

    // the rest of this is just gameplay logic
    for (var i = 0; i < gNumPieces; i++) {
       if ((gPieces[i].row == cell.row) &&
           (gPieces[i].column == cell.column)) {
           clickOnPiece(i);
           return;
       }
    }
    clickOnEmptyCell(cell);
}


The next step is to take the MouseEvent object and calculate which square on the Halma board just got clicked. The Halma board takes up the entire canvas, so every click is somewhere on the board. We just need to figure out where. This is tricky, because mouse events are implemented differently in just about every browser:

function getCursorPosition(e) {
    var x;
    var y;
    if (e.pageX || e.pageY) {
      x = e.pageX;
      y = e.pageY;
    }
    else {
      x = e.clientX + document.body.scrollLeft +
           document.documentElement.scrollLeft;
      y = e.clientY + document.body.scrollTop +
           document.documentElement.scrollTop;
    }


At this point, we have x and y coordinates that are relative to the document (that is, the entire HTML page). But we want coordinates relative to the canvas. We can get them as follows:

x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;


Now we have x and y coordinates that are relative to the canvas. That is, if x is 0 and y is 0 at this point, we know that the user just clicked the top-left pixel of the canvas.

From here, we can calculate which Halma square the user clicked, and then act accordingly:

var cell = new Cell(Math.floor(y/kPieceWidth),
                        Math.floor(x/kPieceHeight));
    return cell;
}


Whew! Mouse events are tough. But you can use the same logic (in fact, this exact code) in all of your own canvas-based applications. Remember: mouse click→document-relative coordinates→canvas-relative coordinates→application-specific code.

OK, let’s look at the main drawing routine. Because the graphics are so simple, I’ve chosen to clear and redraw the board in its entirety every time anything changes within the game. This is not strictly necessary. The canvas drawing context will retain whatever you have previously drawn on it, even if the user scrolls the canvas out of view or changes to another tab and then comes back later. If you’re developing a canvas-based application with more complicated graphics (such as an arcade game), you can optimize performance by tracking which regions of the canvas are “dirty” and redrawing just the dirty regions. But that is outside the scope of this book. Here’s the board-clearing code:

gDrawingContext.clearRect(0, 0, kPixelWidth, kPixelHeight);


The board-drawing routine should look familiar. It’s very similar to how we drew the canvas coordinates diagram:

gDrawingContext.beginPath();

/* vertical lines */
for (var x = 0; x <= kPixelWidth; x += kPieceWidth) {
    gDrawingContext.moveTo(0.5 + x, 0);
    gDrawingContext.lineTo(0.5 + x, kPixelHeight);
}

/* horizontal lines */
for (var y = 0; y <= kPixelHeight; y += kPieceHeight) {
    gDrawingContext.moveTo(0, 0.5 + y);
    gDrawingContext.lineTo(kPixelWidth, 0.5 +  y);
}

/* draw it! */
gDrawingContext.strokeStyle = "#ccc";
gDrawingContext.stroke();


The real fun begins when we go to draw each of the individual pieces. A piece is a circle, something we haven’t drawn before. Furthermore, if the user selects a piece in anticipation of moving it, we want to draw that piece as a filled-in circle. Here, the argument p represents a piece, which has row and column properties that denote the piece’s current location on the board. We use some in-game constants to translate (column, row) into canvas-relative (x, y) coordinates, then draw a circle, then (if the piece is selected) fill in the circle with a solid color:

function drawPiece(p, selected) {
    var column = p.column;
    var row = p.row;
    var x = (column * kPieceWidth) + (kPieceWidth/2);
    var y = (row * kPieceHeight) + (kPieceHeight/2);
    var radius = (kPieceWidth/2) - (kPieceWidth/10);


That’s the end of the game-specific logic. Now we have (x, y) coordinates, relative to the canvas, for the center of the circle we want to draw. There is no circle() method in the canvas API, but there is an arc() method. And really, what is a circle but an arc that goes all the way around? Do you remember your basic geometry? The arc() method takes a center point (x, y), a radius, a start and end angle (in radians), and a direction flag (false for clockwise, true for counterclockwise). We can use the Math module that’s built into Javascript to calculate radians:

gDrawingContext.beginPath();
gDrawingContext.arc(x, y, radius, 0, Math.PI * 2, false);
gDrawingContext.closePath();


But wait! Nothing has been drawn yet. Like moveTo() and lineTo(), the arc() method is a “pencil” method. To actually draw the circle, we need to set the strokeStyle and call stroke() to trace it in “ink”:

gDrawingContext.strokeStyle = "#000";
gDrawingContext.stroke();


What if the piece is selected? We can reuse the same path we created to draw the outline of the piece to fill in the circle with a solid color:

if (selected) {
    gDrawingContext.fillStyle = "#000";
    gDrawingContext.fill();
}


And that’s...well, that’s pretty much it. The rest of the program is game-specific logic—distinguishing between valid and invalid moves, keeping track of the number of moves, detecting whether the game is over. With nine circles, a few straight lines, and an onclick handler, we’ve created an entire game in <canvas>. Huzzah!

HTML5: Up and Running

Learn more about this topic from HTML5: Up and Running.

If you don't know about the new features available in HTML5, now's the time to find out. This book provides practical information about how and why the latest version of this markup language will significantly change the way you develop for the Web. HTML5: Up & Running carefully guides you though the important changes in this version with lots of hands-on examples, including markup, graphics, and screenshots. You'll learn how to use HTML5 markup to add video, offline capabilities, and more -- and you’ll be able to put that functionality to work right away.

See what you'll learn


Tags:
0 Subscribe


0 Replies