Building a Simple Game – Part 3

Continuing our journey to creating a game, after drawing the board, we should make sure the UI is actually responsive and a user can actually play the game.

The first interesting bit here is translating a UI click (mousedown event) to a change in the game state, and drive a redrawing of the new state.

class Board
{
playCell(cell)
{
range(1,this.stonesIn(cell))
.map(steps => this.cellFrom(cell,steps))
.forEach(c => this.addStoneTo(c))
this.setCellStoneCount(cell,0);
}
/**
* Calculate a target cell, given a new cell, walking counter-clockwise a number of steps as given
* @param {number} cell The cell we're starting from
* @param {number} steps The number of steps to take from this cell, counter-clockwise, to reach the new cell
*/
cellFrom(cell,steps)
{
//walk backwards, and if we pass cell 0, add the number of cells again.
return cell – steps + (cell < steps ? this.totalCellCount() : 0);
}
addStoneTo(cell)
{
this.setCellStoneCount(cell,this.stonesIn(cell)+1)
}
}
view raw board.js hosted with ❤ by GitHub
function drawBoardState(cnvs,board,boardClickHandler)
{
function drawOrRemove(boardCell,stoneCount,drawFunc)
{
if (stoneCount > 0)
{
removeDrawingAt(boardCell);
drawFunc(boardCell,stoneCount);
uiObjAt(boardCell).ifPresent(uiObj => {uiObj.on('mousedown', _ => { boardClickHandler(boardCell); })})
}
else removeDrawingAt(boardCell);
}
}
view raw drawing.js hosted with ❤ by GitHub
var canvas = None;
function initGame(cnvsELID)
{
canvas = maybe(initCanvas(cnvsELID));
canvas.ifPresent(cnvs => {
initDrawingElements(board.totalCellCount());
drawBoard(cnvs,CELL_COUNT);
drawBoardState(cnvs,board,boardClickHandler);
})
}
function boardClickHandler(boardCell)
{
board.playCell(boardCell);
canvas.ifPresent(cnvs => {
drawBoardState(cnvs,board,boardClickHandler)
})
}
view raw game.js hosted with ❤ by GitHub
Connecting a UI event to actual state change

So what’s going on here1?

If you remember, we already added an event listener to the mousedown event, when creating the necessary UI objects. At this point, we’re making sure it’s actually responding. Since we want the concern of responding to events to be contained in the controller (the game.js module), we add a callback function to the drawBoardState function and make sure it’s invoked at the correct time – when the mousedown event is fired. Note that at the point we attach the event, we’re already aware of the cell we’re attaching it to (the boardCell parameter is part of the closure), so this saves us the need to translate a UI object (or coordinates) to a game object/concept – the cell.

Looking at the callback function implementation – boardClickHandler in game.js – we see a rather simple implementation. We ask the board module to “play” the cell, i.e. play the stones in that cell – make a move. This is a new mutating method in the Board class. Then, assuming the canvas is available, the handler asks the drawing module to draw the new state again.

Finally, looking at the addition to the Board class, we can see how it’s “taking” all stones in the cell and adding one stone to each of the next cells counter clockwise, as per the Mancala rules + empties the played cell (line 11 in board.js above). The result of this method execution is a new state of the cells in the board data structures – a new count for some of the cells.

Note that all state changes in the board are expressed through one method – setStoneCellCount, this is intentional and will prove useful later. When coding, you don’t always foresee the future and where some choices might be useful. In this case, the benefit wasn’t immediately apparent. Still, I stuck to a simple principle of trying to express higher level functionality (playing a cell) using lower level functionality that’s already defined. This is basic structuring of the code, in this case following the DRY principle. At the very least it helps with readability, which is important by itself. In this case it also helps with encapsulation.

A Short Code Review

It’s important to acknowledge the fact that design choices are made as we write the code, not only upfront. It’s therefore worth stopping and reviewing what choices were made here.

First, if you look at the code, you can see that the Board module exposes the playCell method. This is in fact encoding a game rule – how a certain move is made in the game. We should be asking ourselves whether this is the right place for this kind of concern to be implemented. Admittedly, when writing the code I simply wrote it this way w/o giving it a second thought. This goes to show how sometimes such choices are made when we’re not paying attention. We’ll get back to this point later, when it’s actually fixed.

Second thing to note is that the canvas variable is in fact an Option type. Meaning, we could be running the game without a canvas to paint on. The code in the game module (the controller) is of course aware of this and explicitly asks whether the canvas is there, e.g. lines 6,16 in game.js. This is intentional – we would like to be able to run the game even when technically there’s no way to draw it (can you think why?). Of course, one has to ask whether the way to represent the fact that we’re running w/o a GUI is by managing the underlying drawing object (the canvas) in the controller…

Third thing to note is that throughout the code, we’re referring to the notion of the board cell by implementing it as a simple integer. In most cases this is not evident, we’re just passing a “board cell”, without specifying the type. It’s mostly evident in the board data structure itself (board.js), where we answer some queries by simply comparing numbers to the given cell. This is an intentional design choice made for simplification. I do not see a lot of benefit in defining a separate class representing a board cell. If I was using a more explicitly typed language, I would probably be aliasing the integer to a more readable type, e.g. in Scala: type BoardCell = Int. But this is not available nor needed in JS. As long as we keep that piece of knowledge (“board cell is in fact a simple integer”) confined to one module – the board data structure – we should be fine with this choice.

A Bit of Refactoring and Introducing Players

Our journey continues with a commit that is mostly about refactoring the controller. This is where the controller module (game.js) is rewritten to expose a class – MancalaGame, with an explicit interface:

class MancalaGame
{
constructor(cnvsELID,_updatePlayerCallback)
{
requires(_updatePlayerCallback != null,"Must have a player update callback")
this.board = new Board(CELL_COUNT);
this.player = PLAYER.one;
this.updatePlayerCallback = _updatePlayerCallback;
this.updatePlayerCallback(this.player);
this.canvas = maybe(initCanvas(cnvsELID));
this.canvas.ifPresent(cnvs => {
})
}
handleCellClick(boardCell)
{
this.board.playCell(boardCell);
this.updatePlayerCallback(this.togglePlayer());
this.canvas.ifPresent(cnvs => {
drawBoardState(cnvs,this.board,this)
})
}
togglePlayer()
{
this.player = this.player.theOtherOne();
return this.player;
}
}
view raw game.js hosted with ❤ by GitHub
The new MancalaGame class

This is done mostly for readability and for encapsulating whatever is necessary to run the game, as more and more pieces of logic and data come up. Note specifically that the board data structure, the canvas and associated callbacks are members of this class.

Another notion we’re introducing is that of the current player. The game has a current player in place, and the controller (MancalaGame) keeps track of it. Since we’re working under the assumption that there’re always exactly 2 players in this game, we can simply code them as constant objects:

const PLAYER = {
one : {
toString : () => "ONE"
, theOtherOne : () => PLAYER.two
}
, two : {
toString : () => "TWO"
, theOtherOne : () => PLAYER.one
}
}
view raw game.js hosted with ❤ by GitHub
Defining players

Note that while not explicitly exposed (from module.exports) these objects are in fact an API for this module, since they will be used by other modules. The togglePlayer method in MancalaGame actually exposes these objects. Also note that players are not represented by mere numbers (1,2), but as actual objects2. This is mainly because I want to have an explicit interface that encapsulate the current player, and being able to send messages to that object in different scenarios. For example, I’d like to display the player name (“ONE”, “TWO”) in the UI, and also toggle the players easily. Encapsulating the player behavior in an object (vs. representing as a simple number) is therefore more useful. We’ll see later where this comes in handy in more places.


So far, we’ve setup how the game is displayed, and the main mechanics of interaction. We have a skeleton of the game and the needed data structures to support implementing the logic. Next, we’ll start implementing more and more logic of the game, and see how this affects the code design.

Notes

  1. There are some bugs in the above code. No worries, it will be fixed
  2. Contrast this with choice made about representing a board cell

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.