HTML5/Canvas: Pente Demo

Written By Smooth Fusion

The new <canvas> element in HTML5 presents a great opportunity to produce things that were previously only possible using plugins like Flash. Since this element is so new, the available API methods are still rudimentary – you’ll need to think in terms of drawing primitive shapes like lines, arcs, and rectangles – but these basic methods are all you really need to build some quite complex applications.

In this demo, I’ve used canvas’s 2d drawing context and methods such as lineTo, arc, and fillRect to implement a browser-based version of the board game Pente. If you’ve never played, players take turn placing their markers on the board, and the object of the game is to place five of your markers in a continuous line. You can capture another player’s pieces by surrounding two of your opponent’s pieces with two of yours, and you may also win the game by capturing three sets of your opponent’s pieces. The major components of this application are:

-Drawing the board and pieces on the canvas

-Allowing the user to place elements on the board by interacting with the canvas

-The game logic

I’ll ignore the game logic in this post, and focus on populating the canvas and interacting with it.

Drawing the board and pieces on the canvas

We will be working with canvas’s 2d drawing context, which can be retrieved from a canvas DOM element with the method getContext.

ctx = canvas.getContext('2d');

To draw the board, we begin by erasing anything that is already present on the canvas, using theclearRectmethod.

ctx.clearRect(0, 0, width, height);

To draw the board itself, we call the beginPathmethod, move the cursor to the starting position of the line, then use the lineTomethod to move the cursor to the end of the line, and complete the path by calling thestrokemethod.

for (var i = 0; i <= config.gridSize; i++) {
            var pos = config.padding + (i * config.squareSize);

            // draw horizontal line #i
            ctx.beginPath();
            ctx.moveTo(config.padding, pos);
            ctx.lineTo(width - config.padding, pos);
            ctx.stroke();

            // draw vertical line #i
            ctx.beginPath();
            ctx.moveTo(pos, config.padding);
            ctx.lineTo(pos, height - config.padding);
            ctx.stroke();
}

Drawing a player’s marker is similar, but since it is a solid shape instead of a line, we use the arcmethod to create the path, and the fill method to complete it.

var drawPiece = function (player, row, col) {
    ctx.fillStyle = config.markerColors[player - 1];
    var x = config.padding + ((col + 1) * config.squareSize);
    var y = config.padding + ((row + 1) * config.squareSize);
    ctx.beginPath();
    ctx.arc(x, y, config.markerRadius, 0, Math.PI * 2, false);
    ctx.fill();
}

Allowing the user to place elements on the board by interacting with the canvas

Since the board and the markers we have drawn on it are not DOM elements, just lines and arcs on a canvas, it’s up to us to track how the user interacts with them. This is accomplished by tracking the mouse’s position when it is over the canvas element, and determining the corresponding location on the board.

var boardMouseOver = function (ev) {
    canvas.addEventListener('mousemove', boardMouseMove, false);
}

var boardMouseOut = function (ev) {
    canvas.removeEventListener('mousemove', boardMouseMove, false);
    document.getElementById('debug').innerHTML = '';
}

var boardMouseMove = function (ev) {
    var x = ev.clientX - offsetLeft - config.padding;
    var y = ev.clientY - offsetTop - config.padding;

Once we have the X and Y position of the cursor, relative to the canvas element, we can calculate the corresponding square.

// determine if the cursor is over an intersection, and change to a pointer/hand as appropriate
var row = col = -1;
for (var i = 0; i < (config.gridSize - 1); i++) {
    var pointX = (i + 1) * config.squareSize
    for (var j = 0; j < (config.gridSize - 1); j++) {
        var pointY = (j + 1) * config.squareSize;
        var d = distanceFromPoint(x, y, pointX, pointY);
        if (d <= config.cickRadius) {
            col = i;
            row = j;
            break;
        }
    }

    if (row >= 0 && col >= 0)
        break;
}

if (row >= 0 && col >= 0 && board[row][col] == 0) {
    document.body.style.cursor = 'pointer';
    hoverSquare = [row, col];
} else {
    document.body.style.cursor = 'default';
    hoverSquare = null;
}

The result of this calculation is cached in the hoverSquarevariable. With this value cached, when the user clicks on the canvas, we don’t need to repeat the calculation – we already know the location on the board they are clicking on.

The rest of the application is simple game logic. If you are interested in developing this application further using new web standards and HTML5, here are some great places to start:

-Add support for a single player game, using web workers to handle the computer player’s logic.

-Add support for playing other people over the web, using AJAX and your server-side technology of choice.

-Add chat support for players using web sockets.

Smooth Fusion is custom web and mobile development company and leading Progress Sitefinity CMS Partner. We create functional, usable, secure, and elegant software while striving to make the process painless for our customers. We offer a set of core services that we’ve adapted and refined for more than 250 clients over our 17 years in business. We’ve completed more than 1700 projects across dozens of industries. To talk to us about your project or review our portfolio, send us a message and one of our project managers will reach out to you quickly.