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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 twocommits 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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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))
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.
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
Determining the winner (lines 36-41)
Notifying the UI (lines 35,43)
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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):
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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:
The Board module is essentially a data structure maintaining a board state – how many stones are in each cell, etc.
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.
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.