Making an HTML5 version of the Game Pente: Part 3

This is going to be my third installment in this series about making an HTML5 version of the game Pente. The main goal for this phase was to properly place the stones on the nearest grid intersection from where the user touched the board. That part turned out to be rather simple in the end, but my OCD kicked in along the way and I did some more refactoring and wanted to review some of the choices we made.

First we made some global variables to persist information about the game that needed to be referenced in several places through out game play. If you have been following along you might recognize some of the variables as well as some new names. The context is the DOM reference to the game board's (CANVAS) context member. Next is the block size or how large each square is on the board. The color stores the color used for the last stone. This is ultimately going to be used to determine winning, capturing and simulate proper turned based game play. The lines are the number of lines drawn on the board.

var

context,
boardOffset,
blockSize = 0,
color =

"#000"

,
lines = 12,
boardHeight = 0,
boardWidth = 0,
boardSize = 0,
stones = []; // stone = {x: 1, y: 2, color:

"#000"

}

The boardHeight and boardWidth store the dimensions of the game board. I will discuss how boardSize is calculated shortly, but it holds the square dimensions of the game board. An array of stones is the last global variable. It holds a list of stone objects that correspond to the stones placed on the board. A stone is an object with x, y properties for its placement coordinates and a color property to represent what color stone it is.

We made a few changes to the drawCircle method. The name has been changed to drawStone because that was more descriptive than drawCircle. The default fillStyle is not simply filled with a fixed value. Rather is uses a ternary operation to determine the color value. If the previous color was black it will change it to white and vice versa.

fillStyle: (color === "#000") ? "#fff" : "#000",

After the stone has been drawn on the board the color is set and the new stone object is pushed to the stones array. These are some of the global variables mentioned earlier and will be used as game play progresses.

color = options.fillStyle;
stones.push({
x: options.x,
y: options.y,
color: options.fillStyle
});

We decided to refactor the board drawing method into a method of its own. This makes it easier to redraw the board as the browser is resized or the screen orientation changes. The method sets several of the global variables discussed earlier. Ternary operators are used several places to set the value of some of these variables such as the context, boardOffset, etc. The boardOffset variable accounts for the vertical offset of the CANVAS from the top of the window. Right now the board is placed below a header with a height of 60 pixels. These 60 pixels need to be accounted for when determining the size of the board as where stones should be placed.

function

drawBoard(){

var

$penteBoard = $(

"#PenteBoard"

),
$window = $(window);
context = context ? context : $penteBoard[0].getContext(

"2d"

);
boardOffset = boardOffset ? boardOffset : $penteBoard.offset().top;
boardHeight = $window.height()-boardOffset;
boardWidth = $window.width();
boardSize = (boardHeight > boardWidth) ? boardWidth : boardHeight;
blockSize = boardSize/lines;
i = lines;
$penteBoard.attr(

"height"

, boardSize).attr(

"width"

, boardSize);
intersections = [];
context.strokeStyle =

"#000"

;
context.lineWidth = 0.99;

for

(i = lines; i > 0; i--){
context.beginPath();

var

x = blockSize + (lines - i) * blockSize;
context.moveTo(x + 0.5, 0);
context.lineTo(x + 0.5, boardSize);
context.stroke()
}

for

(i = lines; i > 0; i--){
context.beginPath();

var

y = blockSize + (lines - i) * blockSize;
context.moveTo(0.5, y + 0.5);
context.lineTo(boardSize, y + 0.5);
context.stroke()
}

//insert intersections


//intersections.push({x: ?, y: ?});


/*


var j = 0;


for(i = lines; i > 0; i--){


var x = blockSize + (lines - i) * blockSize;


for(j = lines; j > 0; j--){


var y = blockSize + (lines - j) * blockSize;


intersections.push({x: x, y: y});


}


}


*/


return

$penteBoard;
};

To determine the actual size of the board the height and width of the window are retrieved and we use the smaller of the two to set the square dimensions of the playing surface. Instead of setting the height and width of the Canvas element using CSS you need to set the dimensions using the element attributes. Setting the dimensions using CSS causes the Canvas to be rendered in a zoomed in mode, which is undesirable.

Another change we decided to make was a new method, placeStone, which does some clean up to place the stone on the nearest intersection. When a user touches the playing surface they are most likely not going to select the exact x, y coordinates they want. With a mouse the odds are higher than on a touch surface, but still there will be various degrees of error that need to be cleaned up.

At first I thought this was going be a lot of ugly math, but as it turns out it was really easy. The JavaScript Math library includes a method called round. Simply put this method returns the closest integer to the value passed. This means if you pass in 87.1111 it will return 87 and 87.755 returns 88.

function

placeStone(x, y){
x = Math.round(x/blockSize) * blockSize,
y = Math.round((y - boardOffset)/blockSize) * blockSize;
drawStone({
context: context,
x: x,
y: y,

// - boardOffset,


radius: blockSize * .5
});
};

Simply passing in the raw x, y values would not result in the  desired numbers. To get the right values we need to do some clean up. When the board is drawn a global blockSize variable is set. Since the board is flush with the left-hand side of the window the x coordinate can be divided by the blockSize then multiplied by the blockSize to get a more accurate x position. When the initial x coordinator is divided by the blockSize the remainder is removed.

When the initial x coordinate is 123 and the blockSize is 53.3...6 is divided into the coordinate the result is 2.30625. Using the Math.round method on that value returns 2. Multiplying 2 by blockSize is 106.6...67, which looks like a big discrepancy, but it actually gives us the exact coordinate we need. The same routine is done to the raw y coordinate, but the boardOffset needs to be subtracted from the raw y or pageY value to map to the right y coordinate on the board. The normalized x, y coordinates can then be passed to the drawStone method. One more thing, instead of a fixed number radius, the radius parameter is set to half the block size. This means all the stones will have the same diameter as each block, but be positioned nicely on the grid intersections. The stones will also touch as they are placed on the board. Later we may come back and make that a variable users can set, but for now this works.

The final change made to the code was adding an event handler for the window resize and orientationchange events. When a desktop browser is resized by the user the resize event fires, which will cause for a poor playing experience because the board surface is not taking advantage of the viewable area. On tablets like the iPad when the user rotates the device the same effect comes into play because most tablets and phones are not square, but rather rectangle. So the board needs to be redrawn. This is why the board drawing routine needed to be refactored to its own method.

 

$(document).ready(

function

() {
drawBoard().tap(

function

(e){
placeStone(e.pageX, e.pageY);
});
$(window).on(

"orientationchange, resize"

,

function

(e){
drawBoard();
});
});

Now that we can properly position stones on the board we need to get deeper into the actual game play. I will write about that later this week. Another issue we currently have is redrawing the stones when the playing surface is redrawn. All the stones will be cleared. So my next post will focus on using the stones array to redraw the stones in the new size and appropriate positions.

Share This Article With Your Friends!