Пишем бот для рыбалки в игре Albion Online на языке Python
Всем привет, я являюсь счастливым пользователем операционной системы GNU/Linux.И как многим известно, игрушек идущих на линукс без дополнительных танцев с бубном намного меньше чем в «Винде».
И еще меньше игр в жанре MMORPG.
Однако, где-то пол года или год назад я узнал что под линукс портировали игру Albion Online.
Игра очень занимательная, однако занимает достаточно большое количество времени. И дабы не тратить свои драгоценные часы жизни по напрасну, я решил написать бота. Который будет фармить мне ресурсы, пока я буду заниматься своими делами.
В игре есть много видов ремесла, можно рубить лес, камни копать, даже выращивать огороды, однако мой выбор был сделан в пользу рыбалки.
Итак, суть рыбалки в альбион онлайн проста, берете удочку, подходите к водоему, зажимаете кнопку, дабы закинуть наживку, ждете в течении определённого времени поклевки.
Когда клюет, вы должны опять нажать на кнопки и сыграть в мини-игру.
Тут потребуется попеременно то тянуть, то не тянуть удочку, в зависимости от движений поплавка. Поплавок движется всегда случайно, с разными последовательностями и скоростями. И если вы все сделаете верно и поплавок не выйдет за пределы допустимой зоны, то вы выловите рыбу.
Итак, все это дело мы будем автоматизировать. По средствам компьютерного языка python. Начать я решил с самого сложного, а именно с момента, где начинается игра с поплавком. Тут снова мне на выручку пришла моя любимая библиотека с компьютерным зрением OpenCV. Запустив ее мы можем обнаруживать объекты к примеру на картинках. Однако сама библиотека не знает что именно нам нужно обнаружить. Конечно существуют множество шаблонов где представлены различные предметы для их определения. Однако, там точно нет поплавков для Albion Online.
Зато в данной библиотеке есть замечательная функция поиска по заданному шаблону. И в качестве шаблона я просто взял скриншот нашего поплавка.
И как мы видим все прекрасно нашлось на картинке.
А разница между картинкой и потоковым видео не велика, ведь по сути это просто поток картинок с очень быстрой скоростью, и несколько строчек кода. И вот мы уже можем находить поплавок на потоковом видео.
Идем далее. Сам поплавок двигается туда-сюда и мы также должны его двигать, нажимая кнопку на мышке.
А посему, нам нужны его координаты. И для этого нас выручают вот эти строчки.
Затем мы просто воспользуемся библиотекой PyAutoGUI, которая будет зажимать кнопку мыши и отжимать ее с определенной периодичностью.
И сама мини игра успешно выигрывается.
Засунем все это дело в функцию и пока оставим.
Затем вернемся к изначальной части, где мы должны следить за поплавком.
Тут все несколько иначе, допустим мы можем определить, куда закинется поплавок, и попробовать анализировать ту часть экрана на наличие поплавка. Однако поплавок закинутый в воду предстает под разными углами и постоянно колышется.
В таком случае мы возьмём тот несколько иной метод. Суть его в том, что мы анализируем, скажем так разницу в пикселях внутри отслеживаемого фрагмента. Которая, если поплавка нет приближается к нулю.
В итоге было найдено оптимальное значение при котором при исчезновении поплавка, мы можем производить действия.
И мы их производим, а именно нажимаем на кнопку мыши. Также засовываем это в функцию.
Ну и наконец в заключении мы просто записываем скрипт где в бесконечном цикле мы забрасываем удочку и выполняем попеременно первую и вторую функцию.
Вот полная видео инструкция и пример работы данного бота:
Есть правда некоторые нюансы в том что там надо шаманить с цифрами, так как эти цифры подобраны под мой монитор, локацию, персонажа и экипировку. Но я думаю для грамотного питониста это не составит проблем.
Те кто любит сказать, что это бесполезная трата времени, я замечу, что это лишь инструмент, эти библиотеки вы можете применить и в других проектах. Само компьютерное зрение применяется как при наведении баллистических ракет, так и в программах для помощи инвалидам. Куда вы решите их применить, только ваше желание.
Работа бота: как симулировать нажатия клавиш в Python
Сегодня день рождения у Гвидо ван Россума, создателя Python. Поздравляем! Самое время поговорить о его детище и затронуть животрепещущую тему: как в Питоне имитировать нажатия клавиш при создании ботов. Суть бота — автоматическое выполнение действий, на которые не хочет тратить время человек. У программистов Python есть инструменты, позволяющие имитировать события клавиатуры и реагировать на них. Подборку таких инструментов вы и найдете ниже.
Зачем и кому это нужно
Поводы симулировать нажатие клавиш:
автоматизация — создание ботов для сайтов, приложений и игр. Пример: бот для автоматического заполнения форм или кормления/лечения игровых персонажей;
тестирование десктопных приложений — перебор возможных действий пользователя и реакций программы;
доступность — адаптация интерфейса для людей с ограниченными возможностями здоровья, в том числе за счёт эмуляции мыши (ведь без зрения ею не воспользуешься);
оптимизация — переназначение стандартных клавиш и/или замена недостающих кнопок физической клавиатуры.
Инструменты питониста для разных платформ
Quartz и AppKit — на Mac,
pywin32, pyHook — в Windows.
Для Windows также стоит отметить:
pyautoit — это Python API к средству автоматизации AutoIt;
pywinauto — набор модулей, который автоматизирует взаимодействие с Microsoft Windows GUI. Библиотека написана на чистом Python — простейшие её функции по обработке событий мыши и клавиатуры работают и в Linux. Но более сложные возможности для работы с текстом заточены именно под Windows и доступны только там.
Актуальные версии этих инструментов поддерживают Python 3, более ранние — Python 2.
Как это работает: примеры
В директории examples каталога pywinauto на GitHub собраны два десятка наглядных примеров по использованию библиотеки. С их помощью вы можете прямо сейчас создавать ботов, которые умеют устанавливать/удалять программы, скачивать web-страницы, перетаскивать файлы между окнами, пакетно обрабатывать изображения в MS Paint и решать другие задачи.
Новички оценят читаемость кода:
Хорошо? Представьте, что вам нужно перенастроить приложение из дефолтного состояния на N-ном количестве компьютеров — выбрать нужные пункты меню, заполнить нужные поля. Или вы тестируете свою программу вдоль и поперёк, чтобы выявить сбои при обращении к GUI. Скрипт, который вам потребуется, будет длиннее, чем в примере выше, но по сути усложнится незначительно.
Добавьте сюда возможность работать с любыми приложениями на Win32 GUI и виджетами под интерфейс MS UI Automation.
Сегодня день рождения у Гвидо ван Россума, создателя Python. Поздравляем! Самое время поговорить о его детище и затронуть животрепещущую тему: как в Питоне имитировать нажатия клавиш при создании ботов. Суть бота — автоматическое выполнение действий, на которые не хочет тратить время человек. У программистов Python есть инструменты, позволяющие имитировать события клавиатуры и реагировать на них. Подборку таких инструментов вы и найдете ниже.
Зачем и кому это нужно
Поводы симулировать нажатие клавиш:
автоматизация — создание ботов для сайтов, приложений и игр. Пример: бот для автоматического заполнения форм или кормления/лечения игровых персонажей;
тестирование десктопных приложений — перебор возможных действий пользователя и реакций программы;
доступность — адаптация интерфейса для людей с ограниченными возможностями здоровья, в том числе за счёт эмуляции мыши (ведь без зрения ею не воспользуешься);
оптимизация — переназначение стандартных клавиш и/или замена недостающих кнопок физической клавиатуры.
Инструменты питониста для разных платформ
Quartz и AppKit — на Mac,
pywin32, pyHook — в Windows.
Для Windows также стоит отметить:
pyautoit — это Python API к средству автоматизации AutoIt;
pywinauto — набор модулей, который автоматизирует взаимодействие с Microsoft Windows GUI. Библиотека написана на чистом Python — простейшие её функции по обработке событий мыши и клавиатуры работают и в Linux. Но более сложные возможности для работы с текстом заточены именно под Windows и доступны только там.
Актуальные версии этих инструментов поддерживают Python 3, более ранние — Python 2.
Как это работает: примеры
В директории examples каталога pywinauto на GitHub собраны два десятка наглядных примеров по использованию библиотеки. С их помощью вы можете прямо сейчас создавать ботов, которые умеют устанавливать/удалять программы, скачивать web-страницы, перетаскивать файлы между окнами, пакетно обрабатывать изображения в MS Paint и решать другие задачи.
Новички оценят читаемость кода:
Хорошо? Представьте, что вам нужно перенастроить приложение из дефолтного состояния на N-ном количестве компьютеров — выбрать нужные пункты меню, заполнить нужные поля. Или вы тестируете свою программу вдоль и поперёк, чтобы выявить сбои при обращении к GUI. Скрипт, который вам потребуется, будет длиннее, чем в примере выше, но по сути усложнится незначительно.
Добавьте сюда возможность работать с любыми приложениями на Win32 GUI и виджетами под интерфейс MS UI Automation.
Пишем бот для пазл игры на Python
Давно хотел попробовать свои силы в компьютерном зрении и вот этот момент настал. Интереснее обучаться на играх, поэтому тренироваться будем на боте. В статье я попытаюсь подробно расписать процесс автоматизации игры при помощи связки Python + OpenCV.
Ищем цель
Идем на тематический сайт miniclip.com и ищем цель. Выбор пал на цветовую головоломку Coloruid 2 раздела Puzzles, в которой нам необходимо заполнить круглое игровое поле одним цветом за заданное количество ходов.
Выбранным внизу экрана цветом заливается произвольная область, при этом соседние области одного цвета сливаются в единую.
Подготовка
Использовать будем Python. Бот создан исключительно в образовательных целях. Статья рассчитана на новичков в компьютером зрении, каким я сам и являюсь.
Для работы бота нам понадобятся следующие модули:
Управление браузером
Мы имеем дело с онлайн-игрой, поэтому для начала организуем взаимодействие с браузером. Для этой цели будем использовать Selenium, который предоставит нам API для управления FireFox. Изучаем код страницы игры. Пазл представляет из себя canvas, которая в свою очередь располагается в iframe.
Ожидаем загрузки фрейма с и переключаем контекст драйвера на него. Затем ждем canvas. Она единственная во фрейме и доступна по XPath /html/body/canvas.
Далее наша канва будет доступна через свойство self.__canvas. Вся логика работы с браузером сводится к получению скриншота canvas и клику по ней в заданной координате.
Полный код Browser.py:
Состояния игры
Приступим к самой игре. Вся логика бота будет реализована в классе Robot. Разделим игровой процесс на 7 состояний и назначим им методы для их обработки. Выделим отдельно обучающий уровень. Он содержит большой белый курсор, указывающий куда нажимать, который не позволит корректно распознавать элементы игры.
Для большей стабильности бота будем проверять, успешно ли произошла смена игрового состояния. Если self.state_next_success_condition не вернет True за время self.state_timeout — продолжаем обрабатывать текущее состояние, иначе переключаемся на self.state_next. Также переведем скриншот, полученный от Selenium, в понятный для OpenCV формат.
Реализуем проверку в методах обработки состояний. Ждем кнопку Play на стартовом экране и кликаем по ней. Если в течении 10 секунд мы не получили экран выбора уровней, возвращаемся к предыдущему этапу self.STATE_START, иначе переходим к обработке self.STATE_SELECT_LEVEL.
Зрение бота
Пороговая обработка изображения
Определим цвета, которые используются в игре. Это 5 игровых цветов и цвет курсора на учебном уровне. COLOR_ALL будем использовать, если нужно найти все объекты, независимо от цвета. Для начала этот случай мы и рассмотрим.
Для поиска объекта в первую очередь необходимо упростить изображение. Для примера возьмем символ «0» и применим к нему пороговую обработку, то есть отделим объект от фона. На этом этапе нам не важно, какого цвета символ. Для начала переведем изображение в черно-белое, сделав его 1-канальным. В этом нам поможет функция cv2.cvtColor со вторым аргументом cv2.COLOR_BGR2GRAY, который отвечает за перевод в градации серого. Далее производим пороговую обработку при помощи cv2.threshold. Все пиксели изображения ниже определенного порога устанавливаются в 0, все, что выше, в 255. За значение порога отвечает второй аргумент функции cv2.threshold. В нашем случае там может стоят любое число, так как мы используем cv2.THRESH_OTSU и функция сама определит оптимальный порог по методу Оцу на основе гистограммы изображения.
Цветовая сегментация
Дальше интереснее. Усложним задачу и найдем все символы красного цвета на экране выбора уровней.
По умолчанию, все изображения OpenCV хранит в формате BGR. Для цветовой сегментации больше подходит HSV (Hue, Saturation, Value — тон, насыщенность, значение). Ее преимущество перед RGB заключается в том, что HSV отделяет цвет от его насыщенности и яркости. Цветовой тон кодируется одним каналом Hue. Возьмем для примера салатовый прямоугольник и будем постепенно уменьшать его яркость.
В отличии от RGB, в HSV данное преобразование выглядит интуитивно — мы просто уменьшаем значение канала Value или Brightness. Тут стоит обратить внимание на то, что в эталонной модели шкала оттенков Hue варьируется в диапазоне 0-360°. Наш салатовый цвет соответствует 90°. Для того, чтобы уместить это значение в 8 битный канал, его следует разделить на 2.
Сегментация цветов работает с диапазонами, а не с одним цветом. Определить диапазон можно опытным путем, но проще написать небольшой скрипт.
Запустим его с нашим скриншотом.
Кликаем по красному цвету и смотрим на полученную маску. Если вывод нас не устраивает — выбираем оттенкам красного, увеличивая диапазон и площадь маски. Работа скрипта основана на функции cv2.inRange, которая работает как цветовой фильтр и возвращает пороговое изображение для заданного цветового диапазона.
Остановимся на следующих диапазонах:
Поиск контуров
Вернемся к нашему экрану выбора уровней. Применим цветовой фильтр красного диапазона, который мы только что определили, и передадим найденный порог в cv2.findContours. Функция найдет нам контуры красных элементов. Укажем вторым аргументом cv2.RETR_EXTERNAL — нам нужны только внешние контуры, и третьим cv2.CHAIN_APPROX_SIMPLE — нас интересуют прямые контуры, экономим память и храним только их вершины.
Удаление шума
Полученные контуры содержат много шума от фона. Чтобы убрать его воспользуемся свойством наших цифр. Они состоят из прямоугольников, которые параллельны осям координат. Перебираем все контуры и вписываем каждый в минимальный прямоугольник при помощи cv2.minAreaRect. Прямоугольник определяется 4 точками. Если наш прямоугольник параллелен осям, то одна из координат для каждой пары точек должны совпадать. Значит у нас будет максимум 4 уникальных значения, если представить координаты прямоугольника как одномерный массив. Дополнительно отфильтруем слишком длинные прямоугольники, где соотношение сторон больше, чем 3 к 1. Для этого найдем их ширину и длину при помощи cv2.boundingRect.
Объединение контуров
Уже лучше. Теперь нам нужно объединить найденные прямоугольники в общий контур символов. Нам понадобится промежуточное изображение. Создадим его при помощи numpy.zeros_like. Функция создает копию матрицы image с сохранением ее формы и размера, затем заполняет ее нулями. Другими словами, мы получили копию нашего оригинального изображения, залитую черным фоном. Переводим его в 1-канальное и наносим найденные контуры при помощи cv2.drawContours, заполнив их белым цветом. Получаем бинарный порог, к которому можно применить cv2.dilate. Функция расширяет белую область, соединяя отдельные прямоугольники, расстояние между которыми в пределах 5 пикселей. Еще раз вызываем cv2.findContours и получаем контуры красных цифр.
Оставшийся шум отфильтруем по площади контуров при помощи cv2.contourArea. Убираем все, что занимает меньше 500 пикселей².
Вот теперь отлично. Реализуем все вышеописанное в нашем классе Robot.
Распознание цифр
В OpenCV для сравнения контуров на основе Hu моментов используется функция cv2.matchShapes. Она скрывает от нас детали реализации, принимая на вход два контура и возвращает результат сравнения в виде числа. Чем оно меньше, тем более схожими являются контуры.
Сравниваем текущий контур digit_contour со всеми эталонами и находим минимальное значение cv2.matchShapes. Если минимальное значение меньше 0.15, цифра считается распознанной. Порог минимального значения найден опытным путем. Также объединим близко расположенные символы в одно число.
На выходе метод self.scan_digits выдаст массив, содержащий распознанную цифру и координату клика по ней. Точкой клика будет центроид ее контура.
Радуемся полученной распознавалке цифр, но не долго. Hu моменты помимо масштаба инвариантны также к повороту и зеркальности. Следовательно бот будет путать цифры 6 и 9 / 2 и 5. Добавим дополнительную проверку этих символов по вершинам. 6 и 9 будем отличать по правой верхней точке. Если она ниже горизонтального центра, значит это 6 и 9 для обратного. Для пары 2 и 5 проверяем, лежит ли верхняя правая точка на правой границе символа.
Анализируем игровое поле
Пропустим тренировочный уровень, он заскриптован по клику на белом курсоре и приступаем к игре.
Представим игровое поле как сеть. Каждая область цвета будет узлом, который связан с граничащими рядом соседями. Создадим класс self.ColorArea, который будет описывать область цвета/узел.
Определим список узлов self.color_areas и список того, как часто встречается цвет на игровом поле self.color_areas_color_count. Кадрируем игровое поле из скриншота канвы.
Где pt1, pt2 – крайние точки кадра. Перебираем все цвета игры и применяем к каждому метод self.get_dilate_contours. Нахождение контура узла аналогично тому, как мы искали общий контур символов, с тем отличием, что на игровом поле отсутствуют шумы. Форма узлов может быть вогнутой или иметь отверстие, поэтому центроид будет выпадать за пределы фигуры и не подходит в качестве координата для клика. Для этого найдем экстремальную верхнюю точку и опустимся на 20 пикселей. Способ не универсальный, но в нашем случае рабочий.
Связываем области
Будем считать области соседями, если расстояние между их контурами в пределах 15 пикселей. Перебираем каждый узел с каждым, пропуская сравнение, если их цвета совпадают.
Ищем оптимальный ход
У нас есть вся информация об игровом поле. Приступим к выбору хода. Для этого нам нужен индекс узла и цвета. Количество вариантов хода можно определить формулой:
Варианты ходов = Количество узлов * Количество цветов — 1
Для предыдущего игрового поля у нас есть 7*(5-1) = 28 вариантов. Их немного, поэтому мы можем перебрать все ходы и выбрать оптимальный. Определим варианты как матрицу
select_color_weights, в которой строкой будет индекс узла, столбцом индекс цвета и ячейкой вес хода. Нам нужно уменьшить количество узлов до одного, поэтому отдадим приоритет областям, цвет которых уникален на игровом поле и которые исчезнут после хода на них. Дадим +10 к весу ко все строке узла с уникальным цветом. Как часто встречается цвет на игровом поле, мы ранее собрали в self.color_areas_color_count
Далее рассмотрим цвета соседних областей. Если у узла есть соседи цвета color_inx, и их количество равно общему количеству данного цвета на игровом поле, назначим +10 к весу ячейки. Это также уберет цвет color_inx с поля.
Дадим +1 к весу ячейки за каждого соседа одного цвета. То есть если у нас есть 3 красных соседа, красная ячейка получит +3 к весу.
После сбора всех весов, найдем ход с максимальным весом. Определим к какому узлу и к какому цвету он относится.
Полный код для определения оптимального хода.
Добавим возможность перехода между уровнями и радуемся результату. Бот работает стабильно и проходит игру за одну сессию.
Как написать игрового бота на Python для Web
Подготовка
Этот туториал, и код в нем, требует установки нескольких дополнительных библиотек для Python. Они обеспечивают обертку Python’а в кусок низкоуровневого C-кода, который значительно упрощает создание и скорость исполнения.
Некоторые библиотеки существуют только под Windows. У них могут быть эквиваленты под Mac или linux, но мы не будем их рассматривать.
Вам нужно скачать и установить следующие библиотеки:
Последний инструмент это графический редактор. Я предлагаю использовать Paint.Net как лучший из бесплатных, но подойдет любая программа с линейками и измерениями в пикеслях.
Мы будем использовать несколько игр в качестве примеров.
Введение
Это руководство написано с целью дать базовое понимание основы разработки ботов для браузерных игр. Подход, который мы собираемся дать, вероятно немного отличается от того, что многие ожидают услышать говоря о ботах. Вместо того, чтобы сделать программу, вставляющую код между клиентом и сервером (как боты для Quake или CS), наш бот будет находиться чисто снаружи. Мы будем опираться на методы Компьютерного зрения и вызовы Windows API для сбора необходимой информации и выполнения движений.
С этим подходом мы теряем часть деталей и контроля, но сокращаем время разработки и получим простоту в использовании. Автоматизирование специфичных игровых функций может быть создано в несколько строк кода, и полноценный бот, от начала до конца (для простой игры) может быть собран за несколько часов.
Когда вы привыкните к тому, что компьютер может видеть, начнете смотреть на игры по-другому. Хороший пример это поиск в играх-пазлах. Обычное решение основывается на ограничении скорости игрока, что заставляет принимать не оптимальные решения. Интересно (и довольно легко) «взломать» их скриптами движений которые не повторить человеку.
Исходники примеров из курса, а также одного законченного бота можно найти здесь.
Шаг 1: Создание проекта
В папке с проектом создайте текстовый файл quickGrab, измените расширение на ‘py’ и откройте его в редакторе кода.
Шаг 2: Создаем приложение, которое делает скриншот экрана
Начнем работу с изучения базовой функции, которая делает скриншот экрана. Один раз создав и запустив, мы будем строчка за строчкой, как эту функцию, создавать каркас нашего кода.
Вставим в наш файл с проектом quickGrab.py следующий код:
Запустив этот код, вы получите скриншот экрана:
Данный код забирает всю ширину и высоту области экрана и сохраняет в PNG файл в директорию проекта.
Давайте пошагово разберем код, чтобы понять как это работает. Первые три строки:
Первый модуль Python Image Library мы установили ранее. Как следует из названия, он дает нам функциональность взаимодействия с экраном на которую ссылается бот.
Последний импорт создает модуль работы со временем. Мы используем его для установки текущей даты скриншота, также он может быть очень полезным как таймер для ботов, которые выполняют действие в течение заданного количества секунд.
Первая строка def screenGrab() определяет имя функции. Пустые скобки означают, что она не принимает аргументов.
Строка 2, box = () присваивает пустое значение переменной «box». Мы заполним это значение дальше.
Строка 4, может быть немного сложнее если вы не очень хорошо знакомы с тем как работает Time module. Первая часть im.save( вызывает метод «save». Он принимает два аргумента. Первый это директория в которую нужно сохранить файл, а второй это формат файла.
Следующая часть ‘\\full_snap__ дает нам простое описание в имени файла. Обратный слеш является экранирующим символом в Python, и мы добавили два, чтобы избежать отмены одного из символов.
Далее идет эта сложная конструкция: str(int(time.time())). Она использует встроенные функции Питона. Мы рассмотрим работу этого куска кода изнутри:
Шаг 3: Область видимости
Функция ImageGrab.grab() принимает один аргумент, который определяет область видимости. Это набор координат по шаблону (x,y,x,y), где
Это дает нам возможность скопировать только часть экрана, которая нам нужна.
Рассмотрим это на практике.
Для примера рассмотрим игру Sushi Go Round (Довольно увлекательная. Я Вас предупредил). Откройте игру в новой вкладке и сделайте скриншот использую существующий код screenGrab():
Шаг 4: Задание координат
Пришло время задания координат для нашей области видимости.
Откройте скриншот в редакторе картинок.
Координаты (0,0) это всегда левый верхний угол изображения. Мы хотим заполнить X и Y таким образом, чтобы нашему новому скриншоту функция установила координаты (0,0) в крайний левый угол игровой области.
Этому есть две причины. Во-первых, это упрощает нахождение координат, когда мы должны определять координаты относительно игровой области, по сравнению со всем экраном монитора. Во-вторых, захват меньшей части экрана уменьшает нагрузку на процессор. Полноэкранные скриншоты производят довольно много данных, чтобы их можно было циклично повторять несколько раз в секунду.
Если вы это еще не сделали, включите линейки в вашем графическом редакторе и приблизьте верхний угол игровой области до той степени, пока не увидите рамки пикселей.
Наведите курсор на первый пиксель игровой области и запишите координаты на линейках. Это будут первые два значения для нашей функции. У меня получились значения (305, 243).
Затем следуйте к нижнему краю и запишите вторую пару координат. У меня получилось (945, 723). Вместе эти пары дают область с координатами (305,243,945,723).
Давайте добавим координаты в код:
На строке 6 мы обновили массив для хранения координат игровой области.
Сохраните и запустите код. Откройте новое сохраненное изображение и вы увидите следующее:
Отлично! Это идеальный снимок игровой области. Нам не всегда будет требоваться эта напряженная охота за координатами. После того, как мы узнаем о win32api, мы рассмотрим более быстрые методы для установки координат, когда нам нужна идеальная точность.
Шаг 5: Перспективное планирование для гибкости
Давайте создадим две новые переменные: x_pad и y_pad. В них будет храниться расстояние между игровой областью и остальным экраном. Это поможет легко портировать код с места на место, так как каждая новая координата будет задаваться относительно двух глобальных переменных, которые мы создадим. Чтобы настроить изменения экрана нужно сбросить эти две переменные.
Так как мы уже сделали измерения, установить отступы для нашей текущей системы достаточно просто. Мы собираемся установить отступы, чтобы хранить положение первого пикселя за пределами игровой площадки. От первой пары координат нашего кортежа вычесть по 1. Получается 304 и 242.
Давайте добавим это в наш код:
Теперь, когда они установлены, мы скорректируем координаты игровой области относительно них.
Для второй пары значений мы собираемся сначала найти разницу между первой и второй парой координат, чтобы получить размер игрового окна, а затем использовать эти значения с нашими переменными.
Сперва это может показаться излишним, но это дополнительный шаг к более легкому обслуживанию в будущем.
Шаг 6: Создание документации
Перед тем как перейти дальше, создадим документацию в начале нашего проекта. Так как большая часть нашего кода будет базироваться на особых координатах экрана и отношении к этим координатам, важно понимать окружение в котором всё будет работать правильно. Например, таких условия как разрешение монитора, браузер, включенная панель инструментов (так как это меняет размер окна браузера), и другие настройки необходимые для центровки игровой области на экране, все это влияет на относительные позиции координат. Документирование всего этого сильно помогает в решении проблем, когда код запускается на разных браузерах и компьютерах.
Напоследок, нужно следить за постоянно меняющимся рекламным пространством на популярных игровых сайтах. Если функция захвата экрана перестает себя вести, как ожидалось, стоит добавить координатам немного смещения.
Для примера, я обычно добавляю подобный комментарий в начало моего кода:
Добавление всей этой информации в начало файла позволяет быстро и легко перепроверить все настройки и выравнивание экрана без необходимости корпеть над кодом, пытаясь вспомнить, где вы сохранили конкретную x-координату.
Шаг 7: Делаем quickGrab.py удобным инструментом
Мы собираемся клонировать наш проект в этой точке, создавая два файла: один, чтобы писать код нашего бота, другой чтобы хранить функцию снимка экрана. Мы будем делать много снимков, поэтому с отдельным модулем работа пойдет быстрее.
Сохраните и закройте текущий проект.
Это расширение сообщает Питону, что нужно запускать скрипт без открытия консоли. Двойной клик по файлу и он быстро выполнится в фоне и сохранит скриншот в рабочую директорию.
Держите игру открытой в фоне (не забудьте отключить зацикленную музыку, иначе она сведет вас с ума); мы скоро к ней вернемся. У нас еще есть несколько инструментов для внедрения, прежде чем начать контролировать вещи на экране.
Работать с win32api может быть немного сложно на начальном этапе. Это обертка в низкоуровневый Windows C, которых хорошо задокументирован здесь, но навигация похожа на лабиринт, так что пару раз придется пройти по кругу.
Если при выполнении Вы видите ошибку «ImportError: No module named win32api», значит не установлен этот модуль. Выполните в консоли команду pip install pypiwin32
Прежде чем мы начнем писать код, давайте поближе познакомимся с некоторыми функциями API на которые далее мы будем опираться. После того как у нас появится четкое понимание каждого параметра, мы сможем легко настроить их для наших целей в игре.
Первый параметр dwFlags определяет «действия» мыши. Такие как перемещение, клик, скроллинг и т.п. Следующий список показывает распространенные параметры, используемые для программирования движений.
Имена говорят сами за себя. Если вы хотите выполнить виртуальный правый клик, нужно отправить параметр win32con.MOUSEEVENTF_RIGHTDOWN в dwFlags.
Следующие два параметра, dx и dy, описывают абсолютную позицию вдоль осей x и y. Пока мы будем использовать эти параметры для программирования движения мыши, они будут использовать систему координат отличную от той, которую мы использовали до этого. Мы зададим нули и будем опираться на другую часть API для движения мыши.
Четвертый параметр это dwData. Эта функция используется тогда и только тогда, когда dwFlags содержит MOUSEEVENTF_WHEEL. В других случаях она может быть опущена или установлена в 0. dwData скорость прокрутки колеса мыши.
Простой пример для закрепления
Как вы видите, работа с mouse_event это просто вопрос подключения правильных аргументов в правильном порядке. Давайте перейдем к более полезным функциям.
Шаг 9: Клики мыши
Мы переходим к созданию трех новых функций. Одна общая функция нажатия левой кнопки мыши, и два обработчика состояний нажатия и отпускания.
Откройте code.py в редакторе и добавьте следующее выражение к списку импортов
Как и ранее, это дает нам доступ к содержимому модуля через синтаксис module.attribute
Далее создадим первую функцию клика мыши
Напомню, что все, что мы делаем здесь это назначаем действие первому аргументу mouse_event. Мы не должны указывать никакую информацию о позиционировании, поэтому мы опускаем параметры координат (0,0), и мы не должны указывать дополнительную информацию, такую как dwData. Функция time.sleep(.1) говорит Питону приостановить выполнение на время указанное в скобках. Добавим это в наш код. Обычно это очень короткий промежуток времени. Без этого клик может получиться до того, как меню обновится.
Мы создали функцию для левого клика. Один раз нажать, один раз отпустить. Мы потратили много времени на нее, но давайте создадим еще две вариации. Это тоже самое, но теперь каждый шаг мы разобьем на отдельную функцию. Это можно использовать когда нам надо удерживать нажатой мышь в течение продолжительного времени (например, для перетаскивания предметов или стрельбы.)
Шаг 10: Простые движения мышью
Все, что остается это движение мыши по экрану. Добавим следующие функции в файл code.py
Вторая функция это простой инструмент который мы будем использовать в интерактивном режиме. Он выводит в консоль координаты текущей позиции мыши. Это сильно ускоряет процесс навигации в меню без необходимости делать скриншот и пользоваться линейками. Мы не хотим постоянно использовать эту функцию, так как некоторая активность мыши будет требовать точного позиционирования, но там где это возможно, это хорошо сэкономит нам время.
Шаг 11: Навигация в меню
В этом и в следующих нескольких этапах мы попытаемся собрать координаты для разных событий используя метод get_cords(). Используя его мы сможем быстро построить код для таких вещей как навигация по меню, очистка столов, приготовление еды. После сбора мы встроим их в логику бота.
Теперь прежде чем перейти к игровой части, нужно пройти 4 начальных меню.
Оставьте Shell открытым и настройте экран так, чтобы видеть IDLE редактор. Добавим функцию startGame() и заполним новыми координатами.
Теперь у нас есть компактная функция, которую можно вызывать на старте каждой игры. Она устанавливает курсор на каждую позицию в меню, которую мы заранее определили и кликает. time.sleep(.1) говорит Питону остановить выполнение на 1/10 секунды между каждым кликом, чтобы меню успевало обновляться между кликами. Сохраните и запустите код.
У меня, как у медленного человека, прохождение меню вручную занимает больше секунды, тогда как наш бот может сделать это в течение примерно 0,4 секунд. Совсем неплохо!
Шаг 12: Зададим координаты еды
Давайте повторим процесс для каждой кнопки.
С помощью get_cords(), соберите координаты еды из меню. Еще раз в Python Shell напишите get_cords(), наведите мышь на еду и выполните команду.
Мы будем хранить много наших координат в этом классе, там будет некоторое дублирование, поэтому добавив префикс ‘f_’ мы будем знать, что это ссылка на еду, а не, скажем, на заказ еды по телефону.
Продолжим добавлять координаты.
Шаг 13: Координаты пустых мест
Каждый раз после еды посетители оставляют пустые тарелки, на которые нужно кликать, чтобы убрать. Поэтому нам нужно знать расположение тарелок.
Повторите действия из предыдущих шагов, чтобы получить координаты тарелок. Сохраните их пока в закомментированной строке.
Осталось всего несколько шагов до действительно интересных штук.
Шаг 14: Координаты телефона
Есть шесть меню, через которые нам надо пройти.
Нам нужно получить координаты всех, кроме Саке (можете её тоже добавить, если хотите. На мой взгляд бот отлично работает и без нее).
Окей! Мы наконец собрали все необходимые координаты. Давайте создадим что-нибудь полезное!
Шаг 15: Убираем со стола
Мы используем координаты собранные ранее для создания функции clear_tables().
Как вы можете видеть, это выглядит более менее похоже на нашу прошлую функцию startGame(). С одним небольшим отличием: нет функции time.sleep() между кликами. Нам не нужно ждать обновления меню, поэтому не нужно ждать задержку между кликами.
Однако, у нас есть функция time.sleep() в самом конце. Хотя это и не обязательно, лучше добавить паузы в выполнение кода, чтобы была возможность вручную завершить цикл, если это потребуется. В противном случае скрипт будет менять позицию мыши снова и снова и вы не будете в состоянии переместить фокус на Shell, чтобы остановить сценарий. Это прикольно первые два или три раза, но быстро теряет свое очарование.
Шаг 16: Создание суши
Сначала нужно понять как сделать суши. Кликните на книгу рецептов, чтобы открыть инструкции. Все виды суши, встречающиеся в игре, могут быть найдены на страницах этой книги. Оставлю первые три ниже, остальные вы можете найти в книге по ходу игры.
Теперь давайте создадим функцию, которая будет принимать в виде аргумента тип суши и затем собирать требуемые ингредиенты для переданного значения.
Функция foldMat() вызывается в конце каждого приготовления. Она кликает по циновке, чтобы завернуть суши, которое мы приготовили. Давайте зададим её:
Шаг 17: Навигация в телефонном меню
В этом шаге мы зададим все точки mousePos() для подходящий пунктов меню, координаты которых были оставлены для этого момента. Эта часть программы которая будет обернута и контролируема логикой бота. Мы вернемся к этой функции, после того как обзаведемся несколькими новыми техниками.
Краткое введение в компьютерное зрение
Сейчас в нашем распоряжении имеются очень интересные куски кода. Давайте рассмотрим как научить компьютер «видеть» события. Это очень увлекательная часть процесса.
Еще одна сторона построения бота заключается в том, что в конечном итоге бот может предоставить нам достаточное количество информации, что дальнейшем облегчит работу. Например, в случае суши-бота, как только мы пройдем первый уровень, бот будет предоставлять нам достаточное количество данных о том, что происходит на экране, и все что нам останется сделать это объяснить ему как реагировать на эти новые данные.
Другая большая часть в написании бота это обучение игре. Понимание какие значения нужно отслеживать в игре, а какие можно игнорировать. Например, можно не отслеживать деньги в кассе. Это то, что в конечном счете не имеет отношения к боту. Все, что ему надо знать это достаточно ли еды, чтобы продолжать работать. Таким образом, вместо того, чтобы мониторить количество денег, он просто проверяет, может ли бот что-то купить независимо от цены, потому что в игре это вопрос лишь нескольких секунд. Поэтому если бот не может что-то купить, он просто ждет несколько секунд.
Это подводит нас к финальной точке. Это брутфорс против элегантного решения. Алгоритмы зрения требуют значительного процессорного времени. Проверка нескольких точек во многих разных областях игровой области могут сильно снижать производительность. Таким образом все сводится к вопросу «должен бот узнать о том что что-то случилось или нет?»
Например, посетитель может проходить через четыре состояния: отсутствует, ожидает, ест и закончил есть. Когда закончил есть, он оставляет пустую тарелку. Мы могли бы затратить ресурсы на проверку всех мест просмотрев их, а затем кликнуть напротив ожидаемой тарелки (что может приводить к ошибкам, так как тарелки мигают, давая ложный сигнал). Или можно убирать их простым перебором, прокликивая все места через каждые несколько секунд. На практике прокликивание оказывается таким же эффективным, как и элегантное решение, позволяющее определить состояние клиента. Прокликать шесть мест занимает доли секунды, в то время как захват и обработка шести изображений сравнительно медленный способ. Мы можем использовать сэкономленное время для других более важных задач, связанных с обработкой изображений.
Шаг 18: Импорт библиотек Numpy и ImageOps
Добавим следующие выражения импорта
ImageOps это еще одна библиотека для работы с изображениями Python’а. Она используется для выполнения операций над изображениями (таких как перевод в черно-белый формат).
Символ * означает импорт всего из модуля.
Шаг 19: Создаем компьютерное зрение
Первый метод сравнивает значения RGB у пикселей с ожидаемым значением. Этот метод отлично подходит для статичных объектов, таких как меню. Так как нужно иметь дело с конкретными пикселями, то метод не слишком надежен для движущихся объектов. Однако, это варьируется от случая к случаю. Иногда это отличная техника, а в другой раз приходится искать другой метод.
Запустите Sushi Go Round в браузере и начните новую игру. Откройте телефонное меню. Можете пока не обращать внимание на посетителей. Вы начинаете игру без денег, поэтому все пункты меню будут серыми как показано ниже. Это и будут те значения RGB, которые мы будем проверять.
Мы сделали два небольших изменения. Во—первых, мы закомментировали строку, которая сохраняет скриншот. Во—вторых, на шестой строке мы возвращаем объект с изображением после выполнения функции.
Сохраните и запустите код.
Во время того как открыто телефонное меню и все пункты серые, выполните следующий код:
Мы же имеем необходимые координаты после выполнения предыдущих шагов, поэтому просто передадим их в качестве аргументов в функцию getpixel() и запишем результаты.
Первое, что мы должны сделать это кликнуть на телефон и открыть нужное меню. В этом случае меню с рисом.
Если у нас достаточно денег, мы просто проходим через оставшиеся этапы, необходимые для покупки.
Наконец, если нам не хватает денег, то мы говорим боту закрыть меню, подождать секунду и повторить все сначала. Обычно это вопрос секунд, когда нам снова становится доступна покупка. Довольно просто добавить дополнительную логику, чтобы бот мог решить, нужно ли продолжать ждать или заняться в это время чем-то полезным и вернуться позже.
Шаг 20: Следим за ингредиентами
Окей, до этого момента мы медленно продвигались шаг за шагом. Перепишем часть нашего кода, точнее внешний объект, обеспечивающий входящие данные и принятие решений посредством логики, которая может запускаться сама.
Мы должны придумать способ хранить информацию как много ингредиентов у нас сейчас на руках. Мы будем запрашивать экран в определенной области, или усредняя каждую ячейку ингредиента (к этой технике мы вернемся позже), но в дальнейшем, простой и наиболее быстрый способ просто хранить все элементы в словаре.
Количество каждого ингредиента остается постоянным на старте каждого уровня. Всегда начинаем с 10 обычных ингредиентов (рис, нори, икра), и по 5 дефицитных ингредиентов (креветки, лосось, угорь).
Давайте добавим информацию о них в массив.
Ключи массива содержат имена ингредиентов, по ним мы получаем доступ к значениям.
Шаг 21: Добавим отслеживание продуктов в код
Каждый раз, когда мы готовим, мы расходуем ингредиенты. И также пополняем их, когда делаем покупки. Давайте немного расширим нашу функцию makeFood()
Теперь каждый раз при приготовлении Суши, мы уменьшаем значения наших ингредиентов на соответствующие значения. Теперь дополним код в функции buyFood()
Шаг 22: Проверка запасов еды
Теперь когда функции makeFood() и buyFood() могут менять количество ингредиентов, нам нужно создать функцию, которая будет отслеживать что количество какого-то ингредиента стало ниже критического уровня.
Мы будем циклом обходить пары ключ:значение в массиве с запасами ингредиентов. Если нори, риса или икры останется меньше 4, вызывается функция buyFood(), параметром в которую передается имя ингредиента.
Для того, чтобы двигаться дальше, мы должны получать информацию о том, какой тип суши запрашивает клиент. Сделать это с помощью функции getpixel() было бы достаточно тяжело, так как нам пришлось бы искать область с уникальным значением RGB для каждого вида суши для каждого посетителя. Кроме того, для каждого нового типа суши, вам придется вручную осмотреть его, чтобы увидеть, есть ли у него уникальный RGB, который не найден ни в одном из других типов суши. Это значит, что нам пришлось бы хранить 6 мест, по 8 типов суши. Это 48 уникальных координат.
Очевидно, нам нужен метод попроще.
Тем не менее, для этого метода требуется настройка. Нам нужно получать скрин только той области, в которой отображается желаемое суши конкретного клиента, а не всего игрового окна.
На четвертой строке мы суммируем все точки и выводим их в консоль. Это число по которому мы будем сравнивать изображения.
Шаг 24: Зададим области для скринов заказов
Нам нужно задать ограничивающие области внутри каждого из этих спич-баблов (белые облака с рисунком суши). Приблизьте из в редакторе так, чтобы было видно пиксели:
Для каждого спич-бабла мы должны быть уверены что верхний левый край начинается в одном и том же месте. Для этого отступим два пикселя от внутреннего края спич-бабла. Первый белый пиксель на второй ступеньке будет началом отсчета.
Для создания пары координат, отсчитайте 63 по оси x и 16 по оси y. Это даст подобный прямоугольник:
Не беспокойтесь о том, что не весь рисунок с суши попал в прямоугольник. Когда мы просуммируем все пиксели, даже небольшие изменения в одном пикселе повлияют на сумму, и мы сможем отличить заказ посетителя.
Приступим к созданию 6 новых функций, каждая будет уточнением функции grab() и будет передавать аргументом координаты спич-баблов. Как только закончим, создадим функцию, запускающую их все сразу для тестирования.
Шаг 25: Создаем массив с типами Суши
Как только Вы убедились, что каждый тип суши дает одинаковое значение суммы пикселей, запишите эти суммы в массив:
Здесь значение стоит на месте ключа потому, что по нему будет осуществляться поиск.
Шаг 26: Создаем массив мест без спич-баблов
Для работы нам нужны значения сумм скриншотов мест под спич-баблами, чтобы понимать что посетитель отсутствует в данном месте.
Шаг 27: Соединяем все вместе
Время окончательно передать контроль нашему боту. Напишем скрипт, который позволит ему реагировать на клиентов, готовить еду и пополнять запасы.
Основа логики будет следующая: Проверка мест > Проверка заказов > Если не хватает ингредиентов, то купить > почистить столы > Повторить сначала
Clear_tables() выполняется через проверку каждых двух мест.
Теперь надо это зациклить.
Шаг 28: Главный цикл
Мы создаем цикл. Так как мы не задаем никакого условия выхода из цикла, то чтобы завершить игру, нужно в консоли нажать CTRL + C.
Вот и все! Обновите страницу, дождитесь загрузки игры и запустите бота!
Бот немного неуклюжий и нуждается в доработках. Но это отличный скелет для того чтобы продолжить экспериментировать!