В Heroes of Might and Magic III у каждого игрока есть свои ответы на вечный вопрос: какой юнит сильнее? Кто-то смотрит на урон, кто-то на способности, кто-то вспоминает личный опыт из партий, где один отряд неожиданно перевернул ход всего боя.

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

А сравнивать юнитов хочется! И не мне одному. Вокруг HoMM3 давно существует отдельный жанр контента: сравнения юнитов, рейтинги, дуэли и попытки ответить, кто сильнее в своём тире. Например, можно встретить ролики, где юниты сражаются друг с другом один на один, или авторские тир-листы юнитов. Для подобного сравнения требуется ручной прогон битвы, а для этого надо создавать кастомную карту, брать героев с одинаковыми статами, использовать ландшафт, который не влияет на скорость юнитов, и т.д. Учитывая, что логика боя не очень сложная, появилась идея: почему бы не использовать симуляцию таких боёв? Так появился этот проект: небольшая модель боя HoMM3, которая позволяет моделировать дуэли юнитов и смотреть, кто чаще побеждает при заданных условиях.

Ограничения

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

В рамках данного анализа я провожу сравнение только улучшенных юнитов из актуального набора городов HoMM3 HotA v1.8.0, так как они обычно обладают более интересными способностями по сравнению со своими неулучшенными вариантами. Ну и данный текст лучше воспринимать больше как фан, нежели как "окончательный научный рейтинг юнитов HoMM3". Это скорее возможная рабочая версия методики сравнения юнитов.

Данные

Данные по юнитам я собрал в локальную SQLite-базу. В ней хранятся основные характеристики существ: атака, защита, урон, здоровье, скорость, прирост, стоимость, город, тир, тип передвижения и список способностей. Такой формат удобен тем, что симулятору не нужно каждый раз парсить внешние файлы или вручную задавать характеристики в коде. Достаточно выбрать нужный тир, отфильтровать улучшенных юнитов и создать для них стеки с заданным количеством существ.

Фрагмент собранной БД.
Фрагмент собранной БД.

Методика симуляции

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

Для каждого фиксированного тира берём список улучшенных юнитов HotA. Например, для первого тира это набор улучшенных существ: Halberdier, Centaur Captain, Master Gremlin, Familiar, Skeleton Warrior, Infernal Troglodyte, Hobgoblin, Gnoll Marauder, Sprite, Oceanid, Halfling Grenadier и Kobold Foreman. Мы будем проводить по 50 сражений между каждой парой юнитов. Порядок сторон тоже учитывается: A vs B и B vs A считаются отдельными боями (разница небольшая и влияет только при одинаковых скоростях: кто ходит первым), поэтому суммарно между двумя юнитами будет проведено 100 боёв.

Зачем нужно много боёв?

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

  • одиночные юниты (в каждом стеке ровно 1 юнит);

  • месячный прирост (в каждом стеке число юнитов, равное месячному приросту в замке);

  • покупка на 10к (покупаем максимальное возможное число юнитов на 10 000 золота);

Каждый сценарий симулируем отдельно, так как число юнитов может сильно влиять на исход боя. Если у нас 12 юнитов первого тира, то мы имеем 12 \cdot 11 / 2 = 66 пар, для которых надо провести сражения. Общее число битв, которое необходимо провести:

100 \cdot66 \cdot3=19800

Попробуй запустить столько в самой игре!

В результате для каждого сценария получается матрица (диагональ не учитывается, потому что юнит не дерётся сам с собой):

  • строки - атакующий юнит;

  • столбцы - защищающийся юнит;

  • значение в ячейке - сколько раз атакующий победил в серии боёв против выбранного противника.

Ниже приведены итоговые матрицы для каждого из трёх сценариев симуляции боёв юнитов первого тира. Для наглядности названия юнитов окрашены в цвета городов, а число юнитов в стеке указано в скобках.

Результат симуляции битв между одиночными юнитами.
Результат симуляции битв между одиночными юнитами.
Результат симуляции битв между месячными приростами юнитов.
Результат симуляции битв между месячными приростами юнитов.
Результат симуляции битв между юнитами, закупленными на 10 000 золота.
Результат симуляции битв между юнитами, закупленными на 10 000 золота.

Различия между сценариями заметны сразу. Например, Halfling Grenadier не самый сильный юнит в бою один на один, но показывает себя довольно хорошо, когда его количество увеличивается. То же самое можно сказать про Sprite: в одиночном формате они выглядят как средний юнит, но благодаря высокому приросту и низкой стоимости стабильно обходят почти всех остальных.

Победный score

Для одного сценария скор юнита - это средняя доля побед против всех остальных юнитов того же тира. Условно, если бой каждой пары запускался n раз, то для юнита i считаем:

\text{score}(i) = \text{mean}_j (\text{wins}_{ij} / n)

Где: \text{wins}_{ij} - число побед юнита i над юнитом j (из полученной матрицы); n - число повторов боя для одной пары, а \text{mean}_j - среднее по всем противникам этого тира (столбцам), кроме самого себя. Такой скор лежит в диапазоне от 0 до 1. Значение около 1 означает, что юнит почти всех стабильно побеждает. Значение около 0.5 означает средний результат. Значение около 0 означает, что юнит почти всем проигрывает.

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

Сценарий

Вес

Что означает

unit1

0.2

Один юнит против одного юнита

growth1m

0.4

Месячный прирост юнитов в стеке

gold10k

0.4

Сколько юнитов можно купить на 10 000 золота

Итоговая формула:

\text{score}_{total} = 0.2 \cdot \text{score}_{unit1} + 0.4 \cdot \text{score}_{growth1m} + 0.4 \cdot \text{score}_{gold10k}

Почему веса такие?

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

Победный score (по каждому сценарию и общий) для каждого юнита.
Победный score (по каждому сценарию и общий) для каждого юнита.

Итак, топ-3 юнита с наибольшим скором побед: Halberdier, Sprite, Centaur Captain.

Итоговая таблица лидеров

Аналогичные симуляции и расчёты были проведены для всех тиров юнитов. Финальная таблица победителей приведена ниже.

Итоговый лидерборд по каждому тиру.
Итоговый лидерборд по каждому тиру.

Для наглядности названия юнитов перекрашены в цвета замков. Видно, что в таблицу лидеров попал хотя бы один юнит из каждого замка, кроме Инферно. Видимо не зря он считается одним из самых слабых замков.

Выводы

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

Ещё один плюс такого подхода - расширяемость. Если данные лежат в базе, то можно относительно легко добавлять других юнитов: например, существ из WoG, VCMI-модов или других наборов контента. После этого их можно прогонять через ту же методику и сравнивать уже не только оригинальные/HotA-существа, но и модовых юнитов.

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

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

Что дальше

Следующие шаги проекта планируются такие:

  • проверить и доработать все специальные способности юнитов;

  • улучшить стратегии поведения юнитов;

  • попробовать reinforcement learning для выбора действий: действий у юнитов немного, они дискретны, поэтому обучение потенциально может быть достаточно быстрым;

  • расширить базу юнитов на внешние моды, например WoG или VCMI;

  • в далёком будущем попробовать добавить юнитов из других частей Heroes of Might and Magic (например, 1,2,4,5,...), хотя там уже придётся отдельно думать, как сопоставлять характеристики между разными играми;

  • перейти от дуэлей 1vs1 к более сложным боям между армиями.

Комментарии (16)


  1. vesper-bot
    11.06.2026 13:52

    А юниты в симуляции всегда ходили вперед в свой ход, или могли ждать и нанести двойной урон, или первый удар, если у противника нет дистанционной атаки, а их скорость выше? К примеру, грифон против цербера - у цербера скорость 8, у грифона 11, первый ход грифон ждёт, цербер подходит (потому как больше некуда деваться), грифон бьёт, второй ход - грифон бьет первым, двойная атака. (Лучше работает для стрекозы - она может первый ход не лезть в рукопашную, выиграв одну атаку первой, и с некоторым шансом выиграть больше боёв 1v1 чем налетая сразу, если противник не стреляющий)


    1. DemRyzen Автор
      11.06.2026 13:52

      Да, если у юнита есть преимущество в скорости - он этим пользуется и ждёт, чтобы потом совершить две атаки подряд. У некоторых других особенных юнитов тоже постарался учесть их абилки. Например Чемпионы и Кавалеристы с абилкой JoustingBonus (Кавалерийский бонус - доп. урон за каждую пройденную клетку) стараются максимизировать число пройденных клеток перед ударом.


    1. Leon22
      11.06.2026 13:52

      С интересом прочитал статейку :) в принципе да , вопрос хороший про тактику боя ..

      В воге так вообще та же самая стрекоза может переполошить всю армию противника ,используя заклинание берсерка , а если ещё и герой этим поколдует... )))) не знаю какие плюшки у грифона ,скорее всего у него атаки без ответа, что согласитесь тоже мощное дело - так и заклевать не долго :)

      Есть тут ещё момент, моментище сказал бы - 8 уровень некоторых перекрашеных монстров 7 уровня... Вот там да... Один барон преисподней может удерживать более значительно превосходящую армию противника - он играючи заколдовывает обездвиживающим заклинанием, но если не получится кастануть... То повезет ему ,если не упадет от атак)))) Я вон почти всегда в поход его беру ,ставлю его командиром и попер все крушить ))))) или громовержца по настроению, если захочется пострелять ....


      1. DemRyzen Автор
        11.06.2026 13:52

        В планах есть потом добавить юнитов из модов и дополнений, WoG в том числе)


        1. Leon22
          11.06.2026 13:52

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

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

          Потому ,может оно и действительно нафиг никому это не нужно , но сделайте пожалуйста для статистики ,ради которой написан пост, что то типа заданного алгоритма 7 отрядов с 1лв по 7лв ,где например 7 лв из топа голд (выше там картинка в посте) бьёт строго 6лв из того же топа голд, 2лв например бежит и хочет нанести урон отряду 3лв или 1лв итд , и в целом рассмотреть индивидуально золото серебро и бронзу и пары золото с рангами серебро и бронза ... Такую же сеточку, типа вот они лучшие из лучших, и боёв 20000 - и топ тех самых алгоритмов ;) но при этом алгоритмы должны быть хоть как то обоснованы, грубо говоря не взяты с потолка , например почему 7лв не летит или бежит мочить сразу юнитов 1-3лв ))))

          Вроде понятно изложил :)


        1. Leon22
          11.06.2026 13:52

          В воге 15 уровней "взросления" отряда ))) в учёт пойдет?)))


          1. DemRyzen Автор
            11.06.2026 13:52

            Это у командира же? Я давно играл просто, плохо помню нюансы)


  1. IZh
    11.06.2026 13:52

    В сценариях «покупка на 10k золота», интересно и юниты разных уровней посравнивать. Например, на эти деньги можно купить 166 обычных скелетов, но всего двух архангелов. Да, по моим прикидкам, два архангела таки выкосят 166 скелетов за счёт огромного здоровья. Но вот с более младшими юнитами не очевидно, всегда ли имеет смысл купить кучку более мощных вместо толпы более слабых, или же в игре есть перекос и это не всегда верно?


    1. Leon22
      11.06.2026 13:52

      Тут как бы не про 7лв против 1лв ,а 1лв против 1лв итд


    1. DemRyzen Автор
      11.06.2026 13:52

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


      1. IZh
        11.06.2026 13:52

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


        1. DemRyzen Автор
          11.06.2026 13:52

          Да, звучит резонно, добавлю в ToDo)


  1. RulenBagdasis
    11.06.2026 13:52

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


    1. DemRyzen Автор
      11.06.2026 13:52

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


  1. withkittens
    11.06.2026 13:52

    Юниты и их баланс не существуют в вакууме, поэтому результаты не сильно полезны с практической точки зрения. Три простых примера:

    • Магоги: бьют по площади. В битвах юнит-на-юнит их "рейтинг" будет значительно занижен, потому что их главная абилка не работает. Остаются статы: +1 атака за +50 золота.

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

    • Стрелки в целом: обычно не стоят в открытую, а окружены армией единичек.

    Но технически проект интересный, не хочу умалять ваш труд :)

    Моделировать полные бои я, честно говоря, не знаю как, а если ещё факт наличия героя рассматривать..

    Можно попробовать посмотреть в сторону самообучающейся модельки ИИ. Убиваем вражеских юнитов и мало теряем своих - получаем плюс к социальному рейтингу и кошку-жену, бестолково ходим или пропускаем ходы - минус к рейтингу.

    авторские тир-листы юнитов

    Так и знал, что по ссылке литовский бро Lexiav :D


    1. DemRyzen Автор
      11.06.2026 13:52

      Спасибо за комментарий!
      Полностью согласен, что тут многие абилки бессмысленны, FireBall магогов, Кастование заклинаний юнитами (например Архангелы, Мастер-Джинны, Йотуны), Дыхание драконов и много других. И это искажает общий рейтинг юнита.

      Можно попробовать посмотреть в сторону самообучающейся модельки ИИ

      Тоже думал про это, но сначала надо сделать движок симуляции полных боёв, а это существенно сложнее) Вынес в планы на необозримое будущее)