Prism

Prism Game
Prism (source) is a 2048 clone, using colors instead of numbers. It was created by Austin and myself in about a day, mostly for the purposes of getting to know each other better (this was before I joined Clay.io) as well as to show investors that we could produce content quickly. We have since changed course, and are focusing on the marketplace instead of being a first-party app developer, but it was still a valuable experience.

My very first commit (with code) implemented the board logic of the game (this was a clone, not a fork of 2048), with tests! These tests were invaluable for helping us quickly iterate on the game mechanics, without spending time dealing with game logic errors.

Prism Game

Game Logic

To be fair, I should have looked at how the game logic was implemented in the original 2048 game before writing it myself, but then again that wouldn't have been nearly as fun.

Here is the logic, commented for clarity:

this._move = function(dir) {

  // Directions to move values
  var keymap = {
    up: [-1, 0],
    down: [1, 0],
    left: [0, -1],
    right: [0, 1]
  }

  var grid = this.grid
  var diff = keymap[dir]

  // These represent the order in which the rows/columns will be traversed
  var rows = _.range(0, 4)
  var cols = _.range(0, 4)

  // Change traversal order to make the movement logic simpler
  if(dir === 'down') {
    rows.reverse()
  }
  if(dir === 'right') {
    cols.reverse()
  }

  // In each row, take each column and if it has a value in it, move it
  // Above, we made sure to always travel along the path of cell movement
  // That way we can avoid any recursion
  // The logic is similar to a sideways waterfall

  // ----                ----
  // --a-   -> left ->   a---
  // ----                ----
  // ----                ----

  for(var r=0;r<rows.length;r++) {
    for(var c=0;c<cols.length;c++) {
      var row = rows[r], col = cols[c]
      if (!grid[row] || !grid[row][col]) return

      // While the current cell has an item, and can move into the next cell
      while (grid[row + diff[0]] && (grid[row + diff[0]][col + diff[1]] === 0 ||
            grid[row][col] === grid[row + diff[0]][col + diff[1]])) {

        var combine = grid[row][col]
        if (grid[row][col] === grid[row + diff[0]][col + diff[1]]) {

          // Hack, if something has been combined, it has 0.1 added to it temporarily
          combine = grid[row][col] + 1.1
        }

        // Update previous cell
        grid[row][col] = 0
        row += diff[0]
        col += diff[1]

        // Update new cell
        grid[row][col] = combine
      }
    }
  }

  // This un-does the hack
  this.grid = _.map(grid, function(row) {
    return _.map(row, Math.floor.bind(Math))
  })
}

Gestures

In order to get Hammer.js to work on older devices I had to bind to the dragX events instead of the swipeX events:

Hammer(window, {
    drag_min_distance:5,
    drag_block_horizontal:true,
    drag_block_vertical:true
}).on("dragleft", function(e) {
    e.preventDefault()
    e.gesture.preventDefault()
    move='left'
}).on("dragright", function(e) {
    e.preventDefault()
    e.gesture.preventDefault()
    move='right'
}).on("dragup", function(e) {
    e.preventDefault()
    e.gesture.preventDefault()
    move='up'
}).on("dragdown", function(e) {
    e.preventDefault()
    e.gesture.preventDefault()
    move='down'
}).on('dragend', function(e) {
    GAME.board.move(move)
})

Side Note

During the development, I got to use my micro-events library, which I wrote in contest with minivents.js a while back.

The entire thing is 345 bytes un-gzipped (with further optimizations still possible):

(function(){var d={},c=0,a,b;this.Events={on:function(a,c,b){d[a]=d[a]||[];d[a].push({f:c,c:b})},off:function(b,e){a=d[b]||[];if(!e)return a.length=0;for(c=a.length;0<=--c;)e==a[c].f&&a.splice(c,1)},emit:function(){b=Array.apply([],arguments);a=d[b.shift()]||[];b=b[0]instanceof Array&&b[0]||b;for(c=a.length;0<=--c;)a[c].f.apply(a[c].c,b)}}})()

It turns out that you can convert arguments to an array by simply calling apply:

Array.apply([], arguments)