Fixed typo.
<!DOCTYPE html>
<html>
<head>
<title>2048 AI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="utf-8"/>
<script src="rule.js"></script>
<script src="board.js"></script>
<script src="perf.js"></script>
<script src="ai.js"></script>
<link rel="stylesheet" type="text/css" href="rst.css">
<style>
body {
font-size: 11pt;
}
h1, div.area {
text-align: center;
margin: 10px auto;
}
#board {
margin: 10px auto;
}
#board td {
width: 40px;
height: 40px;
border: 1px solid red;
margin: 0;
text-align: center;
}
div.control {
text-align: left;
border: 1px red solid;
display: inline-block;
margin: 3px 2px;
background-color: cornsilk;
padding: 2px;
}
div.move.control button.suggestion {
background-color: cyan;
}
div.wide.control {
display: block;
}
div.ai.control {
position: relative;
margin-bottom: 1em;
}
div.ai.control.enabled {
background-color: wheat;
}
div.control h5 {
text-align: center;
margin: 2px 0;
}
div.ai.control h5 {
margin-left: 6em;
}
button.ai {
position: absolute;
left: 1em;
top: -1em;
}
button#statistic, button#statistic-clean {
display: inline-block;
margin: 3px 4px;
}
div.control > div.option {
display: inline-block;
margin: 1px 4px;
padding: 2px;
border: 1px solid tan;
border-radius: 4px;
}
div.control input {
text-align: right;
max-width: 4em;
margin-right: 2px;
}
#test {
display: none;
}
table.report-by-maxval {
border-collapse: collapse;
margin: 2px;
font-size: 10pt;
text-align: right;
}
table.report-by-maxval > tr > th {
border: 2px solid green;
padding: 2px;
}
table.report-by-maxval > tr > td {
border: 1px solid green;
padding: 2px;
}
table.report-by-maxval > tr > td:nth-child(3) {
background-color: gold;
}
table.report-by-maxval > tr > td:nth-child(5),
table.report-by-maxval > tr > td:nth-child(8),
table.report-by-maxval > tr > td:nth-child(11) {
background-color: wheat;
}
</style>
</head>
<body>
<div class="area">[ <a href="README.html">About</a> | <a href="CHANGES.html">Changes</a> | <a href="AIs.html">AIs</a> | <a href="HACKING.html">Hacking</a> | <a href="AUTHORS.html">Authors</a> ]</div>
<h1>2048</h1>
<div id="score-area" class="area">
<div class="control">
Score: <span id="score">0</span>, Max: <span id="max">0</span>, Turn: <span id="turn">0</span>, Speed: <span id="speed">0</span> t/s
</div>
</div>
<div id="message-area" class="area"></div>
<table id="board">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
<div id="control-area" class="area">
<div class="control">
<button id="reset">Reset</button>
<div class="option"><input type="checkbox" id="2048" checked> 2048</div>
<div class="option">
<select id="board-engine">
<option value="BoardObj">BoardObj</option>
<option value="BoardArr2d">BoardArr2d</option>
<option value="BoardArr">BoardArr</option>
</select> engine
</div>
</div>
<br>
<div class="control">
<button id="suggest">Suggest</button>
<button id="step">Step</button>
<button id="visual-finish">Visual</button>
<button id="finish">Finish</button>
<div class="option"><input type="checkbox" id="profile"> profile</div>
</div>
<br>
<div class="control">
<button id="until">Continue</button>
<div class="option">until <input type="text" class="positive" id="until-score" value="10000"> score</div>
and <div class="option"><input type="text" class="positive" id="until-max-value" value="9"> max value</div>
</div>
<br>
<div class="move control">
<table>
<tr>
<td></td><td><button id="up">↑</button></td><td></td>
</tr>
<tr>
<td><button id="left">←</button></td><td><button id="down">↓</button></td><td><button id="right">→</button></td>
</tr>
</table>
<button id="test">test</button>
</div>
<h1>AIs</h1>
<div class="ai-list">
<div class="ai wide control" id="ai-blind-random">
<button class="ai">enable</button>
<h5>bling random</h5>
</div>
<div class="ai wide control" id="ai-always-up">
<button class="ai">enable</button>
<h5>always up</h5>
</div>
<div class="ai wide control" id="ai-blind-weight-random">
<button class="ai">enable</button>
<h5>bling weight random</h5>
<div class="option">
<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="16"/> right weight
</div>
<div class="option">
<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="8"/> down weight
</div>
</div>
<div class="ai wide control" id="ai-blind-cycle">
<button class="ai">enable</button>
<h5>blind cycle</h5>
<div class="option">
<input type="checkbox" name="clockwise"/> clockwise
</div>
<div class="option">
<input type="checkbox" name="whilePossible"/> max move in one direction
</div>
</div>
<div class="ai wide control" id="ai-one-step-ahead">
<button class="ai">enable</button>
<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>
<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>
<div class="ai wide control" id="ai-static-deep-merges">
<button class="ai">enable</button>
<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="utilityThreshold" class="positive" pattern="[0-9]*[.]?[0-9]*" value="10"/> score threshold
</div>
</div>
<div class="ai wide control" id="ai-expectimax">
<button class="ai">enable</button>
<h5>expectimax</h5>
<div class="option">
<input type="text" name="depth" class="positive" pattern="[0-9]*" value="3"/> recursion depth
</div>
<div class="option">
<input type="text" name="balance" class="positive" pattern="[0-9]*[.]?[0-9]*" value=".9"/> probability of 2
</div>
<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>
<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 class="ai wide control" id="ai-monte-carlo">
<button class="ai">enable</button>
<h5>Monte Carlo</h5>
<div class="option">
<input type="text" name="maxDepth" class="positive" pattern="[0-9]*" value="4"/> depth limit
</div>
<div class="option">
<input type="text" name="simulations" class="positive" pattern="[0-9]*" value="30"/> simulations
</div>
<div class="option">
<input type="text" name="cornerBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value=".2"/> max value at corner bonus
</div>
<div class="option">
<input type="text" name="edgeBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value=".1"/> max value at edge bonus
</div>
<div class="option">
<input type="text" name="freeBonus" class="positive" pattern="[0-9]*[.]?[0-9]*" value=".2"/> free cell coefficient
</div>
</div>
</div>
</div>
<div id="report-area" class="area">
<h1>Reports</h1>
<div class="control">
<div class="option">up to <input type="text" id="stat-count-limit" value="100"> times</div>
<div class="option">up to <input type="text" id="stat-time-limit" value="10"> sec</div>
<button id="statistic-start">Start</button>
<button id="statistic-stop">Stop</button>
<button id="statistic-clean">Clean</button>
<div class="option"><input type="checkbox" id="report-score"/> score</div>
<div class="option"><input type="checkbox" id="report-turn"/> turn</div>
<div class="option"><input type="checkbox" id="report-speed"/> speed</div>
</div>
<div id="reports" class="wide control"></div>
</div>
<script>
"use strict";
var ui = {};
////////////////////////////////////////////////////////////////
// UI widgets.
ui.dom = {};
ui.dom.putBr = function(parentDom, text) {
var lines = text.split('\n');
parentDom.appendChild(document.createTextNode(lines[0]));
for (var i = 1; i < lines.length; i++) {
parentDom.appendChild(document.createElement('br'));
parentDom.appendChild(document.createTextNode(lines[i]));
}
}
ui.dom.table = function(tbl, cols, cfg) {
var tableDom = document.createElement('table');
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');
ui.dom.putBr(thDom, cols[i]);
trDom.appendChild(thDom);
}
tableDom.appendChild(trDom);
for (var i = 0; i < tbl.length; i++) {
var row = tbl[i];
var trDom = document.createElement('tr');
for (var j = 0; j < row.length; j++) {
var tdDom = document.createElement('td');
tdDom.appendChild(document.createTextNode(row[j]));
trDom.appendChild(tdDom);
}
tableDom.appendChild(trDom);
}
return tableDom;
};
////////////////////////////////////////////////////////////////
// Board.
var boardDom = document.getElementById("board");
ui.board = {};
ui.board.val2048Dom = document.getElementById('2048');
ui.board.val2048Dom.addEventListener("click", function(event) {
event.stopPropagation();
ui.board.update(ui.board.position);
localStorage.val2048 = ui.board.val2048Dom.checked;
event.target.blur();
}, false);
ui.board.val2048 = function(val) {
if (ui.board.val2048Dom.checked)
return Math.pow(2, val);
return val;
}
var boardEngineDom = document.getElementById("board-engine");
boardEngineDom.addEventListener("change", function() {
var engine = boardEngineDom.value;
ui.brdEngine = window[engine];
localStorage.brdEngine = engine;
});
/* 'val' typesafe. */
ui.board.set = function(i, j, val) {
if (typeof val !== 'number')
val = 0;
var dom = boardDom.querySelectorAll("tr")[i].querySelectorAll("td")[j];
if (0 < val && val < 15) {
dom.innerHTML = ui.board.val2048(val);
var rgb = 'rgb(255,'+ (255-5*val-2*(val%2)) +','+ (255-10*val-5*((val-1)%2)) +')';
} else {
dom.innerHTML = '';
var rgb = 'white';
}
dom.style.backgroundColor = rgb;
}
ui.board.update = function(brd) {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
ui.board.set(i, j, brd[i][j]);
}
}
}
ui.board.handleEdit = function(tdDom, i, j) {
if (!tdDom.firstChild || tdDom.firstChild.nodeType === Node.TEXT_NODE) {
if (tdDom.firstChild)
tdDom.removeChild(tdDom.firstChild);
var inputDom = document.createElement('input');
inputDom.value = ui.board.position[i][j];
inputDom.style.width = '1em';
tdDom.appendChild(inputDom);
inputDom.focus();
inputDom.select();
inputDom.addEventListener("blur", function(tdDom, i, j) {
return function(event) {
event.preventDefault(); event.stopPropagation();
ui.board.handleEdit(tdDom, i, j);
}
}(tdDom, i, j), false);
} else {
var inputDom = tdDom.firstChild;
var val = parseInt(inputDom.value);
if (0 <= val && val < 15) {
ui.board.position[i][j] = val;
ui.board.set(i, j, val);
} else {
ui.board.set(i, j, ui.board.position[i][j]);
}
}
}
ui.board.enableEdit = function() {
var trDoms = boardDom.querySelectorAll("tr");
for (var i = 0; i < 4; i++) {
var tdDoms = trDoms[i].querySelectorAll("td");
for (var j = 0; j < 4; j++) {
var tdDom = tdDoms[j];
tdDom.addEventListener("click", function(tdDom, i, j) {
return function(event) {
event.preventDefault(); event.stopPropagation();
ui.board.handleEdit(tdDom, i, j);
}
}(tdDom, i, j), false);
tdDom.addEventListener("keyup", function(tdDom, i, j) {
return function(event) {
event.preventDefault(); event.stopPropagation();
if (event.keyCode === 13)
ui.board.handleEdit(tdDom, i, j);
}
}(tdDom, i, j), false);
}
}
}
ui.board.enableEdit();
////////////////////////////////////////////////////////////////
// Score statistic.
ui.score = {};
var scoreDom = document.getElementById("score");
var maxDom = document.getElementById("max");
var speedDom = document.getElementById("speed");
var turnDom = document.getElementById("turn");
ui.score.clear = function(brd) {
scoreDom.innerHTML = '0';
maxDom.innerHTML = '0';
speedDom.innerHTML = '0';
turnDom.innerHTML = '0';
}
ui.score.update = function(brd, turn) {
var score = board.score(brd);
scoreDom.innerHTML = '' + score.score;
maxDom.innerHTML = '' + ui.board.val2048(score.max);
turnDom.innerHTML = '' + turn;
}
ui.score.speed = function(speed) {
speedDom.innerHTML = '' + speed;
}
////////////////////////////////////////////////////////////////
// Suggestion UI.
ui.suggestion = {};
ui.suggestion.btns = document.querySelectorAll('div.move.control button');
ui.suggestion.clear = function() {
for (var i = 0; i < ui.suggestion.btns.length; i++)
ui.suggestion.btns[i].classList.remove('suggestion');
}
ui.suggestion.set = function(dir) {
document.getElementById(dir).classList.add('suggestion');
}
////////////////////////////////////////////////////////////////
// Game flow.
ui.game = {};
ui.game.dirs = ["up", "right", "down", "left"];
var messageDom = document.getElementById("message-area");
ui.game.clearMessage = function() {
messageDom.innerHTML = "";
}
ui.game.setMessage = function(msg) {
messageDom.innerHTML = msg;
}
ui.game.checkGameOver = function() {
if (board.gameOver(ui.board.position)) {
ui.game.setMessage("Game over!");
return true;
} else {
return false;
}
}
ui.game.checkMoveValid = function(move) {
var tmpBrd = board.create();
board.copy(ui.board.position, tmpBrd);
if (ui.game.dirs.indexOf(move) === -1) {
ui.game.setMessage("AI can't find move!");
return false;
}
if ( ! board.move[move].call(null, tmpBrd)) {
ui.game.setMessage("AI move '"+move+"' is ivalid!");
return false;
}
return true;
}
ui.game.prepareStep = function() {
ui.suggestion.clear();
ui.game.clearMessage();
if (ui.game.checkGameOver())
return false;
return true;
}
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.
ui.action = {};
ui.action.reset = function() {
ui.score.clear();
ui.suggestion.clear();
ui.game.clearMessage();
ui.board.turn = 0;
ui.board.position = board.create();
board.putRandom(ui.board.position);
ui.board.update(ui.board.position);
}
document.getElementById("reset").addEventListener("click", ui.action.reset, false);
for (var i = 0; i < ui.game.dirs.length; i++) {
var dir = ui.game.dirs[i];
ui.action[dir] = function(dir) {
return function(event) {
event.preventDefault(); event.stopPropagation();
if (ui.game.checkGameOver())
return;
ui.suggestion.clear();
var updated = board.move[dir](ui.board.position);
if (updated) {
ui.game.finishStep();
ui.ai.current && ui.ai.current.cleanup();
}
}
} (dir);
document.getElementById(dir).addEventListener("click", ui.action[dir], false);
}
document.body.addEventListener("keydown", function(event) {
if (document.activeElement.value)
return;
if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey)
return;
var key = event.keyCode || event.which;
var keyMoves = {38: "up", 40: "down", 37: "left", 39: "right"};
if (key in keyMoves) {
ui.action[keyMoves[key]](event);
}
}, false);
ui.swipe = {startX: 0, startY: 0, endX: 0, endY: 0};
ui.swipe.direction = function(x, y) {
var sensitivityThreshold = boardDom.width * boardDom.height / 10;
if (x*x + y*y < sensitivityThreshold)
return null;
if (Math.abs(x) < Math.abs(y)) {
if (y > 0)
return "down";
else
return "up";
} else {
if (x > 0)
return "right";
else
return "left";
}
}
boardDom.addEventListener("touchstart", function(ev) {
ui.swipe.startX = ev.touches[0].pageX;
ui.swipe.startY = ev.touches[0].pageY;
ev.preventDefault();
}, false);
boardDom.addEventListener("touchmove", function(ev) {
ui.swipe.endX = ev.touches[0].pageX;
ui.swipe.endY = ev.touches[0].pageY;
ev.preventDefault();
}, false);
boardDom.addEventListener("touchend", function(ev) {
var dir = ui.swipe.direction(ui.swipe.endX - ui.swipe.startX, ui.swipe.endY - ui.swipe.startY);
if (dir) {
ui.action[dir](event);
ev.preventDefault();
}
}, false);
document.getElementById("test").addEventListener("click", function() {
event.preventDefault(); event.stopPropagation();
board.move.replaceByBoardJS(ui.brdEngine);
}, false);
ui.action.suggest = function(event) {
event.preventDefault(); event.stopPropagation();
if ( ! ui.ai.current) {
ui.game.setMessage('Select AI!');
return;
}
if ( ! ui.game.prepareStep())
return;
var tmpBrd = board.create();
board.copy(ui.board.position, tmpBrd);
var move = ui.ai.current.analyse(tmpBrd);
ui.ai.current.cleanup();
if ( ! ui.game.checkMoveValid(move))
return;
ui.suggestion.set(move);
}
document.getElementById("suggest").addEventListener("click", ui.action.suggest, false);
ui.action.step = function() {
if ( ! ui.ai.current) {
ui.game.setMessage('Select AI!');
return;
}
if ( ! ui.game.prepareStep())
return;
var tmpBrd = board.create();
board.copy(ui.board.position, tmpBrd);
var move = ui.ai.current.analyse(tmpBrd);
ui.ai.current.cleanup();
if ( ! ui.game.checkMoveValid(move))
return;
board.move[move].call(null, ui.board.position);
ui.game.finishStep();
}
document.getElementById("step").addEventListener("click", ui.action.step, false);
ui.action.visualFinishAux = function() {
if ( ! ui.game.prepareStep())
return;
var brd = board.create();
board.copy(ui.board.position, brd);
var move = ui.ai.current.analyse(brd);
if ( ! ui.game.checkMoveValid(move))
return;
board.move[move].call(null, ui.board.position);
ui.game.finishStep();
setTimeout(ui.action.visualFinishAux, 10);
}
ui.action.visualFinish = function() {
if ( ! ui.ai.current) {
ui.game.setMessage('Select AI!');
return;
}
ui.action.visualFinishAux();
};
document.getElementById("visual-finish").addEventListener("click", ui.action.visualFinish, false);
ui.action.finish = function() {
var isProfile = document.getElementById("profile").checked;
if (isProfile)
console.profile();
if ( ! ui.ai.current) {
ui.game.setMessage('Select AI!');
return;
}
ui.game.prepareStep();
var step = 0;
var safeBdr = board.create();
var tsFrom = new Date().getTime();
while (!board.gameOver(ui.board.position)) {
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.board.turn += step;
ui.game.refresh();
ui.game.setMessage("Wrong move!");
return;
}
step++;
}
var tsTo = new Date().getTime();
ui.board.turn += step;
ui.game.refresh();
ui.score.speed(parseFloat((step*1000.0/(tsTo-tsFrom)).toPrecision(3)));
ui.game.setMessage("Game over!");
ui.ai.current.cleanup();
if (isProfile)
console.profileEnd();
}
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.prepareStep())
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;
}
step++;
}
var tsTo = new Date().getTime();
ui.board.turn += step;
ui.game.refresh();
ui.score.speed(parseFloat((step*1000.0/(tsTo-tsFrom)).toPrecision(3)));
ui.ai.current.cleanup();
}
document.getElementById("until").addEventListener("click", ui.action.until, false);
////////////////////////////////////////////////////////////////
// Register AIs.
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);
if (val === NaN || val < 0) {
ui.game.setMessage('' + optDoms[i].name + ' is not a positive number!');
cfg[optDoms[i].name] = 1;
continue;
}
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 = {
"ai-blind-random": function() {
return new ai.BlindRandom(ui.brdEngine);
},
"ai-always-up": function() {
return new ai.AlwaysUp(ui.brdEngine);
},
"ai-blind-weight-random": function(aiDom) {
var cfg = ui.ai.parseCfg(aiDom);
return new ai.BlindWeightRandom(ui.brdEngine, cfg);
},
"ai-blind-cycle": function(aiDom) {
var cfg = ui.ai.parseCfg(aiDom);
return new ai.BlindCycle(ui.brdEngine, cfg);
},
"ai-one-step-ahead": function(aiDom) {
var cfg = ui.ai.parseCfg(aiDom);
return new ai.OneStepAhead(ui.brdEngine, cfg);
},
"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);
return new ai.expectimax(ui.brdEngine, cfg);
},
"ai-survive": function(aiDom) {
var cfg = ui.ai.parseCfg(aiDom);
return new ai.survive(ui.brdEngine, cfg);
},
"ai-monte-carlo": function(aiDom) {
var cfg = ui.ai.parseCfg(aiDom);
return new ai.MonteCarlo(ui.brdEngine, cfg);
},
// "": function() {
// return new ai.(ui.brdEngine);
// },
};
ui.ai.domList = document.querySelectorAll('div.ai-list > div.ai');
for (var i = 0; i < ui.ai.domList.length; i++) {
ui.ai.domList[i].querySelectorAll('button.ai')[0].addEventListener("click", function (event) {
ui.ai.enable(event.target.parentNode);
}, false);
}
ui.ai.moveToTop = function(aiDom) {
for (var i = 0; i < ui.ai.domList.length; i++) {
ui.ai.domList[i].classList.remove('enabled');
}
var rootDom = aiDom.parentNode;
rootDom.removeChild(aiDom);
rootDom.insertBefore(aiDom, rootDom.firstChild);
aiDom.classList.add('enabled');
}
ui.ai.enable = function(aiDom) {
if (ui.ai.current)
ui.ai.current.cleanup();
ui.ai.moveToTop(aiDom);
ui.ai.current = ui.ai.algList[aiDom.id](aiDom);
ui.ai.currentName = aiDom.id;
}
////////////////////////////////////////////////////////////////
// Reports and statistic.
var reportsDom = document.getElementById('reports');
ui.report = {};
ui.report.statNo = 1;
ui.report.update = function(histo, brd, tsDelta, turn) {
var stat = board.score(brd);
var speed = turn * 1000 / tsDelta;
var score = stat.score;
var max = stat.max;
if ( ! histo[max]) {
var row = histo[max] = { n: 1 };
row.minScore = score; row.meanScore = score; row.maxScore = score;
row.minSpeed = speed; row.meanSpeed = speed; row.maxSpeed = speed;
row.minTurn = turn; row.meanTurn = turn; row.maxTurn = turn;
} else {
var row = histo[max];
row.n++;
row.minScore = Math.min(row.minScore, score);
row.meanScore += score;
row.maxScore = Math.max(row.maxScore, score);
row.minSpeed = Math.min(row.minSpeed, speed);
row.meanSpeed += speed;
row.maxSpeed = Math.max(row.maxSpeed, speed);
row.minTurn = Math.min(row.minTurn, turn);
row.meanTurn += turn;
row.maxTurn = Math.max(row.maxTurn, turn);
}
}
ui.report.fix = function(histo) {
for (var i in histo) {
var row = histo[i];
var n = row.n;
row.meanScore = row.meanScore / n;
row.meanTurn = row.meanTurn / n;
row.meanSpeed = row.meanSpeed / n;
}
}
ui.report.show = function(histo, gameCnt) {
var scoreChecked = document.getElementById('report-score').checked;
var turnChecked = document.getElementById('report-turn').checked;
var speedChecked = document.getElementById('report-speed').checked;
var reportDom = document.createElement('div');
reportDom.classList.add('option');
var h5Dom = document.createElement('h5');
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 = [];
for (var i = maxVals.length-1; i >= 0; i--) {
var maxVal = maxVals[i];
var row = histo[maxVals[i]];
var perc = parseFloat((100 * row.n / gameCnt).toPrecision(3));
var tblRow = [maxVal, row.n, perc];
if (scoreChecked) {
tblRow.push(row.minScore);
var meanScore = parseFloat(row.meanScore.toPrecision(3));
tblRow.push(meanScore);
tblRow.push(row.maxScore);
}
if (turnChecked) {
tblRow.push(row.minTurn);
tblRow.push(Math.floor(row.meanTurn));
tblRow.push(row.maxTurn);
}
if (speedChecked) {
tblRow.push(parseFloat(row.minSpeed.toPrecision(3)));
tblRow.push(parseFloat(row.meanSpeed.toPrecision(3)));
tblRow.push(parseFloat(row.maxSpeed.toPrecision(3)));
}
tbl.push(tblRow);
}
var tblCols = ['max\nval', 'n', '%'];
if (scoreChecked) {
tblCols.push('min\nscore');
tblCols.push('mean\nscore');
tblCols.push('max\nscore');
}
if (turnChecked) {
tblCols.push('min\nturn');
tblCols.push('mean\nturn');
tblCols.push('max\nturn');
}
if (speedChecked) {
tblCols.push('min\nspeed');
tblCols.push('mean\nspeed');
tblCols.push('max\nspeed');
}
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);
}
ui.report.start = function() {
ui.report.executed = true;
var histo = {};
var gameCntLimit = parseInt(document.getElementById('stat-count-limit').value);
if (isNaN(gameCntLimit) || !isFinite(gameCntLimit) || gameCntLimit < 1)
gameCntLimit = 100;
var tsLimit = parseFloat(document.getElementById('stat-count-limit').value);
if (isNaN(tsLimit) || !isFinite(tsLimit) || tsLimit < 1 || tsLimit > 60)
tsLimit = 1000 * 10;
else
tsLimit = 1000 * tsLimit;
var safeBdr = board.create();
function play() {
var turn = 0;
var brd = board.create();
board.putRandom(brd);
var tsFrom = new Date().getTime();
while (!board.gameOver(brd)) {
board.copy(brd, safeBdr);
var move = ui.ai.current.analyse(safeBdr);
if (ui.game.dirs.indexOf(move) < 0) {
ui.game.setMessage("I don't know how to move!");
throw Error("AI make illegal move '" + move + "'...");
}
var updated = board.move[move].call(null, brd);
if (!updated) {
ui.game.setMessage("Wrong move!");
throw Error("AI make illegal move '" + move + "'...");
}
board.putRandom(brd);
turn++;
}
var tsTo = new Date().getTime();
ui.report.update(histo, brd, tsTo-tsFrom, turn);
}
var gameCnt = 0;
var tsFrom = new Date().getTime(), tsFrame = tsFrom;
function batch() {
/* console.profile(); */
while (gameCnt++ < gameCntLimit && ui.report.executed) {
play();
var tsTo = new Date().getTime();
if (tsTo - tsFrame > 7000) {
tsFrame = tsTo;
break;
}
if (tsTo - tsFrom >= tsLimit)
break;
}
/* console.profileEnd(); */
if (tsTo - tsFrom >= tsLimit || gameCnt >= gameCntLimit || ! ui.report.executed) {
ui.report.fix(histo);
ui.report.show(histo, gameCnt);
} else {
setTimeout(batch, 10);
}
}
batch();
}
var statisticStartBtn = document.getElementById("statistic-start");
statisticStartBtn.addEventListener("click", ui.report.start, false);
ui.report.stop = function() {
ui.report.executed = false;
}
var statisticStopBtn = document.getElementById("statistic-stop");
statisticStopBtn.addEventListener("click", ui.report.stop, false);
ui.report.clean = function() {
while (reportsDom.hasChildNodes()) {
reportsDom.removeChild(reportsDom.lastChild);
}
}
var statisticCleanBtn = document.getElementById('statistic-clean');
statisticCleanBtn.addEventListener("click", ui.report.clean, false);
////////////////////////////////////////////////////////////////
// Initialise game.
if (localStorage.brdEngine) {
boardEngineDom.value = localStorage.brdEngine;
ui.brdEngine = window[localStorage.brdEngine];
} else {
ui.brdEngine = BoardArr2d;
}
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);
} catch (ex) { }
}
if (localStorage.savedBoard) {
try {
ui.board.position = JSON.parse(localStorage.savedBoard);
if (localStorage.savedTurn)
ui.board.turn = JSON.parse(localStorage.savedTurn);
else
ui.board.turn = 0;
ui.board.update(ui.board.position);
ui.score.update(ui.board.position, ui.board.turn);
ui.game.checkGameOver();
} catch (ex) { }
} else {
ui.action.reset();
}
</script>
</body>
</html>