Казуальный бот для казуальной игры

в 14:36, , рубрики: bot, игры, искусственный интеллект, искуственный интеллект, Песочница, метки: , ,

Казуальный бот для казуальной игрыОценивая новый дизайн google+, случайно наткнулся на простенькую флеш игру — нажимаем кубики одинакового цвета, они лопаются, и мы получаем очки и profit. Но, вместо того, чтобы самому кликать по кубикам, я решил написать бота, который будет делать все это за меня.

Бот для этой многострадальной игры уже существует (и не один), но мне захотелось сделать не отслеживание протоколов передачи, ковыряние чужого и внедрение своего кода, а попробовать реализовать распознавание образов и принятие на их основе решений, то есть некую эмуляцию действий реального игрока. Никакого матана, исключительно for fun. Реализация на .NET под хаброкатом.

Блоки в игре являются статичным (не анимированными) спрайтами, причем с жестким позиционированием. Проще всего было бы отмерить по пикселям положение каждого блока, а затем определять их по созданному вручную шаблону. Но так будет не интересно и скучно, тем более, такой метод будет жестко привязан конкретно к этой игре. Поэтому, я решил сделать бота, который будет (отчасти) сам определять положение игрового поля и элементов на нем.

Казуальный бот для казуальной игры

Итак, в первую очередь нужно выделить все возможные элементы. Всего их получилось 5 штук*. Вырезаем в паинте, сохраняем в папку. Далее, нужно определить ширину и высоту игрового поля, количество помещающихся на нем элементов по горизонтали и вертикали.

Казуальный бот для казуальной игры

Эти элементы и нужно искать на экране. Практически, поле начинается там, где впервые встретиться хоть один из них. Определять положение поля нужно всего один раз — в начале игры, поэтому нет причин излишне задумываться о скорости работы этой части. Попиксельно сравнивать слишком долго, да и такая точность нам ни к чему, поэтому мы выбираем наиболее отличные друг от друга точки в спрайтах элементов (у меня получились где-то в середине). Затем получаем скриншот экрана и ищем на нем этот пиксель. Если пиксель найден, то проверяем, принадлежит ли найденный пиксель искомому элементу.

Казуальный бот для казуальной игры

Первый же найденный элемент и будет означать начальную границу игрового поля. Дабы укоротить поиск этого элемента, снимаем не весь экран, а предоставляем пользователю определить примерную область игрового поля (я использовал для этого главную форму с прозрачностью).

Дальше все еще проще, разбиваем поле на кусочки, каждому из которых нам нужно сопоставить имеющийся блок. Для этого, мы определяем «средний» цвет каждого блока и средний цвет «куска» поля и сравниваем их**.

Код C#

//"Средний" цвет
//detail - обратное качеству получаемого цвета.
private Color getMidColor(Bitmap inpBit, int x, int y, int w, int h, int detail)
        {
            double r = 0, g = 0, b = 0;
            for (var ii = x; ii < x + w; ii += detail)
                for (var jj = y; jj < y + h; jj += detail)
                {
                    var pix = inpBit.GetPixel(ii, jj);
                    r += pix.R;
                    g += pix.G;
                    b += pix.B;
                }

            r /= (w * h / detail / detail);// :-(
            g /= (w * h / detail / detail);
            b /= (w * h / detail / detail);
            
            return Color.FromArgb((int)r, (int)g, (int)b);
        }

//Сравнение цветов
//Поиск минимально отличающегося
//blcColors - массив "средних" цветов элементов.

private int colorCompare(Color c)
        {
            int max=255;
            int cur = -1;
            int imax;

            for(var i=0;i<blcColors.Count;i++)
            {
                imax=Math.Abs(c.R - blcColors[i].R) + Math.Abs(c.G - blcColors[i].G) + Math.Abs(c.B - blcColors[i].B) / 3;
                if (imax < max)
                {
                    max=imax;
                    cur = i;
                }
            }

            if (max > 50)//Максимальная ошибка в цвете
                return -1;//Будем проверять картинку еще раз

            return cur;
        }

После чего создаем матрицу блоков и ищем на ней самый большой кусок соприкасающихся блоков (глубина). Задачка тривиальная, немного похожа на поиск выхода из лабиринта.

//.....
            for (var i = 0; i < 10; i++)
                for (var j = 0; j < 9; j++)
                {
                    if (nots[i, j])//Если блок не просмотрен
                        count[i, j] = 
//То определяем его "глубину"
                            searching(i, j, 0);
//Самый "глубокий" блок будет на выходе
                    if(count[i, j]>max)
                    {
                        x=i;
                        y=j;
                        max = count[i, j];
                    }
                }
//.....

//Рекурсивный метод для поиска глубины
        private int searching(int i, int j, int c)
        {
            nots[i, j] = false;

//Много проверок в пирамидку
            for (var di = -1; di <= 1; di++)
                for (var dj = -1; dj <= 1; dj++)
                    if (di+i >= 0 && di+i < 10 && dj+j >= 0 && dj+j < 9) //Не выходит за границы?
                        if (Math.Abs(di) != Math.Abs(dj)) //Блоки не диагонально относительно друг друга?
                            if (cube[i, j] == cube[i + di, j + dj]) //Блоки одинаковые?
                                if (nots[i + di, j + dj]) //Не просмотрен?
                                {
                                    c = searching(i + di, j + dj, c+1); // Ищем дальше!                        
                                }
            return c;
        }

    }

Код оболочки и формы, наверное, писать смысла нет: если нашли игровое поле на экране, то запускаем таймер (например, на четверть секунды). При срабатывании таймера определяем наиболее «глубокий» блок и нажимаем на него на экране.

Пример работы бота. Причем, хоть на распознавание блоков и на принятие решения в сумме программа тратит 15-20 мс, толку от этого не много, т.к. на моей форточке флешка и так выдает скудные 10-20 fps, а со снятием видео так и вовсе слайд-шоу. Максимум мой бот смог набить 300к.

____
* На самом деле их оказалось 6. Я об этом узнал только после того, как дописал бота, т.к. когда играл сам (плохо), этот блок не появлялся.
** Можно было сделать и разбиение на 2, 4 и более блоков, но тут это было лишним.

Автор: mamap

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js