# HG changeset patch # User Oleksandr Gavenko # Date 1410050013 -10800 # Node ID a9a44cfc3e082e56753da7b968c5b77c9dd3edaf # Parent 2264f7a3f7e067ef4802ffb1dd5ca5fe360c1e6b Moves for 2d array board + test toolkit. diff -r 2264f7a3f7e0 -r a9a44cfc3e08 2048.html --- 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 @@ +

AI

@@ -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"); diff -r 2264f7a3f7e0 -r a9a44cfc3e08 ai.js --- 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() { } diff -r 2264f7a3f7e0 -r a9a44cfc3e08 board.js --- 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; diff -r 2264f7a3f7e0 -r a9a44cfc3e08 rule.js --- 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"); +}