Последние 10 лет я играл в такие игры, как TownsMen 6, Clash of the Clans, SimCity и мою любимую OpenTTD (с открытым исходным кодом!).

Попробовав City Island 5, я был раздражен от того, что предметы не накапливались, пока я находился вне игры. У меня может быть самый лучший бизнес, стратегия и т.д., но я должен быть в игре, чтобы обеспечить сбор денег/ключей/золота с течением времени. Например, если моя пекарня зарабатывает 100 евро в минуту, я заработаю 100 евро только после того, как выйду из игры и вернусь через 24 часа.

Это стало особенно утомительным, когда я пытался накопить €5 000 000, необходимых для покупки острова, показанного ниже. Это займет у меня примерно две недели игры, если я не буду тратить деньги - оно того не стоит!

Создание скрипта Python для сбора ценностей для меня

Это проблема, которую можно решить с помощью машинного обучения.

a. Захват фреймов игры

Мне нужен был способ захвата фреймов игры в реальном времени.

Проще всего сделать снимок экрана в игре и передать его на следующие шаги сценария.

Для создания скриншота я использую библиотеку Python MSS. Это простая библиотека, которая позволяет захватить экран и сохранить его в файл. Мы также можем использовать библиотеку для выбора монитора и получения его свойств, таких как ширина и высота.

Мы будем использовать OpenCv (cv2) для части сценария, связанной с компьютерным зрением. Это библиотека, которая позволяет нам выполнять задачи обработки изображений и компьютерного зрения. Здесь мы используем метод cv2.imread() для загрузки изображения из указанного файла.

import cv2 
import mss

sct = mss.mss() 
default_monitor = sct.monitors[1]

def click_template_image(monitor=default_monitor):

    # Screenshot
    game_screenshot_path = "sct_{width}x{height}.png".format(**monitor)
    sct_img = sct.grab((0, 0, monitor["width"], monitor["height"]))
    mss.tools.to_png(sct_img.rgb, sct_img.size, output=game_screenshot_path)

    game_screenshot = cv2.imread(game_screenshot_path, 1)

b. Распознаем ресурсы на скриншоте

Нам нужен способ обнаружить ресурсы игры и затем вернуть их координаты.

Алгоритмы OpenCv TemplateMatching идеально подходят для этого.

Они используются для поиска и определения местоположения шаблонного изображения (например, ценного предмета) в большом изображении (например, в фиде игры). OpenCV просто накладывает изображение шаблона на входное изображение (как в 2D-свертке) и сравнивает шаблон и участок входного изображения под изображением шаблона. В OpenCV реализовано несколько методов сравнения. (Более подробную информацию вы можете найти в документации). Мы используем его в методе: cv2.matchTemplate(... ).

Для достижения этой цели мне понадобились изображения шаблонов. Я сделал скриншоты вручную, а затем обрезал кеш, звезду и ключ:


В приведенном ниже примере кода мы распознаем кеш.

import cv2
import mss
import numpy as np

sct = mss.mss()
default_monitor = sct.monitors[1]

def click_template_image(monitor=default_monitor):

    # 1. Screenshot
    game_screenshot_path = "sct_{width}x{height}.png".format(**monitor)
    sct_img = sct.grab((0, 0, monitor["width"], monitor["height"]))
    mss.tools.to_png(sct_img.rgb, sct_img.size, output=game_screenshot_path)
    game_screenshot = cv2.imread(game_screenshot_path, 1)


    # 2. Find a way to identify the valuables in the screenshot
    template_image = cv2.imread("images/cash.png", 1)

    search_result = cv2.matchTemplate(game_screenshot, template_image, cv2.TM_CCOEFF_NORMED)

    y_coords, x_coords = np.where(search_result >= threshold)

    for idx in range(len(x_coords)):
        x, y = x_coords[idx], y_coords[idx]

c. Собираем ресурсы кликом

Получив координаты элемента, мы должны кликнуть по нему.

Функция pyautogui.click(x,y) работает для этого замечательно. Она щелкает по экрану по координатам x,y. Подробнее о ней можно узнать здесь.

Примечание:

  • Мы выбираем координаты, которые соответствуют определенному порогу доверия. Показатель уверенности - это число от 0 до 1, которое представляет собой вероятность того, что результат модели верен и удовлетворит запрос пользователя. Например, мы можем отобрать координаты, уровень доверия к которым составляет 0,7 или выше. Именно для этого мы и используем пороговую переменную. Алгоритм matchTemplate() дает нам несколько точек на карте, которые соответствуют нашему запросу. Затем я решил отфильтровать точки, которые находятся ниже порога: y_coords, x_coords = np.where(search_result >= threshold).

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

  • Я обнаружил, что щелчок по центру изображения работает лучше, чем щелчок по левому верхнему краю, то есть по координатам, которые нам дала наша функция подбора шаблона. Мы можем использовать высоту и ширину изображения шаблона для вычисления координат центра: x_c = int((x + x + w) // 2) & y_c = int((y + y + h) // 2)

import cv2
import mss
import numpy as np
import pyautogui

pyautogui.FAILSAFE = False

sct = mss.mss()
default_monitor = sct.monitors[1]

def click_template_image(monitor=default_monitor, number_of_clicks=1, threshold=0.7):

    # 1. Screenshot
    game_screenshot_path = "sct_{width}x{height}.png".format(**monitor)
    sct_img = sct.grab((0, 0, monitor["width"], monitor["height"]))
    mss.tools.to_png(sct_img.rgb, sct_img.size, output=game_screenshot_path)
    game_screenshot = cv2.imread(game_screenshot_path, 1)

    # 2. Find a way to identify the valuables in the screenshot
    template_image = cv2.imread("images/cash.png", 1)
    search_result = cv2.matchTemplate(game_screenshot, template_image, cv2.TM_CCOEFF_NORMED)
    y_coords, x_coords = np.where(search_result >= threshold)

    # get the width and height of the template image
    w, h = template_image.shape[1], template_image.shape[0]
    for idx in range(number_of_clicks):
        if idx + 1 > len(x_coords):
            continue
        x, y = x_coords[idx], y_coords[idx]

        # 3. Collect the valuables by clicking on them
        # get centres
        x_c = int((x + x + w) // 2)
        y_c = int((y + y + h) // 2)

        pyautogui.click(x=x_c, y=y_c)

d. Закрываем всплывающие окна

Наши нажатия выше могут привести к появлению всплывающих окон, когда мы получаем награду, повышаем уровень и т.д.

Нам нужно закрыть окно, прежде чем снова попытаться собрать ценности. Мы используем ту же логику, что и при поиске и нажатии на ценные вещи.

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

Для закрытия я использовал тот же код, что выше.

Результаты после запуска на ночь

Я начал игру с €316,415 в кармане.

На следующее утро у меня было €6,463,870.

И я смог купить тот остров, что я хотел

В заключение

Вообще-то это называется использовать читы, но так ли это плохо в данном случае?

Полный код автора доступен по ссылке.

Еще больше примеров использования ML в современных сервисах можно посмотреть в моем телеграм канале. Я пишу про ML, стартапы и релокацию в UK для IT специалистов.