Building a Board Game with Node

Juan Lasheras @jlas_
February 12, 2013

Qwirkle

Source: http://boardgamebackroom.blogspot.com/

Qwirkle Rules

Similar to Scrabble. Played with 2-6 players. Goal is to form lines (rows & columns) of blocks such that:

Scoring:

http://quirky.juanl.org

Demo

Representing the game in software

Representing the game in software

How can we represent a board-less game in software?

represent board

Representing the game in software

This way, we can represent the board with a simple 2D array!

  this.boardmat = [];  // matrix representation
  for (var i=0; i<181; i++) {
    this.boardmat[i] = new Array(181);
  }
          

Enforcing the rules

Enforcing the rules

Prevent illegal moves and tell the player why.


badmove

Enforcing the rules

// return: integer of points if Success, otherwise return an error string
function addGamePiece(game, gamepiece) {

  var row = gamepiece.row;
  var col = gamepiece.column;
  var points = 0;

  if (game.boardmat[row][col] !== undefined) {
    return "GamePiece already exists.";
  }
  ...
function addGamePiece(game, gamepiece) {
    ...

    /**
     * Helper function, to check whether it is valid to place a piece
     * param piece: the piece object being placed
     * param getAdjacent: a function that returns an adjacent GamePiece
     * return: false if valid placement, otherwise return the offending
     * GamePiece
     */
    function _adjacentPieces(piece, getAdjacent) {
        for (var i=1; i<=6; i++) {
            adjacent = getAdjacent(i);
            if (typeof adjacent === 'undefined') {
                return false;
            } else if (i === 6) {
                // can't have more than 6 pieces in a valid line
                return adjacent;
            }

            var samecolor = (adjacent.piece.color === piece.color);
            var sameshape = (adjacent.piece.shape === piece.shape);

            // either samecolor or sameshape, not both
            if ((samecolor || sameshape) && !(samecolor && sameshape)) {
                // add 1 pt for adjacent piece, if not been played this turn
                if (!game.turn_pieces.some(function(x) {
                    return x.equals(adjacent);})) {
                    points += 1;
                }
                continue;
            }
            return adjacent;
        }
        return false;
    }

    // check if adjacent pieces are compatible
    var checkLeft = _adjacentPieces(gamepiece.piece, function(offset) {
        var _row = row-offset;
        var piece = game.boardmat[_row][col];
        return piece && new GamePiece(piece, _row, col);
    });
    var checkRight =_adjacentPieces(gamepiece.piece, function(offset) {
        var _row = row+offset;
        var piece = game.boardmat[_row][col];
        return piece && new GamePiece(piece, _row, col);
    });
    var checkUp =_adjacentPieces(gamepiece.piece, function(offset) {
        var _col = col-offset;
        var piece = game.boardmat[row][_col];
        return piece && new GamePiece(piece, row, _col);
    });
    var checkDown =_adjacentPieces(gamepiece.piece, function(offset) {
        var _col = col+offset;
        var piece = game.boardmat[row][_col];
        return piece && new GamePiece(piece, row, _col);
    });
    var badPiece = (checkLeft || checkRight || checkUp || checkDown);

    if (badPiece !== false) {
        return ("GamePiece adjacent to incompatible piece: " +
                badPiece.piece.color + " " + badPiece.piece.shape);
    }
    ....
        
function addGamePiece(game, gamepiece) {
    ...

    function sameRowOrCol(otherpiece) {
        return (otherpiece.row === row || otherpiece.column === col);
    }
    if (game.turn_pieces) {
        if (!game.turn_pieces.every(sameRowOrCol)) {
            return ("GamePiece must be in same row or column as " +
                    "others placed this turn.");
        }
    }
    ...

Project Layout

http://www.github.com/jlas/quirky

Source

Third Party

ReST

Quirky server implements a ReSTful* interface.




*maybe

game.js

server.on('request', function(request, response) {

    var u = url.parse(request.url);
    var path = u.pathname.split('/').map(function(x) {
        return decodeURIComponent(x);
    }).filter(function(x) {
        return Boolean(x);
    });

    switch(path[0]) {
    case 'games':
        handleGames(request, response, path.slice(1));
        break;
    case 'chat':
        handleChat(request, response, chat);
        break;
    default:
        var f = static_files[path[0]];
        if (f !== undefined) {
            var type = 'text/html';
            if (path[0].search('css$') >= 0) {
                type = 'text/css';
            } else if (path[0].search('js$') >= 0) {
                type = 'text/javascript';
            }
            respOk(response, f, type);
        } else {
            respOk(response, static_files['index.html'], 'text/html');
        }
        break;
    }
});

Client side considerations

Client side considerations

How do we deal with growing board area?

big2small

Client side considerations

Shrink pieces with CSS

function getBoard() {
    ...
        $(GRIDCLS).css("width", (100/cols)+"%");
        $(GRIDCLS).css("height", (100/rows)+"%");
    ...
}






How do we draw the piece icons?

Static images are annoying. Use unicode!
          <span style="color:#A0E7A0">&9679;</span>
          <span style="color:#FFFB8C">&10022;</span>
          <span style="color:#CD2626">&9670;</span>
          <span style="color:#FFAF5A">&9632;</span>
          <span style="color:#FF8CB6">&9650;</span>
          <span style="color:#97D0F9">&9827;</span>
        

Building the Chat widget

Building the Chat widget

chat

Building the Chat widget

          {
            id: (int),
            name: (string),
            input: (string)
          }

Handling user <input> gracefullly

Handling user <input> gracefullly

Disallow everything but ASCII?

Overkill because server code is insensitive to weird characters.
function esc(str) {
    return $('<div/>').text(str).html();
}



Heroku & npm

Deploying to Heroku

  1. Sign up for a free account at heroku.com
  2. Store the app in Git
  3. Use npm to manage dependancies
  4. Create a special Procfile that defines what command runs the app
  5. Use the Heroku Toolbelt command line tool to create and deploy the app:
$ heroku create
$ git push heroku master

Publishing on npm

  1. Sign up for a free account at npmjs.org
  2. Create a package.json file
    • This is where you declare other Node package dependancies!
  3. Publish from the command line:
$ npm publish <folder>

Using npm config

npm config allows you to specify runtime configuration for your app

var port = (process.env.PORT || process.env.npm_package_config_port || 8010);
server.listen(port);



Specify a special port with npm and run the app:

$ npm config set quirky:port <port>
$ npm start quirky

TODO

TODO

Send pull requests!

Thank you!

http://www.github.com/jlas/quirky

http://www.juanl.org/talks/quirky/index.html

@jlas_


Sponsors

spanishdict tixelated.jpeg