Join all one step deep AIs into one with linear weight function.
--- 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() { }