Join all one step deep AIs into one with linear weight function.
authorOleksandr Gavenko <gavenkoa@gmail.com>
Thu, 11 Sep 2014 19:22:04 +0300
changeset 53 ee53cd2cb69a
parent 52 e4e21d2fcbe7
child 54 2c389325d13b
Join all one step deep AIs into one with linear weight function.
2048.html
ai.js
--- a/2048.html	Thu Sep 11 18:12:24 2014 +0300
+++ b/2048.html	Thu Sep 11 19:22:04 2014 +0300
@@ -185,9 +185,25 @@
         </div>
         <div class="clearfix"></div>
       </div>
-      <div class="ai" id="ai-next-max-value">
+      <div class="ai" id="ai-one-step-deep">
         <button class="ai">enable</button>
         <h5>next merge makes max value</h5>
+        <div class="option">
+          <input type="text" name="scoreCoef" class="int" pattern="[0-9]*" value="1"/> score weight
+        </div>
+        <div class="option">
+          <input type="text" name="maxValCoef" class="int" pattern="[0-9]*" value="0"/> max value weight
+        </div>
+        <div class="option">
+          <input type="text" name="cornerBonus" class="int" pattern="[0-9]*" value="100"/> max value at corner bonus
+        </div>
+        <div class="option">
+          <input type="text" name="edgeBonus" class="int" pattern="[0-9]*" value="0"/> max value at edge bonus
+        </div>
+        <div class="option">
+          <input type="text" name="freeBonus" class="int" pattern="[0-9]*" value="10"/> free cell coefficient
+        </div>
+        <div class="clearfix"></div>
       </div>
       <div class="ai" id="ai-deep-max-score">
         <button class="ai">enable</button>
@@ -528,6 +544,17 @@
 
     ui.ai = {};
     ui.ai.current = null;
+    ui.ai.parseCfg = function(aiDom, cfg) {
+      var optDoms = aiDom.querySelectorAll("div.option > input.int[type='text']");
+      for (var i = 0; i < optDoms.length; i++) {
+        var val = parseFloat(optDoms[i].value);
+        if (val === NaN) {
+          ui.game.setMessage('' + optDoms[i].name + ' setting is not a number!');
+          continue;
+        }
+        cfg[optDoms[i].name] = val;
+      }
+    }
     ui.ai.algList = {
       "ai-blind-random": function() {
         return new ai.blindRandom(ui.brdEngine);
@@ -546,11 +573,11 @@
         cfg.whilePossible = aiDom.querySelectorAll("input[name='whilePossible']")[0].checked;
         return new ai.blindCycle(ui.brdEngine, cfg);
       },
-      "ai-next-max-score": function() {
-        return new ai.nextMaxScore(ui.brdEngine);
-      },
-      "ai-next-max-value": function() {
-        return new ai.nextMaxValue(ui.brdEngine);
+      "ai-one-step-deep": function(aiDom) {
+        var cfg = {};
+        ui.ai.parseCfg(aiDom, cfg);
+        console.log(cfg);
+        return new ai.oneStepDeep(ui.brdEngine, cfg);
       },
       "ai-deep-max-score": function() {
         return new ai.deepMaxScore(ui.brdEngine);
--- a/ai.js	Thu Sep 11 18:12:24 2014 +0300
+++ b/ai.js	Thu Sep 11 19:22:04 2014 +0300
@@ -4,6 +4,19 @@
 ai.dirs = ["up", "right", "down", "left"];
 ai.canDirs = ["canUp", "canRight", "canDown", "canLeft"];
 
+/* Create empty 'to' if argument missing. */
+ai.copyObj = function(from, to) {
+    if (to == null || typeof to !== "object")
+        to = {};
+    if (from == null || typeof obj !== "object")
+        return to;
+    for (var attr in cfg) {
+        if (from.hasOwnProperty(attr))
+            to[attr] = from[attr];
+    }
+    return to;
+}
+
 // 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.
 
@@ -109,60 +122,51 @@
 
 
 ////////////////////////////////////////////////////////////////
-// 1 level deep on max scores.
+// 1 step deep with linear weight function on score, max value,
+// bonuses for max value stay at corner or edge and bonuses
+// for each free field.
 ////////////////////////////////////////////////////////////////
 
-ai.nextMaxScore = function(brdEngine) {
+ai.oneStepDeep = function(brdEngine, cfg) {
     this.brdEngine = brdEngine;
+    this.cfg = ai.copyObj(ai.oneStepDeep.bestCfg);
+    ai.copyObj(cfg, this.cfg);
 }
-ai.nextMaxScore.prototype.analyse = function(brd) {
+ai.oneStepDeep.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
+ai.oneStepDeep.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.oneStepDeep.prototype.analyse = function(brd) {
     var origBrd = new this.brdEngine(brd);
     var nextBrd = new this.brdEngine();
-    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)) {
-            var score = nextBrd.score();
-            if (maxScore < score) {
+            var weight = this.weight(nextBrd);
+            if (maxWeight < weight) {
                 bestDir = dir;
-                maxScore = score;
+                maxWeight = weight;
             }
         }
     }
     return bestDir;
 }
 /* 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(brdEngine) {
-    this.brdEngine = brdEngine;
-}
-ai.nextMaxValue.prototype.analyse = function(brd) {
-    var origBrd = new this.brdEngine(brd);
-    var nextBrd = new this.brdEngine();
-    var maxMax = -1;
-    var bestDir;
-    for (var i = 0; i < ai.dirs.length; i++) {
-        var dir = ai.dirs[i];
-        if (origBrd[dir](nextBrd)) {
-            var max = nextBrd.score();
-            if (maxMax < max) {
-                maxMax = max;
-                bestDir = dir;
-            }
-        }
-    }
-    return bestDir;
-}
-/* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
-ai.nextMaxValue.prototype.cleanup = function() { }
+ai.oneStepDeep.prototype.cleanup = function() { }