In Chapter 3, Multiplayer Game API – Connect we built a multiplayer game API for the game Connect 4 in which we focused on the general mechanics of creating a game, joining a game, and playing it. This Appendix shows the accompanying game logic that we omitted in Chapter 3, Multiplayer Game API main text.
src/lib/connect4.js /* Connect 4 Game logic Written for Blueprints: Express.js, Chapter 3 */ var MIN_ROWS = 6, MIN_COLUMNS = 7, players = ['x','o']; // Initializes and returns the board as a 2D array. // Arguments accepted are int rows, int columns, // Default values: rows = 6, columns = 7 exports.initializeBoard = function initializeBoard(rows, columns){ var board = []; rows = rows || MIN_ROWS; columns = columns || MIN_COLUMNS; // Default values is minimum size of the game if (rows < MIN_ROWS) { rows = MIN_ROWS; } if (columns < MIN_COLUMNS) { columns = MIN_COLUMNS; } // Generate board for (var i = 0; i < rows; i++){ var row = []; for (var j = 0; j < columns; j++){ row.push(' '); } board.push(row); } return board; }; // Used to draw the board to console, mainly for debugging exports.drawBoard = function drawBoard(board){ var numCols = board[0].length, numRows = board.length; consolePrint(' '); for (var i = 1; i <= numCols; i++){ consolePrint(i+''); consolePrint(' '); } consolePrint(' '); for (var j = 0; j < numCols*2+1; j++){ consolePrint('-'); } consolePrint(' '); for (i = 0; i < numRows; i++){ consolePrint('|'); for (j = 0; j < numCols; j++){ consolePrint(board[i][j]+''); consolePrint('|'); } consolePrint(' '); for (j = 0; j < numCols*2+1; j++){ consolePrint('-'); } consolePrint(' '); } }; // Make a move for the specified player, at the indicated column for this board // Player should be the player number, 1 or 2 exports.makeMove = function makeMove(player, column, board){ if (player !== 1 && player !== 2) { return false; } var p = players[player-1]; for (var i = board.length-1; i >= 0; i--){ if (board[i][column-1] === ' '){ board[i][column-1] = p; return board; } } return false; } // Check for victory on behalf of the player on this board, starting at location (row, column) // Player should be the player number, 1 or 2 exports.checkForVictory = function checkForVictory(player, lastMoveColumn, board){ if (player !== 1 && player !== 2) { return false; } var p = players[player-1], directions = [[1,0],[1,1],[0,1],[1,-1]], rows = board.length, columns = board[0].length, lastMoveRow; lastMoveColumn--; // Get the lastMoveRow based on the lastMoveColumn for (var r = 0; r < rows; r++) { if(board[r][lastMoveColumn] !== ' ') { lastMoveRow = r; break; } } for (var i = 0; i<directions.length; i++){ var matches = 0; // Check in the 'positive' direction for (var j = 1; j < Math.max(rows,columns); j++){ if (board[lastMoveRow + j*directions[i][1]] && p === board[lastMoveRow + j*directions[i][1]][lastMoveColumn + j*directions[i][0]]){ matches++; } else { break; } } // Check in the 'negative' direction for (j = 1; j < Math.max(rows,columns); j++){ if (board[lastMoveRow - j*directions[i][1]] && p === board[lastMoveRow - j*directions[i][1]][lastMoveColumn - j*directions[i][0]]){ matches++; } else { break; } } // If there are greater than three matches, then that means there are at least 4 in a row if (matches >= 3){ return true; } } return false; }; function consolePrint(msg) { process.stdout.write(msg); } And the accompanying unit tests: var expect = require('chai').expect; var connect4 = require('../src/lib/connect4'); describe('Connect 4 Game Logic | ', function() { describe('#Create a board ', function() { var board = connect4.initializeBoard(); it('should return game boards of the defaults length when too small', function(done) { var board2 = connect4.initializeBoard(3,3), board3 = connect4.initializeBoard(5), board4 = connect4.initializeBoard(3,10), board5 = connect4.initializeBoard(10,3); // Make sure the board is a 2D array expect(board2).to.be.an('array'); expect(board2.length).to.equal(board.length); expect(board2[0].length).to.equal(board[0].length); for(var i = 0; i < board2.length; i++){ expect(board2[i]).to.be.an('array'); } // Make sure the board is a 2D array expect(board3).to.be.an('array'); expect(board3.length).to.equal(board.length); expect(board3[0].length).to.equal(board[0].length); for(var i = 0; i < board3.length; i++){ expect(board3[i]).to.be.an('array'); } // Board initialized with 3 rows, but should default to 6 expect(board4).to.be.an('array'); expect(board4.length).to.equal(board.length); for(var i = 0; i < board4.length; i++){ expect(board4[i]).to.be.an('array'); } // Board initialized with 3 columns, but should default to 7 expect(board5).to.be.an('array'); expect(board5[0].length).to.equal(board[0].length); for(var i = 0; i < board5.length; i++){ expect(board5[i]).to.be.an('array'); } done(); }); it('should only allow pieces to be placed #row amount of times', function(done) { board = connect4.initializeBoard(); for (var i = 0; i < board.length; i++) { board = connect4.makeMove(1, 1, board); } // Column should be full expect(connect4.makeMove(1, 1, board)).to.be.an('boolean').and.equal(false); // Out of bounds expect(connect4.makeMove(1, 0, board)).to.be.an('boolean').and.equal(false); expect(connect4.makeMove(1, board[0].length+1, board)).to.be.an('boolean').and.equal(false); done(); }); it('should return victory if there are 4 in a row', function(done) { // Vertical Win board = connect4.initializeBoard(); for (var i = 0; i < 3; i++) { board = connect4.makeMove(1, 1, board); expect(connect4.checkForVictory(1, 1, board)).to.equal(false); } board = connect4.makeMove(1, 1, board); expect(connect4.checkForVictory(1, 1, board)).to.equal(true); // Horizontal Win board = connect4.initializeBoard(); for (var i = 1; i < 4; i++) { board = connect4.makeMove(1, i, board); expect(connect4.checkForVictory(1, 1, board)).to.equal(false); } board = connect4.makeMove(1, 4, board); expect(connect4.checkForVictory(1, 4, board)).to.equal(true); // Diagonal Win board = connect4.initializeBoard(); for (var i = 1; i < 4; i++) { for (var j = 1; j <= i; j++){ if (j===i){ board = connect4.makeMove(1, i, board); } else { board = connect4.makeMove(2, i, board); } expect(connect4.checkForVictory(1, 1, board)).to.equal(false); } } for (var i = 0; i < 3; i++) { board = connect4.makeMove(2, 4, board); expect(connect4.checkForVictory(2, 4, board)).to.equal(false); } board = connect4.makeMove(1, 4, board); expect(connect4.checkForVictory(1, 4, board)).to.equal(true); done(); }); }); });