Move rule and AI engine to separate files.
--- 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;
+};