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 commit1, 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 message2. 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”3 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.

Notes

  1. I casually skipped this commit here, since it’s really just about aligning UI elements. But it’s there, have a look.
  2. The showMsg function is a callback to the UI (the index.html) that was fed to the MancalaGame class during initialization. It’s introduced in the same commit.
  3. or my “code smell nose”

Leave a Reply

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