AI: Survive as long as possible.
--- a/2048.html Sun Oct 19 01:04:19 2014 +0300
+++ b/2048.html Sun Oct 19 01:13:42 2014 +0300
@@ -281,6 +281,16 @@
<input type="text" name="freeBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value="10"/> free cell coefficient
</div>
</div>
+ <div class="ai wide control" id="ai-survive">
+ <button class="ai">enable</button>
+ <h5>survive</h5>
+ <div class="option">
+ <input type="text" name="maxDepth" class="positive" pattern="[0-9]*" value="5"/> depth
+ </div>
+ <div class="option">
+ <input type="text" name="freeCells" class="positive" pattern="[1-9][0-9]?" value="8"/> free cells
+ </div>
+ </div>
</div>
</div>
@@ -785,6 +795,10 @@
var cfg = ui.ai.parseCfg(aiDom);
return new ai.expectimax(ui.brdEngine, cfg);
},
+ "ai-survive": function(aiDom) {
+ var cfg = ui.ai.parseCfg(aiDom);
+ return new ai.survive(ui.brdEngine, cfg);
+ },
// "": function() {
// return new ai.(ui.brdEngine);
// },
--- a/ai.js Sun Oct 19 01:04:19 2014 +0300
+++ b/ai.js Sun Oct 19 01:13:42 2014 +0300
@@ -435,3 +435,99 @@
ai.expectimax.prototype.cleanup = function() {
}
+
+
+////////////////////////////////////////////////////////////////
+// Survive as long as possible.
+////////////////////////////////////////////////////////////////
+
+/**
+ * Defines coefficient for linear resulted weight function.
+ * @name ai.survive.cfg
+ * @namespace
+ * @property {number} scoreCoef multiplicator for score
+ * @property {number} maxValCoef multiplicator for max value
+ * @property {number} cornerBonus bonus for max value at board corner
+ * @property {number} edgeBonus bonus for max value at board edge
+ * @property {number} freeBonus bonus for each free cell
+ */
+
+/** N level deep with random simulation.
+ * @param {Board} brdEngine board engine from board.js
+ * @param {ai.survive.cfg} cfg configuration settings
+ * @constructor */
+ai.survive = function(brdEngine, cfg) {
+ this.brdEngine = brdEngine;
+ this.cfg = ai.copyObj(ai.survive.bestCfg);
+ ai.copyObj(cfg, this.cfg);
+ if (this.cfg.freeCells <= 0)
+ this.cfg.freeCells = ai.survive.bestCfg.freeCells;
+ if (!this.cfg.maxDepth || this.cfg.maxDepth < 0 || 20 <= this.cfg.maxDepth)
+ this.cfg.maxDepth = ai.survive.bestCfg.maxDepth;
+ this.cfg.altAI = new ai.StaticDeepMerges(brdEngine, ai.survive.altAICfg);
+}
+ai.survive.bestCfg = {freeCells: 8, maxDepth: 5};
+ai.survive.altAICfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, weightThreshold: 0};
+/** Select best direction for next step. */
+ai.survive.prototype.analyse = function(brd2d) {
+ var origBrd = new this.brdEngine(brd2d);
+ var nextBrd = new this.brdEngine();
+ var bestW = -1;
+ var bestDir;
+ var freeCnt = origBrd.freeCnt();
+ if (freeCnt >= this.cfg.freeCells)
+ return this.cfg.altAI.analyse(brd2d);
+ for (var i = 0; i < ai.dirs.length; i++) {
+ var dir = ai.dirs[i];
+ if (origBrd[dir](nextBrd)) {
+ var w = this.evalFn(nextBrd, 1);
+ console.log("dir: %s, w: %d", dir, w);
+ if (w > bestW) {
+ bestW = w;
+ bestDir = dir;
+ }
+ }
+ }
+ return bestDir;
+}
+ai.survive.prototype.evalFn = function(brd, depth) {
+ if (brd.freeCnt() >= this.cfg.freeCells)
+ return 1;
+ if (depth >= this.cfg.maxDepth)
+ return 0;
+ var wMin = +Infinity;
+ var randBoard = new this.brdEngine();
+ var nextBrd = new this.brdEngine();
+exit:
+ for (var i = 0; i < 3; i++) {
+ for (var j = 0; j < 3; j++) {
+ if (brd.get(i, j) !== 0)
+ continue;
+ brd.copy(randBoard);
+ randBoard.set(i, j, 1);
+ var wMax = -1;
+ for (var diri = 0; diri < ai.dirs.length; diri++) {
+ if (randBoard[ai.dirs[diri]](nextBrd)) {
+ var w = this.evalFn(nextBrd, depth+1);
+ if (w === 1) {
+ wMax = 1;
+ break;
+ }
+ wMax = Math.max(wMax, w);
+ }
+ }
+ if (wMax === -1) {
+ wMin = -1;
+ break exit;
+ }
+ wMin = Math.min(wMin, wMax);
+ }
+ }
+ if (wMin === +Infinity)
+ return -1;
+ return wMin;
+}
+/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
+ai.survive.prototype.cleanup = function() {
+}
+