2048 game bot
Есть такая игра 2048, суть ее в том чтобы собрать квадрат 2048 из других меньших квадратов, складываются квадраты только с одинаковой цифрой 2+2, 4+4, 8+8 и т.д
Игра написана на Javascript и HTML, а это значит можно попробовать написать бота. Открыв инспектор я сразу посмотрел на структуру этих квадратов, все оказалось очень просто об этом ниже под катом.
<div class="tile tile-8 tile-position-3-3"><div class="tile-inner">8</div></div>
как видно это это обычный div с классами tile-8 например означало что это “8”, чтобы распарсить все квадраты достаточно было сделать следующее:
var tiles = document.getElementsByClassName('tile');
После чего получив все элементы div с классом tile можно было легко с ними работать. Заморчиватся с умным алгоритмом нехотелось поэтому простой рандом из 4 типов ходов (влево, вверх, вправо, вниз) вполне подходил.
Чтобы реализовать эмуляцию нажатия кнопки в браузере нужно сделать следюющее
function keyDown(evt)
{
var key;
if(!evt)
{
evt = window.event;
if(!evt.which)
{
key = evt.keyCode;
}
}
else if(evt)
{
key = evt.which;
}
}
function fireKey(el, key)
{
if(document.createEventObject)
{
var eventObj = document.createEventObject();
eventObj.keyCode = key;
el.fireEvent("onkeydown", eventObj);
}
else if(document.createEvent)
{
var eventObj = document.createEvent("Events");
eventObj.initEvent("keydown", true, true);
eventObj.which = key;
el.dispatchEvent(eventObj);
}
}
fireKey(document.getElementByClassName('game-container')[0], 40);
Нужно создать обьект события нажатия кнопки, и присовать нужные свойства, в данном случае это keycode нужной кнопки. В параметрах которые передаются в метод fireKey разберетесь сами.
Еще один момент, когда просиходит проигрышь, надо перезапускать игру, для этого надо отслеживать специальный div который становится видимым, и после этого надо нажимать кнопку New Game. Для этого надо просто отследить его свойство display.
На этом собствтенно все, в коде заложена возможность сделать бота более интелектуальным, можете попробовать сами.
Github: https://github.com/noroot/2048-bot
Результат работы на видео:
Ну и для удобства код целиком здесь, чтобы его запустить надо скопировать, открыть игру и открыть консоль, и вставить туда этот код целиком, он сразу начнет работать.
/**
* 2048 game bot
* usage: startgame and paste code to browser development console
*/
function explode( delimiter, string ) { // Split a string by string
//
// + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: kenneth
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
var emptyArray = { 0: '' };
if ( arguments.length != 2
|| typeof arguments[0] == 'undefined'
|| typeof arguments[1] == 'undefined' )
{
return null;
}
if ( delimiter === ''
|| delimiter === false
|| delimiter === null )
{
return false;
}
if ( typeof delimiter == 'function'
|| typeof delimiter == 'object'
|| typeof string == 'function'
|| typeof string == 'object' )
{
return emptyArray;
}
if ( delimiter === true ) {
delimiter = '1';
}
return string.toString().split ( delimiter.toString() );
}
function keyDown(evt)
{
var key;
if(!evt)
{
evt = window.event;
if(!evt.which)
{
key = evt.keyCode;
}
}
else if(evt)
{
key = evt.which;
}
}
function fireKey(el, key)
{
if(document.createEventObject)
{
var eventObj = document.createEventObject();
eventObj.keyCode = key;
el.fireEvent("onkeydown", eventObj);
}
else if(document.createEvent)
{
var eventObj = document.createEvent("Events");
eventObj.initEvent("keydown", true, true);
eventObj.which = key;
el.dispatchEvent(eventObj);
}
}
function logscore()
{
var tiles = [128, 256, 512, 1024, 2048];
var arlen = tiles.length;
for(var i = 0; i <= arlen;i++)
{
score = tiles[i];
if (document.getElementsByClassName("tile-" + score).length != 0)
{
console.log("MAX SCORE=" + score);
}
else
{
console.log("FAIL");
}
}
}
// define key codes
var left = 37;
var up = 38;
var right = 39;
var down = 40;
var clicks = 0;
var tries = 0;
document.documentElement.focus();
document.onkeydown = keyDown;
//var fired = 0;
var matrix_object = [];
var matrix = [];
var selected_turn = 0;
var prefered_turn = 0;
var gamebox = document.getElementsByClassName("game-container")[0];
function calculate_turn()
{
var fired = 0;
var arr = document.getElementsByClassName('tile');
selected_turn = 0;
prefered_turn = 0;
var turns = [];
matrix_object = [];
// prepare object for more smart solving (not used in future)
for(var i=0; i <= arr.length; i++)
{
var el = arr[i];
if (el)
{
var t = explode("-", el.classList[1]);
var tilescore = t[1];
var t = explode("-", el.classList[2]);
var tp = {'score': tilescore, 'x':t[2], 'y':t[3]};
if (tp.x!=='undefined')
{
matrix_object.push(tp);
}
}
}
// TODO:
// make clever work here
// until clever work done just go random
if (prefered_turn == 0 || selected_turn == 0)
{
var keys = [up,right,down]; // tricky turns, without left turn in case of win
var key = keys[Math.floor(Math.random() * keys.length)];
selected_turn = key;
}
return selected_turn;
}
function make_turn()
{
if (document.getElementsByClassName("game-over").length > 0)
{
console.clear();
logscore();
tries++;
console.log("Restart (" + tries + ", "+clicks+")");
clicks = 0;
document.getElementsByClassName("restart-button")[0].click();
}
else if (document.getElementsByClassName("game-won").length > 0)
{
clearInterval(interval);
console.log("win");
}
else
{
clicks++;
var turn = calculate_turn();
fireKey(gamebox, turn);
}
}
var interval = setInterval(make_turn, 500);
console.log(matrix_object);