--- 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_ ]