[Из песочницы] История 3 места Russian AI Cup 2017

Всем привет! В этой статье я хочу кратко изложить ключевые моменты своей стратегии в ходе прошедшего соревнования по программированию искусственного интеллекта Russian AI Cup.

6nauovctjocniui6wx6itj2tyao.png

Немного о Russian AI Cup


Суть мероприятия заключается в том, что нужно было написать бота для игры, правила которой задали организаторы и меняли по ходу мероприятия.

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

Правила можно найти здесь.

Немного обо мне


Мой профиль — Leos.

Я принимал участие в подобном мероприятии второй раз.

Первый опыт был в 2015 году — Russian AI Cup 2015, CodeRacing. Тогда все, чего я добился — это попадание во второй раунд. И на этом все могло закончиться и никакой истории не было бы, но однажды на Хабре наткнулся на статью одного из призеров, которая в корне изменила мое представление о том, что надо было делать. С того момента я ждал следующей возможности принять участия в подобном евенте.

2016 год пришлось пропустить — армия.

В этом соревновании я начал участвовать практически с самого запуска открытого бета-теста.

Стратегия


Первой осмысленной стратегией был простой бутерброд плюс истребители, которые действовали самостоятельно.

Однако было ясно, что во втором раунде бутерброды не будут эффективны, поэтому надо было искать что-то другое. Напрашивались потенциальные поля и после того, как увидел их в действии от GreenTea, стало ясно, что именно их я и буду использовать.

Второй стратегией стали маленькие бутербродики (большой бутер разбивал на 10 групп). Истребители по-прежнему действовали самостоятельно.
Вот несколько примеров, как она работала против разных стратегей противников в первом раунде:
У противника несколько групп: russianaicup.ru/game/view/106992
У противника бутерброд: russianaicup.ru/game/view/124483

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

Но недостатки оказались существеннее:

  • Во-первых, построение этих бутербродиков у меня занимало около 1500 тиков на старте, а когда появились сооружения было важно, как можно быстрее начать действовать по карте.
  • Во-вторых, бутербродики вынуждены были двигаться с минимальной скоростью, чтобы не нарушать своего строя.
  • И наконец, сами бутербродики были довольно уязвимы, и, если мои истребители погибали в начале сражения, то истребителей и вертолетов противника было достаточно, чтобы полностью уничтожить мои войска.


Третьей стратегией стало использование 5 групп, как есть. Наверное, это самая оптимальная стратегия разбиения войск, поскольку большинство участников финала к этому и пришли.

Очередность хода


В первых версиях ходил группами по очереди раз в 10 тиков, и порядок нарушался только ядерными ударами противника.

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

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

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

Выбор приоритетных групп на основе разности потенциалов


Потенциалы рассчитывал для всех своих групп.

Перебирал 64 разных направления. Сначала формула:

$$display$$RRPD = \frac{max (P_k — P_c)}{dT}$$display$$

где $inline$P_c$inline$ — потенциал группы в центре,
$inline$P_k$inline$ — потенциал в k-ой точке,
$inline$dT$inline$ — количество тиков, необходимых для того, чтобы добраться до k-ой точки.

Собственно, RPDD — это относительная разность потенциалов. Её я рассчитывал для каждого направления. Точки $inline$P_k$inline$ — это просто некоторые точки на этом направлении удаленные на заданное расстояние.

По этой же формуле я рассчитывал относительную разность потенциалов для текущего направления движения — RDPD.

Самой приоритетной считал группу с максимальной разностью RPDD — RDPD.

Расчет потенциалов


Основной упор я сделал именно на подбор потенциалов. По сути здесь должно быть много математики, но я напишу только суть:

Было 3 вида потенциалов:

  • Сильно убывающий (задавался экспонентой)
  • Средне убывающий (задавался 1/r^2)
  • Слабо убывающий (задавался 1/r)


Было 4 объекта генерирующих потенциалы:

1) Границы карты

Сильно убывающий, отталкивающий потенциал, чтобы не загоняться в угол, не врезаться в края карты.

2) Союзные юниты

  • Сильно убывающий, отталкивающий потенциал, для групп, которые могут столкнуться.
  • ARRV генерировали средне убывающий, притягивающий потенциал, для отхила воздушных групп.
  • TANK и ARRV генерировали средне убывающий, притягивающий потенциал для FIGHTER.
  • IFV генерировали средне убывающий, притягивающий потенциал, для HELICOPTER.


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

3) Вражеские юниты

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

4) Сооружения

Слабо убывающий потенциал, про коэффициент для которого я напишу подробнее.

Захват сооружений


Я рассчитывал коэффициенты от каждого сооружения для всех своих групп. Акцент сделал на следующие вещи:

  • Не бежать толпой захватывать одно сооружение.
  • Защита своих сооружений, при захвате противника.
  • Не захватывать сооружения, которые защищает противник, в том числе отступать, бросая захват, если на меня нападают.


Ничего заумного здесь не придумывал, прописал все ифами, потом методом проб и ошибок подобрал более-менее адекватные коэффициенты.

Производство техники


Тип производимой техники определялся с помощью симулятора сражения.

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

Симулятор сражения


Уже упоминался дважды, но ничего особенного в нем нет. Целью симулятора было определить насколько одна группа сильнее другой.

В сражении принимают участие 2 группы. Считаю их полное здоровье и урон с учетом всех особенностей (земля/воздух, тип техники, отхил ARRV).
После этого запускаю итеративный процесс. Урон на каждой итерации пропорционален текущему здоровью. Здоровье на каждой итерации убывает на величину урона противника.
Количество итераций в последней версии было 10.

В целом работало неплохо и в проигрышные стычки мои юниты не лезли.

Кластеризация


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

Разбивал область на тайлы 8×8, и, если в соседних тайлах есть вражеские юниты на расстоянии меньше 9, то объединял их в одну группу.

Аппроксимация групп противника


Аппроксимировал группы противника эллипсом. Заведомо неверный алгоритм, который опять-таки использовал в качестве заглушки, но дошел со мной до конца.

Определял основную ось и нормаль к ней, которые проходили через центр группы в направлении к самой удаленной техники в группе и в перпендикулярном ему, соответственно. После чего определял радиусы.

Туман войны


Ничего особенного с введением тумана войны придумывать не стал. Запоминал позиции противника, добавил дополнительные потенциалы, чтобы воздушные войска прикрывали наземные.

Визуализатор


С визулизатором сильно заморачиваться не стал. Отрисовывал только потенциальные поля. И при анализе игр использовал его совместно с локал раннером.

Добавлю несколько примеров, чтобы были картинки.

-vqe7qn5rlufohl2pbw6dsbezco.png

Здесь жирное зеленное пятно — это мои вертолеты, и потенциальное поле построено для них.

Синий цвет соответствует положительным потенциалам, красный — отрицательным, черный на красном фоне — очень большим по модулю отрицательным.

Справа-снизу три наземные группы противника, которые притягивают мои вертолеты. В центре истребители противника, которых мои вертолеты боятся. Большое черное пятно — мои истребители, с которыми нельзя сталкиваться.

3wzmjblauijcdcqinpj7trw-ecu.png

Ещё один пример, в котором действующее лицо — мои IFV. Здесь все группы противника, а также два справа являются положительными зарядами.

Черные пятна соответствуют местам, в которые нежелательно перемещаться из-за столкновений со своими группами. Левое нижнее сооружение не оказывает никакого воздействия, так как группа ARRV уже выдвинулась его захватывать.

Оба примера взяты из этого боя — russianaicup.ru/game/view/310222, 730 и 1800 тики соответственно.

Код


Вот ссылка на мой репозиторий.
Писал на Java. Качество кода — какое есть, когда писал я не предполагал, что его кто-то увидит. Ну и я не джавист, курсы по Java проходил как раз одновременно с аи капом.

И все?


Итого: много костылей и заглушек, никаких заумных алгоритмов.

Получилось совсем без эмоций и суховато, но надеюсь кто-нибудь найдет для себя что-нибудь полезное.

P.S. Чуть не забыл, всех с Новым Годом! :)

© Habrahabr.ru