merged
authorOleksandr Gavenko <gavenkoa@gmail.com>
Mon, 22 Sep 2014 22:36:03 +0300
changeset 111 7b9d1bb9c471
parent 110 e3a91b336976 (diff)
parent 93 c2bf15c3b80b (current diff)
child 112 4bb8ed8448bc
merged
2048.html
--- a/.hgignore	Mon Sep 22 21:29:21 2014 +0300
+++ b/.hgignore	Mon Sep 22 22:36:03 2014 +0300
@@ -6,3 +6,4 @@
 CHANGES.html
 README.html
 HACKING.html
+AIs.html
--- a/.hgtags	Mon Sep 22 21:29:21 2014 +0300
+++ b/.hgtags	Mon Sep 22 22:36:03 2014 +0300
@@ -4,3 +4,4 @@
 58adeb5e426478540b5116518f1db2b0dd60f024 v0.2
 58adeb5e426478540b5116518f1db2b0dd60f024 v0.2
 97acd9f4288b5cf8f90ef483f23bcb349cee4ade v0.2
+6a0997efaa532ddfb9e6f94a5e34e2fbd0d6b436 v0.3
--- a/2048.html	Mon Sep 22 21:29:21 2014 +0300
+++ b/2048.html	Mon Sep 22 22:36:03 2014 +0300
@@ -66,20 +66,22 @@
       left: 1em;
       top: -1em;
     }
-    button#statistic {
+    button#statistic, button#statistic-clean {
       display: inline-block;
       float: left;
       margin: 3px 4px;
     }
     div.ai > div.option, div.setting, div.report {
       display: inline-block;
-      float: left;
       margin: 1px 4px;
       padding: 2px;
       border: 1px solid tan;
       border-radius: 4px;
     }
-    div.ai > div.option > input.positive, div.settings > div.setting > input {
+    div.ai > div.option, div.setting {
+      float: left;
+    }
+    div.ai > div.option > input.positive, div.settings > div.setting > input, div.ai-control input.positive {
       text-align: right;
       max-width: 4em;
       margin-right: 2px;
@@ -162,6 +164,11 @@
       <button id="step">Step</button>
       <button id="finish">Finish</button>
     </div>
+    <div class="ai-control">
+      <button id="until">Continue</button>
+      until <input type="text" class="positive" id="until-score" value="10000"> score
+      and <input type="text" class="positive" id="until-max-value" value="9"> max value
+    </div>
     <div class="clearfix"></div>
     <div class="move-control">
       <table>
@@ -187,13 +194,13 @@
           <input type="text" name="left" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> left weight
         </div>
         <div class="option">
-          <input type="text" name="right" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> right weight
+          <input type="text" name="right" class="positive" pattern="[0-9]*[.]?[0-9]*" value="16"/> right weight
         </div>
         <div class="option">
-          <input type="text" name="up" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> up weight
+          <input type="text" name="up" class="positive" pattern="[0-9]*[.]?[0-9]*" value="4"/> up weight
         </div>
         <div class="option">
-          <input type="text" name="down" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> down weight
+          <input type="text" name="down" class="positive" pattern="[0-9]*[.]?[0-9]*" value="8"/> down weight
         </div>
         <div class="clearfix"></div>
       </div>
@@ -208,9 +215,9 @@
         </div>
         <div class="clearfix"></div>
       </div>
-      <div class="ai" id="ai-one-step-deep">
+      <div class="ai" id="ai-one-step-ahead">
         <button class="ai">enable</button>
-        <h5>next merge makes max value</h5>
+        <h5>one step ahead</h5>
         <div class="option">
           <input type="text" name="scoreCoef" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> score weight
         </div>
@@ -228,13 +235,28 @@
         </div>
         <div class="clearfix"></div>
       </div>
-      <div class="ai" id="ai-deep-max-score">
+      <div class="ai" id="ai-static-deep-merges">
         <button class="ai">enable</button>
-        <h5>deep merges without simulation make max score</h5>
-      </div>
-      <div class="ai" id="ai-deep-max-score-corner">
-        <button class="ai">enable</button>
-        <h5>deep merges without simulation make max score + bonus if max value at corner/edge</h5>
+        <h5>deep merges without random simulation</h5>
+        <div class="option">
+          <input type="text" name="scoreCoef" class="positive" pattern="[0-9]*[.]?[0-9]*" value="1"/> score weight
+        </div>
+        <div class="option">
+          <input type="text" name="maxValCoef" class="positive" pattern="[0-9]*[.]?[0-9]*" value="0"/> max value weight
+        </div>
+        <div class="option">
+          <input type="text" name="cornerBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value="100"/> max value at corner bonus
+        </div>
+        <div class="option">
+          <input type="text" name="edgeBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value="0"/> max value at edge bonus
+        </div>
+        <div class="option">
+          <input type="text" name="freeBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value="10"/> free cell coefficient
+        </div>
+        <div class="option">
+          <input type="text" name="weightThreshold" class="positive" pattern="[0-9]*[.]?[0-9]*" value="10"/> score threshold
+        </div>
+        <div class="clearfix"></div>
       </div>
       <div class="ai" id="ai-expectimax">
         <button class="ai">enable</button>
@@ -265,12 +287,13 @@
     </div>
   </div>
 
-  <div id="report-area" class="area">
+  <div id="report-area">
     <h1>Reports</h1>
     <div class="settings">
       <div class="setting"><input type="text" id="stat-count-limit" value="100"> times</div>
       <div class="setting">limit to <input type="text" id="stat-time-limit" value="10"> sec</div>
       <button id="statistic">Start</button>
+      <button id="statistic-clean">Clean</button>
       <div class="setting"><input type="checkbox" id="report-score"/> score</div>
       <div class="setting"><input type="checkbox" id="report-turn"/> turn</div>
       <div class="setting"><input type="checkbox" id="report-speed"/> speed</div>
@@ -290,10 +313,17 @@
     // UI widgets.
 
     ui.dom = {};
+    ui.dom.clearfix = function() {
+      var divDom = document.createElement('div');
+      divDom.classList.add("clearfix");
+      return divDom;
+    }
     ui.dom.table = function(tbl, cols, cfg) {
       var tableDom = document.createElement('table');
-      if (typeof cfg.tableClass === 'string')
-        tableDom.classList.add(cfg.tableClass);
+      if (typeof cfg.tblClass === 'string')
+        tableDom.classList.add(cfg.tblClass);
+      if (typeof cfg.tblTitle === 'string')
+        tableDom.title = cfg.tblTitle;
       var trDom = document.createElement('tr');
       for (var i = 0; i < cols.length; i++) {
         var thDom = document.createElement('td');
@@ -423,9 +453,8 @@
       maxDom.innerHTML = '' + ui.board.val2048(score.max);
       turnDom.innerHTML = '' + turn;
     }
-    ui.score.speed = function(speed, turn) {
+    ui.score.speed = function(speed) {
       speedDom.innerHTML = '' + speed;
-      turnDom.innerHTML = '' + turn;
     }
 
     ////////////////////////////////////////////////////////////////
@@ -484,14 +513,17 @@
         return false;
       return true;
     }
-    ui.game.finishStep = function() {
-      board.putRandom(ui.board.position);
-      ui.board.turn++;
+    ui.game.refresh = function() {
       ui.board.update(ui.board.position);
       ui.score.update(ui.board.position, ui.board.turn);
       localStorage.savedBoard = JSON.stringify(ui.board.position);
       localStorage.savedTurn = ui.board.turn;
     }
+    ui.game.finishStep = function() {
+      board.putRandom(ui.board.position);
+      ui.board.turn++;
+      ui.game.refresh();
+    }
 
     ////////////////////////////////////////////////////////////////
     // Actions.
@@ -599,6 +631,62 @@
         if (updated) {
           board.putRandom(ui.board.position);
         } else {
+          ui.board.turn += step;
+          ui.game.refresh();
+          ui.game.setMessage("Wrong move!");
+          return;
+        }
+        step++; 
+      }
+      var tsTo = new Date().getTime();
+      ui.board.turn += step;
+      ui.game.finishStep();
+      ui.score.speed(parseFloat((step*1000.0/(tsTo-tsFrom)).toPrecision(3)));
+      ui.game.setMessage("Game over!");
+      ui.ai.current.cleanup();
+    }
+    document.getElementById("finish").addEventListener("click", ui.action.finish, false);
+
+    ui.action.until = function() {
+      if ( ! ui.ai.current) {
+        ui.game.setMessage('Select AI!');
+        return;
+      }
+      if ( ! ui.game.beginStep())
+        return;
+      var step = 0;
+      var safeBdr = board.create();
+      var tsFrom = new Date().getTime();
+      var scoreLimit = parseInt(document.getElementById("until-score").value);
+      if (!isFinite(scoreLimit) || scoreLimit < 0) {
+        scoreLimit = 1;
+        document.getElementById("until-score").value = scoreLimit;
+      }
+      var maxValLimit = parseInt(document.getElementById("until-max-value").value);
+      if (!isFinite(maxValLimit) || maxValLimit < 0 || maxValLimit > 13) {
+        maxValLimit = 1;
+        document.getElementById("until-max-value").value = maxValLimit;
+      }
+      localStorage.untilScore = scoreLimit;
+      localStorage.untilMaxVal = maxValLimit;
+      while (true) {
+        if (board.gameOver(ui.board.position)) {
+          ui.game.setMessage("Game over!");
+          break;
+        }
+        var stat = board.score(ui.board.position);
+        if (stat.score >= scoreLimit && stat.max >= maxValLimit)
+          break;
+        board.copy(ui.board.position, safeBdr);
+        var move = ui.ai.current.analyse(safeBdr);
+        if (typeof move === 'undefined') {
+          ui.game.setMessage("I don't know how to move!");
+          return;
+        }
+        var updated = board.move[move].call(null, ui.board.position);
+        if (updated) {
+          board.putRandom(ui.board.position);
+        } else {
           ui.game.finishStep();
           ui.game.setMessage("Wrong move!");
           return;
@@ -606,12 +694,12 @@
         step++; 
       }
       var tsTo = new Date().getTime();
-      ui.game.finishStep();
-      ui.score.speed(parseFloat((step*1000.0/(tsTo-tsFrom)).toPrecision(3)), step);
-      ui.game.setMessage("Game over!");
-      ui.ai.current && ui.ai.current.cleanup();
+      ui.board.turn += step;
+      ui.game.refresh();
+      ui.score.speed(parseFloat((step*1000.0/(tsTo-tsFrom)).toPrecision(3)));
+      ui.ai.current.cleanup();
     }
-    document.getElementById("finish").addEventListener("click", ui.action.finish, false);
+    document.getElementById("until").addEventListener("click", ui.action.until, false);
 
     ////////////////////////////////////////////////////////////////
     // Register AIs.
@@ -619,6 +707,7 @@
     ui.ai = {};
     ui.ai.current = null;
     ui.ai.parseCfg = function(aiDom, cfg) {
+      cfg = cfg || {};
       var optDoms = aiDom.querySelectorAll("div.option > input.positive[type='text']");
       for (var i = 0; i < optDoms.length; i++) {
         var val = parseFloat(optDoms[i].value);
@@ -629,6 +718,17 @@
         }
         cfg[optDoms[i].name] = val;
       }
+      var optDoms = aiDom.querySelectorAll("div.option > input[type='checkbox']");
+      for (var i = 0; i < optDoms.length; i++) {
+        cfg[optDoms[i].name] = optDoms[i].checked;
+      }
+      return cfg;
+    }
+    ui.ai.cfgTitle = function(aiName) {
+      var title = JSON.stringify(ui.ai.parseCfg(document.getElementById(aiName))).replace(":", ": ", "g").replace(",", "\n", "g");
+      title = title.substr(1);
+      title = title.substr(0, title.length-1);
+      return title;
     }
 
     ui.ai.algList = {
@@ -636,30 +736,23 @@
         return new ai.BlindRandom(ui.brdEngine);
       },
       "ai-blind-weight-random": function(aiDom) {
-        var cfg = {};
-        ui.ai.parseCfg(aiDom, cfg);
+        var cfg = ui.ai.parseCfg(aiDom);
         return new ai.BlindWeightRandom(ui.brdEngine, cfg);
       },
       "ai-blind-cycle": function(aiDom) {
-        var cfg = {};
-        cfg.clockwise = aiDom.querySelectorAll("input[name='clockwise']")[0].checked;
-        cfg.whilePossible = aiDom.querySelectorAll("input[name='whilePossible']")[0].checked;
+        var cfg = ui.ai.parseCfg(aiDom);
         return new ai.BlindCycle(ui.brdEngine, cfg);
       },
-      "ai-one-step-deep": function(aiDom) {
-        var cfg = {};
-        ui.ai.parseCfg(aiDom, cfg);
-        return new ai.OneStepDeep(ui.brdEngine, cfg);
+      "ai-one-step-ahead": function(aiDom) {
+        var cfg = ui.ai.parseCfg(aiDom);
+        return new ai.OneStepAhead(ui.brdEngine, cfg);
       },
-      "ai-deep-max-score": function() {
-        return new ai.DeepMaxScore(ui.brdEngine);
-      },
-      "ai-deep-max-score-corner": function() {
-        return new ai.DeepMaxScoreCorner(ui.brdEngine);
+      "ai-static-deep-merges": function(aiDom) {
+        var cfg = ui.ai.parseCfg(aiDom);
+        return new ai.StaticDeepMerges(ui.brdEngine, cfg);
       },
       "ai-expectimax": function(aiDom) {
-        var cfg = {};
-        ui.ai.parseCfg(aiDom, cfg);
+        var cfg = ui.ai.parseCfg(aiDom);
         return new ai.expectimax(ui.brdEngine, cfg);
       },
       // "": function() {
@@ -697,6 +790,7 @@
     var reportsDom = document.getElementById('reports');
 
     ui.report = {};
+    ui.report.statNo = 1;
 
     ui.report.stat = function() {
       /* console.profile(); */
@@ -779,7 +873,7 @@
       var reportDom = document.createElement('div');
       reportDom.classList.add('report');
       var h5Dom = document.createElement('h5');
-      h5Dom.appendChild(document.createTextNode(ui.ai.currentName));
+      h5Dom.appendChild(document.createTextNode(ui.ai.currentName + " #" + ui.report.statNo++));
       reportDom.appendChild(h5Dom);
       var maxVals = Object.keys(histo).sort(function(a,b){return a - b});
       var tbl = [];
@@ -822,15 +916,22 @@
         tblCols.push('mean speed');
         tblCols.push('max speed');
       }
-      var tableDom = ui.dom.table(tbl, tblCols, { tableClass: 'report-by-maxval' });
+      var tableDom = ui.dom.table(tbl, tblCols, { tblClass: 'report-by-maxval', tblTitle: ui.ai.cfgTitle(ui.ai.currentName) });
       reportDom.appendChild(tableDom);
       reportsDom.insertBefore(reportDom, reportsDom.firstChild);
       /* console.profileEnd(); */
     }
-
     var statisticBtn = document.getElementById('statistic');
     statisticBtn.addEventListener("click", ui.report.stat, false);
 
+    ui.report.statClean = function() {
+      while (reportsDom.hasChildNodes()) {
+        reportsDom.removeChild(reportsDom.lastChild);
+      }
+      reportsDom.appendChild(ui.dom.clearfix());
+    }
+    var statisticCleanBtn = document.getElementById('statistic-clean');
+    statisticCleanBtn.addEventListener("click", ui.report.statClean, false);
 
     ////////////////////////////////////////////////////////////////
     // Initialise game.
@@ -838,6 +939,17 @@
     ui.brdEngine = BoardArr2d; // TODO make user selectable
     // ui.brdEngine = BoardObj; // TODO make user selectable
 
+    if (localStorage.untilScore) {
+      var scoreLimit = parseInt(localStorage.untilScore);
+      if (isFinite(scoreLimit) && scoreLimit > 0)
+        document.getElementById("until-score").value = scoreLimit;
+    }
+    if (localStorage.untilMaxVal) {
+      var maxValLimit = parseInt(localStorage.untilMaxVal);
+      if (isFinite(maxValLimit) && maxValLimit > 0 && maxValLimit <= 13)
+        document.getElementById("until-max-value").value = maxValLimit;
+    }
+
     if (localStorage.val2048) {
       try {
         ui.board.val2048Dom.checked = JSON.parse(localStorage.val2048);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AIs.rst	Mon Sep 22 22:36:03 2014 +0300
@@ -0,0 +1,34 @@
+.. -*- mode: rst; coding: utf-8; fill-column: 80 -*-
+
+.. include:: header.rst
+
+======
+ AIs.
+======
+.. contents::
+
+Document version.
+=================
+
+.. include:: VERSION.rst
+
+AI discussion.
+==============
+
+ * http://math.stackexchange.com/questions/727076/probability-that-random-moves-in-the-game-2048-will-win
+ * http://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048
+ * http://codegolf.stackexchange.com/questions/25226/2048-king-on-the-hill
+
+Blind strategies.
+=================
+
+Blind random moves with shifted probabilities of moves (weighted random) are
+better then random moves.
+
+Blind moves with repeating pattern (like switching direction in clock order)
+better then weighted random moves.
+
+See:
+
+ * http://math.stackexchange.com/questions/727076/probability-that-random-moves-in-the-game-2048-will-win/
+
--- a/CHANGES.rst	Mon Sep 22 21:29:21 2014 +0300
+++ b/CHANGES.rst	Mon Sep 22 22:36:03 2014 +0300
@@ -12,6 +12,17 @@
 
 .. include:: VERSION.rst
 
+v0.4, 2014-xx-xx.
+=================
+
+ * Add AI descriptions.
+
+v0.3, 2014-09-20.
+=================
+
+ * Add AI looping until condition meet.
+ * Improve statistic UI.
+
 v0.2, 2014-09-18.
 =================
 
--- a/README.rst	Mon Sep 22 21:29:21 2014 +0300
+++ b/README.rst	Mon Sep 22 22:36:03 2014 +0300
@@ -13,14 +13,16 @@
 Game rules.
 ===========
 
-TODO
+4x4 board tiles filled by nambers or empty.
+
+During each move one of free tiles was filled by value 1 or 2 (2 with probability 10%).
 
-AI discussion.
-==============
+You can move tiles in one of four possible direction: left/right/up/dowm.
 
- * http://math.stackexchange.com/questions/727076/probability-that-random-moves-in-the-game-2048-will-win
- * http://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048
- * http://codegolf.stackexchange.com/questions/25226/2048-king-on-the-hill
+Each move every tiles fill empty space in selected direction. If two tiles have same numbers ``N``
+them was merged into tile with number ``N+1``. Merges performed from head of direction.
+
+Your goal is to reach tile with number ``11`` or better.
 
 Alternatives.
 =============
--- a/ai.js	Mon Sep 22 21:29:21 2014 +0300
+++ b/ai.js	Mon Sep 22 22:36:03 2014 +0300
@@ -76,7 +76,7 @@
     this.threshold2 = (this.cfg.left + this.cfg.down)/total;
     this.threshold3 = (this.cfg.left + this.cfg.down + this.cfg.right)/total;
 }
-ai.BlindWeightRandom.bestCfg = { left: 1, down: 10, right: 5, up: 1 };
+ai.BlindWeightRandom.bestCfg = { left: 1, right: 16, up: 4, down: 8 };
 ai.BlindWeightRandom.prototype.analyse = function(brd) {
     var origBrd = new this.brdEngine(brd);
     while (true) {
@@ -153,26 +153,26 @@
 
 /**
  * Defines coefficient for linear resulted weight function.
- * @name ai.OneStepDeep.cfg
+ * @name ai.OneStepAhead.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 foe each free cell
+ * @property {number} freeBonus    bonus for each free cell
  */
 
 /** 1 step deep with * AI.
  * @param {Board} brdEngine  board engine from board.js
- * @param {ai.OneStepDeep.cfg} cfg  configuration settings
+ * @param {ai.OneStepAhead.cfg} cfg  configuration settings
  * @constructor */
-ai.OneStepDeep = function(brdEngine, cfg) {
+ai.OneStepAhead = function(brdEngine, cfg) {
     this.brdEngine = brdEngine;
-    this.cfg = ai.copyObj(ai.OneStepDeep.bestCfg);
+    this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
     ai.copyObj(cfg, this.cfg);
 }
-ai.OneStepDeep.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
-ai.OneStepDeep.prototype.weight = function(brd) {
+ai.OneStepAhead.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
+ai.OneStepAhead.prototype.weight = function(brd) {
     var weight = 0;
     if (this.cfg.scoreCoef > 0)
         weight += this.cfg.scoreCoef * brd.score();
@@ -187,7 +187,7 @@
         weight += this.cfg.freeBonus * brd.free();
     return weight;
 }
-ai.OneStepDeep.prototype.analyse = function(brd) {
+ai.OneStepAhead.prototype.analyse = function(brd) {
     var origBrd = new this.brdEngine(brd);
     var nextBrd = new this.brdEngine();
     var maxWeight = -1;
@@ -205,7 +205,7 @@
     return bestDir;
 }
 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
-ai.OneStepDeep.prototype.cleanup = function() { }
+ai.OneStepAhead.prototype.cleanup = function() { }
 
 
 
@@ -213,151 +213,85 @@
 // N level deep on score value without random simulation.
 ////////////////////////////////////////////////////////////////
 
-/** N level deep on score value without random simulation.
+/**
+ * Defines coefficient for linear resulted weight function.
+ * @name ai.StaticDeepMerges.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
+ */
+
+/** Deep merges AI without random simulation.
  * @param {Board} brdEngine  board engine from board.js
+ * @param {Object} cfg  configuration settings
  * @constructor */
-ai.DeepMaxScore = function(brdEngine) {
+ai.StaticDeepMerges = function(brdEngine, cfg) {
     this.brdEngine = brdEngine;
+    this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
+    ai.copyObj(cfg, this.cfg);
 }
-ai.DeepMaxScore.prototype.analyse = function(brd) {
+ai.StaticDeepMerges.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, weightThreshold: 10};
+ai.StaticDeepMerges.prototype.weight = function(brd) {
+    var weight = 0;
+    if (this.cfg.scoreCoef > 0)
+        weight += this.cfg.scoreCoef * brd.score();
+    var max = brd.max();
+    if (this.cfg.maxValCoef > 0)
+        weight += this.cfg.maxValCoef * max;
+    if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
+        weight += this.cfg.cornerBonus;
+    if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
+        weight += this.cfg.edgeBonus;
+    if (this.cfg.freeBonus > 0)
+        weight += this.cfg.freeBonus * brd.free();
+    return weight;
+}
+ai.StaticDeepMerges.prototype.analyse = function(brd) {
     var origBrd = new this.brdEngine(brd);
     var nextBrd = new this.brdEngine();
     var prevScore = -1, nextScore = -1;
-    var maxScore = -1;
+    var maxWeight = -1;
     var bestDir;
     for (var i = 0; i < ai.dirs.length; i++) {
         var dir = ai.dirs[i];
         if (origBrd[dir](nextBrd)) {
-            nextScore = nextBrd.score();
-            var score = this.bestScore(nextBrd);
-            // console.log("dir: %o, prevScore: %o, nextScore: %o, maxScore: %o, score: %o", dir, prevScore, nextScore, maxScore, score);
-            if (maxScore < score || (maxScore === score && prevScore < nextScore)) {
+            var weight = this.evalFn(nextBrd);
+            var ok = (weight - maxWeight) > this.cfg.weightPriority;
+            if ( ! ok && maxWeight <= weight) {
+                nextScore = this.weight(nextBrd);
+                ok = prevScore < nextScore;
+            }
+            if (ok) {
                 prevScore = nextScore;
-                maxScore = score;
+                maxWeight = weight;
                 bestDir = dir;
             }
         }
     }
     return bestDir;
 }
-ai.DeepMaxScore.prototype.bestScore = function(brd, seenBrds) {
-    if (seenBrds) {
-        for (var i = 0; i < seenBrds.length; i++)
-            if (brd.equals(seenBrds[i]))
-                return 0;
-    } else {
-        seenBrds = [];
-    }
-    seenBrds.push(brd);
+ai.StaticDeepMerges.prototype.evalFn = function(brd) {
     var currScore = brd.score();
-    var maxScore = currScore;
+    var maxWeight = currScore;
     var nextBrd = new this.brdEngine();
     for (var i = 0; i < ai.dirs.length; i++) {
         if (brd[ai.dirs[i]](nextBrd)) {
             var score = nextBrd.score();
             if (score > currScore)
-                maxScore = Math.max(maxScore, this.bestScore(nextBrd, seenBrds));
+                maxWeight = Math.max(maxWeight, this.evalFn(nextBrd));
         }
     }
-    return maxScore;
+    return maxWeight;
 }
 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
-ai.DeepMaxScore.prototype.cleanup = function() { }
+ai.StaticDeepMerges.prototype.cleanup = function() { }
 
 
 
 ////////////////////////////////////////////////////////////////
-// N level deep on score value + max value prefer corner,
-// without random simulation.
-////////////////////////////////////////////////////////////////
-
-/**
- * Defines coefficient for linear resulted weight function.
- * @name ai.DeepMaxScoreCorner.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 foe each free cell
- */
-
-/** N level deep AI without random simulation.
- * @param {Board} brdEngine  board engine from board.js
- * @param {Object} cfg  configuration settings
- * @constructor */
-ai.DeepMaxScoreCorner = function(brdEngine, cfg) {
-    this.brdEngine = brdEngine;
-    this.cfg = cfg || {};
-    this.cfg.cornerBonus = this.cfg.cornerBonus || 20000;
-    this.cfg.edgeBonus = this.cfg.edgeBonus || 100;
-    this.cfg.freeBonus = this.cfg.edgeBonus || 100;
-}
-ai.DeepMaxScoreCorner.prototype.scoreCorner = function(brd) {
-    var score = brd.score();
-    var max = brd.max();
-    if (brd.atCorner(max))
-        score += this.cfg.cornerBonus;
-    else if (brd.atEdge(max))
-        score += this.cfg.edgeBonus;
-    score += brd.free() * this.cfg.freeBonus;
-    return score;
-}
-ai.DeepMaxScoreCorner.prototype.scoreEdge = function(brd) {
-    var score = brd.score();
-    var max = brd.max();
-    if (brd.atEdge(max))
-        score += this.cfg.edgeBonus;
-    score += brd.free() * this.cfg.freeBonus;
-    return score;
-}
-ai.DeepMaxScoreCorner.prototype.analyse = function(brd) {
-    var origBrd = new this.brdEngine(brd);
-    var nextBrd = new this.brdEngine();
-    var prevScore = -1, nextScore = -1;
-    var maxScore = -1;
-    var bestDir;
-    for (var i = 0; i < ai.dirs.length; i++) {
-        var dir = ai.dirs[i];
-        if (origBrd[dir](nextBrd)) {
-            nextScore = this.scoreCorner(nextBrd);
-            var score = this.bestScore(nextBrd);
-            // console.log("dir: %o, prevScore: %o, nextScore: %o, maxScore: %o, score: %o", dir, prevScore, nextScore, maxScore, score);
-            if (maxScore < score || (maxScore === score && prevScore < nextScore)) {
-                prevScore = nextScore;
-                maxScore = score;
-                bestDir = dir;
-            }
-        }
-    }
-    return bestDir;
-}
-ai.DeepMaxScoreCorner.prototype.bestScore = function(brd, seenBrds) {
-    if (seenBrds) {
-        for (var i = 0; i < seenBrds.length; i++)
-            if (brd.equals(seenBrds[i]))
-                return 0;
-    } else {
-        seenBrds = [];
-    }
-    seenBrds.push(brd);
-    var currScore = this.scoreEdge(brd);
-    var maxScore = currScore;
-    var nextBrd = new this.brdEngine();
-    for (var i = 0; i < ai.dirs.length; i++) {
-        if (brd[ai.dirs[i]](nextBrd)) {
-            var score = this.scoreEdge(nextBrd);
-            if (score > currScore)
-                maxScore = Math.max(maxScore, this.bestScore(nextBrd, seenBrds));
-        }
-    }
-    return maxScore;
-}
-/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
-ai.DeepMaxScoreCorner.prototype.cleanup = function() { }
-
-
-////////////////////////////////////////////////////////////////
 // N level deep with random simulation.
 ////////////////////////////////////////////////////////////////
 
@@ -369,7 +303,7 @@
  * @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 foe each free cell
+ * @property {number} freeBonus    bonus for each free cell
  */
 
 /** N level deep with random simulation.
@@ -415,7 +349,7 @@
     for (var i = 0; i < ai.dirs.length; i++) {
         var dir = ai.dirs[i];
         if (origBrd[dir](nextBrd)) {
-            var weight = this.recWeight(nextBrd, 1);
+            var weight = this.evalFn(nextBrd, 1);
             var ok = (weight - maxWeight) > this.cfg.weightPriority;
             if ( ! ok && maxWeight <= weight) {
                 nextScore = this.weight(nextBrd);
@@ -431,7 +365,7 @@
     this.cleanup();
     return bestDir;
 }
-ai.expectimax.prototype.recWeight = function(brd, depth) {
+ai.expectimax.prototype.evalFn = function(brd, depth) {
     if (depth >= this.cfg.depth)
         return this.weight(brd);
     if (this.cache[depth]) {
@@ -454,7 +388,7 @@
                 var n = 0, w = 0;
                 for (var diri = 0; diri < ai.dirs.length; diri++) {
                     if (randBoard[ai.dirs[diri]](nextBrd)) {
-                        w += this.recWeight(nextBrd, depth+1);
+                        w += this.evalFn(nextBrd, depth+1);
                         n++;
                     }
                 }
@@ -466,7 +400,7 @@
                     var n = 0, w = 0;
                     for (var diri = 0; diri < ai.dirs.length; diri++) {
                         if (randBoard[ai.dirs[diri]](nextBrd)) {
-                            w += this.recWeight(nextBrd, depth+1);
+                            w += this.evalFn(nextBrd, depth+1);
                             n++;
                         }
                     }
--- a/header.rst	Mon Sep 22 21:29:21 2014 +0300
+++ b/header.rst	Mon Sep 22 22:36:03 2014 +0300
@@ -5,8 +5,9 @@
 .. _README: README.html
 .. _CHANGES: CHANGES.html
 .. _Changes: CHANGES.html
+.. _AIs: AIs.html
 .. _Hacking: HACKING.html
 .. _Authors: AUTHORS.html
 
-[ Play_ | About_ | Changes_ | Hacking_ | Authors_ ]
+[ Play_ | About_ | Changes_ | AIs_ | Hacking_ | Authors_ ]