Вместо предисловия

Сегодня я получил ссылочку на статью на хабре о технологии создания «макроса-бота для браузерной игры». Там же было написано с сожалением, что AutoIT мало представлен на хабре. Со штуками, описанными в статье я баловался год-два назад. В последнее время использую библиотеку IE.au3, которая позволяет творить с браузерными игрушками просто чудеса.Собственно информацией об этом и хотел бы поделиться. Только сразу предупреждаю — речь идет только работе под MS-Internet Explorer. Фанатам других браузеров скажу сразу — можно тоже самое делать наверное под любым браузером, только нужно искать соответствующую библиотеку и как они работают я сказать не могу. Скажу только о библиотеке IE.au3 — она входит в комплект стандартной установки AutoIT-а, достаточно хорошо протестирована, снабжена комментариями и примерами, описанные в ней функции удобно подсвечиваются и предлагаются к завершению при наборе с соответствующими подсказками, как стандартные функции пакета (или как там назвать этот самый AutoIT).

Выбор жертвы

В качестве примера работы AutoIT-а с браузерными играми предлагаю игру «Моя деревня» (оригинальное название игры “My Free Farm”) — http://www.mojaderewnja.ru/.Игра реально напрашивается на автоматизацию, потому что заставляет выполнять слишком много рутинных действий. Например, если Вы не будите платить в проект около 180 рублей ежемесячно, то посадку и поливку растений Вам нужно будет делать вручную. А это обычно 120 кликов по полю, только чтобы посадить растения, затем 120 кликов чтобы полить. Полей может быть не один и не два, а до десятка. А некоторые растения растут минут 10, так что такие кликанья либо отбивают желание играть либ�� заставляют платить за казуальную игрушку ежемесячно сумму, за которую можно иметь месяц скромного интернета или хорошее кабельное телевидение. Так что автоматизировать «Мою деревню» — это просто то, что нам нужно.Выглядит поле для обтыкивания так:
image

Вариант №1 — примитивный вариант

Ищем на экране куда тыкать, и тыкаем «мышей» куда надо. Сразу скажу – это вариант неправильный, но для общей эрудиции я приведу текст скрипта с пояснениями.Кусок №1: ищем на экране «точку отсчета», за которую можно будет зацепиться и от неё считать местоположение квадратиков куда будем обтыкивать поле.
Func GetTopLeftCorner($window)
 Global $x = 0, $y = 0
 WinActivate($window, "")
 If WinActive($window) Then
  $size = WinGetPos("[active]")
  For $i = 1 To $size[3]
   If CheckRGB(PixelGetColor($x + $i, $y + $i), Dec("402215"), Dec("5A352A")) Then
    If CheckRGB(PixelGetColor($x + $i - 259, $y + $i), Dec("331A0D"), Dec("624232")) Then
     For $j = $i To 1 Step -1
      If not CheckRGB(PixelGetColor($i + 1, $j), Dec("000000"), Dec("3A301D")) Then
       $x = $i
       $y = $j
       ExitLoop(2)
      EndIf
     Next
     ExitLoop
    EndIf
   EndIf
  Next
 EndIf
EndFunc
Функция проверяет цвета точек, пробегая по диагонали, ищет левый край окна игры в браузере. Затем просматривает цвета точек, пробегая вверх, ищет верхнюю границу окна игры. Найденную точку я буду считать «точкой отсчета». Расстояние от неё до каждой клетки на поле игры фиксированное и легко вычисляется по индексу.Результат работы функции возвращается в глобальных переменных $x и $y, которые соответствуют координатам точки по горизонтали и вертикали соответственно.При вызове функции в качестве параметра нужно указать заголовок окна программы. Его можно задать вручную, определив предварительно с помощью утилиты AutoIt Window Info, которая устанавливается вместе с AutoIt-ом.Приведенный выше скрипт использует самодельную функцию CheckRGB:
Func CheckRGB($color, $min, $max)
 Local $rgb[3][3]
 $res = True
 $rgb[2][0] = BitAND($min, Dec("FF0000")) / Dec("10000")
 $rgb[1][0] = BitAND($min, Dec("00FF00")) / Dec("100")
 $rgb[0][0] = BitAND($min, Dec("0000FF"))
 $rgb[2][1] = BitAND($color, Dec("FF0000")) / Dec("10000")
 $rgb[1][1] = BitAND($color, Dec("00FF00")) / Dec("100")
 $rgb[0][1] = BitAND($color, Dec("0000FF"))
 $rgb[2][2] = BitAND($max, Dec("FF0000")) / Dec("10000")
 $rgb[1][2] = BitAND($max, Dec("00FF00")) / Dec("100")
 $rgb[0][2] = BitAND($max, Dec("0000FF"))
 For $i = 0 To 2
  If $rgb[$i][0] > $rgb[$i][1] Or $rgb[$i][1] > $rgb[$i][2] Then
   $res = False
   ExitLoop
  EndIf
 Next
 Return $res
EndFunc
— Функция проверяет цвет точки, передаваемой в качестве первого параметра $color на то, чтобы она находилась в цветовом диапазоне от $min до $max, с учетом трех составляющих цвета точки (RGB). Возвращает, соответственно True или False.

Непосредственно процедура обтыкивания всех клеток на поле выглядит так:
For $i = 1 To 12
 For $j = 1 To 10
  MouseClick("left", $x + ($i * 40) + 103, $y + ($j * 40) + 172, 1, 1)
  Sleep(25)
 Next
Next
Тут все понятно. 10 и 12 в заголовках цикла это размер поля в клетках. Константы в параметрах MouseClick вычислены с помощью той же утилиты AutoIt Window Info, относительно определенной ранее «точки отсчета». Значение задержки в Sleep можно менять по своему желанию – это будет влиять на скорость обтыкивания, но слишком быстрое может приводить к ошибкам выполнения скрипта игры.

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

Вариант №2 — правильный вариант

Мы не используем указатель мыши вообще. Вместо этого, мы тыкаем на нужном объекте, загруженном в браузере IE по его ID. (ID этих клеток можно посмотреть в HTML коде загруженной страницы, они имеют названия f1-f120 соотвественно).
Local $i, $j, $n
Local $oIE = _IEAttach($gamewindowname)
For $i = 1 To 12
 For $j = 0 To 9
  $obj = _IEGetObjById($oIE, "f" & (12 * $j + $i))
  If $obj <> 0 Then $obj.click()
 Next
Next


Вариант №3 — самый правильный вариант

Недостатком предыдущего варианта является то, что когда нам нужно обтыкать поле, нам нужно переключиться из игры и запустить скрипт. Это неудобно. По-настоящему удобный скрипт не должен заставлять выполнять дополнительные действия в целях экономии на других рутинных операциях. Поэтому, я просто заменяю стандартное действие в игре «посадить одну семачку» на «засадить все поле выбранными семенами». Выглядит этот так:
$obj = _IEGetObjById($oIE, "anpflanzen")
If $obj <> 0  Then _
 $obj.outerHTML = "… тут кусок HTML кода, который для удобства привожу отдельно “
Вот этот кусок HTML-кода для вставки в предыдущий фрагмент:
<SPAN id=anpflanzen class=link onclick="
 selectMode(0,true,selected);
 var i, j, s, n = 0;
 for (i = 1; i < 13; i = i + rackElement[selected].x)
  for (j = 0; j < 10; j = j + rackElement[selected].y) {
   s = 12 * j + i; cache_me(" & $zone & ", s, garten_prod[s], garten_kategorie[s], n++);}">

<IMG onmouseover="this.src='http://mff.wavecdn.de/mff/garden_menue_seed.gif'; showDiv('tooltipseed')" onmouseout="this.src='http://mff.wavecdn.de/mff/leer.gif'; hideDiv('tooltipseed')" src="http://mff.wavecdn.de/mff/leer.gif" width=53 height=68>
<DIV style="DISPLAY: none" id=tooltipseed class=blackbox onmouseover="showDiv('tooltipseed')" onmouseout="hideDiv('tooltipseed')" src="http://mff.wavecdn.de/mff/leer.gif">посадить всё</DIV></SPAN>
Сам код и скрипты на JavaScript позаимствованы из оригинального HTML кода игры и подгружаемого вместе с ней файла скриптов, и немного переделаны с полива на посадку.