Move rule and AI engine to separate files.
authorOleksandr Gavenko <gavenkoa@gmail.com>
Sun, 07 Sep 2014 00:19:00 +0300
changeset 10 70ece7f758a0
parent 9 961eff57a23f
child 11 2264f7a3f7e0
Move rule and AI engine to separate files.
2048.html
ai.js
rule.js
--- a/2048.html	Sun Sep 07 00:18:35 2014 +0300
+++ b/2048.html	Sun Sep 07 00:19:00 2014 +0300
@@ -5,8 +5,10 @@
   <meta name="viewport" content="width=device-width; initial-scale=1.0"/>
   <meta charset="utf-8"/>
 
+  <script src="rule.js"></script>
   <script src="board.js"></script>
   <script src="perf.js"></script>
+  <script src="ai.js"></script>
 
   <style>
     body {
@@ -113,192 +115,6 @@
   <script>
     "use strict";
 
-    var board = {};
-    board.create = function() {
-      var brd = [];
-      for (var i = 0; i < 4; i++) {
-        brd[i] = [];
-        for (var j = 0; j < 4; j++) {
-          brd[i][j] = 0;
-        }
-      }
-      return brd;
-    }
-    board.copy = function(from, to) {
-      for (var i = 0; i < 4; i++) {
-        for (var j = 0; j < 4; j++) {
-          to[i][j] = from[i][j];
-        }
-      }
-    }
-    board.freeCnt = function(brd) {
-      var cnt = 0;
-      for (var i = 0; i < 4; i++) {
-        for (var j = 0; j < 4; j++) {
-          if (brd[i][j] === 0)
-            cnt++;
-        }
-      }
-      return cnt;
-    }
-    board.gameOver = function(brd) {
-      if (board.freeCnt(brd) > 0)
-        return false;
-      for (var i = 0; i < 4; i++) {
-        for (var j = 0; j < 3; j++) {
-          if (brd[i][j] === brd[i][j+1])
-            return false;
-        }
-      }
-      for (var j = 0; j < 4; j++) {
-        for (var i = 0; i < 3; i++) {
-          if (brd[i][j] === brd[i+1][j])
-            return false;
-        }
-      }
-      return true;
-    }
-    board.putRandom = function(brd) {
-      var cnt = board.freeCnt(brd);
-      cnt = Math.floor(Math.random() * cnt)+1;
-      for (var i = 0; i < 4 && cnt > 0; i++) {
-        for (var j = 0; j < 4 && cnt > 0; j++) {
-          if (brd[i][j] !== 0)
-            continue;
-          if (cnt === 1)
-            brd[i][j] = (Math.random() > .9) ? 2 : 1;
-          cnt--;
-        }
-      }
-    }
-    /* http://www.reddit.com/r/2048/comments/214njx/highest_possible_score_for_2048_warning_math */
-    var boardScoreTbl = [0];
-    for (var i = 1, exp = 2; i < 16; i++, exp *= 2) {
-      boardScoreTbl[i] = (i-1)*exp;
-    }
-    board.score = function(brd) {
-      var score = 0;
-      var max = 0;
-      for (var i = 0; i < 4; i++) {
-        for (var j = 0; j < 4; j++) {
-          var val = brd[i][j];
-          score += boardScoreTbl[val];
-          if (max < val)
-            max = val;
-        }
-      }
-      return {score: score, max: Math.pow(2, max)};
-    }
-
-    board.row = {};
-    board.row.init = function() {
-      return {stack: [], curr: 0};
-    }
-    board.row.push = function(state, val) {
-      if (val === 0)
-        return;
-      if (state.curr === 0) {
-        state.curr = val;
-        return;
-      }
-      if (state.curr === val) {
-        state.stack.push(state.curr+1);
-        state.curr = 0;
-      } else {
-        state.stack.push(state.curr);
-        state.curr = val;
-      }
-    }
-    board.row.finish = function(state) {
-      if (state.curr !== 0)
-        state.stack.push(state.curr);
-    }
-    board.move = {};
-    board.move.up = function(brd) {
-      var updated = false;
-      for (var j = 0; j < 4; j++) {
-        var state = board.row.init();
-        for (var i = 0; i < 4; i++) {
-          board.row.push(state, brd[i][j]);
-        }
-        board.row.finish(state);
-        for (var i = 0; i < state.stack.length; i++) {
-          if (brd[i][j] !== state.stack[i])
-            updated = true;
-          brd[i][j] = state.stack[i];
-        }
-        for (; i < 4; i++) {
-          if (brd[i][j] !== 0)
-            updated = true;
-          brd[i][j] = 0;
-        }
-      }
-      return updated;
-    };
-    board.move.down = function(brd) {
-      var updated = false;
-      for (var j = 0; j < 4; j++) {
-        var state = board.row.init();
-        for (var i = 3; i >= 0; i--) {
-          board.row.push(state, brd[i][j]);
-        }
-        board.row.finish(state);
-        for (var i = 0; i < state.stack.length; i++) {
-          if (brd[3-i][j] !== state.stack[i])
-            updated = true;
-          brd[3-i][j] = state.stack[i];
-        }
-        for (; i < 4; i++) {
-          if (brd[3-i][j] !== 0)
-            updated = true;
-          brd[3-i][j] = 0;
-        }
-      }
-      return updated;
-    };
-    board.move.left = function(brd) {
-      var updated = false;
-      for (var i = 0; i < 4; i++) {
-        var state = board.row.init();
-        for (var j = 0; j < 4; j++) {
-          board.row.push(state, brd[i][j]);
-        }
-        board.row.finish(state);
-        for (var j = 0; j < state.stack.length; j++) {
-          if (brd[i][j] !== state.stack[j])
-            updated = true;
-          brd[i][j] = state.stack[j];
-        }
-        for (; j < 4; j++) {
-          if (brd[i][j] !== 0)
-            updated = true;
-          brd[i][j] = 0;
-        }
-      }
-      return updated;
-    };
-    board.move.right = function(brd) {
-      var updated = false;
-      for (var i = 0; i < 4; i++) {
-        var state = board.row.init();
-        for (var j = 3; j >= 0; j--) {
-          board.row.push(state, brd[i][j]);
-        }
-        board.row.finish(state);
-        for (var j = 0; j < state.stack.length; j++) {
-          if (brd[i][3-j] !== state.stack[j])
-            updated = true;
-          brd[i][3-j] = state.stack[j];
-        }
-        for (; j < 4; j++) {
-          if (brd[i][3-j] !== 0)
-            updated = true;
-          brd[i][3-j] = 0;
-        }
-      }
-      return updated;
-    };
-
     var boardDom = document.getElementById("board");
     var ui = {};
     ui.board = {};
@@ -407,12 +223,12 @@
       }
       var tmpBrd = board.create();
       board.copy(board.current, tmpBrd);
-      var fn = ai.current(tmpBrd);
-      if (typeof fn === 'undefined') {
+      var move = ui.ai.analyse(tmpBrd);
+      if (typeof move === 'undefined') {
         ui.message.set("I don't know how to move!");
         return;
       }
-      var updated = board.move[fn].call(null, board.current);
+      var updated = board.move[move].call(null, board.current);
       if (updated) {
         board.putRandom(board.current);
         ui.board.update(board.current);
@@ -430,12 +246,12 @@
       while (!board.gameOver(board.current)) {
         var tmpBrd = board.create();
         board.copy(board.current, tmpBrd);
-        var fn = ai.current(tmpBrd);
-        if (typeof fn === 'undefined') {
+        var move = ui.ai.analyse(tmpBrd);
+        if (typeof move === 'undefined') {
           ui.message.set("I don't know how to move!");
           return;
         }
-        var updated = board.move[fn].call(null, board.current);
+        var updated = board.move[move].call(null, board.current);
         if (updated) {
           board.putRandom(board.current);
         } else {
@@ -454,97 +270,19 @@
     }
     document.getElementById("finish").addEventListener("click", finish);
 
-    var ai = {};
+    ////////////////////////////////////////////////////////////////
+    // Register AIs.
 
-    ai.random = function(brd) {
-      var tmpBrd = board.create();
-      do {
-        var action = ["up", "down", "left", "right"][Math.floor(Math.random()*4)];
-        board.copy(brd, tmpBrd);
-      } while (!board.move[action](tmpBrd));
-      return action;
-    }
-    document.getElementById("ai-random").addEventListener("click", function() {
-      ai.current = ai.random;
-    });
+    ui.brdEngine = BoardArr2d; // TODO make user selectable
 
-    ai.nextMaxScore = function(brd) {
-      var tmpBrd = board.create();
-      board.copy(brd, tmpBrd);
-      var maxScore = -1;
-      var action;
-      if (board.move.up(tmpBrd)) {
-        maxScore = board.score(tmpBrd).score;
-        action = "up";
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.left(tmpBrd)) {
-        var score = board.score(tmpBrd).score;
-        if (maxScore < score) {
-          action = "left";
-          maxScore = score;
-        }
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.down(tmpBrd)) {
-        var score = board.score(tmpBrd).score;
-        if (maxScore < score) {
-          action = "down";
-          maxScore = score;
-        }
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.right(tmpBrd)) {
-        var score = board.score(tmpBrd).score;
-        if (maxScore < score) {
-          action = "right";
-          maxScore = score;
-        }
-      }
-      return action;
-    }
-    document.getElementById("ai-next-max-score").addEventListener("click", function() {
-      ai.current = ai.nextMaxScore;
+    document.getElementById("ai-random").addEventListener("click", function() {
+      ui.ai = new ai.random(ui.brdEngine);
     });
-
-
-    ai.nextMaxValue = function(brd) {
-      var tmpBrd = board.create();
-      board.copy(brd, tmpBrd);
-      var maxMax = -1;
-      var action;
-      if (board.move.up(tmpBrd)) {
-        maxMax = board.score(tmpBrd).max;
-        action = "up";
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.left(tmpBrd)) {
-        var max = board.score(tmpBrd).max;
-        if (maxMax < max) {
-          action = "left";
-          maxMax = max;
-        }
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.down(tmpBrd)) {
-        var max = board.score(tmpBrd).max;
-        if (maxMax < max) {
-          action = "down";
-          maxMax = max;
-        }
-      }
-      board.copy(brd, tmpBrd);
-      if (board.move.right(tmpBrd)) {
-        var max = board.score(tmpBrd).max;
-        if (maxMax < max) {
-          action = "right";
-          maxMax = max;
-        }
-      }
-      return action;
-    }
+    document.getElementById("ai-next-max-score").addEventListener("click", function() {
+      ui.ai = new ai.nextMaxScore(ui.brdEngine);
+    });
     document.getElementById("ai-next-max-value").addEventListener("click", function() {
-      ai.current = ai.nextMaxValue;
+      ui.ai = new ai.nextMaxValue(ui.brdEngine);
     });
 
   </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ai.js	Sun Sep 07 00:19:00 2014 +0300
@@ -0,0 +1,106 @@
+"use strict";
+
+var ai = {};
+
+// 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.
+////////////////////////////////////////////////////////////////
+
+ai.random = function(brdEngine) {
+    this.brdEngine = brdEngine;
+}
+ai.random.prototype.analyse = function(brd) {
+    var tmpBrd = new this.brdEngine(brd);
+    while (true) {
+        var rnd = Math.floor(Math.random()*4);
+        if (tmpBrd[["canUp", "canDown", "canLeft", "canRight"][rnd]]())
+            return ["up", "down", "left", "right"][rnd];
+    }
+}
+
+
+////////////////////////////////////////////////////////////////
+// 1 level deep on max scores.
+////////////////////////////////////////////////////////////////
+
+ai.nextMaxScore = function(brd) {
+    var tmpBrd = board.create();
+    board.copy(brd, tmpBrd);
+    var maxScore = -1;
+    var action;
+    if (board.move.up(tmpBrd)) {
+        maxScore = board.score(tmpBrd).score;
+        action = "up";
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.left(tmpBrd)) {
+        var score = board.score(tmpBrd).score;
+        if (maxScore < score) {
+            action = "left";
+            maxScore = score;
+        }
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.down(tmpBrd)) {
+        var score = board.score(tmpBrd).score;
+        if (maxScore < score) {
+            action = "down";
+            maxScore = score;
+        }
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.right(tmpBrd)) {
+        var score = board.score(tmpBrd).score;
+        if (maxScore < score) {
+            action = "right";
+            maxScore = score;
+        }
+    }
+    return action;
+}
+
+
+////////////////////////////////////////////////////////////////
+// 1 level deep on max value.
+////////////////////////////////////////////////////////////////
+
+ai.nextMaxValue = function(brd) {
+    var tmpBrd = board.create();
+    board.copy(brd, tmpBrd);
+    var maxMax = -1;
+    var action;
+    if (board.move.up(tmpBrd)) {
+        maxMax = board.score(tmpBrd).max;
+        action = "up";
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.left(tmpBrd)) {
+        var max = board.score(tmpBrd).max;
+        if (maxMax < max) {
+            action = "left";
+            maxMax = max;
+        }
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.down(tmpBrd)) {
+        var max = board.score(tmpBrd).max;
+        if (maxMax < max) {
+            action = "down";
+            maxMax = max;
+        }
+    }
+    board.copy(brd, tmpBrd);
+    if (board.move.right(tmpBrd)) {
+        var max = board.score(tmpBrd).max;
+        if (maxMax < max) {
+            action = "right";
+            maxMax = max;
+        }
+    }
+    return action;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rule.js	Sun Sep 07 00:19:00 2014 +0300
@@ -0,0 +1,187 @@
+"use strict";
+
+var board = {};
+board.create = function() {
+    var brd = [];
+    for (var i = 0; i < 4; i++) {
+        brd[i] = [];
+        for (var j = 0; j < 4; j++) {
+            brd[i][j] = 0;
+        }
+    }
+    return brd;
+}
+board.copy = function(from, to) {
+    for (var i = 0; i < 4; i++) {
+        for (var j = 0; j < 4; j++) {
+            to[i][j] = from[i][j];
+        }
+    }
+}
+board.freeCnt = function(brd) {
+    var cnt = 0;
+    for (var i = 0; i < 4; i++) {
+        for (var j = 0; j < 4; j++) {
+            if (brd[i][j] === 0)
+                cnt++;
+        }
+    }
+    return cnt;
+}
+board.gameOver = function(brd) {
+    if (board.freeCnt(brd) > 0)
+        return false;
+    for (var i = 0; i < 4; i++) {
+        for (var j = 0; j < 3; j++) {
+            if (brd[i][j] === brd[i][j+1])
+                return false;
+        }
+    }
+    for (var j = 0; j < 4; j++) {
+        for (var i = 0; i < 3; i++) {
+            if (brd[i][j] === brd[i+1][j])
+                return false;
+        }
+    }
+    return true;
+}
+board.putRandom = function(brd) {
+    var cnt = board.freeCnt(brd);
+    cnt = Math.floor(Math.random() * cnt)+1;
+    for (var i = 0; i < 4 && cnt > 0; i++) {
+        for (var j = 0; j < 4 && cnt > 0; j++) {
+            if (brd[i][j] !== 0)
+                continue;
+            if (cnt === 1)
+                brd[i][j] = (Math.random() > .9) ? 2 : 1;
+            cnt--;
+        }
+    }
+}
+/* http://www.reddit.com/r/2048/comments/214njx/highest_possible_score_for_2048_warning_math */
+var boardScoreTbl = [0];
+for (var i = 1, exp = 2; i < 16; i++, exp *= 2) {
+    boardScoreTbl[i] = (i-1)*exp;
+}
+board.score = function(brd) {
+    var score = 0;
+    var max = 0;
+    for (var i = 0; i < 4; i++) {
+        for (var j = 0; j < 4; j++) {
+            var val = brd[i][j];
+            score += boardScoreTbl[val];
+            if (max < val)
+                max = val;
+        }
+    }
+    return {score: score, max: Math.pow(2, max)};
+}
+
+board.row = {};
+board.row.init = function() {
+    return {stack: [], curr: 0};
+}
+board.row.push = function(state, val) {
+    if (val === 0)
+        return;
+    if (state.curr === 0) {
+        state.curr = val;
+        return;
+    }
+    if (state.curr === val) {
+        state.stack.push(state.curr+1);
+        state.curr = 0;
+    } else {
+        state.stack.push(state.curr);
+        state.curr = val;
+    }
+}
+board.row.finish = function(state) {
+    if (state.curr !== 0)
+        state.stack.push(state.curr);
+}
+board.move = {};
+board.move.up = function(brd) {
+    var updated = false;
+    for (var j = 0; j < 4; j++) {
+        var state = board.row.init();
+        for (var i = 0; i < 4; i++) {
+            board.row.push(state, brd[i][j]);
+        }
+        board.row.finish(state);
+        for (var i = 0; i < state.stack.length; i++) {
+            if (brd[i][j] !== state.stack[i])
+                updated = true;
+            brd[i][j] = state.stack[i];
+        }
+        for (; i < 4; i++) {
+            if (brd[i][j] !== 0)
+                updated = true;
+            brd[i][j] = 0;
+        }
+    }
+    return updated;
+};
+board.move.down = function(brd) {
+    var updated = false;
+    for (var j = 0; j < 4; j++) {
+        var state = board.row.init();
+        for (var i = 3; i >= 0; i--) {
+            board.row.push(state, brd[i][j]);
+        }
+        board.row.finish(state);
+        for (var i = 0; i < state.stack.length; i++) {
+            if (brd[3-i][j] !== state.stack[i])
+                updated = true;
+            brd[3-i][j] = state.stack[i];
+        }
+        for (; i < 4; i++) {
+            if (brd[3-i][j] !== 0)
+                updated = true;
+            brd[3-i][j] = 0;
+        }
+    }
+    return updated;
+};
+board.move.left = function(brd) {
+    var updated = false;
+    for (var i = 0; i < 4; i++) {
+        var state = board.row.init();
+        for (var j = 0; j < 4; j++) {
+            board.row.push(state, brd[i][j]);
+        }
+        board.row.finish(state);
+        for (var j = 0; j < state.stack.length; j++) {
+            if (brd[i][j] !== state.stack[j])
+                updated = true;
+            brd[i][j] = state.stack[j];
+        }
+        for (; j < 4; j++) {
+            if (brd[i][j] !== 0)
+                updated = true;
+            brd[i][j] = 0;
+        }
+    }
+    return updated;
+};
+board.move.right = function(brd) {
+    var updated = false;
+    for (var i = 0; i < 4; i++) {
+        var state = board.row.init();
+        for (var j = 3; j >= 0; j--) {
+            board.row.push(state, brd[i][j]);
+        }
+        board.row.finish(state);
+        for (var j = 0; j < state.stack.length; j++) {
+            if (brd[i][3-j] !== state.stack[j])
+                updated = true;
+            brd[i][3-j] = state.stack[j];
+        }
+        for (; j < 4; j++) {
+            if (brd[i][3-j] !== 0)
+                updated = true;
+            brd[i][3-j] = 0;
+        }
+    }
+    return updated;
+};