board.js
author Oleksandr Gavenko <gavenkoa@gmail.com>
Thu, 02 Jul 2015 01:43:15 +0300
changeset 161 ddc36c6cc097
parent 154 c6d9b84d9391
child 162 f2c55c5744ab
permissions -rw-r--r--
Refactoring: rename arrMap to dim2Map.

"use strict";

/** @fileOverview Board engines with optimised primitives. */

////////////////////////////////////////////////////////////////
/** Create board on linear array.
 * Extract data from 'brd' if present.
 * @param brd  2d array
 * @constructor */
////////////////////////////////////////////////////////////////
function BoardArr(brd) {
    this.brd = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
    if (brd) {
        for (var i = 0; i < 4; i++)
            for (var j = 0; j < 4; j++)
                this.brd[4*i + j] = brd[i][j];
    }
}
/** Get [i][j] element. */
BoardArr.prototype.get = function(i, j) {
    return this.brd[4*i + j];
}
/** Set [i][j] element. */
BoardArr.prototype.set = function(i, j, val) {
    this.brd[4*i + j] = val;
}
/** Compare boards. */
BoardArr.prototype.equals = function(brd) {
    var from = this.brd, to = brd.brd;
    // More often corners differ, check it first.
    return from[0] === to[0] && from[3] === to[3] && from[12] === to[12] && from[15] === to[15]
        && from[1] === to[1] && from[2] === to[2]
        && from[4] === to[4] && from[5] === to[5] && from[6] === to[6] && from[7] === to[7]
        && from[8] === to[8] && from[9] === to[9] && from[10] === to[10] && from[11] === to[11]
        && from[13] === to[13] && from[14] === to[14];
}
BoardArr.prototype.compress = function() {
    var brd = this.brd;
    var h1 = 0;
    for (var j = 0; j < 4; j++)
        h1 = (h1 << 4) | brd[0][j];
    for (var j = 0; j < 4; j++)
        h1 = (h1 << 4) | brd[1][j];
    var h2 = 0;
    for (var j = 0; j < 4; j++)
        h2 = (h2 << 4) | brd[2][j];
    for (var j = 0; j < 4; j++)
        h2 = (h2 << 4) | brd[3][j];
    return [h1, h2];
}
/* Return and optionally fill 2d board.
 * Doesn't designed to be efficient. */
BoardArr.prototype.exportTo = function(brd) {
    brd = brd || [[],[],[],[]];
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            brd[i][j] = this.brd[4*i + j];
    return brd;
}
BoardArr.prototype.copy = function(brd) {
    brd = brd || new BoardArr();
    for (var i = 0; i < 16; i++)
        brd.brd[i] = this.brd[i];
    return brd;
}
/** Number of free cell. */
BoardArr.prototype.freeCnt = function() {
    var cnt = 0;
    for (var i = 0; i < 16; i++)
        if (this.brd[i] === 0)
            cnt++;
    return cnt;
}
BoardArr.prototype.score = function() {
    var score = 0;
    for (var i = 0; i < 16; i++) {
        var v = this.brd[i][j];
        if (v > 1)
            score += (v-1)*(1 << v);
    }
    return score;
}
BoardArr.prototype.maxVal = function() {
    var max = 0;
    for (var i = 0; i < 4; i++) {
        for (var j = 0; j < 4; j++) {
            max = Math.max(max, this.brd[i][j]);
        }
    }
    return max;
}



////////////////////////////////////////////////////////////////
/** Create board on 2d array.
 * Extract data from 'brd' if present.
 * @param brd  2d array
 * @constructor */
////////////////////////////////////////////////////////////////
function BoardArr2d(brd) {
    this.brd = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
    if (brd) {
        for (var i = 0; i < 4; i++)
            for (var j = 0; j < 4; j++)
                this.brd[i][j] = brd[i][j];
    }
}
/** Get [i][j] element. */
BoardArr2d.prototype.get = function(i, j) {
    return this.brd[i][j];
}
/** Set [i][j] element. */
BoardArr2d.prototype.set = function(i, j, val) {
    this.brd[i][j] = val;
}
BoardArr2d.prototype.equals_loop = function(brd) {
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            if (this.brd[i][j] !== brd.brd[i][j])
                return false;
    return true;
}
BoardArr2d.prototype.equals_unrolled = function(brd) {
    var x0 = this.brd[0], x1 = this.brd[1], x2 = this.brd[2], x3 = this.brd[3]
    var y0 = brd.brd[0], y1 = brd.brd[1], y2 = brd.brd[2], y3 = brd.brd[3];
    // More often corners differ, check it first.
    return x0[0] === y0[0] && x0[3] === y0[3] && x3[0] === y3[0] && x3[3] === y3[3]
        && x0[1] === y0[1] && x0[2] === y0[2]
        && x3[1] === y3[1] && x3[2] === y3[2]
        && x1[0] === y1[0] && x1[1] === y1[1] && x1[2] === y1[2] && x1[3] === y1[3]
        && x2[0] === y2[0] && x2[1] === y2[1] && x2[2] === y2[2] && x2[3] === y2[3];
}
/** Compare boards. */
BoardArr2d.prototype.equals = BoardArr2d.prototype.equals_unrolled;
BoardArr2d.prototype.compress = function() {
    var brd = this.brd;
    var h1 = brd[0][0];
    h1 = (h1 << 4) | brd[0][1];
    h1 = (h1 << 4) | brd[0][2];
    h1 = (h1 << 4) | brd[0][3];
    h1 = (h1 << 4) | brd[1][0];
    h1 = (h1 << 4) | brd[1][1];
    h1 = (h1 << 4) | brd[1][2];
    h1 = (h1 << 4) | brd[1][3];
    var h2 = brd[2][0];
    h2 = (h2 << 4) | brd[2][1];
    h2 = (h2 << 4) | brd[2][2];
    h2 = (h2 << 4) | brd[2][3];
    h2 = (h2 << 4) | brd[3][0];
    h2 = (h2 << 4) | brd[3][1];
    h2 = (h2 << 4) | brd[3][2];
    h2 = (h2 << 4) | brd[3][3];
    return [h1, h2];
}

/* Return and optionally fill 2d board. */
BoardArr2d.prototype.exportTo = function(brd) {
    brd = brd || [[],[],[],[]];
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            brd[i][j] = this.brd[i][j];
    return brd;
}
BoardArr2d.prototype.copy = function(brd) {
    brd = brd || new BoardArr2d();
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            brd.brd[i][j] = this.brd[i][j];
    return brd;
}
/** Number of free cell. */
BoardArr2d.prototype.freeCnt = function() {
    var cnt = 0;
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            if (this.brd[i][j] === 0)
                cnt++;
    return cnt;
}
BoardArr2d.prototype.score = function() {
    var score = 0;
    for (var i = 0; i < 4; i++) {
        for (var j = 0; j < 4; j++) {
            var v = this.brd[i][j];
            if (v > 1)
                score += (v-1)*(1 << v);
        }
    }
    return score;
}
BoardArr2d.prototype.maxVal = function() {
    var max = 0;
    for (var i = 0; i < 4; i++) {
        for (var j = 0; j < 4; j++) {
            max = Math.max(max, this.brd[i][j]);
        }
    }
    return max;
}
BoardArr2d.prototype.find = function(val) {
    var xy = [];
    for (var i = 0; i < 4; i++) {
        for (var j = 0; j < 4; j++) {
            if (this.brd[i][j] === val)
            xy.push([i,j]);
        }
    }
    return xy;
}
BoardArr2d.prototype.atEdge = function(val) {
    var brd = this.brd;
    return (brd[0][0] === val) || (brd[0][1] === val) || (brd[0][2] === val) || (brd[0][3] === val)
        || (brd[1][0] === val) || (brd[1][3] === val)
        || (brd[2][0] === val) || (brd[2][3] === val)
        || (brd[3][0] === val) || (brd[3][1] === val) || (brd[3][2] === val) || (brd[3][3] === val);
}
BoardArr2d.prototype.atCorner = function(val) {
    var brd = this.brd;
    return (brd[0][0] === val) || (brd[0][3] === val) || (brd[3][0] === val) || (brd[3][3] === val);
}

BoardArr2d.prototype.canRight = function() {
    for (var i = 0; i < 4; i++) {
        var f0 = this.brd[i][0], f1 = this.brd[i][1], f2 = this.brd[i][2], f3 = this.brd[i][3];
        if (f2 > 0 && (f2 === f3 || f3 === 0))
            return true;
        if (f1 > 0 && (f1 === f2 || f2 === 0))
            return true;
        if (f0 > 0 && (f0 === f1 || f1 === 0))
            return true;
    }
    return false;
}
BoardArr2d.prototype.canLeft = function() {
    for (var i = 0; i < 4; i++) {
        var f0 = this.brd[i][0], f1 = this.brd[i][1], f2 = this.brd[i][2], f3 = this.brd[i][3];
        if (f1 > 0 && (f1 === f0 || f0 === 0))
            return true;
        if (f2 > 0 && (f2 === f1 || f1 === 0))
            return true;
        if (f3 > 0 && (f3 === f2 || f2 === 0))
            return true;
    }
    return false;
}
BoardArr2d.prototype.canUp = function() {
    for (var j = 0; j < 4; j++) {
        var f0 = this.brd[0][j], f1 = this.brd[1][j], f2 = this.brd[2][j], f3 = this.brd[3][j];
        if (f1 > 0 && (f1 === f0 || f0 === 0))
            return true;
        if (f2 > 0 && (f2 === f1 || f1 === 0))
            return true;
        if (f3 > 0 && (f3 === f2 || f2 === 0))
            return true;
    }
    return false;
}
BoardArr2d.prototype.canDown = function() {
    for (var j = 0; j < 4; j++) {
        var f0 = this.brd[0][j], f1 = this.brd[1][j], f2 = this.brd[2][j], f3 = this.brd[3][j];
        if (f2 > 0 && (f2 === f3 || f3 === 0))
            return true;
        if (f1 > 0 && (f1 === f2 || f2 === 0))
            return true;
        if (f0 > 0 && (f0 === f1 || f1 === 0))
            return true;
    }
    return false;
}

BoardArr2d.prototype.shiftLeft_unrolled = function(brd) {
    var from = this.brd, to = brd.brd;
    for (var i = 3; i >= 0; i--) {
        var f0 = from[i][0], f1 = from[i][1], f2 = from[i][2], f3 = from[i][3];
        if (f3 === 0) {
            to[i][0] = 0;
            if (f2 === 0) {
                to[i][1] = 0;
                if (f1 === 0) {      // a 0 0 0
                    to[i][2] = 0;
                    to[i][3] = f0;
                } else {
                    if (f0 === f1) {   // a a 0 0
                        to[i][2] = 0;
                        to[i][3] = f0 + 1;
                    } else {           // a b 0 0
                        to[i][2] = f0;
                        to[i][3] = f1;
                    }
                }
            } else {               // f2 !== 0
                if (f1 === 0) {
                    to[i][1] = 0;
                    if (f0 === f2) {   // a 0 a 0
                        to[i][3] = f2 + 1;
                        to[i][2] = 0;
                    } else {           // a 0 b 0
                        to[i][3] = f2;
                        to[i][2] = f0;
                    }
                } else {             // f1 !== 0
                    if (f1 === f2) {   // a b b 0
                        to[i][3] = f2 + 1;
                        to[i][2] = f0;
                        to[i][1] = 0;
                    } else {           // f1 !== f2
                        to[i][3] = f2;
                        if (f0 === f1) { // a a b 0
                            to[i][2] = f1 + 1;
                            to[i][1] = 0;
                        } else {         // a b c 0
                            to[i][2] = f1;
                            to[i][1] = f0;
                        }
                    }
                }
            }
        } else {                 // f3 !== 0
            if (f2 === f3) {
                to[i][3] = f2 + 1;
                to[i][0] = 0;
                if (f1 === 0) { // a 0 b b
                    to[i][2] = f0;
                    to[i][1] = 0;
                } else {
                    if (f0 === f1) { // a a b b
                        to[i][2] = f1 + 1;
                        to[i][1] = 0;
                    } else { // a b c c
                        to[i][2] = f1;
                        to[i][1] = f0;
                    }
                }
            } else { // f2 !== f3
                to[i][3] = f3;
                if (f2 === 0) {
                    to[i][0] = 0;
                    if (f1 === 0) { // a 0 0 b
                        to[i][1] = 0;
                        to[i][2] = f0;
                    } else {
                        if (f0 === f1) { // a a 0 b
                            to[i][1] = 0;
                            to[i][2] = f1 + 1;
                        } else { // a b 0 c
                            to[i][1] = f0;
                            to[i][2] = f1;
                        }
                    }
                } else { // f2 !== 0 && f2 !== f3
                    if (f1 === 0) { // a 0 b c
                        to[i][0] = 0;
                        to[i][1] = f0;
                        to[i][2] = f2;
                    } else {
                        if (f0 === f1) { // a a b c
                            to[i][0] = 0;
                            to[i][1] = f1 + 1;
                            to[i][2] = f2;
                        } else { // a b c d
                            to[i][0] = f0;
                            to[i][1] = f1;
                            to[i][2] = f2;
                        }
                    }
                }
            }
        }
    }
}

BoardArr2d.prototype.shiftRight_mostly_unrolled = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    for (var i = 0; i < 4; i++) {
        var moved = true;
        var f0 = from[i][0], f1 = from[i][1], f2 = from[i][2], f3 = from[i][3];
        if (f3 === 0) {
            if (f2 === 0) {
                if (f1 === 0) {  // a 0 0 0
                    if (f0 === 0)
                        moved = false;
                    f3 = f0;
                    f2 = 0;
                } else {         // a b 0 0
                    f3 = f1;
                    f2 = f0;
                }
                f1 = 0;
            } else {             // f2 !== 0 && f3 === 0
                if (f1 === 0) {  // a 0 b 0
                    f3 = f2;
                    f2 = f0;
                } else {         // a b c 0
                    f3 = f2;
                    f2 = f1;
                    f1 = f0;
                }
            }
            f0 = 0;
        } else {                 // f3 !== 0
            if (f2 === 0) {
                if (f1 === 0) {  // a 0 0 b
                    if (f0 === 0)
                        moved = false;
                    f2 = f0;
                } else {         // a b 0 c
                    f2 = f1;
                    f1 = f0;
                }
                f0 = 0;
            } else {             // f2 !== 0 && f3 !== 0
                if (f1 === 0) {  // a 0 b c
                    if (f0 === 0)
                        moved = false;
                    f1 = f0;
                    f0 = 0;
                } else {         // else: a b c d
                    moved = false;
                }
            }
        }
        updated = updated || moved;
        if (f2 === 0) {
            to[i][0] = 0;
            to[i][1] = 0;
            to[i][2] = 0;
            to[i][3] = f3;
            continue;
        }
        if (f1 === 0) {
            to[i][0] = 0;
            to[i][1] = 0;
            if (f2 === f3) {
                updated = true;
                to[i][2] = 0;
                to[i][3] = f3 + 1;
            } else {
                to[i][2] = f2;
                to[i][3] = f3;
            }
            continue;
        }
        if (f2 === f3) {
            updated = true;
            to[i][0] = 0;
            to[i][3] = f3 + 1;
            if (f0 === f1) {
                to[i][1] = 0;
                to[i][2] = f1 + 1;
            } else {
                to[i][1] = f0;
                to[i][2] = f1;
            }
        } else {
            to[i][3] = f3;
            if (f1 === f2) {
                updated = true;
                to[i][0] = 0;
                to[i][1] = f0;
                to[i][2] = f2 + 1;
            } else {
                if (f0 === f1) {
                    updated = true;
                    to[i][0] = 0;
                    to[i][1] = f1+1;
                    to[i][2] = f2;
                } else {
                    to[i][0] = f0;
                    to[i][1] = f1;
                    to[i][2] = f2;
                }
            }
        }
    }
    return updated;
}
BoardArr2d.prototype.shiftDown_mostly_unrolled = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    for (var j = 0; j < 4; j++) {
        var moved = true;
        var f0 = from[0][j], f1 = from[1][j], f2 = from[2][j], f3 = from[3][j];
        if (f3 === 0) {
            if (f2 === 0) {
                if (f1 === 0) {  // a 0 0 0
                    if (f0 === 0)
                        moved = false;
                    f3 = f0;
                    f2 = 0;
                } else {         // a b 0 0
                    f3 = f1;
                    f2 = f0;
                }
                f1 = 0;
            } else {             // f2 !== 0 && f3 === 0
                if (f1 === 0) {  // a 0 b 0
                    f3 = f2;
                    f2 = f0;
                } else {         // a b c 0
                    f3 = f2;
                    f2 = f1;
                    f1 = f0;
                }
            }
            f0 = 0;
        } else {                 // f3 !== 0
            if (f2 === 0) {
                if (f1 === 0) {  // a 0 0 b
                    if (f0 === 0)
                        moved = false;
                    f2 = f0;
                } else {         // a b 0 c
                    f2 = f1;
                    f1 = f0;
                }
                f0 = 0;
            } else {             // f2 !== 0 && f3 !== 0
                if (f1 === 0) {  // a 0 b c
                    if (f0 === 0)
                        moved = false;
                    f1 = f0;
                    f0 = 0;
                } else {         // else: a b c d
                    moved = false;
                }
            }
        }
        updated = updated || moved;
        if (f2 === 0) {
            to[0][j] = 0;
            to[1][j] = 0;
            to[2][j] = 0;
            to[3][j] = f3;
            continue;
        }
        if (f1 === 0) {
            to[0][j] = 0;
            to[1][j] = 0;
            if (f2 === f3) {
                updated = true;
                to[2][j] = 0;
                to[3][j] = f3 + 1;
            } else {
                to[2][j] = f2;
                to[3][j] = f3;
            }
            continue;
        }
        if (f2 === f3) {
            updated = true;
            to[0][j] = 0;
            to[3][j] = f3 + 1;
            if (f0 === f1) {
                to[1][j] = 0;
                to[2][j] = f1 + 1;
            } else {
                to[1][j] = f0;
                to[2][j] = f1;
            }
        } else {
            to[3][j] = f3;
            if (f1 === f2) {
                updated = true;
                to[0][j] = 0;
                to[1][j] = f0;
                to[2][j] = f2 + 1;
            } else {
                if (f0 === f1) {
                    updated = true;
                    to[0][j] = 0;
                    to[1][j] = f1+1;
                    to[2][j] = f2;
                } else {
                    to[0][j] = f0;
                    to[1][j] = f1;
                    to[2][j] = f2;
                }
            }
        }
    }
    return updated;
}
BoardArr2d.prototype.shiftLeft_mostly_unrolled = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    for (var i = 0; i < 4; i++) {
        var moved = true;
        var f0 = from[i][0], f1 = from[i][1], f2 = from[i][2], f3 = from[i][3];
        if (f0 === 0) {
            if (f1 === 0) {
                if (f2 === 0) {  // 0 0 0 a
                    if (f3 === 0)
                        moved = false;
                    f0 = f3;
                    f1 = 0;
                } else {         // 0 0 a b
                    f0 = f2;
                    f1 = f3;
                }
                f2 = 0;
            } else {             // f1 !== 0 && f0 === 0
                if (f2 === 0) {  // 0 a 0 b
                    f0 = f1;
                    f1 = f3;
                } else {         // 0 a b c
                    f0 = f1;
                    f1 = f2;
                    f2 = f3;
                }
            }
            f3 = 0;
        } else {                 // f0 !== 0
            if (f1 === 0) {
                if (f2 === 0) {  // a 0 0 b
                    if (f3 === 0)
                        moved = false;
                    f1 = f3;
                } else {         // a 0 b c
                    f1 = f2;
                    f2 = f3;
                }
                f3 = 0;
            } else {             // f1 !== 0 && f0 !== 0
                if (f2 === 0) {  // a b 0 c
                    if (f3 === 0)
                        moved = false;
                    f2 = f3;
                    f3 = 0;
                } else {         // else: a b c d
                    moved = false;
                }
            }
        }
        updated = updated || moved;
        if (f1 === 0) {          // a 0 0 0
            to[i][3] = 0;
            to[i][2] = 0;
            to[i][1] = 0;
            to[i][0] = f0;
            continue;
        }
        if (f2 === 0) {
            to[i][3] = 0;
            to[i][2] = 0;
            if (f1 === f0) {     // a a 0 0
                updated = true;
                to[i][1] = 0;
                to[i][0] = f0 + 1;
            } else {             // a b 0 0
                to[i][1] = f1;
                to[i][0] = f0;
            }
            continue;
        }
        if (f1 === f0) {
            updated = true;
            to[i][3] = 0;
            to[i][0] = f0 + 1;
            if (f3 === f2) {     // a a b b
                to[i][2] = 0;
                to[i][1] = f2 + 1;
            } else {             // a a b c
                to[i][2] = f3;
                to[i][1] = f2;
            }
        } else {
            to[i][0] = f0;
            if (f2 === f1) {     // a b b c
                updated = true;
                to[i][3] = 0;
                to[i][2] = f3;
                to[i][1] = f1 + 1;
            } else {
                to[i][1] = f1;
                if (f2 === f3) { // a b c c
                    updated = true;
                    to[i][3] = 0;
                    to[i][2] = f2+1;
                } else {         // a b c d
                    to[i][3] = f3;
                    to[i][2] = f2;
                }
            }
        }
   }
    return updated;
}
BoardArr2d.prototype.shiftUp_mostly_unrolled = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    for (var j = 0; j < 4; j++) {
        var moved = true;
        var f0 = from[0][j], f1 = from[1][j], f2 = from[2][j], f3 = from[3][j];
        if (f0 === 0) {
            if (f1 === 0) {
                if (f2 === 0) {  // 0 0 0 a
                    if (f3 === 0)
                        moved = false;
                    f0 = f3;
                    f1 = 0;
                } else {         // 0 0 a b
                    f0 = f2;
                    f1 = f3;
                }
                f2 = 0;
            } else {             // f1 !== 0 && f0 === 0
                if (f2 === 0) {  // 0 a 0 b
                    f0 = f1;
                    f1 = f3;
                } else {         // 0 a b c
                    f0 = f1;
                    f1 = f2;
                    f2 = f3;
                }
            }
            f3 = 0;
        } else {                 // f0 !== 0
            if (f1 === 0) {
                if (f2 === 0) {  // a 0 0 b
                    if (f3 === 0)
                        moved = false;
                    f1 = f3;
                } else {         // a 0 b c
                    f1 = f2;
                    f2 = f3;
                }
                f3 = 0;
            } else {             // f1 !== 0 && f0 !== 0
                if (f2 === 0) {  // a b 0 c
                    if (f3 === 0)
                        moved = false;
                    f2 = f3;
                    f3 = 0;
                } else {         // else: a b c d
                    moved = false;
                }
            }
        }
        updated = updated || moved;
        if (f1 === 0) {          // a 0 0 0
            to[3][j] = 0;
            to[2][j] = 0;
            to[1][j] = 0;
            to[0][j] = f0;
            continue;
        }
        if (f2 === 0) {
            to[3][j] = 0;
            to[2][j] = 0;
            if (f1 === f0) {     // a a 0 0
                updated = true;
                to[1][j] = 0;
                to[0][j] = f0 + 1;
            } else {             // a b 0 0
                to[1][j] = f1;
                to[0][j] = f0;
            }
            continue;
        }
        if (f1 === f0) {
            updated = true;
            to[3][j] = 0;
            to[0][j] = f0 + 1;
            if (f3 === f2) {     // a a b b
                to[2][j] = 0;
                to[1][j] = f2 + 1;
            } else {             // a a b c
                to[2][j] = f3;
                to[1][j] = f2;
            }
        } else {
            to[0][j] = f0;
            if (f2 === f1) {     // a b b c
                updated = true;
                to[3][j] = 0;
                to[2][j] = f3;
                to[1][j] = f1 + 1;
            } else {
                to[1][j] = f1;
                if (f2 === f3) { // a b c c
                    updated = true;
                    to[3][j] = 0;
                    to[2][j] = f2+1;
                } else {         // a b c d
                    to[3][j] = f3;
                    to[2][j] = f2;
                }
            }
        }
    }
    return updated;
}

BoardArr2d.prototype.left = BoardArr2d.prototype.shiftLeft_mostly_unrolled;
BoardArr2d.prototype.right = BoardArr2d.prototype.shiftRight_mostly_unrolled;
BoardArr2d.prototype.up = BoardArr2d.prototype.shiftUp_mostly_unrolled;
BoardArr2d.prototype.down = BoardArr2d.prototype.shiftDown_mostly_unrolled;

/** Array of which values will be merged. */
BoardObj.prototype.rightMerges = function() {
    var merges = [];
    var brd = this.brd;
    for (var i = 0; i < 3; i++) {
        var f0 = brd[i][0], f1 = brd[i][1], f2 = brd[i][2], f3 = brd[i][3];
        if (f2 === f3) {
            if (f2 === 0) {
                if (f0 === f1 && f0 !== 0) {
                    merges.push(f0);
                }
            } else {
                merges.push(f2);
            }
        } else if (f1 === f2) {
            if (f1 !== 0) {
                merges.push(f1);
            }
        } else if (f0 === f1 && f0 !== 0) {
            merges.push(f0);
        }
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.downMerges = function() {
    var merges = [];
    var brd = this.brd;
    for (var j = 0; j < 3; j++) {
        var f0 = brd[0][j], f1 = brd[1][j], f2 = brd[2][j], f3 = brd[3][j];
        if (f2 === f3) {
            if (f2 === 0) {
                if (f0 === f1 && f0 !== 0) {
                    merges.push(f0);
                }
            } else {
                merges.push(f2);
            }
        } else if (f1 === f2) {
            if (f1 !== 0) {
                merges.push(f1);
            }
        } else if (f0 === f1 && f0 !== 0) {
            merges.push(f0);
        }
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.leftMerges = function() {
    var merges = [];
    var brd = this.brd;
    for (var i = 0; i < 3; i++) {
        var f0 = brd[i][0], f1 = brd[i][1], f2 = brd[i][2], f3 = brd[i][3];
        if (f1 === f0) {
            if (f1 === 0) {
                if (f3 === f2 && f3 !== 0) {
                    merges.push(f3);
                }
            } else {
                merges.push(f1);
            }
        } else if (f2 === f1) {
            if (f2 !== 0) {
                merges.push(f2);
            }
        } else if (f3 === f2 && f3 !== 0) {
            merges.push(f3);
        }
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.upMerges = function() {
    var merges = [];
    var brd = this.brd;
    for (var j = 0; j < 3; j++) {
        var f0 = brd[0][j], f1 = brd[1][j], f2 = brd[2][j], f3 = brd[3][j];
        if (f1 === f0) {
            if (f1 === 0) {
                if (f3 === f2 && f3 !== 0) {
                    merges.push(f3);
                }
            } else {
                merges.push(f1);
            }
        } else if (f2 === f1) {
            if (f2 !== 0) {
                merges.push(f2);
            }
        } else if (f3 === f2 && f3 !== 0) {
            merges.push(f3);
        }
    }
    return merges;
}



////////////////////////////////////////////////////////////////
/** Create board as properties of object.
 * Extract data from 'brd' if present.
 * @param brd  2d array
 * @constructor */
////////////////////////////////////////////////////////////////
function BoardObj(brd) {
    if (brd)
        this.brd = { aa: brd[0][0], ab: brd[0][1], ac: brd[0][2], ad: brd[0][3],
                     ba: brd[1][0], bb: brd[1][1], bc: brd[1][2], bd: brd[1][3],
                     ca: brd[2][0], cb: brd[2][1], cc: brd[2][2], cd: brd[2][3],
                     da: brd[3][0], db: brd[3][1], dc: brd[3][2], dd: brd[3][3] };
    else
        this.brd = { aa: 0, ab: 0, ac: 0, ad: 0,
                     ba: 0, bb: 0, bc: 0, bd: 0,
                     ca: 0, cb: 0, cc: 0, cd: 0,
                     da: 0, db: 0, dc: 0, dd: 0 };
}
BoardObj.dim2Map = [["aa", "ab", "ac", "ad"], ["ba", "bb", "bc", "bd"], ["ca", "cb", "cc", "cd"], ["da", "db", "dc", "dd"]];
/** Get [i][j] element. */
BoardObj.prototype.get = function(i, j) {
    return this.brd[BoardObj.dim2Map[i][j]];
}
/** Set [i][j] element. */
BoardObj.prototype.set = function(i, j, val) {
    this.brd[BoardObj.dim2Map[i][j]] = val;
}
/* Return and optionally fill 2d board.
 * Doesn't designed to be efficient. */
BoardObj.prototype.exportTo = function(brd) {
    brd = brd || [[],[],[],[]];
    for (var i = 0; i < 4; i++)
        for (var j = 0; j < 4; j++)
            brd[i][j] = this.brd[4*i + j];
    return brd;
}
/** Compare boards. */
BoardObj.prototype.equals = function(brd) {
    var self = this.brd;
    return self.aa == brd.aa && self.ad == brd.ad && self.da == brd.da && self.dd == brd.dd
        && self.ab == brd.ab && self.ac == brd.ac
        && self.ba == brd.ba && self.ca == brd.ca
        && self.bd == brd.bd && self.cd == brd.cd
        && self.db == brd.db && self.dc == brd.dc
        && self.bb == brd.bb && self.bc == brd.bc && self.cb == brd.cb && self.cc == brd.cc;
};
BoardObj.prototype.compress = function() {
    var brd = this.brd;
    var h1 = brd.aa;
    h1 = (h1 << 4) | brd.ab;
    h1 = (h1 << 4) | brd.ac;
    h1 = (h1 << 4) | brd.ad;
    h1 = (h1 << 4) | brd.ba;
    h1 = (h1 << 4) | brd.bb;
    h1 = (h1 << 4) | brd.bc;
    h1 = (h1 << 4) | brd.bd;
    var h2 = brd.ca;
    h2 = (h2 << 4) | brd.cb;
    h2 = (h2 << 4) | brd.cc;
    h2 = (h2 << 4) | brd.cd;
    h2 = (h2 << 4) | brd.da;
    h2 = (h2 << 4) | brd.db;
    h2 = (h2 << 4) | brd.dc;
    h2 = (h2 << 4) | brd.dd;
    return [h1, h2];
}
BoardObj.prototype.copy = function(brd) {
    var self = this.brd;
    if ( ! brd) {
        var newBrd = new Object(BoardObj.prototype);
        newBrd.brd = { aa: self.aa, ab: self.ab, ac: self.ac, ad: self.ad,
                       ba: self.ba, bb: self.bb, bc: self.bc, bd: self.bd,
                       ca: self.ca, cb: self.cb, cc: self.cc, cd: self.cd,
                       da: self.da, db: self.db, dc: self.dc, dd: self.dd };
        return newBrd;
    }
    brd.aa = self.aa; brd.ab = self.ab; brd.ac = self.ac; brd.ad = self.ad;
    brd.ba = self.ba; brd.bb = self.bb; brd.bc = self.bc; brd.bd = self.bd;
    brd.ca = self.ca; brd.cb = self.cb; brd.cc = self.cc; brd.cd = self.cd;
    brd.da = self.da; brd.db = self.db; brd.dc = self.dc; brd.dd = self.dd;
    return brd;
}
/** Number of free cell. */
BoardObj.prototype.freeCnt = function() {
    var cnt = 0;
    var brd = this.brd;
    if (brd.aa === 0) cnt++; if (brd.ab === 0) cnt++; if (brd.ac === 0) cnt++; if (brd.ad === 0) cnt++;
    if (brd.ba === 0) cnt++; if (brd.bb === 0) cnt++; if (brd.bc === 0) cnt++; if (brd.bd === 0) cnt++;
    if (brd.ca === 0) cnt++; if (brd.cb === 0) cnt++; if (brd.cc === 0) cnt++; if (brd.cd === 0) cnt++;
    if (brd.da === 0) cnt++; if (brd.db === 0) cnt++; if (brd.dc === 0) cnt++; if (brd.dd === 0) cnt++;
    return cnt;
}
BoardObj.scoreLookup = [0]; // [ 0, 4, 16, 48, 128, 320, 768, 1792, 4096, 9216, 20480, 45056, 98304];
(function() {
    for (var i = 1; i < 16; i++)
        BoardObj.scoreLookup[i] = (i-1)*(1 << i);
})();
BoardObj.prototype.score = function() {
    var brd = this.brd;
    var lookup = BoardObj.scoreLookup;
    var score = lookup[brd.aa];
    score += lookup[brd.ab];
    score += lookup[brd.ac];
    score += lookup[brd.ad];
    score += lookup[brd.ba];
    score += lookup[brd.bb];
    score += lookup[brd.bc];
    score += lookup[brd.bd];
    score += lookup[brd.ca];
    score += lookup[brd.cb];
    score += lookup[brd.cc];
    score += lookup[brd.cd];
    score += lookup[brd.da];
    score += lookup[brd.db];
    score += lookup[brd.dc];
    score += lookup[brd.dd];
    return score;
}
BoardObj.prototype.maxVal = function() {
    var brd = this.brd;
    var max = brd.aa;
    max = Math.max(max, brd.ab);
    max = Math.max(max, brd.ac);
    max = Math.max(max, brd.ad);
    max = Math.max(max, brd.ba);
    max = Math.max(max, brd.bb);
    max = Math.max(max, brd.bc);
    max = Math.max(max, brd.bd);
    max = Math.max(max, brd.ca);
    max = Math.max(max, brd.cb);
    max = Math.max(max, brd.cc);
    max = Math.max(max, brd.cd);
    max = Math.max(max, brd.da);
    max = Math.max(max, brd.db);
    max = Math.max(max, brd.dc);
    max = Math.max(max, brd.dd);
    return max;
}
BoardObj.prototype.find = function(val) {
    var xy = [];
    var brd = this.brd;
    if (brd.aa === val) xy.push([0,0]); if (brd.ab === val) xy.push([0,1]); if (brd.ac === val) xy.push([0,2]); if (brd.ad === val) xy.push([0,3]);
    if (brd.ba === val) xy.push([1,0]); if (brd.bb === val) xy.push([1,1]); if (brd.bc === val) xy.push([1,2]); if (brd.bd === val) xy.push([1,3]);
    if (brd.ca === val) xy.push([2,0]); if (brd.cb === val) xy.push([2,1]); if (brd.cc === val) xy.push([2,2]); if (brd.cd === val) xy.push([2,3]);
    if (brd.da === val) xy.push([3,0]); if (brd.db === val) xy.push([3,1]); if (brd.dc === val) xy.push([3,2]); if (brd.dd === val) xy.push([3,3]);
    return xy;
}
BoardObj.prototype.atEdge = function(val) {
    var brd = this.brd;
    return (brd.aa === val) || (brd.ab === val) || (brd.ac === val) || (brd.ad === val)
        || (brd.ba === val) || (brd.bd === val)
        || (brd.ca === val) || (brd.cd === val)
        || (brd.da === val) || (brd.db === val) || (brd.dc === val) || (brd.dd === val);
}
BoardObj.prototype.atCorner = function(val) {
    var brd = this.brd;
    return (brd.aa === val) || (brd.ad === val) || (brd.da === val) || (brd.dd === val);
}

BoardObj.prototype.canRight = function() {
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ab, f2 = brd.ac, f3 = brd.ad;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.ba, f1 = brd.bb, f2 = brd.bc, f3 = brd.bd;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.ca, f1 = brd.cb, f2 = brd.cc, f3 = brd.cd;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.da, f1 = brd.db, f2 = brd.dc, f3 = brd.dd;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    return false;
}
BoardObj.prototype.canLeft = function() {
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ab, f2 = brd.ac, f3 = brd.ad;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.ba, f1 = brd.bb, f2 = brd.bc, f3 = brd.bd;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.ca, f1 = brd.cb, f2 = brd.cc, f3 = brd.cd;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.da, f1 = brd.db, f2 = brd.dc, f3 = brd.dd;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    return false;
}
BoardObj.prototype.canUp = function() {
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ba, f2 = brd.ca, f3 = brd.da;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.ab, f1 = brd.bb, f2 = brd.cb, f3 = brd.db;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.ac, f1 = brd.bc, f2 = brd.cc, f3 = brd.dc;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    var f0 = brd.ad, f1 = brd.bd, f2 = brd.cd, f3 = brd.dd;
    if (f1 > 0 && (f1 === f0 || f0 === 0)) return true;
    if (f2 > 0 && (f2 === f1 || f1 === 0)) return true;
    if (f3 > 0 && (f3 === f2 || f2 === 0)) return true;
    return false;
}
BoardObj.prototype.canDown = function() {
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ba, f2 = brd.ca, f3 = brd.da;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.ab, f1 = brd.bb, f2 = brd.cb, f3 = brd.db;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.ac, f1 = brd.bc, f2 = brd.cc, f3 = brd.dc;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    var f0 = brd.ad, f1 = brd.bd, f2 = brd.cd, f3 = brd.dd;
    if (f2 > 0 && (f2 === f3 || f3 === 0)) return true;
    if (f1 > 0 && (f1 === f2 || f2 === 0)) return true;
    if (f0 > 0 && (f0 === f1 || f1 === 0)) return true;
    return false;
}

BoardObj.prototype.right = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    var moved = true;
    var f0 = from.aa, f1 = from.ab, f2 = from.ac, f3 = from.ad;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.aa = 0;
        to.ab = 0;
        to.ac = 0;
        to.ad = f3;
    } else if (f1 === 0) {
        to.aa = 0;
        to.ab = 0;
        if (f2 === f3) {
            updated = true;
            to.ac = 0;
            to.ad = f3 + 1;
        } else {
            to.ac = f2;
            to.ad = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.aa = 0;
        to.ad = f3 + 1;
        if (f0 === f1) {
            to.ab = 0;
            to.ac = f1 + 1;
        } else {
            to.ab = f0;
            to.ac = f1;
        }
    } else {
        to.ad = f3;
        if (f1 === f2) {
            updated = true;
            to.aa = 0;
            to.ab = f0;
            to.ac = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.aa = 0;
                to.ab = f1+1;
                to.ac = f2;
            } else {
                to.aa = f0;
                to.ab = f1;
                to.ac = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ba, f1 = from.bb, f2 = from.bc, f3 = from.bd;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.ba = 0;
        to.bb = 0;
        to.bc = 0;
        to.bd = f3;
    } else if (f1 === 0) {
        to.ba = 0;
        to.bb = 0;
        if (f2 === f3) {
            updated = true;
            to.bc = 0;
            to.bd = f3 + 1;
        } else {
            to.bc = f2;
            to.bd = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.ba = 0;
        to.bd = f3 + 1;
        if (f0 === f1) {
            to.bb = 0;
            to.bc = f1 + 1;
        } else {
            to.bb = f0;
            to.bc = f1;
        }
    } else {
        to.bd = f3;
        if (f1 === f2) {
            updated = true;
            to.ba = 0;
            to.bb = f0;
            to.bc = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.ba = 0;
                to.bb = f1+1;
                to.bc = f2;
            } else {
                to.ba = f0;
                to.bb = f1;
                to.bc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ca, f1 = from.cb, f2 = from.cc, f3 = from.cd;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.ca = 0;
        to.cb = 0;
        to.cc = 0;
        to.cd = f3;
    } else if (f1 === 0) {
        to.ca = 0;
        to.cb = 0;
        if (f2 === f3) {
            updated = true;
            to.cc = 0;
            to.cd = f3 + 1;
        } else {
            to.cc = f2;
            to.cd = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.ca = 0;
        to.cd = f3 + 1;
        if (f0 === f1) {
            to.cb = 0;
            to.cc = f1 + 1;
        } else {
            to.cb = f0;
            to.cc = f1;
        }
    } else {
        to.cd = f3;
        if (f1 === f2) {
            updated = true;
            to.ca = 0;
            to.cb = f0;
            to.cc = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.ca = 0;
                to.cb = f1+1;
                to.cc = f2;
            } else {
                to.ca = f0;
                to.cb = f1;
                to.cc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.da, f1 = from.db, f2 = from.dc, f3 = from.dd;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.da = 0;
        to.db = 0;
        to.dc = 0;
        to.dd = f3;
    } else if (f1 === 0) {
        to.da = 0;
        to.db = 0;
        if (f2 === f3) {
            updated = true;
            to.dc = 0;
            to.dd = f3 + 1;
        } else {
            to.dc = f2;
            to.dd = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.da = 0;
        to.dd = f3 + 1;
        if (f0 === f1) {
            to.db = 0;
            to.dc = f1 + 1;
        } else {
            to.db = f0;
            to.dc = f1;
        }
    } else {
        to.dd = f3;
        if (f1 === f2) {
            updated = true;
            to.da = 0;
            to.db = f0;
            to.dc = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.da = 0;
                to.db = f1+1;
                to.dc = f2;
            } else {
                to.da = f0;
                to.db = f1;
                to.dc = f2;
            }
        }
    }
    return updated;
}

BoardObj.prototype.down = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    var moved = true;
    var f0 = from.aa, f1 = from.ba, f2 = from.ca, f3 = from.da;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.aa = 0;
        to.ba = 0;
        to.ca = 0;
        to.da = f3;
    } else if (f1 === 0) {
        to.aa = 0;
        to.ba = 0;
        if (f2 === f3) {
            updated = true;
            to.ca = 0;
            to.da = f3 + 1;
        } else {
            to.ca = f2;
            to.da = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.aa = 0;
        to.da = f3 + 1;
        if (f0 === f1) {
            to.ba = 0;
            to.ca = f1 + 1;
        } else {
            to.ba = f0;
            to.ca = f1;
        }
    } else {
        to.da = f3;
        if (f1 === f2) {
            updated = true;
            to.aa = 0;
            to.ba = f0;
            to.ca = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.aa = 0;
                to.ba = f1+1;
                to.ca = f2;
            } else {
                to.aa = f0;
                to.ba = f1;
                to.ca = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ab, f1 = from.bb, f2 = from.cb, f3 = from.db;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.ab = 0;
        to.bb = 0;
        to.cb = 0;
        to.db = f3;
    } else if (f1 === 0) {
        to.ab = 0;
        to.bb = 0;
        if (f2 === f3) {
            to.cb = 0;
            to.db = f3 + 1;
            updated = true;
        } else {
            to.cb = f2;
            to.db = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.ab = 0;
        to.db = f3 + 1;
        if (f0 === f1) {
            to.bb = 0;
            to.cb = f1 + 1;
        } else {
            to.bb = f0;
            to.cb = f1;
        }
    } else {
        to.db = f3;
        if (f1 === f2) {
            updated = true;
            to.ab = 0;
            to.bb = f0;
            to.cb = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.ab = 0;
                to.bb = f1+1;
                to.cb = f2;
            } else {
                to.ab = f0;
                to.bb = f1;
                to.cb = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ac, f1 = from.bc, f2 = from.cc, f3 = from.dc;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.ac = 0;
        to.bc = 0;
        to.cc = 0;
        to.dc = f3;
    } else if (f1 === 0) {
        to.ac = 0;
        to.bc = 0;
        if (f2 === f3) {
            to.cc = 0;
            to.dc = f3 + 1;
            updated = true;
        } else {
            to.cc = f2;
            to.dc = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.ac = 0;
        to.dc = f3 + 1;
        if (f0 === f1) {
            to.bc = 0;
            to.cc = f1 + 1;
        } else {
            to.bc = f0;
            to.cc = f1;
        }
    } else {
        to.dc = f3;
        if (f1 === f2) {
            updated = true;
            to.ac = 0;
            to.bc = f0;
            to.cc = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.ac = 0;
                to.bc = f1+1;
                to.cc = f2;
            } else {
                to.ac = f0;
                to.bc = f1;
                to.cc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ad, f1 = from.bd, f2 = from.cd, f3 = from.dd;
    if (f3 === 0) {
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 0
                if (f0 === 0)
                    moved = false;
                f3 = f0;
                f2 = 0;
            } else {         // a b 0 0
                f3 = f1;
                f2 = f0;
            }
            f1 = 0;
        } else {             // f2 !== 0 && f3 === 0
            if (f1 === 0) {  // a 0 b 0
                f3 = f2;
                f2 = f0;
            } else {         // a b c 0
                f3 = f2;
                f2 = f1;
                f1 = f0;
            }
        }
        f0 = 0;
    } else {                 // f3 !== 0
        if (f2 === 0) {
            if (f1 === 0) {  // a 0 0 b
                if (f0 === 0)
                    moved = false;
                f2 = f0;
            } else {         // a b 0 c
                f2 = f1;
                f1 = f0;
            }
            f0 = 0;
        } else {             // f2 !== 0 && f3 !== 0
            if (f1 === 0) {  // a 0 b c
                if (f0 === 0)
                    moved = false;
                f1 = f0;
                f0 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f2 === 0) {
        to.ad = 0;
        to.bd = 0;
        to.cd = 0;
        to.dd = f3;
    } else if (f1 === 0) {
        to.ad = 0;
        to.bd = 0;
        if (f2 === f3) {
            to.cd = 0;
            to.dd = f3 + 1;
            updated = true;
        } else {
            to.cd = f2;
            to.dd = f3;
        }
    } else if (f2 === f3) {
        updated = true;
        to.ad = 0;
        to.dd = f3 + 1;
        if (f0 === f1) {
            to.bd = 0;
            to.cd = f1 + 1;
        } else {
            to.bd = f0;
            to.cd = f1;
        }
    } else {
        to.dd = f3;
        if (f1 === f2) {
            updated = true;
            to.ad = 0;
            to.bd = f0;
            to.cd = f2 + 1;
        } else {
            if (f0 === f1) {
                updated = true;
                to.ad = 0;
                to.bd = f1+1;
                to.cd = f2;
            } else {
                to.ad = f0;
                to.bd = f1;
                to.cd = f2;
            }
        }
    }
    return updated;
}

BoardObj.prototype.left = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    var moved = true;
    var f0 = from.aa, f1 = from.ab, f2 = from.ac, f3 = from.ad;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.ad = 0;
        to.ac = 0;
        to.ab = 0;
        to.aa = f0;
    } else if (f2 === 0) {
        to.ad = 0;
        to.ac = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.ab = 0;
            to.aa = f0 + 1;
        } else {             // a b 0 0
            to.ab = f1;
            to.aa = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.ad = 0;
        to.aa = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.ac = 0;
            to.ab = f2 + 1;
        } else {             // a a b c
            to.ac = f3;
            to.ab = f2;
        }
    } else {
        to.aa = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.ad = 0;
            to.ac = f3;
            to.ab = f1 + 1;
        } else {
            to.ab = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.ad = 0;
                to.ac = f2+1;
            } else {         // a b c d
                to.ad = f3;
                to.ac = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ba, f1 = from.bb, f2 = from.bc, f3 = from.bd;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.bd = 0;
        to.bc = 0;
        to.bb = 0;
        to.ba = f0;
    } else if (f2 === 0) {
        to.bd = 0;
        to.bc = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.bb = 0;
            to.ba = f0 + 1;
        } else {             // a b 0 0
            to.bb = f1;
            to.ba = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.bd = 0;
        to.ba = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.bc = 0;
            to.bb = f2 + 1;
        } else {             // a a b c
            to.bc = f3;
            to.bb = f2;
        }
    } else {
        to.ba = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.bd = 0;
            to.bc = f3;
            to.bb = f1 + 1;
        } else {
            to.bb = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.bd = 0;
                to.bc = f2+1;
            } else {         // a b c d
                to.bd = f3;
                to.bc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ca, f1 = from.cb, f2 = from.cc, f3 = from.cd;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.cd = 0;
        to.cc = 0;
        to.cb = 0;
        to.ca = f0;
    } else if (f2 === 0) {
        to.cd = 0;
        to.cc = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.cb = 0;
            to.ca = f0 + 1;
        } else {             // a b 0 0
            to.cb = f1;
            to.ca = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.cd = 0;
        to.ca = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.cc = 0;
            to.cb = f2 + 1;
        } else {             // a a b c
            to.cc = f3;
            to.cb = f2;
        }
    } else {
        to.ca = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.cd = 0;
            to.cc = f3;
            to.cb = f1 + 1;
        } else {
            to.cb = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.cd = 0;
                to.cc = f2+1;
            } else {         // a b c d
                to.cd = f3;
                to.cc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.da, f1 = from.db, f2 = from.dc, f3 = from.dd;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.dd = 0;
        to.dc = 0;
        to.db = 0;
        to.da = f0;
    } else if (f2 === 0) {
        to.dd = 0;
        to.dc = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.db = 0;
            to.da = f0 + 1;
        } else {             // a b 0 0
            to.db = f1;
            to.da = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.dd = 0;
        to.da = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.dc = 0;
            to.db = f2 + 1;
        } else {             // a a b c
            to.dc = f3;
            to.db = f2;
        }
    } else {
        to.da = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.dd = 0;
            to.dc = f3;
            to.db = f1 + 1;
        } else {
            to.db = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.dd = 0;
                to.dc = f2+1;
            } else {         // a b c d
                to.dd = f3;
                to.dc = f2;
            }
        }
    }
    return updated;
}

BoardObj.prototype.up = function(brd) {
    var updated = false;
    var from = this.brd, to = brd.brd;
    var moved = true;
    var f0 = from.aa, f1 = from.ba, f2 = from.ca, f3 = from.da;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.da = 0;
        to.ca = 0;
        to.ba = 0;
        to.aa = f0;
    } else if (f2 === 0) {
        to.da = 0;
        to.ca = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.ba = 0;
            to.aa = f0 + 1;
        } else {             // a b 0 0
            to.ba = f1;
            to.aa = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.da = 0;
        to.aa = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.ca = 0;
            to.ba = f2 + 1;
        } else {             // a a b c
            to.ca = f3;
            to.ba = f2;
        }
    } else {
        to.aa = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.da = 0;
            to.ca = f3;
            to.ba = f1 + 1;
        } else {
            to.ba = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.da = 0;
                to.ca = f2+1;
            } else {         // a b c d
                to.da = f3;
                to.ca = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ab, f1 = from.bb, f2 = from.cb, f3 = from.db;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.db = 0;
        to.cb = 0;
        to.bb = 0;
        to.ab = f0;
    } else if (f2 === 0) {
        to.db = 0;
        to.cb = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.bb = 0;
            to.ab = f0 + 1;
        } else {             // a b 0 0
            to.bb = f1;
            to.ab = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.db = 0;
        to.ab = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.cb = 0;
            to.bb = f2 + 1;
        } else {             // a a b c
            to.cb = f3;
            to.bb = f2;
        }
    } else {
        to.ab = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.db = 0;
            to.cb = f3;
            to.bb = f1 + 1;
        } else {
            to.bb = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.db = 0;
                to.cb = f2+1;
            } else {         // a b c d
                to.db = f3;
                to.cb = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ac, f1 = from.bc, f2 = from.cc, f3 = from.dc;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.dc = 0;
        to.cc = 0;
        to.bc = 0;
        to.ac = f0;
    } else if (f2 === 0) {
        to.dc = 0;
        to.cc = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.bc = 0;
            to.ac = f0 + 1;
        } else {             // a b 0 0
            to.bc = f1;
            to.ac = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.dc = 0;
        to.ac = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.cc = 0;
            to.bc = f2 + 1;
        } else {             // a a b c
            to.cc = f3;
            to.bc = f2;
        }
    } else {
        to.ac = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.dc = 0;
            to.cc = f3;
            to.bc = f1 + 1;
        } else {
            to.bc = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.dc = 0;
                to.cc = f2+1;
            } else {         // a b c d
                to.dc = f3;
                to.cc = f2;
            }
        }
    }
    var moved = true;
    var f0 = from.ad, f1 = from.bd, f2 = from.cd, f3 = from.dd;
    if (f0 === 0) {
        if (f1 === 0) {
            if (f2 === 0) {  // 0 0 0 a
                if (f3 === 0)
                    moved = false;
                f0 = f3;
                f1 = 0;
            } else {         // 0 0 a b
                f0 = f2;
                f1 = f3;
            }
            f2 = 0;
        } else {             // f1 !== 0 && f0 === 0
            if (f2 === 0) {  // 0 a 0 b
                f0 = f1;
                f1 = f3;
            } else {         // 0 a b c
                f0 = f1;
                f1 = f2;
                f2 = f3;
            }
        }
        f3 = 0;
    } else {                 // f0 !== 0
        if (f1 === 0) {
            if (f2 === 0) {  // a 0 0 b
                if (f3 === 0)
                    moved = false;
                f1 = f3;
            } else {         // a 0 b c
                f1 = f2;
                f2 = f3;
            }
            f3 = 0;
        } else {             // f1 !== 0 && f0 !== 0
            if (f2 === 0) {  // a b 0 c
                if (f3 === 0)
                    moved = false;
                f2 = f3;
                f3 = 0;
            } else {         // else: a b c d
                moved = false;
            }
        }
    }
    updated = updated || moved;
    if (f1 === 0) {          // a 0 0 0
        to.dd = 0;
        to.cd = 0;
        to.bd = 0;
        to.ad = f0;
    } else if (f2 === 0) {
        to.dd = 0;
        to.cd = 0;
        if (f1 === f0) {     // a a 0 0
            updated = true;
            to.bd = 0;
            to.ad = f0 + 1;
        } else {             // a b 0 0
            to.bd = f1;
            to.ad = f0;
        }
    } else if (f1 === f0) {
        updated = true;
        to.dd = 0;
        to.ad = f0 + 1;
        if (f3 === f2) {     // a a b b
            to.cd = 0;
            to.bd = f2 + 1;
        } else {             // a a b c
            to.cd = f3;
            to.bd = f2;
        }
    } else {
        to.ad = f0;
        if (f2 === f1) {     // a b b c
            updated = true;
            to.dd = 0;
            to.cd = f3;
            to.bd = f1 + 1;
        } else {
            to.bd = f1;
            if (f2 === f3) { // a b c c
                updated = true;
                to.dd = 0;
                to.cd = f2+1;
            } else {         // a b c d
                to.dd = f3;
                to.cd = f2;
            }
        }
    }
    return updated;
}

/** Array of which values will be merged. */
BoardObj.prototype.rightMerges = function() {
    var merges = [];
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ab, f2 = brd.ac, f3 = brd.ad;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.ba, f1 = brd.bb, f2 = brd.bc, f3 = brd.bd;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.ca, f1 = brd.cb, f2 = brd.cc, f3 = brd.cd;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.da, f1 = brd.db, f2 = brd.dc, f3 = brd.dd;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.downMerges = function() {
    var merges = [];
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ba, f2 = brd.ca, f3 = brd.da;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.ab, f1 = brd.bb, f2 = brd.cb, f3 = brd.db;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.ac, f1 = brd.bc, f2 = brd.cc, f3 = brd.dc;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    var f0 = brd.ad, f1 = brd.bd, f2 = brd.cd, f3 = brd.dd;
    if (f2 === f3) {
        if (f2 === 0) {
            if (f0 === f1 && f0 !== 0) {
                merges.push(f0);
            }
        } else {
            merges.push(f2);
        }
    } else if (f1 === f2) {
        if (f1 !== 0) {
            merges.push(f1);
        }
    } else if (f0 === f1 && f0 !== 0) {
        merges.push(f0);
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.leftMerges = function() {
    var merges = [];
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ab, f2 = brd.ac, f3 = brd.ad;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.ba, f1 = brd.bb, f2 = brd.bc, f3 = brd.bd;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.ca, f1 = brd.cb, f2 = brd.cc, f3 = brd.cd;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.da, f1 = brd.db, f2 = brd.dc, f3 = brd.dd;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    return merges;
}
/** Array of which values will be merged. */
BoardObj.prototype.upMerges = function() {
    var merges = [];
    var brd = this.brd;
    var f0 = brd.aa, f1 = brd.ba, f2 = brd.ca, f3 = brd.da;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.ab, f1 = brd.bb, f2 = brd.cb, f3 = brd.db;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.ac, f1 = brd.bc, f2 = brd.cc, f3 = brd.dc;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    var f0 = brd.ad, f1 = brd.bd, f2 = brd.cd, f3 = brd.dd;
    if (f1 === f0) {
        if (f1 === 0) {
            if (f3 === f2 && f3 !== 0) {
                merges.push(f3);
            }
        } else {
            merges.push(f1);
        }
    } else if (f2 === f1) {
        if (f2 !== 0) {
            merges.push(f2);
        }
    } else if (f3 === f2 && f3 !== 0) {
        merges.push(f3);
    }
    return merges;
}