Moves for 2d array board + test toolkit.
authorOleksandr Gavenko <gavenkoa@gmail.com>
Sun, 07 Sep 2014 03:33:33 +0300
changeset 12 a9a44cfc3e08
parent 11 2264f7a3f7e0
child 13 b3bfa9d1b537
Moves for 2d array board + test toolkit.
2048.html
ai.js
board.js
rule.js
--- a/2048.html	Sun Sep 07 00:33:47 2014 +0300
+++ b/2048.html	Sun Sep 07 03:33:33 2014 +0300
@@ -80,6 +80,7 @@
       <button id="up">up</button>
       <button id="down">down</button>
       <button id="right">right</button>
+      <button id="test">test</button>
     </div>
     <h1>AI</h1>
     <div>
@@ -205,6 +206,11 @@
       }
       return false;
     });
+
+    document.getElementById("test").addEventListener("click", function() {
+      console.log("x1: %o", ui.brdEngine);
+      board.move.replaceByBoardJS(ui.brdEngine);
+    });
     
     ui.message = {};
     var messageDom = document.getElementById("message-area");
--- a/ai.js	Sun Sep 07 00:33:47 2014 +0300
+++ b/ai.js	Sun Sep 07 03:33:33 2014 +0300
@@ -5,6 +5,7 @@
 // Each strategy is a function that except current board position as 2d array and context from
 // previous call to share state/precomputed values between calls.
 
+
 
 ////////////////////////////////////////////////////////////////
 // Random AI.
@@ -14,25 +15,31 @@
     this.brdEngine = brdEngine;
 }
 ai.random.prototype.analyse = function(brd) {
-    var tmpBrd = new this.brdEngine(brd);
+    var origBrd = new this.brdEngine(brd);
     while (true) {
         var rnd = Math.floor(Math.random()*4);
-        if (tmpBrd[["canUp", "canDown", "canLeft", "canRight"][rnd]]())
+        if (origBrd[["canUp", "canDown", "canLeft", "canRight"][rnd]]())
             return ["up", "down", "left", "right"][rnd];
     }
 }
+/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
+ai.random.prototype.cleanup = function() { }
 
+
 
 ////////////////////////////////////////////////////////////////
 // 1 level deep on max scores.
 ////////////////////////////////////////////////////////////////
 
-ai.nextMaxScore = function(brd) {
-    var tmpBrd = board.create();
-    board.copy(brd, tmpBrd);
+ai.nextMaxScore = function(brdEngine) {
+    this.brdEngine = brdEngine;
+}
+ai.nextMaxScore.prototype.analyse = function(brd) {
+    var origBrd = new this.brdEngine(brd);
+    var nextBrd = new this.brdEngine();
     var maxScore = -1;
     var action;
-    if (board.move.up(tmpBrd)) {
+    if (origBrd.up(nextBrd)) {
         maxScore = board.score(tmpBrd).score;
         action = "up";
     }
@@ -62,13 +69,19 @@
     }
     return action;
 }
+/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
+ai.nextMaxScore.prototype.cleanup = function() { }
 
+
 
 ////////////////////////////////////////////////////////////////
 // 1 level deep on max value.
 ////////////////////////////////////////////////////////////////
 
-ai.nextMaxValue = function(brd) {
+ai.nextMaxValue = function(brdEngine) {
+    this.brdEngine = brdEngine;
+}
+ai.nextMaxValue.prototype.analyse = function(brd) {
     var tmpBrd = board.create();
     board.copy(brd, tmpBrd);
     var maxMax = -1;
@@ -103,4 +116,6 @@
     }
     return action;
 }
+/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
+ai.nextMaxScore.prototype.cleanup = function() { }
 
--- a/board.js	Sun Sep 07 00:33:47 2014 +0300
+++ b/board.js	Sun Sep 07 03:33:33 2014 +0300
@@ -53,14 +53,14 @@
                 this.brd[i][j] = brd[i][j];
     }
 }
-BoardArr.prototype.get = function(i, j) {
+BoardArr2d.prototype.get = function(i, j) {
     return this.brd[i][j];
 }
-BoardArr.prototype.set = function(i, j, val) {
+BoardArr2d.prototype.set = function(i, j, val) {
     this.brd[i][j] = val;
 }
 /* Return and optionally fill 2d board. */
-BoardArr.prototype.exportTo = function(brd) {
+BoardArr2d.prototype.exportTo = function(brd) {
     brd = brd || [[],[],[],[]];
     for (var i = 0; i < 4; i++)
         for (var j = 0; j < 4; j++)
@@ -74,6 +74,17 @@
             brd.brd[i][j] = this.brd[i][j];
     return brd;
 }
+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 > 0)
+                score += (v-1)*Math.pow(2, v);
+        }
+    }
+    return score;
+}
 
 BoardArr2d.prototype.canRight = function() {
     for (var i = 0; i < 4; i++) {
@@ -225,47 +236,58 @@
     }
 }
 
-BoardArr2d.prototype.shiftLeft_mostly_unrolled = function(brd) {
+BoardArr2d.prototype.shiftRight_mostly_unrolled = function(brd) {
+    var updated = false;
     var from = this.brd, to = brd.brd;
-    for (var i = 3; i >= 0; i--) {
+    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 (f1 === 0) {  // a 0 0 0
+                    if (f0 === 0)
+                        moved = false;
                     f3 = f0;
                     f2 = 0;
-                } else {  // a b 0 0
+                } else {         // a b 0 0
                     f3 = f1;
                     f2 = f0;
                 }
                 f1 = 0;
-            } else { // f2 !== 0 && f3 === 0
-                if (f1 === 0) { // a 0 b 0
+            } else {             // f2 !== 0 && f3 === 0
+                if (f1 === 0) {  // a 0 b 0
                     f3 = f2;
                     f2 = f0;
-                } else { // a b c 0
+                } else {         // a b c 0
                     f3 = f2;
                     f2 = f1;
                     f1 = f0;
                 }
             }
             f0 = 0;
-        } else { // f3 !== 0
+        } else {                 // f3 !== 0
             if (f2 === 0) {
-                if (f1 === 0) { // a 0 0 b
+                if (f1 === 0) {  // a 0 0 b
+                    if (f0 === 0)
+                        moved = false;
                     f2 = f0;
-                } else {  // a b 0 c
+                } else {         // a b 0 c
                     f2 = f1;
                     f1 = f0;
                 }
                 f0 = 0;
-            } else { // f2 !== 0 && f3 !== 0
-                if (f1 === 0) { // a 0 b c
+            } else {             // f2 !== 0 && f3 !== 0
+                if (f1 === 0) {  // a 0 b c
+                    if (f0 === 0)
+                        moved = false;
                     f1 = f0;
                     f0 = 0;
-                } // else: a b c d
+                } else {         // else: a b c d
+                    moved = false;
+                }
             }
         }
+        updated = updated || moved;
         if (f2 === 0) {
             to[i][0] = 0;
             to[i][1] = 0;
@@ -277,6 +299,7 @@
             to[i][0] = 0;
             to[i][1] = 0;
             if (f2 === f3) {
+                updated = true;
                 to[i][2] = 0;
                 to[i][3] = f3 + 1;
             } else {
@@ -286,6 +309,7 @@
             continue;
         }
         if (f2 === f3) {
+            updated = true;
             to[i][0] = 0;
             to[i][3] = f3 + 1;
             if (f0 === f1) {
@@ -298,17 +322,347 @@
         } else {
             to[i][3] = f3;
             if (f1 === f2) {
+                updated = true;
                 to[i][0] = 0;
                 to[i][1] = f0;
                 to[i][2] = f2 + 1;
             } else {
-                to[i][0] = f0;
-                to[i][1] = f1;
-                to[i][2] = f2;
+                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) {
+                to[2][j] = 0;
+                to[3][j] = f3 + 1;
+                updated = true;
+            } 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) {
+            to[3][j] = 0;
+            to[0][j] = f0 + 1;
+            if (f3 === f2) {     // a a b b
+                updated = true;
+                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;
 
 
 
--- a/rule.js	Sun Sep 07 00:33:47 2014 +0300
+++ b/rule.js	Sun Sep 07 03:33:33 2014 +0300
@@ -101,7 +101,7 @@
         state.stack.push(state.curr);
 }
 board.move = {};
-board.move.up = function(brd) {
+board.move.up = board.move.upOrig = function(brd) {
     var updated = false;
     for (var j = 0; j < 4; j++) {
         var state = board.row.init();
@@ -122,7 +122,7 @@
     }
     return updated;
 };
-board.move.down = function(brd) {
+board.move.down = board.move.downOrig = function(brd) {
     var updated = false;
     for (var j = 0; j < 4; j++) {
         var state = board.row.init();
@@ -143,7 +143,7 @@
     }
     return updated;
 };
-board.move.left = function(brd) {
+board.move.left = board.move.leftOrig = function(brd) {
     var updated = false;
     for (var i = 0; i < 4; i++) {
         var state = board.row.init();
@@ -164,7 +164,7 @@
     }
     return updated;
 };
-board.move.right = function(brd) {
+board.move.right = board.move.rightOrig = function(brd) {
     var updated = false;
     for (var i = 0; i < 4; i++) {
         var state = board.row.init();
@@ -185,3 +185,41 @@
     }
     return updated;
 };
+
+/* Restore after testing board.js moves. */
+board.move.restore = function() {
+    board.move.up = board.move.upOrig;
+    board.move.down = board.move.downOrig;
+    board.move.left = board.move.leftOrig;
+    board.move.right = board.move.rightOrig;
+}
+/* For testing board.js moves. */
+board.move.replaceMoveByBoardJS = function(brdEngine, dir) {
+    return function(brd) {
+        var engOrigBrd = new brdEngine(brd);
+        var engBrd = new brdEngine();
+        var changed = engOrigBrd[dir](engBrd);
+        var newBrd = engBrd.exportTo();
+        var changed2 = board.move[dir+"Orig"](brd);
+        for (var i = 0; i < 4; i++) {
+            for (var j = 0; j < 4; j++) {
+                if (newBrd[i][j] !== brd[i][j]) {
+                    console.log("[%d][%d] original: %o, expected: %o, actual: %o", i, j, engOrigBrd.exportTo(), brd, newBrd);
+                    return false;
+                }
+            }
+        }
+        if (changed2 !== changed) {
+            console.log("original: %o, changed: %o, changed2: %o", engOrigBrd.exportTo(), changed, changed2);
+            return false;
+        }
+        return changed;
+    }
+}
+board.move.replaceByBoardJS = function(brdEngine) {
+    // console.log("x2: %o", brdEngine);
+    board.move.up = board.move.replaceMoveByBoardJS(brdEngine, "up");
+    board.move.down = board.move.replaceMoveByBoardJS(brdEngine, "down");
+    board.move.left = board.move.replaceMoveByBoardJS(brdEngine, "left");
+    board.move.right = board.move.replaceMoveByBoardJS(brdEngine, "right");
+}