--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/2048.html Tue Sep 02 01:10:03 2014 +0300
@@ -0,0 +1,507 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>2048 AI</title>
+ <meta name="viewport" content="width=device-width; initial-scale=1.0"/>
+ <meta charset="utf-8"/>
+
+ <style>
+ body {
+ width: 100%;
+ }
+ h1, .score-area, .control-area, #message-area {
+ text-align: center;
+ }
+ #board {
+ margin: 10px auto;
+ }
+ #board td {
+ width: 40px;
+ height: 40px;
+ border: 1px solid red;
+ margin: 0;
+ text-align: center;
+ }
+ </style>
+</head>
+<body>
+
+ <h1>2048</h1>
+
+ <div class="score-area">Score: <span id="score">0</span>, Max: <span id="max">0</span></div>
+
+ <div id="message-area"></div>
+
+ <table id="board">
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ </table>
+
+ <div class="control-area">
+ <div>
+ <button id="start">Start</button>
+ <button id="step">Step</button>
+ <button id="loop">Loop</button>
+ <button id="finish">Finish</button>
+ </div>
+ <div>
+ <button id="left">left</button>
+ <button id="up">up</button>
+ <button id="down">down</button>
+ <button id="right">right</button>
+ </div>
+ <h1>AI</h1>
+ <div>
+ <button id="ai-random">random</button>
+ <button id="ai-next-max-score">next max score</button>
+ <button id="ai-next-max-value">next max value</button>
+ </div>
+ </div>
+
+ <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.random = 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] = 2;
+ cnt--;
+ }
+ }
+ }
+ /* http://www.reddit.com/r/2048/comments/214njx/highest_possible_score_for_2048_warning_math */
+ 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];
+ if (val > 2)
+ score += Math.log2(val) * val;
+ if (max < val)
+ max = val;
+ }
+ }
+ return {score: score, max: 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*2);
+ 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 = {};
+ ui.board.set = function(i, j, val) {
+ boardDom.querySelectorAll("tr")[i].querySelectorAll("td")[j].innerHTML = val;
+ }
+ ui.board.update = function(brd) {
+ for (var i = 0; i < 4; i++) {
+ for (var j = 0; j < 4; j++) {
+ ui.board.set(i, j, (brd[i][j] >= 2) ? brd[i][j] : "");
+ }
+ }
+ }
+ ui.score = {};
+ var scoreDom = document.getElementById("score");
+ var maxDom = document.getElementById("max");
+ ui.score.clear = function(brd) {
+ scoreDom.innerHTML = '0';
+ maxDom.innerHTML = '0';
+ }
+ ui.score.update = function(brd) {
+ var score = board.score(brd);
+ scoreDom.innerHTML = '' + score.score;
+ maxDom.innerHTML = '' + score.max;
+ }
+
+ function start() {
+ ui.score.clear();
+ ui.message.clear();
+ board.current = board.create();
+ board.random(board.current);
+ ui.board.update(board.current);
+ }
+ document.getElementById("start").addEventListener("click", start);
+
+ function up() {
+ var updated = board.move.up(board.current);
+ if (updated) {
+ board.random(board.current);
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ }
+ }
+ document.getElementById("up").addEventListener("click", up);
+ function down() {
+ var updated = board.move.down(board.current);
+ if (updated) {
+ board.random(board.current);
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ }
+ }
+ document.getElementById("down").addEventListener("click", down);
+ function left() {
+ var updated = board.move.left(board.current);
+ if (updated) {
+ board.random(board.current);
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ }
+ }
+ document.getElementById("left").addEventListener("click", left);
+ function right() {
+ var updated = board.move.right(board.current);
+ if (updated) {
+ board.random(board.current);
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ }
+ }
+ document.getElementById("right").addEventListener("click", right);
+
+ document.body.addEventListener("keydown", function(event) {
+ var key = event.keyCode || event.which;
+ switch (key) {
+ case 38: up(); break;
+ case 40: down(); break;
+ case 37: left(); break;
+ case 39: right(); break;
+ }
+ return false;
+ });
+
+ ui.message = {};
+ var messageDom = document.getElementById("message-area");
+ ui.message.clear = function() {
+ messageDom.innerHTML = "";
+ }
+ ui.message.set = function(msg) {
+ messageDom.innerHTML = msg;
+ }
+
+ function step() {
+ ui.message.clear();
+ if (board.gameOver(board.current)) {
+ ui.message.set("Game over!");
+ return;
+ }
+ var tmpBrd = board.create();
+ board.copy(board.current, tmpBrd);
+ var fn = ai.current(tmpBrd);
+ if (typeof fn === 'undefined') {
+ ui.message.set("I don't know how to move!");
+ return;
+ }
+ var updated = board.move[fn].call(null, board.current);
+ if (updated) {
+ board.random(board.current);
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ } else {
+ ui.message.set("Wrong move!");
+ }
+ }
+ document.getElementById("step").addEventListener("click", step);
+
+ function finish() {
+ ui.message.clear();
+ while (!board.gameOver(board.current)) {
+ var tmpBrd = board.create();
+ board.copy(board.current, tmpBrd);
+ var fn = ai.current(tmpBrd);
+ if (typeof fn === 'undefined') {
+ ui.message.set("I don't know how to move!");
+ return;
+ }
+ var updated = board.move[fn].call(null, board.current);
+ if (updated) {
+ board.random(board.current);
+ } else {
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ ui.message.set("Wrong move!");
+ return;
+ }
+ }
+ ui.board.update(board.current);
+ ui.score.update(board.current);
+ ui.message.set("Game over!");
+ }
+ document.getElementById("finish").addEventListener("click", finish);
+
+ var ai = {};
+
+ 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;
+ });
+
+ 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;
+ });
+
+
+ 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-value").addEventListener("click", function() {
+ ai.current = ai.nextMaxValue;
+ });
+
+ </script>
+
+</body>
+</html>