Tag Archives: code

Adding Bots to a Simple Game

We have previously seen how we can create a fairly simple game in a short amount of time. In the closing post of the series, I mentioned one possible direction for evolving the code was adding the option to “play against the computer”, i.e. have the option for an automated player, or even two.

This is not only interesting from the coding point of view. It also allows us to explore how to code heuristics and/or “AI” into the game, compare strategies, etc.

In this post, I will describe the necessary changes made in the code in order to incorporate these bots into the game. This is not meant to be anyway authoritative source on how to write good AI for games, or how to implement it efficiently. It’s just one way to add this kind of feature.

If you want to see the end result, have a look at the deployed game instance, and follow the instructions for specifying automated players.

Enabling Automated Players

The first order of business is to facilitate introduction of AI players (“bots”). So far, the code we built was responding to mouse clicks in the browser, in other words, driven by human interaction. We’d like to allow for moves to be decided by code, and for the game to invoke such code and proceed with the game, without any human interaction.

Keeping to the spirit of the original design, we’re still building the automated players as part of the Javascript, browser-based code. We don’t want to introduce another separate runtime component to the system.

So the next two commits (6a4c509, 5489462) introduce this facility:

const PLAYER = {
one : {
toString : () => "ONE"
, theOtherOne : () => PLAYER.two
, number : 1
, ai : () => PLAYER.one._aiPlayer
, _aiPlayer : None
}
, two : {
toString : () => "TWO"
, theOtherOne : () => PLAYER.one
, number : 2
, ai : () => PLAYER.two._aiPlayer
, _aiPlayer : None
}
}
class MancalaGame
{
constructor(gameSize,cnvsELID,_updatePlayerCallback,_showMsgCallback,requestedAIPlayers)
{
this._setupAIPlayersIfRequested(requestedAIPlayers);
}
_setupAIPlayersIfRequested(requestedAIPlayers)
{
dbg("Setting AI Players…");
PLAYER.one._aiPlayer = maybe(determineAIPlayer(requestedAIPlayers.p1))
PLAYER.two._aiPlayer = maybe(determineAIPlayer(requestedAIPlayers.p2))
dbg("AI Players: P1: " + PLAYER.one._aiPlayer + ", P2: " + PLAYER.two._aiPlayer);
function determineAIPlayer(requestedAI)
{
return requestedAI ? new SimpleAIPlayer() : null;
}
}
start()
{
this._makeNextMoveIfCurrentPlayerIsAI();
}
handleCellClick(boardCell)
{
this._makeNextMoveIfCurrentPlayerIsAI()
}
_makeNextMoveIfCurrentPlayerIsAI()
{
if (!this.gameDone)
{
this.player.ai().ifPresent(aiPlayer => {
let aiMove = aiPlayer.nextMove(this.board,this.player.number)
setTimeout(() => { this._makeMove(aiMove)}, 200) //artifical wait, so we can "see" the ai playing
})
}
}
}
view raw game.js hosted with ❤ by GitHub
const P2_PARAM_NAME = "p2";
const P1_PARAM_NAME = "p1";
function setup()
{
game = new main.MancalaGame(resolveGameSize(),'cnvsMain',updateCurrentPlayer,showMsg, resolveRequestedAI());
game.start();
}
function resolveRequestedAI()
{
let params = new URLSearchParams(window.location.search);
let p2 = params.has(P2_PARAM_NAME) ? params.get(P2_PARAM_NAME) : "";
let p1 = params.has(P1_PARAM_NAME) ? params.get(P1_PARAM_NAME) : "";
return { p1 : p1, p2 : p2}
}
view raw index.html hosted with ❤ by GitHub
class SimpleAIPlayer
{
constructor()
{
}
/**
* Given a board and side to play, return the cell to play
* @param {Board} board The current board to play
* @param {number} side The side to player, either 1 or 2
*
* @returns The board cell to play
*/
nextMove(board,side)
{
requires(board != null, "Board can't be null for calculating next move")
requires(side == 1 || side == 2,"Side must be either 1 or 2")
//Simple heuristic: choose the cell with the largest number of stones.
var maxStoneCell = side == 1 ? 1 : board.totalCellCount()-1;
switch (side)
{
case 1 : board.forAllPlayer1Cells(c => { if (board.stonesIn(c) > board.stonesIn(maxStoneCell)) maxStoneCell = c })
case 2 : board.forAllPlayer2Cells(c => { if (board.stonesIn(c) > board.stonesIn(maxStoneCell)) maxStoneCell = c })
}
dbg("Playing cell: " + maxStoneCell + " with " + board.stonesIn(maxStoneCell) + " for player " + side);
return maxStoneCell;
}
}
module.exports = {
SimpleAIPlayer
}
Mancala Bots

You can see that the setup of the AI players is done during the construction of the MancalaGame class (game.js, line 24). The requested AI player type is passed using URL parameters (index.html, lines 6,10-16) which are then parsed and used for initializing the bots. Note that the necessary player class is setup in the global PLAYER objects1. At this point we have only one type of player – SimpleAIPlayer.

The invocation of the AI player happens when responding to the player’s click (game.js, line 50). The _makeNextMoveIfCurrentPlayerIsAI function (game.js lines 53-62) simply checks that the game isn’t done, and whether the current player is an AI one. If it is, we invoke the nextMove function which is the main function in the AI player’s interface. The result of this function is simply the cell to play. The function then invokes the makeMove function, which is also used for human players from the UI.

Another small addition is the start function for the MancalaGame class (game.js, lines 42-45). Which simply tests if player one (the current player at the beginning of the game), is an AI, and makes a move. This is used to kickstart a game where the first player is a bot. The 2nd player being a bot, and further moves of the 1st player, is handled through the mechanism described above.

We Start Simple

The first implementation of a bot in this game (SimpleAIPlayer.js) is dead simple – it’s based on a very naïve heuristic: play the cell with the most cells in it. To be honest, it’s only here to serve as a straw man for testing the whole bot mechanism; there’s not a lot of wisdom in this heuristic. It can also serve as a benchmark for testing other strategies.

The implementation of the nextMove method – the only method in the bot interface – is pretty straightforward: based on the current player playing (the side parameter), simply search for the cell with the most stones on that side of the board (lines 21-26 in SimpleAIPlayer.js). Note that this strategy is deterministic – given a board state, it will always choose the same cell2. This is important when testing this, and also for testing this against other bot strategies.

Mancala Bot Wars

The next commit adds another simple AI bot player – Random AI Player, which simply picks a cell to play at random. This in itself isn’t very interesting. What is interesting3 is that now we have two types of bots, and we can run experiments on what strategy is better – we just pit the two bots against each other.

In order to do that, we’ll write some code that enables us to run the game with two bots and record the result. The next commit takes care of that:

program
.version('0.0.1')
.usage("Mancala Bot War: Run Mancala AI Experiment")
.option('-p1, –player1 <AI 1 type>', "The bot to use for player 1")
.option('-p2, –player2 <AI 2 type>', "The bot to use for player 2")
.option('-r, –rounds <rnds>', "Number of games to play")
.option('-o, –out <dir>', "output file")
.parse(process.argv)
range(1,program.rounds*1).forEach(round => {
dbg(`Round ${round}`)
let game = new MancalaGame(14,'',_ => {},gameMsg,{p1 : program.player1, p2:program.player2},results => {
dbg (`Round ${round} Results: ${results}`)
let line = []
line.push(results.player1StoneCount)
line.push(results.player2StoneCount)
line.push(results.winner || 0) //write the player who one or 0 for draw
fs.appendFileSync(program.out,line.join(',') + "\n",{flag :'a'})
})
game.start();
})
view raw bots.js hosted with ❤ by GitHub
class MancalaGame
{
constructor(gameSize,cnvsELID,_updatePlayerCallback,_showMsgCallback,requestedAIPlayers,_gameOverCallback)
{
this.gameOverCallback = _gameOverCallback
if (cnvsELID)
{
this.boardUI = maybe(new BoardUI(cnvsELID,this.cellCount,this))
this.boardUI.ifPresent(bui => bui.initializeBoardDrawing());
}
else this.boardUI = None; //headless mode
}
_redraw()
{
this.boardUI.ifPresent(_ => _.drawBoardState(this.board,this));
}
_gameOver()
{
let player1StoneCount = this.board.player1StoneCount();
let player2StoneCount = this.board.player2StoneCount();
let results = { player1StoneCount : player1StoneCount, player2StoneCount : player2StoneCount}
switch (true)
{
case player1StoneCount > player2StoneCount : results.winner = 1; break;
case player2StoneCount > player1StoneCount : results.winner = 2; break;
default : results.isDraw = true; break;
}
this.gameOverCallback(results);
}
togglePlayer()
{
this.player = this.player.theOtherOne();
this.boardUI.ifPresent(_ => _.toggleHighlights(this.player.number));
return this.player;
}
}
view raw game.js hosted with ❤ by GitHub
<script>
function setup()
{
game = new main.MancalaGame(resolveGameSize(),'cnvsMain',updateCurrentPlayer,showMsg, resolveRequestedAI(),gameOver);
game.start();
}
function gameOver(results)
{
let a = ["Game Over","# Stones P1:" + results.player1StoneCount,"# Stones P2: " + results.player2StoneCount];
if (!results.isDraw)
a.push(`Player ${results.winner} Wins!`)
else
a.push('Draw!')
showMsg(a.join('<br/>'))
}
</script>
view raw index.html hosted with ❤ by GitHub
Mancala Bot Wars!

First, you’ll note the bots.js file, which is a simple script, invoked from the command line, that simply runs several rounds of the game and writes the results to a file. Every round creates a new game instance with the chosen player types, and calls game.start. It’s a script with some parameters, nothing fancy.

In order to support this, we need to refactor the MancalaGame class a bit. Specifically, we need it to be able to run w/o UI. This means two things: the game needs to run without a UI, and delivering the results has to be decoupled from the UI as well.

This is what happens in the game.js file. In lines 3,6 you can see we add a callback to announce that the game is over, and transmit the result. This callback is used in the _gameOver method, in line 35. Note that the code for showing the game over message moved out from this class (as it should); look for it in the index.html file.

In addition, we enable the client of the MancalaGame class to pass a null value for the cnvsELID constructor parameter, which signals to us that there’s no canvas UI to be used for this game instance. The boardUI member then becomes optional (lines 8-13) and whenever we access the boardUI, we need to make sure it’s present (lines 18,41).

We now have a game that can be run without human intervention, and play out solely with bots. We can now start experimenting with different game parameters, and see what strategy plays out better.

Prime geeky goodness.


Later commits clean up the bot experiment script, and add features. We also add other types of both player, with different heuristics (e.g. greedy capture, minimax player) so we can compare more strategies.

Building a Simple Game – Part 6

Last time we implemented the ending of the game, and cleaned up the code a bit.

In this post we’ll look into an enhancement that allows us to change the size of the game, some more refactoring and bug fixes. At the end of this post we’ll be at the point where we have a playable game.

Altering the Game Size

One feature we’d like to add is the ability to play variable size of the Mancala game. We treat the total number of cells as the “size” of the game. Since each player has the same number of cells, this needs to be an even number, and cells are split evenly between players.

The next commit adds this feature:

class MancalaGame
{
constructor(gameSize,cnvsELID,_updatePlayerCallback,_showMsgCallback)
{
this.cellCount = gameSize;
this.board = new Board(this.cellCount);
}
_initializeBoardDrawing()
{
drawBoard(cnvs,this.cellCount);
}
}
view raw game.js hosted with ❤ by GitHub
<script>
const SIZE_PARAM_NAME = 'size';
const DEFAULT_GAME_SIZE = 14;
const GAME_SIZE_UPPER_BOUND = 28;
const GAME_SIZE_LOWER_BOUND = 6;
function setup()
{
game = new main.MancalaGame(resolveGameSize(),'cnvsMain',updateCurrentPlayer,showMsg);
}
function resolveGameSize()
{
let params = new URLSearchParams(window.location.search);
let size = (params.has(SIZE_PARAM_NAME) && !isNaN(params.get(SIZE_PARAM_NAME))) ?
params.get(SIZE_PARAM_NAME)*1 : DEFAULT_GAME_SIZE;
let ret = Math.max( //we bound the game size between the minimum and the upper bound
Math.min(size
,GAME_SIZE_UPPER_BOUND || DEFAULT_GAME_SIZE)
,GAME_SIZE_LOWER_BOUND);
if (ret % 2 != 0) ret -= 1; //we have to make sure it's a divisble by 2.
console.log("Game size: " + ret);
return ret;
}
</script>
view raw index.html hosted with ❤ by GitHub
Adding option for variable game size

The implementation is pretty straightforward. Instead of having a constant (CELL_COUNT) in the controller module (game.js), we pass a parameter in the constructor (game.js, line 3), that is then fed to the Board data structure. The Board class already had this parameter, so it was already ready to work with any size, and did not assume a constant size.

What remains is simply having a way for the user to specify this. I decided to go with a simple URL parameter. This is simple enough to pass. So resolveGameSize in index.html (lines 15-27) takes care of reading the parameter from the URL query string and validating it. We then initialize the MancalaGame instance with this number (index.html line 12).

Again, Some Cleanup

The next two commits are really about a small but important refactoring. We’re encapsulating the board drawing in a class that takes care of all the drawing business, essentially encapsulating how the board is drawn and the user interaction with the board. The changes in drawing.js is mostly re-organization of different functions into class method (the BoardUI class). The more interesting part is how it’s actually used:

const {BoardUI} = require("./drawing.js")
class MancalaGame
{
constructor (…)
{
this.boardUI = new BoardUI(cnvsELID,this.cellCount,this)
this.boardUI.initializeBoardDrawing();
}
_redraw()
{
this.boardUI.drawBoardState(this.board,this);
}
togglePlayer()
{
this.player = this.player.theOtherOne();
this.boardUI.toggleHighlights(this.player.number);
return this.player;
}
}
view raw game.js hosted with ❤ by GitHub
Encapsulating the drawing code

The controller now doesn’t maintain a pointer to the canvas instance. Instead, it works through API provided by the BoardUI class, which is at a higher level of abstraction – initializeBoardDrawing, drawBoardState, toggleHighlights. The motivation here is really better encapsulation of the UI implementation.

The last commit for today takes care of one bug fix – making sure we skip the opponent’s mancala when making a move:

class Board
{
/**
* Retrieve the cell number for the home (Mancala) of the given player.
* @param {numer} player The number of the player whose Mancala we're seeking (1 or 2)
* @returns The cell number for the given player's Mancala ( either 0 or totalCellCount/2)
*/
homeOf(player)
{
requires(player == 1 || player == 2,"Player number can be either 1 or 2");
return player == 1 ? this.player1Home() : this.player2Home();
}
}
view raw board.js hosted with ❤ by GitHub
class MancalaGame
{
playCell(boardCell)
{
let targetCells = this._calculateTargetCellsForMove(boardCell);
}
_calculateTargetCellsForMove(fromCell)
{
let _ = this.board;
let stepCount = _.stonesIn(fromCell);
dbg("Playing " + stepCount + " stones from cell " + fromCell)
let targetCells = range(1,stepCount)
.map(steps => _.cellFrom(fromCell,steps,this.player.number))
.filter(c => c != _.homeOf(this.player.theOtherOne().number)) //remove, if applicable, the cell of the other player's mancala
while (targetCells.length < stepCount) //add any cells, until we reach a situation where we have enough holes to fill (per the stone count in the played cell)
{
let addedCell = _.cellFrom(targetCells[targetCells.length-1],1)
if (addedCell == _.homeOf(this.player.theOtherOne().number))
targetCells.push(_.cellFrom(addedCell,1))
else
targetCells.push(addedCell)
}
return targetCells;
}
}
view raw game.js hosted with ❤ by GitHub
Fixing a bug – skipping the opponents Mancala (home)

There’s nothing too fancy here. The gist of the fix is in lines 17-19 in the MancalaGame class4.

Incorporating this fix, however, prompted me to extract the calculation of the target cells to a different function (_calculateTargetCellsForMove), so the playCell function remains at the same level of abstraction, and is still readable5.

And We’re Done …

At this point, one should be able to build the code (npm run build) and point his browser to the resulting index.html. Look for it in the dist directory.

A working version of the game is available here, embedded here for your convenience:

Admittedly, it’s not much to look in terms of UI design. But it’s a simple game we did in a few hours time, and it works. If you followed along so far, give yourself a pat on the shoulder.

… But Wait, There’s More

The story of how to create a simple game is pretty much done. But there’s more that can be said and done here, more features to build, tweaks to do.

Concrete examples for more directions include better UI design, more features (undo/redo, saving game states, showing a log, etc.). I welcome any more suggestions, and of course pull requests.

If you look at the repo, you’ll see that I went in another direction for enhancement. I was more interested in incorporating bots (“AI”) into the game as a feature; so you could play against a bot, or even have two bots play against each other. Stay tuned for more on that front.

Building a Simple Game – Part 5

Last time we started really adding meat – the logic of the game rules. Today we’re going to look into some more logic, and how we wrap it up.

Ending a Game

It’s a game, but it does have to end at some point. This commit take care of exactly that – identifying the condition that ends the game and stops processing new moves:

class MancalaGame
{
constructor(cnvsELID,_updatePlayerCallback,_showMsgCallback)
{
this.gameDone = false;
}
handleCellClick(boardCell)
{ //todo: clean this up
if (this.gameDone)
{
dbg("Game over, get outta here");
return;
}
let currentPlayerHasNoMoreMoves = this.player1Playing(this.board.allPlayer1Cells(c => this.board.stonesIn(c) <= 0)) ||
this.player2Playing(this.board.allPlayer2Cells(c => this.board.stonesIn(c) <= 0))
if (currentPlayerHasNoMoreMoves)
this.gameOver();
this.canvas.ifPresent(cnvs => {
drawBoardState(cnvs,this.board,this)
})
}
}
gameOver()
{
let player1StoneCount = this.board.player1StoneCount();
let player2StoneCount = this.board.player2StoneCount();
let a = ["Game Over","# Stones P1:" + player1StoneCount,"# Stones P2: " + player2StoneCount];
switch (true)
{
case player1StoneCount > player2StoneCount : a.push("Player 1 Wins!"); break;
case player2StoneCount > player1StoneCount : a.push("Player 2 Wins!"); break;
default : a.push("Draw!"); break;
}
this.showMsg(a.join("<br/>"));
this.setGameOver();
}
setGameOver() { this.gameDone = true; }
}
view raw game.js hosted with ❤ by GitHub
Game over functionality

The implementation is pretty straightforward: we add a simple flag (gameOver) to the MancalaGame class (line 6) and consult it before processing any move (line 12). Of course, we have to take care of setting the flag properly, which happens at lines 18-21.

The gameOver function (lines 30-45) takes care of 3 main things6:

  1. Determining the winner (lines 36-41)
  2. Notifying the UI (lines 35,43)
  3. Setting the gameOver flag (line 44).

The implementation is pretty simple and straightforward, but admittedly, this function does a bit too much. We’ll take care of that right away.

Also, taking care of the message displayed to the user is not ideal thing to do here, but it’s not terrible, in my opinion, in this case.

Some Cleanup Is In Order

At this point, it has become quite clear that the handleCellClick function was becoming convoluted. It was quickly approaching the point of being unmanageable, doing too many things and operating at different levels of abstraction; e.g. encoding game rules while also taking care of UI messages. It was time for some cleaning.

The next commit 7 did exactly that:

class MancalaGame
{
constructor(cnvsELID,_updatePlayerCallback,_showMsgCallback)
{
this._initializeBoardDrawing();
}
_initializeBoardDrawing()
{
this.canvas.ifPresent(cnvs => {
})
}
handleCellClick(boardCell)
{
if (!this.gameDone)
{
this._resetGameMessagePanel();
if (this.isValidMove(boardCell))
this._makeMove(boardCell)
else
this.showMsg("Invalid Move")
}
else dbg("Game over, stop procrastinating")
}
_makeMove(boardCell)
{
let lastCell = this.playCell(boardCell);
this._togglePlayerOrExtraTurn(lastCell)
this._checkAndDeclareGameOverIfNecessary()
this._redraw();
}
_resetGameMessagePanel()
{
this.showMsg(" ")
}
_togglePlayerOrExtraTurn(lastCell)
{
let lastCellIsHomeOfCurrentPlayer = this.player1Playing(this.board.isPlayer1Home(lastCell)) ||
this.player2Playing(this.board.isPlayer2Home(lastCell))
if (!lastCellIsHomeOfCurrentPlayer)
this.updatePlayerCallback(this.togglePlayer());
else
this.showMsg("Extra Turn")
}
_checkAndDeclareGameOverIfNecessary()
{
let currentPlayerHasNoMoreMoves = this.player1Playing(this.board.allPlayer1Cells(c => this.board.stonesIn(c) <= 0)) ||
this.player2Playing(this.board.allPlayer2Cells(c => this.board.stonesIn(c) <= 0))
if (currentPlayerHasNoMoreMoves)
this._gameOver();
}
_redraw()
{
this.canvas.ifPresent(cnvs => {
drawBoardState(cnvs,this.board,this)
})
}
playCell(boardCell)
{
let _ = this.board;
let targetCells = range(1,_.stonesIn(boardCell)).map(steps => _.cellFrom(boardCell,steps))
let lastCell = targetCells[targetCells.length-1];
let lastCellWasEmpty = _.stonesIn(lastCell) == 0;
targetCells.forEach(c => _.addStoneTo(c));
this._checkAndCaptureIfNecessary(lastCell,lastCellWasEmpty);
_.setCellStoneCount(boardCell,0);
return lastCell;
}
_checkAndCaptureIfNecessary(lastCell,lastCellWasEmpty)
{
let _ = this.board;
let isLastCellAHomeCell = _.isPlayer1Home(lastCell) || _.isPlayer2Home(lastCell);
let lastCellBelongsToCurrentPlayer = this.player1Playing(_.isPlayer1Cell(lastCell)) ||
this.player2Playing(_.isPlayer2Cell(lastCell))
if (lastCellWasEmpty && !isLastCellAHomeCell && lastCellBelongsToCurrentPlayer)
{ //capture the stones from the other player
let acrossCell = _.totalCellCount() – lastCell;
dbg("Capturing stones from " + acrossCell + " to " + lastCell)
_.setCellStoneCount(lastCell,_.stonesIn(lastCell) + _.stonesIn(acrossCell));
_.setCellStoneCount(acrossCell,0);
}
}
}
view raw game.js hosted with ❤ by GitHub
Cleaning up

The gist of what we’re doing here is a simple refactoring of “extract method”: taking a few lines of code, extracting them into a separate function/method and invoking that function in the right location. The main motivation is breaking down a long function into more digestible pieces, making the code more readable. There’s not even a lot of code reuse going on around here, which might be another motivation for extracting a piece of code into another function. The resulting code is, in my opinion, easier to follow, and troubleshoot in the future. Function logic is expressed more succinctly and in a more consistent level of abstraction.

Take for example the new handleCellClick function (lines 16-27 above). If you read it, its functionality can be summarized in one sentence: “reset the message panel, then assuming the game isn’t over and the move is valid, make the move”.

Similarly, the _makeMove function (lines 29-35 above), that is being called from the handleCellClick function, can be summarized in one sentence: “play the cell passed, then considering the last cell reached, decide if an extra turn is in place; check if we reached the end of the game and redraw the board.”8

This mental exercise of trying to describe a function’s implementation in a sentence or two9 is an important one when trying to assess the readability of the code, which from my experience is a crucial quality factor. I believe it’s hard to assess readability with an objective criteria, but when writing and reading my code, this is how I try to assess it.

Bug Fixing and a UI Improvement

The next two commits, affectionately known as 38feec5 and 8879f3110, take care of fixing a bug, and making a small (but significant) addition to the user interface:

_checkAndCaptureIfNecessary(lastCell,lastCellWasEmpty)
{
let _ = this.board;
let isLastCellAHomeCell = _.isPlayer1Home(lastCell) || _.isPlayer2Home(lastCell);
let lastCellBelongsToCurrentPlayer = this.player1Playing(_.isPlayer1Cell(lastCell)) ||
this.player2Playing(_.isPlayer2Cell(lastCell))
if (lastCellWasEmpty && !isLastCellAHomeCell && lastCellBelongsToCurrentPlayer)
{ //capture the stones from the other player
let acrossCell = _.totalCellCount() – lastCell;
let targetHome = this.player == PLAYER.one ? _.player1Home() : _.player2Home();
let totalCapturedStones = _.stonesIn(lastCell) + _.stonesIn(acrossCell);
dbg("Capturing stones from " + acrossCell + " and " + lastCell + " to " + targetHome + ". Total: " + totalCapturedStones )
_.setCellStoneCount(targetHome,_.stonesIn(targetHome) + totalCapturedStones);
_.setCellStoneCount(acrossCell,0);
_.setCellStoneCount(lastCell,0);
}
}
view raw game.js hosted with ❤ by GitHub
Properly Capturing Stones

The logic for capturing the stones is pretty straightforward. One thing to note is that the bug fix is localized in one place11 – a testament to good code structure; though, to be fair, this isn’t really a cross-cutting concern. Another thing to note is the calculation of the cell across from the last cell – acrossCell (line 9) – the simple calculation can be done because we rely on array indices. A better implementation would have deferred this to the Board class, and exposed a method named something like getAcrossCell(fromCell), so we can let this implementation detail remain in the Board class; this is an example of an abstraction leak (anyone up for a pull request to fix this?).

The second commit takes care of creating and toggling the highlighting of the current player:

function createHighlights(cellCount)
{
let w = (_boardWidthInCells -2)* CELL_SIZE;
let t = TOP_LEFT.y;
let l = TOP_LEFT.x + CELL_SIZE;
let boardHeightInCells = 3;
p1Highlight = new fabric.Line([l,t,l+w,t],{selectable:false,stroke:'red',strokeWidth:3})
p2Highlight = new fabric.Line([l,t+(boardHeightInCells*CELL_SIZE),l+w,t+(boardHeightInCells*CELL_SIZE)],{selectable:false,stroke:'red',strokeWidth:3})
}
function toggleHighlights(canvas,player)
{
requires(player == 1 || player == 2,"Invalid player when switching highlights")
switch (player)
{
case 1 :
canvas.add(p1Highlight);
canvas.remove(p2Highlight);
break;
case 2 :
canvas.add(p2Highlight);
canvas.remove(p1Highlight);
break;
}
}
view raw drawing.js hosted with ❤ by GitHub
_initializeBoardDrawing()
{
toggleHighlights(cnvs,this.player.number)
}
togglePlayer()
{
this.canvas.ifPresent( cnvs => {
toggleHighlights(cnvs,this.player.number);
})
}
view raw game.js hosted with ❤ by GitHub
Highlighting the Current Player

Technically, the highlight itself is simply drawing a red line on the border of the current player’s side of the board. We create 2 instances of fabric.Line in the drawing module, and then simply add and remove them to the canvas when necessary. Note that the toggleHighlights function in the drawing module (line 13) receives the player’s number and queries it directly. I don’t see this as a case of using magic numbers since the numbers themselves are clearly representative of the player object they’re representing, 1 for player 1, and 2 for player 2. I preferred avoiding exposing directly the the PLAYER objects in the game module (game.js).


We’re almost done. Next time we’re going to add a feature, cleanup a bit, and fix a bug; at which point we should have a working version of the game.

Building a Simple Game – Part 4

So after we hooked up and setup the skeleton of the game, it’s time to add some more meat – actually implementing the rules of the game.

Validating a Move

In our next commit12, we introduce the notion of validating a move:

handleCellClick(boardCell)
{
if (!this.isValidMove(boardCell))
this.showMsg("Invalid Move")
else
{
this.showMsg(" ")
this.board.playCell(boardCell);
this.updatePlayerCallback(this.togglePlayer());
this.canvas.ifPresent(cnvs => {
drawBoardState(cnvs,this.board,this)
})
}
}
isValidMove(boardCell)
{
let isValidPlayer1Move = this.player == PLAYER.one && this.board.isPlayer1Cell(boardCell);
let isValidPlayer2Move = this.player == PLAYER.two && this.board.isPlayer2Cell(boardCell);
return isValidPlayer1Move || isValidPlayer2Move;
}
view raw game.js hosted with ❤ by GitHub
Validating a move

The logic is simple: when we’re handling a cell click, i.e. a request to make a move, we first make sure the move is legal/valid. If it is, we make the move (line 8), and update the UI as before. If it’s not valid, we ask the UI to show some error message13. Note that some UI work – showing a message, displaying who is the current player – happen regardless of the canvas being there or not.

The validation function itself – isValidMove – is fairly straightforward at this point. It merely checks that the move is of a cell that belongs to the current player. What’s more important is that we have a specific place to validate a move. We can augment it with further rules later. Another design choice made here is that the isValidMove function returns a boolean result – a move is either valid or not, without any specification of the nature of the problem. A more robust mechanism would’ve returned some indication of the actual problem with the move; mostly for the purpose of showing the player a reason for rejecting the move.

Capturing Stones

Our next commit does two important things:

handleCellClick(boardCell)
{
if (!this.isValidMove(boardCell))
else
{
this.playCell(boardCell); //was: this.board.playCell(boardCell);
}
}
playCell(boardCell)
{
let _ = this.board;
let targetCells = range(1,_.stonesIn(boardCell)).map(steps => _.cellFrom(boardCell,steps))
let lastCell = targetCells[targetCells.length-1];
let isLastCellEmpty = _.stonesIn(lastCell) == 0;
let isLastCellAHomeCell = _.isPlayer1Home(lastCell) || _.isPlayer2Home(lastCell);
let lastCellBelongsToCurrentPlayer = (_.isPlayer1Cell(lastCell) && this.player == PLAYER.one) ||
(_.isPlayer2Cell(lastCell) && this.player == PLAYER.two)
targetCells.forEach(c => _.addStoneTo(c));
if (isLastCellEmpty && !isLastCellAHomeCell && lastCellBelongsToCurrentPlayer)
{ //get the stones from the other player
let acrossCell = _.totalCellCount() – lastCell;
_.setCellStoneCount(lastCell,_.stonesIn(lastCell) + _.stonesIn(acrossCell));
_.setCellStoneCount(acrossCell,0);
}
_.setCellStoneCount(boardCell,0);
}
view raw game.js hosted with ❤ by GitHub
Capturing stones + important refactoring

First, we fix an old “wrong”. We move the function the implements the game rule into the MancalaGame class. Remember that when we reviewed the code in the past we took note of this issue; we’re now fixing it.

Next, we’re implementing another game rule – capturing stones. The logic isn’t very complicated, it’s basically encoding the game rule directly – see if we finished in an empty cell, and assuming it’s in the current player’s side, capture the stones from the opponent’s cell right across the board.

From a design point of view, we’re only doing here a change in the board’s state – mutating the local Board instance using its mutating methods (addStoneTo, setCellStoneCount). So we maintain the separation of concerns as we intended: isValidMove validates the move, playCell changes the board as necessary and drawBoardState updates the UI with the new state.

Extra Turn

Our next commit takes care of another rule – a player gets an extra turn if his move ended in his home (his Mancala):

handleCellClick(boardCell)
{
this.showMsg(" ")
if (!this.isValidMove(boardCell))
this.showMsg("Invalid Move")
else
{
let lastCell = this.playCell(boardCell);
let lastCellIsHomeOfCurrentPlayer = this.player1Playing(this.board.isPlayer1Home(lastCell)) ||
this.player2Playing(this.board.isPlayer2Home(lastCell))
if (!lastCellIsHomeOfCurrentPlayer)
this.updatePlayerCallback(this.togglePlayer());
else
this.showMsg("Extra Turn")
}
}
player1Playing(andAlso)
{
return this.player == PLAYER.one && (typeof(andAlso) == undefined ? true : andAlso);
}
player2Playing(andAlso)
{
return this.player == PLAYER.two && (typeof(andAlso) == undefined ? true : andAlso);
}
playCell(boardCell)
{
let lastCellBelongsToCurrentPlayer = this.player1Playing(_.isPlayer1Cell(lastCell)) ||
this.player2Playing(_.isPlayer2Cell(lastCell))
return lastCell;
}
view raw game.js hosted with ❤ by GitHub
Adding the rule for extra turn

The logic of the rule itself is in lines 9-14 above, and pretty straightforward to follow. Note how we create here an interaction with the playCell function. The playCell is primarily concerned with updating the board data structure (this.board). And returns an indication of the last cell played. The rest of the logic is in its calling function (handleCellClick), which checks for validity and tests for the extra turn. The game logic is still centralized in the same class, MancalaGame, but we’re also keeping a separation between the different functions, trying to maintain the cohesion of each function on its own.

Another small point to take note of here is a small refactoring, mainly for code readability. There are now at least 2 places checking for a specific condition only if a specific player is currently playing (lines 9,10 and 33,34 in the snippet above). My “DRY itch”14 came alive when seeing this, so the pattern is extracted into separate functions – player1Playing, player2Playing. We’ll see later how this pattern of identifying a condition based on the current player that’s playing repeats itself so this will come in handy down the road as well.


Next we’re going to look into how we identify when the game is over, and implement it. We’ll also wrap up this basic version of the game, and look into what else can be done.

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 here15?

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 objects16. 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.

Building a Simple Game – Part 2

So after a short introduction to the project, let’s dive into some commits.

Our story starts with this commit, where we set the basis for drawing the board.

A lot of the files there are boiler plate (webpack config, package.json). The interesting parts are the index.js and drawing.js files:

function initCanvas(canvasEl)
{
let ret = canvasEl && (new fabric.Canvas(canvasEl));
if (ret)
{
ret.line = (x1,y1,x2,y2,c) => drawLineOn(ret,x1,y1,x2,y2,c);
}
return ret;
}
function drawBoard(cnvs)
{
let CELL_SIZE = 50;
let CELL_COUNT = 14;
let TOP_LEFT = { x : 50, y : 50};
let playerCellCount = CELL_COUNT/2-1;
let boardWidthInCells = playerCellCount+2 //for the side cells (player homes)
let boardHeightInCells = 3;
//frame
horizLine(TOP_LEFT.x,TOP_LEFT.y,boardWidthInCells * CELL_SIZE); //top left to top right
verticalLine(TOP_LEFT.x,TOP_LEFT.y,CELL_SIZE*boardHeightInCells); //top left to bottom left
horizLine(TOP_LEFT.x,TOP_LEFT.y + CELL_SIZE*boardHeightInCells,boardWidthInCells * CELL_SIZE); // bottom left to bottom right
verticalLine(TOP_LEFT.x + boardWidthInCells * CELL_SIZE,TOP_LEFT.y,CELL_SIZE * boardHeightInCells); //top right to bottom right
//home cells
verticalLine(TOP_LEFT.x + CELL_SIZE,TOP_LEFT.y,CELL_SIZE*boardHeightInCells)
verticalLine(TOP_LEFT.x + CELL_SIZE*(boardWidthInCells-1),TOP_LEFT.y,CELL_SIZE*boardHeightInCells)
//cell horizontal lines
let upperCellY = TOP_LEFT.y + CELL_SIZE;
let lowerCellY = TOP_LEFT.y + CELL_SIZE*2;
let lineLen = (boardWidthInCells-2)*CELL_SIZE;
horizLine(TOP_LEFT.x + CELL_SIZE,upperCellY,lineLen)
horizLine(TOP_LEFT.x + CELL_SIZE,lowerCellY,lineLen)
//cell borders
range(2,CELL_COUNT/2).map(cellNum => {
verticalLine(TOP_LEFT.x + cellNum*CELL_SIZE,TOP_LEFT.y,CELL_SIZE)
verticalLine(TOP_LEFT.x + cellNum*CELL_SIZE,TOP_LEFT.y+CELL_SIZE*(boardHeightInCells-1),CELL_SIZE)
} )
function verticalLine(x,y,len) { cnvs.line(x,y,x,y+len); }
function horizLine(x,y,len) { cnvs.line(x,y,x+len,y); }
view raw drawing.js hosted with ❤ by GitHub
function initGame(cnvsELID)
{
drawBoard(initCanvas(cnvsELID));
}
view raw index.js hosted with ❤ by GitHub
index.js and drawing.js – the interesting parts.

The index.js is the start of the MancalaGame I mentioned previously. Similarly, the drawing.js is the start of the BoardUI module mentioned as well. Both are written as node modules that export the necessary API as functions to be used by other modules.

At this point, not a lot is going on. We expose a single function from index.js which basically only asks the drawing module to initialize the canvas and draw the board.

Initializing the canvas is simply initializing a fabric.js object with our given HTML canvas element. We’re adding there another function for easier writing (and reading) later, but not much more. We’re doing this so we can leverage the fabric.js API for drawing on the canvas. We’ll be using this object from now on to manipulate the canvas.

Drawing the board itself is simply a matter of calculating line lengths and locations, nothing more. Note how the cell size, number and location of the board are all hard-coded into the function. We’re avoiding magic numbers, which is good. But this is clearly not very extendible or configurable code.

At the end of this step we can launch the index.html file which simply calls the game initGame function we mentioned above when the page loads.


Our next commit is a simple refactoring – renaming the index.js file to game.js. This is a simple enough refactoring, but I wouldn’t dismiss it so easily. It’s important to maintain a mental model of what each module does and how it’s connected to other modules in the code. I described that model in the previous post, and this is where it is manifested. It starts with the file names; designing the API and how it’s used is easier when we have some idea of where different responsibilities lie. It’s easier to track this when we know what module we’re working on by simply looking at the file name17.

Drawing Board State

So far, we only drew the board itself, but no stones in it. If we only do minimal changes, we’d probably only be drawing circles and numbers at this point (to represent how much stones are in each cell). But there’s a more fundamental idea here. We’re in fact drawing the state of the game onto the board. At the start of the game there’s obviously an initial state of the board setup. But the more prominent idea here is that of drawing the state of the board onto the canvas, in correct places. In other words, we need some representation of the board state.

Enter board.js:

class Board
{
constructor(_cellCount)
{
this.cellCount = _cellCount;
this.board = [];
range(1,this.cellCount).forEach(_ => this.board.push(0))
this.forAllPlayer1Cells(cellInd => this.setCellStoneCount(cellInd,INITIAL_STONE_COUNT))
this.forAllPlayer2Cells(cellInd => this.setCellStoneCount(cellInd,INITIAL_STONE_COUNT))
dbg("Board initialized")
}
forAllPlayer2Cells(f) { … }
forAllPlayer1Cells(f) { … }
setCellStoneCount(boardCell,cnt) { this.board[boardCell] = cnt }
stonesIn(boardCell) { return this.board[boardCell]}
forAllCells(f) { … }
isPlayer1Cell(boardCell) { return boardCell >= 1 && boardCell <= this.cellCount/2-1; }
isPlayer2Cell(boardCell) { return boardCell >= this.cellCount/2+1 && boardCell <= this.cellCount-1; }
isPlayer1Home(boardCell) { return boardCell == 0; }
isPlayer2Home(boardCell) { return boardCell == this.cellCount/2; }
totalCellCount() { return this.cellCount; }
}
view raw board.js hosted with ❤ by GitHub
Board.js, initial version

This module exports a single class – Board, which maintains the data structure for maintaining the board state.

The state itself is maintained as a simple array of numbers – one number per board cell + the board’s cell count (the game’s “size”). But the array itself is not directly exposed. All API of the class does some query/manipulation over that array.

Note that there’s only one mutating method – setCellCount, which is the only method (for now) that changes the state. All other methods are simply querying the board’s state, or iterating over it.

One other noteworthy choice at this point: the API encodes the fact that there are only 2 possible players, e.g. by defining methods such as isPlayer1Cell, isPlayer2Cell. This is a deliberate choice, working under the assumption that the game, and its accompanying state, would only have 2 players playing. Accounting for a possibility of more players doesn’t seem useful, and more importantly worth the complication it could cause in the code. We’re “hard-coding” this choice (the number of players), but benefit by having a simpler and explicit API to the game’s state. This will also translate into easier expression of the game rules as we’ll see later.

Drawing the board state requires then a new function – drawBoardState in the drawing module:

function drawBoardState(cnvs,board)
{
board.forAllCells(boardCell => {
let stonesInCell = board.stonesIn(boardCell);
switch (true)
{
case board.isPlayer1Home(boardCell) : drawPlayer1Home(stonesInCell); break;
case board.isPlayer2Home(boardCell) : drawPlayer2Home(stonesInCell); break;
case board.isPlayer1Cell(boardCell) || board.isPlayer2Cell(boardCell): drawCell(boardCell,stonesInCell); break;
default : ERR ("Invalid board cell when drawing state: " + boardCell); break;
}
})
function drawPlayer1Home(stoneCount)
{
drawText(stoneCount,TOP_LEFT.x + CELL_SIZE / 2 – 10,TOP_LEFT.y + CELL_SIZE * 1.5 – 10)
}
function drawPlayer2Home(stoneCount)
{
drawText(stoneCount,TOP_LEFT.x + boardWidthInCells(board.totalCellCount()) * CELL_SIZE – CELL_SIZE/2 – 10,TOP_LEFT.y + CELL_SIZE*1.5-10)
}
function drawText(txt,left,top)
{
cnvs.add(new fabric.Text(txt+'',{fontSize : 20, left : left, top : top}))
}
}
view raw drawing.js hosted with ❤ by GitHub
Querying and drawing the board state

This function is now exposed outside, as another API function from drawing.js. There’s not a lot going on here except calculating positions and sizes for text elements. We can already see the meaning of choosing to be explicit in our API about player 1 and 2 – the function simply dispatches the correct calculation based on the characterization of the cell.

Tying It Together

To make sure all this drawing actually takes place, we make sure to tie it together. This happens by calling the new function from our controller module, `game.js`:

const CELL_COUNT = 14;
let board = new Board(CELL_COUNT)
function initGame(cnvsELID)
{
drawBoard(initCanvas(cnvsELID));
let cnvs = initCanvas(cnvsELID)
drawBoard(cnvs,CELL_COUNT);
drawBoardState(cnvs,board);
}
view raw game.js hosted with ❤ by GitHub
Making sure we actually draw the state

At this point, we’re only adding the call to draw the state from the game initialization function. Note how at this point we already need to extract the CELL_COUNT constant. It was previously in the drawBoard state, in drawing.js; now it’s extracted to the game module, and used both to initialize the board data structure itself, as well as the drawing the board (lines 3,10 in the above snippet).

Drawing the Cell

Our next commit takes care of drawing the actual cell count in the cells:

function drawCell(boardCell,stoneCount)
{
var left = 0;
var top = 0;
switch (true)
{
case board.isPlayer1Cell(boardCell) :
top = CELL_SIZE /2 – FONT_SIZE/2;
left = boardCell * CELL_SIZE + CELL_SIZE/2 – FONT_SIZE/2;
break;
case board.isPlayer2Cell(boardCell) :
top = CELL_SIZE * 2.5 – FONT_SIZE/2;
left = (board.totalCellCount() – boardCell) * CELL_SIZE + CELL_SIZE/2 – FONT_SIZE/2;
break;
default : ERR("Invalid board cell: must be either player 1 or player 2 cell");
}
drawText(stoneCount,TOP_LEFT.x + left,TOP_LEFT.y + top);
}
view raw drawing.js hosted with ❤ by GitHub
Drawing the cell

The function itself is simple enough, simply calculating the position of the drawn elements based on whether we’re drawing for player 1 (top row) or 2 (bottom row). The text itself is the stone count.

Note also how we eliminate a magic number – the font number.

But a Cell Can Be Empty…

An acute reader should note that at this point we’re only drawing the text figures in cells. But updating the state of the board will require us to also remove the text, in case no stones are actually in the cell. This is a design choice – not showing a circle with a text (number of stones) in an empty cell; but it’s one we’re making for the sake of a nicer UI. We’d like an empty cell to be really empty, and not show ‘0’.

The next commit takes care of that. We add the code to essentially remove an existing drawing object. This leads us to the need to remember which UI objects belong to which cell. This is done by maintaining an array of canvas elements18 that correspond to each cell, when drawn:

let stoneUIElement = [];
function initDrawingElements(cellCount)
{
range(1,cellCount).forEach( _ => stoneUIElement.push(None));
}
function rememberUIObj(boardCell,el) { stoneUIElement[boardCell] = maybe(el); }
function forgetUIObj(boardCell) { stoneUIElement[boardCell] = None; }
function uiObjAt(boardCell) { return stoneUIElement[boardCell]; }
view raw drawing.js hosted with ❤ by GitHub
Maintaining an array of UI canvas objects

Each element in the cell is an Option object 19 allowing to write more easily read code.

All that’s left to do is to keep track (“remember” and “forget”) the drawn elements, whenever we draw them. Of course, when the stone count is 0, we remove the canvas object, and “forget” it:

function drawBoardState(cnvs,board)
{
board.forAllCells(boardCell => {
let stonesInCell = board.stonesIn(boardCell);
switch (true)
{
case board.isPlayer1Home(boardCell) : drawOrRemove(boardCell,stonesInCell,(_,stoneCount) => { drawPlayer1Home(stoneCount); }); break;
case board.isPlayer2Home(boardCell) : drawOrRemove(boardCell,stonesInCell,(_,stoneCount) => { drawPlayer2Home(stoneCount); }); break;
case board.isPlayer1Cell(boardCell) || board.isPlayer2Cell(boardCell):
drawOrRemove(boardCell,stonesInCell,(boardCell,stoneCount) => { drawCell(boardCell,stoneCount); });
break;
default : ERR ("Invalid board cell when drawing state: " + boardCell); break;
}
})
function drawPlayer1Home(stoneCount)
{
rememberUIObj(board.player1Home(),
drawStones(stoneCount,
TOP_LEFT.x + CELL_SIZE / 2 – FONT_SIZE/2-MARGIN,
TOP_LEFT.y + CELL_SIZE * 1.5 – FONT_SIZE/2-MARGIN));
}
function drawPlayer2Home(stoneCount)
{
rememberUIObj(board.player2Home(),
drawStones(stoneCount,
/* left = */TOP_LEFT.x + boardWidthInCells(board.totalCellCount()) * CELL_SIZE – CELL_SIZE/2 – FONT_SIZE/2-MARGIN,
/* top = */TOP_LEFT.y + CELL_SIZE*1.5-FONT_SIZE/2-MARGIN));
}
function drawCell(boardCell,stoneCount)
{
switch (true)
{
case board.isPlayer1Cell(boardCell) :
… //calculating top,left
break;
case board.isPlayer2Cell(boardCell) :
… //calculating top,left
break;
default : ERR("Invalid board cell: must be either player 1 or player 2 cell");
}
rememberUIObj(boardCell,drawStones(stoneCount,TOP_LEFT.x + left,TOP_LEFT.y + top));
}
function drawOrRemove(boardCell,stoneCount,drawFunc)
{
if (stoneCount > 0)
{
drawFunc(boardCell,stoneCount);
uiObjAt(boardCell).ifPresent(uiObj => {uiObj.on('mousedown', _ => {dbg('Clicked cell: ' + boardCell + '!')})})
}
else removeDrawingAt(boardCell);
}
function removeDrawingAt(boardCell)
{
uiObjAt(boardCell).ifPresent(uiObj => {
cnvs.remove(uiObj);
forgetUIObj(boardCell);
});
}
function drawStones(stoneCount,left,top)
{
… //create canvas objects and add them to the canvas
//return the created canvas object
}
view raw drawing.js hosted with ❤ by GitHub
Drawing and keeping track of canvas objects

Note that the drawStones function does the actual “drawing” – creating the canvas objects and adding it to the canvas, but it also returns the created object (a Group). This allows calling code to “remember” the object. I intentionally did not add the code to remember the objects in this function, since it would’ve made the function’s code more complicated – mixing separate concerns 20. This way, there’s place in the code that does the drawing, and a place that does the accounting of the objects, each place operates at a different level of abstraction – the game board vs. the canvas objects.

Another thing to note is that regardless of the cell being drawn, the pattern of either drawing it or removing it is the same. The difference lies in how we calculate the coordinates of where to put it. We therefore create a single function – drawOrRemove – that encodes this repeating pattern. The function accepts another function – the drawFunc parameter – that does the actual drawing (calls drawCell or drawPlayer1Home, drawPlayer2Home). We could’ve chosen to abstract it differently – pass functions that simply calculate the coordinates. I eventually leaned towards this solution – a slightly higher abstraction (a function that draws the cell and not a function that calculates coordinates) thinking that I might want to alter the look and feel of different cells in the future.

One last thing to note in this part: when drawing the cell canvas objects, we’re already adding the mouse listener to respond to events (line 54 in the above snippet), at this point doing nothing except issuing a log message. We do this here since it’s the low level operation of attaching an event listener to the object. I’d like to maintain the rest of the code oblivious as much as possible to the details of the drawing objects.


Next, we’re going to look how the drawing will start responding to events, and what does that entail.

Coding for Fun – Building a Simple Game

I know this may come as a shock to some readers, but I love writing computer programs. Alas, my code-writing time is somewhat limited these days. So I try to find some side projects which won’t consume too much time, with little overhead and will still be interesting enough to be considered fun.

So I turned to games, of the computerized kind. It still requires quite some coding, but fairly isolated from other dependencies, and not too complicated. If the game is simple enough, you can end up with a working prototype in one or two evenings. Plus, you end up with something you can play with and manual testing (shiver… ) is more fun.

Aside from having a fun end result, games can be a great practice for a lot of subjects in programming. User interfaces is an obvious one; but think also how you model data structure for a game, use AI, persist state (or not), deploy it, etc.

In this post (with follow ups) I’d like to go through the process of one such game I created, completely in JS. I’ll go through the actual evolution and design choices and try to show the interesting evolution points in the code. This is not intended as an ultimate guide to how write such a (simple) game. Nor do I claim to show the best way to write it, only one specific way. The objective is to show how the design decisions are made, and how they’re reflected in actual code.

The Game: Mancala

In this case, I chose a fairly simple 2-player game called Mancala. I’m far from an expert on the game itself, but after playing it a few times with my kids, I can relate to how simple and yet not trivial game it can be. The simplicity of the rules and user interface lends it self perfectly for a simple project that can be expressed in code fairly easily.

I won’t dive into the details and rules of the game itself, only where it’s needed. If you need a short description of the game rules, this 1-pager does a decent job of it. Otherwise, google is your friend.

Where relevant, I will refer to the rules of the game, and specify where and if I took some liberty for the programmed version.

Before Coding – Choosing a Path

My main purpose here is to provide a step by step explanation of how the game was created and why. But before we dive into that, I believe it’s worth having some kind of an idea of what we want to achieve, and why. This step is often referred to as “Design”21.

Besides having fun coding, my goal is to create a game that is easily deployed and played, with minimal dependencies on libraries, and can be played by as many people as possible, on standard modern technical infrastructure.

Enter Javascript and a Browser.

So as a first choice, I choose to have this deployed as a purely Javascript-based application (in a web page), with no server component. Running the game is as simple as pointing the browser to a web page and loading it. This of course means that there’s no persisted game state (we could use local storage, but I don’t see the need so far), no user identity that’s relevant, etc. It’s just a game. Also, running the entire game in a single JS file, makes it easier to run in a different scenario, i.e. not just in a browser, but more on that later.

I use npm for package management, with webpack to package it all into a single JS file. This results in a simple deployment – an HTML + single JS file that contains all the game.

Naturally, some libraries are used to make life easier when coding, but I try to minimize the use of libraries as well.

The Code

The code for the game itself can be found in this github repo (ignore other folders in that repo). I’ll be going through the different commits to show how it was built, and link back to this repo.

Before diving into actual code, I believe some high level explanation on how the code is structured can help a lot. Note that the code structure changes and evolves during development, but the basic simple pattern is stable enough and should be kept in mind22.

Mancala Game High Level Components

At a very high level, the code is centered around 3 main components:

  1. The Board module is essentially a data structure maintaining a board state – how many stones are in each cell, etc.
  2. The BoardUI module is what draws the board into an HTML canvas. It queries the board to understand what to draw and then translates that into shapes to be drawn on the canvas. The drawing itself timed by the MancalaGame module.
  3. The MancalaGame module is the glue connecting all the parts. It gets UI events, translates them into changes in the board state, and times the redrawing of the board. This is where all the game rules are encoded.

The actual manifestation of this design changed a bit as the code evolved, and we’ll see that when we look into the code; but the core thinking is pretty much the same throughout.

Next, we’ll start looking at the code, specifically how we draw the board.

Code Writing from a UX Perspective

We often talk and discuss coding style and guidelines. Some of these discussions grow to be (some would say useless) flame wars, while others invoke thoughts and sometimes even reach a consensus, e.g. “GOTO considered harmful” (PDF). These discussions tend to be motivated by good intentions – making software development better; delivering software faster, with better quality and precision.

One area that keeps coming up is the need to have code written clearly; to make code easily understandable and changeable. It is generally agreed that code readability is important. And yet, the discussions are sometimes endless. We don’t seem to be able to agree what constitutes code that is written well enough, one that is easily understood. Claims made are often seen as very subjective and “a matter of taste”, as readability and code quality deals with human interpretation and judgement of said code.

At the same time, another field that deals with human-computer interaction seems to achieve better success in establishing at least some level of standard or agreed understanding of quality – software user experience (UX). Naturally, taste also plays a role in this case, but practitioners seem to agree more on some underlying practical principles to follow; similar to how coders have established some common understanding of good code design.

Which then begs the question – what if we approach code writing as a user experience problem, where the users are coders (incl. the code writer)?

Designers of applications’ user experience often deal with how the application is to be used. Characterizing its users and their main use cases. The answers to most of these questions are already known to us in the case of coders – we are the users. We write code, read it, test it, debug it. We know how we navigate code, skim through it, or how we approach refactorings.

Given that profiling the users is mostly out of the way, can we use this knowledge by taking a page from the UX designers’ playbook and applying it to code writing?
Can we design the code itself in such a way that will make our experience with it more productive and enjoyable, or dare I say delightful?

A naive approach would be to look at UI patterns and trying to apply them to code. While some may be somewhat useful (e.g. “inline hints”), it appears that the context of graphical UI design and its associated patterns does not immediately map to textual code writing. So while some insights can be had, this kind of mapping doesn’t seem to offer great value.

UX Principles in Code

But what if we go deeper?
Can we apply established principles of software-human interaction to code writing?

Take for example clarity. Good user interface designs are clear – at any point in the application they provide the user with an immediate understanding of what just happened, where is the user, what are the options available and their implications and so on. Think breadcrumbs, messages displayed, contextual disabling/enabling of buttons and so on.
What does this mean to code? Saying that “the code needs to be clear” is stating the obvious. Thinking about it from a user (=programmer) experience point of view we should be paying attention to the exact same things:

  • Is it clear to the user where he is from looking at the code?
  • Is the code conducive to understanding the options available at any given point?
  • Are implications of changes communicated clearly?

For example, consider a long file containing a lot of functionality, however related, a file with a lot of definitions, a class with a lot of methods and internal classes, nested callbacks, etc. These are examples of code that does not lend itself immediately to the structure and location in the code. It is harder to follow when reading and especially when debugging it; as debugging often involves jumping from file to file.
Similarly, having too many options to do one thing, e.g. overloaded functions, might introduce some confusion on what is the best way to do things. So there’s a clear tradeoff here between convenience (having the same method name, but with different parameters), and creating a confusing API.

Instead, keeping code listings shorter, tying code artifacts more closely to their location in the code base – e.g. one or two type(s) per file, can help with guiding the user of the code to understand what is the piece of code he is looking at. On the same note, providing one or two ways to do something, e.g. reading from a file or from stream, could actually make it easier for the caller of an API to understand what’s expected and how to work with that API.

Another principle to think about is familiarity and consistency. User interfaces, especially graphical ones, tend to prefer conventional solutions (hence patterns) for common tasks. Icons, links, color coding (red vs. green) are all examples of reusing familiar visual cues to convey meaning and being useful.

Familiarity can apply also to code writing. We can think of several places where this principle is applied:

  • Using common verbs, e.g. names of well known code design patterns
  • Naming getters and setters
  • Consistent naming for classes with similar roles
  • Well used parameter names – i for index, callback for callback functions, onXXX for event handlers
  • Packages following a similar scheme

Using well known patterns for organizing code, naming artifacts, etc. gives the programmer a better chance of understanding and navigating the code. At the project level it might make refactoring easier as it already suggests some code/module structure that can be followed, and may lead to more thoughtfulness with changes that break these familiar patterns.

Efficiency, of course, is also something to look into. Good user interfaces often emphasize the main scenarios supported by making them very efficient for the user. We count clicks, measure how much scrolling is needed, carefully design menus, etc. only to make sure that the common usage scenarios are easy to follow and accessible. In code this may translate to looking at the prevalent scenarios when coding. Readability is of course important, since we mostly read code. But another thing to consider is, for example, debugging and monitoring the running code. Do we insist, for example, on writing one operation per line, with variables defined for intermediate values; or do we cram together several access function calls making it harder to debug and trace the code?

Discouraging Bad Practices with Programmer’s User Experience

Another angle to look into the programmer’s user experience is to try and come with ways where the user experience actually encourages us to write better code.

An example for this is fibonacci indentation where each level of text indentation in the code is indented by a number of spaces matching the fibonacci number at that level. So deeper nesting levels are indented further away. At first, this may seem weird, but when you consider this, you quickly see how this discourages too much nesting – thus improving code readability and the overall experience when dealing with the code.

Another example might be to mandate no empty lines between a variable declaration and where the variable is used. This encourages declaring variables closer to where they are used, keeping related expressions closer visually; otherwise you end up with a big block of code – making it harder to follow.

How is this Useful?

These examples, and some of the conclusions presented here are nothing new to anyone considering the readability and clarity of code. Proper naming, for example, has always been in focus when considering code quality and readability. Still, it might be useful to frame the code readability/usability question as a user experience question, and borrow some practices from the field of user experience design. It helps to reason about better practices for structuring code.

Practically, this could be part of our code reviews, and configured/programmed into our already established set of coding tools – IDEs, linters, static code analysis tools, style guides, etc. The amount of tools available and polyglot nature of today’s software systems, where it’s not uncommon for a system to be coded in several languages, may in fact highlight the value of having a set of overarching principles and insights to guide us to better code writing. We should be able to define and follow principles that can be applied consistently and successfully in several languages at the same time.

One might argue, and justifiably so, that the job of making a programmer’s life easier is handled pretty well with the plethora of tools available, namely modern IDEs. But code can be made better (=easier to work with) by not relying on such mechanisms alone. Besides, not all editors are created equal, and code is viewed in tools other than the original IDE used to write it; e.g. code review tools, diff utilities. And of course, different people use different editors. So editors are very helpful, but I believe we can write clearer code if we don’t necessarily rely on that crutch.

Similarly, some languages also tend to lend themselves better to making code more readable. Type inference and lambda expressions help us make code more succinct and often more readable (though some would argue that explicit typing actually delivers a better experience). The user experience view of code writing is still useful in choosing how to use such language features, where choices are available.


To conclude, considering code writing from a user experience perspective might not be the most natural approach when programming. But if we agree that code readability and clarity is important – directly influencing quality and speed of development – then we should probably look at it from a proven perspective, using considerations and principles otherwise proven for software writing. A programmer’s “code experience” provides another perspective on how code is written and read and should probably be examined when making choices on how to write and structure code. Instead of debating on why a given code is written better this way or that, we can apply user experience guidelines to show us the path to a better code writing.