PDA

Просмотр полной версии : [Статья] AutoIt пишем бота сами!


Chosen by god
21.06.2012, 12:27
Скачать ([Ссылки могут видеть только зарегистрированные и активированные пользователи])
VIRUSOTAL ([Ссылки могут видеть только зарегистрированные и активированные пользователи] 8a1dcd19027e16/analysis/)

Не так давно на Google+ появились игры. Прочитав топик об этом, я решил во что нибудь поиграть. Выбор пал на игру Diamond Dash. Через некоторое время игры программист во мне заговорил, что однотипные действия нужно автоматизировать. И вот что из этого вышло…
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
*Примечание: «руками» даже опытному игроку сложно набрать больше 400к
Раньше я никогда не сталкивался с задачами работы с экраном и мышкой. После непродолжительного гугления было решено для решения использовать язык макросов AutoIt.
Под катом вы найдете краткое описание игры, мой способ распознавания поля, алгоритм определения точки нажатия, и некоторое количество оптимизаций. А так же ссылку на github-репозиторий скрипта.
UPD Добавлено видео работы скрипта.
Краткое описание игры
Игра представляет из себя простую «кликни-на-область-больше-трех-квадратиков-одного-цвета» головоломку.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Есть поле 9 на 10, заполненное квадратиками 5 цветов. У нас есть одна минута на то, чтобы набрать максимальное количество очков. При нажатии на область из 3 или более одноцветных клеток, она исчезает, то что над ней проваливается, а сверху падает недостающее. Количество начисленных очков зависит от размера области: чем она больше — тем больше очков.
Кроме того, если делать клики быстро, и почти безошибочно, поле вокруг загорается, а каждое удаление(в данном случае взрыв), захватывает соседние с удаляемой областью клетки.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
И наконец последняя особенность: наверху есть шкала с алмазом в конце, которая заполняется по мере уничтожения клеток(и уменьшается при ошибочных кликах). Когда она заполняется, на поле в случайном месте появляется горящий алмаз. При клике на него время останавливается, а сверху поля падает огненный шар, уничтожая все клетки в строке и столбце, где находился камень.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Определение координат окна
Эта часть была добавлена в самую последнюю очередь, до этого координаты угла были жестко прописаны в коде. Используется функция из сторонней библиотеки ImageSearch для поиска сохраненного шаблона 10 на 10 пикселей. Судя по всему, фон слегка меняется от игры к игре, потому что не любой кусок подходил.
На форумах повсеместно не рекомендуют использование ImageSearch из-за долгого времени работы. Но так как нам нужно определить координаты только один раз в начале игры, провисаний по времени можно не опасаться.
Распознавание цветов и сохранение скриншотов
Для определения цвета пикселя по его координатам в AutoIt есть функция PixelGetColor. Но как показала практика, измерение всего 90 пикселей занимает полторы секунды, что, конечно, не очень хорошо. Но справедливости ради надо сказать, что бот написанный с использованием этой функции набирал 400-600 тысяч очков, а это больше чем может набрать человек, даже при большой сноровке.
На форумах был найден метод сохранения текущего состояния монитора в Bitmap, используя WinAPI. Кстати, этот битмап, при необходимости(например для дебага), можно сохранить в файл функцией _ScreenCapture. Далее смотрим цвета 90 пикселей, расположенных по решетке и по ним строим таблицу цветов квадратиков.
1. Func _GetField(ByRef $aiField) ; получение массива цветов поля
2.; получение BitMap-снимка экрана с помощью WinAPI
3. Local $hWnd = WinGetHandle("Игры Google+ - Google Chrome")
4. Local $Size = WinGetClientSize($hWnd)
5. Local $hDC = _WinAPI_GetDC($hWnd)
6. Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC)
7. Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $Size[0], $Size[1])
8. Local $hSv = _WinAPI_SelectObject($hMemDC, $hBitmap)
9. _WinAPI_BitBlt($hMemDC, 0, 0, $Size[0], $Size[1], $hDC, 0, 0, $SRCCOPY)
10. _WinAPI_SelectObject($hMemDC, $hSv)
11. _WinAPI_DeleteDC($hMemDC)
12. _WinAPI_ReleaseDC($hWnd, $hDC)
13. Local $L = $Size[0] * $Size[1]
14. Local $tBits = DllStructCreate('dword[' & $L & ']')
15. _WinAPI_GetBitmapBits($hBitmap, 4 * $L, DllStructGetPtr($tBits))
16. ; определение цветов клеток
17. For $iCol = 0 To $iNumCols - 1
18. For $iRow = $iNumRows - 1 to 0 Step -1
19. ; замер цвета квадратика
20. $iX = $iCornerX + ($iCol * 40) + $iDeltaX
21. $iY = $iCornerY + ($iRow * 40) + $iDeltaY
22. $iPixelColor = Mod(DllStructGetData($tBits, 1, $iY * $Size[0] + $iX),
23. 0x1000000)
24. $aiField[$iRow][$iCol] = _GetCheckColor($iPixelColor)
25. Next
26. Next
27. ; удаление данных для избежаня утечки памяти
28._WinAPI_DeleteObject($hBitmap)
29._WinAPI_DeleteObject($hMemDC)
30._WinAPI_DeleteObject($tBits)
31. EndFunc
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Тут стоит оговориться, почему замер происходит всего по 1 точке. Этот метод был испробован мной в первую очередь, и остался в финальной версии. Между этими двумя моментами было испробовано довольно большое количество альтернативных способов, среди которых были: замер 64 точек на каждый квадратик(решетка 8 на 8) и различные усреднения полученных значений, случайный выбор координат для замера, хранение истории нескольких последних замеров для лучшей точности… Но все они оказались менее точными или удобными, чем самый первый способ.
Возможно, что так как я весьма далек от темы распознавания изображений, я не знаю чего-то простого, способного помочь мне в этом вопросе. В таком случае буду рад любым предложениям. =)
Определение одноцветной области по таблице цветов
Ну, и, наконец, осталось найти подходящее место и кликнуть туда. Для этого обходим поле снизу вверх (потому что все падает вниз, а значит снизу менее пусто чем сверху), и проверяем одноцветные части. Я делал это с помощью алгоритма безрекурсивного поиска в глубину (DFS). Вкратце суть такова: кладем в стек стартовую клетку, а дальше, пока стек не пуст, достаем из него текущую клетку и обходим ее соседей, при совпадении цвета кладем в стек. Ну да что говорить, код понятней. =)
Оптимизации

Оптимизация 1. Алмазики
Написанного выше уже хватало для получения миллиона. Но мне хотелось больше. Самым большим недостатком вышеописанного алгоритма было, пожалуй, неумение определять алмазы. Поэтому под конец минуты поле выглядело примерно так:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
А между тем, алмазы — очень полезная вещь, потому что пока падает огненный шар, таймер останавливается, а квадратики падают. А значит пробелы заполняются, и ошибок будет меньше.
Для определения алмазов для начала пришлось поиграть с координатами замеров, чтобы цветные клетки определялись корректно, а алмазы — не попадали ни под один из их цветов. После этого создаем массив $aiDiams размером 3 (будем проверять только нижние 3 строки, потому что все алмазы рано или поздно туда упадут) * ширину (в нашем случае — 10). При каждом замере просматриваем нижние 3 строки, и если ячейка определилась, то обнуляем соответствующее место в $aiDiams, иначе инкрементируем его. Таким образом для клеток с алмазиками значения будут велики. При накоплении некоего порога, кликаем.
Оптимизация 2. Over Explosion
Тут надо объяснить, почему очень важно уменьшить количество ошибок, и почему в моем скрипте между соседними снимками экрана стоит задержка в 1/10 секунды. Дело в том, что когда поле загорается и ячейки начинают взрываться, количество очков многократно возрастает. Но если слишком много ошибаться, поле перестает загораться. Поэтому минимизация ошибок не менее важная часть, чем оптимизация времени распознавания (а учитывая запас времени, вообще единственно важная).
Несмотря на задержку в 1/10 секунды между соседними снимками экрана, некоторые ячейки все равно не успевают упасть, и определяются не на своих местах. Чтобы уменьшить их количество, была введена проверка на взрыв. При взрыве в квадратике появляется ореол почти чисто-белого цвета (#fffefc если быть точным), а это легко определить. Все клетки над взрывом, не мудрствуя лукаво, можно проставить как неопределенные.
Оптимизация 3. Область последнего клика
Защита от повторной ошибки, в эффективности которой я не уверен. Дело в том, что в самой игре при ошибке клетка становится серого цвета, а серый цвет алгоритмом определяется как неопределенный (тавтология получилась =) ). Но хуже эта проверка точно сделать не может, поэтому пусть живет.
Суть в том, что при каждом клике сохраняем область, по которой кликаем, и при следующем клике не трогаем ее.
Итог
После всего вышеописанного, мой рекорд стал примерно таким:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

leproroot
01.07.2012, 01:57
Источник ([Ссылки могут видеть только зарегистрированные и активированные пользователи])