05 Apr 2014

2048 game bot

Category howto

Есть такая игра 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);

Comments