MOCHET [7] 
28.07.2018 18:23
 0просмотров 30 0

Обзор бота для аркомага с hardcoded scoring [Code Review #1]

Disclaimer

Я не продаю здесь ботов и не пользуюсь ими в браузерной игре HeroesWM и также не мотивирую ими пользоваться там. Всё это только в вашей фантазии :). Мне лично всё равно что происходит в ГВД. Если я и тестирую ботов, то использую их локально, на своём персональном компьютере. К этим ботам здесь только академический интерес и нет интереса навредить как-либо игре. Если кто и собирает здесь идеи, для бота в ГВД, то вся ответственность лежит на нём за несанкционированое (запрещено правилами ГВД) использование ботов.
Я использую свой собственый симулятор, на котором тестирую ботов и проверяю их качество. Данная статья является обзором чужого кода и обсуждением положительных и отрицательных качеств ИИ для аркомага - на этом и всё.



Краткая информация

Предположительное время создания и использования: ~ 2009-2010 год (Скорее всего уже больше нигде не используется).
Место использования: HeroesWM.ru
Дополнительная информация: За него выписывали штрафы по 500к.
Написан на: PHP 5(?)
Количество файлов: 3 файла (один для debugging, один для запуска и один со всей логикой)
Количество строчек кода с комментариями: 1056+92 = 1148 (без кода для дебага)
Количество строчек кода без комментариев: 998+31 = 1029 (без кода для дебага)
Комментарии (1-1029/1148)*100% = 10.36% комментариев в сорс коде. Из них половина мусор (код засунутый в комменты). Поэтому на самом деле примерно половина, т.е. 5%.
Количество функций: 14
Количество классов: 0
Доступность: Очень сложная (искал год точно). Слышал, что стоит 50$. Достался бесплатно где-то год назад (спасибо большое автору).
Язык: русский
Автор: Не разглашается

Примечания

• код сложно читать (мало комментатриев) (дисклайк)
• нет классов (дисклайк)
• забагован (undefined globals, $foo[лол], ...) (дисклайк)
• на русском - названия переменных русофицированны - Primer: неужели вместо "ocenka_list" нельзя написать "cards_scoring_list"? (dislajk)
• почти всё в одном файле (дисклайк)
• hardcoded card scoring (but with dynamic elements) (дисклайк)
• без понятия как тестировать его на своём симуляторе. придётся сначала дебажить и настраивать. Легче наверное просто взять идею и переписать например на питоне.
• Indentation: Использует 4 пробела (лайк)
• Интеллектуальная сложность: низкая (крайне субьективно)



Cards defintions

Строчек кода: 134
Отдельный файл: нет
Как работают сложные эффекты: eval("PHP CODE")
Примечания: нет карт с номерами 100, 101 (сбросить карту, играем снова). Были введены в 2010 году (Обновление таверны).


Кликните по картинке, чтобы она открылась в новом табе в увеличенном размере.


Оценка карт

Строчек кода: 216
Отдельный файл: нет
Примечания: горы if + else + "xx?aa:bb" + switch-case


Оценка карты проходит в нескольких этапах:
1. Проверяем карту напрямую через её ID
2. Проверка на победу сразу (почему это вообще сразу не проверяется?) (без ID карты)
3. Бонусы за ресурсы (без ID карты)
4. Бонусы за строения (без ID карты)
5. Бонусы за наши башни и стены (без ID карты)
6. Бонусы за вражескую башню и стены (без ID карты)
7. Бонусы за перевес строений (без ID карты)


Какой-либо оценки при победе через ресурсы я не нашёл.

Устроено так: Есть функция-оценщик карты. Оценка инициализируется 0. После этого куча if-ов (см. колонка Условие), где к оценке либо что-то постоянно прибавляется, либо вычитается. Под конец выдаётся оценка. Только в трёх случаях можно выйти преждевременно (см. колонка Описание) из функции с оценкой 999 или -999 (при победе либо при проигрыше).

Если в таблице (см. колонка Название) указано 1,2,3,... - это последовательность кода. Код разобран сверху вниз.

Функия-оценщик получает 4 параметра:
_me (array)
_enemy (array)
turn (integer)
card_id (integer)
А также использует 3 глобальные переменные:
game (array)
me (array)
enemy (array)


Примечание
me, _me, enemy, _enemy - это 4 разных массива с данными игроков, чтобы узнать эффект карты на данный момент и дать карте какую-то оценку основанной на этой разнице

Симуляция

Функция оценки карт запрашивается в двух местах в коде - всё в одной функции, которая отвечает за конечный выбор карты. Параметров она никаких не получает, но использует следующие глобальные переменные:
me (array: мои данные)
enemy (array: данные противника)
game (array: игровые данные)
scoring_list (array: список с оценкой карт)


После этого происходит следующее (почти нет комментариев в коде - сложно читать):
1. Иницализируем переменные: best_ocenka=-9999999 (-Inf), best=array(), combinations=array() (6-7 карт на моей руке)
2. Инициализируем список с оценками оценкой -999 для каждой карты (6-7 карт на моей руке)
3. Иницализируем список вариантов и собираем комбинации с максимальной глубиной: 3-1 = 2
4. Проверка всех переборов (так и написано в комментарии в коде - это самая большая часть этой функции). Здесь автор симулирует игру картой и потом в оценщике карты проверяет что именно даёт данная карта (см. таблицы ниже той где идёт проверка карт через ID). Т.е. оценщик судит уже по реальным эффектам
5. Под конец идёт сравнение оценки с лучшими оценками и выбирается карта с самой лучшей оценкой.


Строчек кода в функции выборки карт: 181
Примечания: 4 отдельных блока с for-loops.

Количество дополнительных внутренних for/foreach-loops в этих блоках:
1. 0x (в сумме 1x) (инициализация)
2. 0x (в сумме 1x) (инициализация)
3. 2x (в сумме 3x) (собираем комбинации карт)
4. 1x (в сумме 2x) (симуляция игры)


Максимальная оценка: +999
Минимальная оценка: -999
Оценка, если башню врага можно разрушить за один ход: +999
Оценка, если можно построить свою башню по максимум за один ход: +999
Оценка, если можно разрушить свою башню: -999



1 - Оценка карт напрямую

В колонке условие - используется if statement. Для читабельности все if-ы убраны.
В if суммируется scoring, т.е. так: "score += 50".



2 - Проверка на победу сразу

В if-else суммируется scoring, т.е. так: "score += (me["руда"] ......".
Здесь более-менее динамичный scoring.



3 - Бонусы за ресурсы

В if-else суммируется scoring, т.е. так: "score += (me["руда"] ......".
Здесь более-менее динамичный scoring.



4 - Бонусы за строения

В if-else суммируется scoring, т.е. так: "score += (me["руда"] ......".
Здесь более-менее динамичный scoring.



5+6 - Бонусы за наши/вражеские башни и стены

В if-else суммируется scoring, т.е. так: "score += (me["руда"] ......".
Здесь более-менее динамичный scoring.



7 - Бонусы за перевес строений

В if-else суммируется scoring, т.е. так: "score += (me["руда"] ......".
Здесь более-менее динамичный scoring.

Примечания
• Для читабельности убрал знаки "$" и ";"
ocen = оценка = scoring




Примечния к оценкам из первой таблицы

В первой таблице идёт проверка карт через ID.

Бракованная руда: Везде только отрицательный скоринг. Совершенно не учитывается ситуация, где мы на грани победить через постройку/разрушение башни, а противник через фарм ресурсов. В таких случаях есть смысл украсть у него руду. Или другой пример: Если у нас 0 шахт и 0 руды, то можно смело пользоваться этой картой, если у противника например где-то 10 руды и например 1-2 шахты.

Аналогично с картой Землятресение.

Вот например с картой Обвал рудника такие ситуации учтены.

Паритет: Даёт -100 если у нас монастырь выше чем у противника. Значит, что этой картой не играем - можно и сбросить. Тут как с картой сдвиг - если есть возможностъ лучше её оставить, чтобы противнику не так просто было бы уравнять монастыри. К тому же она может пригодится.

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

И похожии ситуации с другими картами.

Автор бота не учёл какой-то дополнительный scoring для сброса карт. Нужна не только оценка на то что бы сыграть картой, а ещё на то какую карту лучше оставить. Там всё решается через один тип оценки. Например снова посмотрим на карту сдвиг, если наша стена выше чем у противника, то оценка будет -100, т.е. по сути это кандидат на выброс, а это довольно критично.


Этапы в оценщике карт

1. Проверяем карту напрямую через её ID
2. Проверка на победу сразу (почему это вообще сразу не проверяется?) (без ID карты)
3. Бонусы за ресурсы (без ID карты)
4. Бонусы за строения (без ID карты)
5. Бонусы за наши башни и стены (без ID карты)
6. Бонусы за вражескую башню и стены (без ID карты)
7. Бонусы за перевес строений (без ID карты)


Тут я согласен, довольно неплохо продуманно.


Проблемы оценки карт

Scoring карт отчасти динамичный. Но во многих местах значения фиксированны рукой создателя.

Так как всё добавляется в ручную, то было пропущено много случаев (1 - Оценка карт напрямую), где можно было бы эксплоитить какие-то специальные эффекты карт. Примеры:
Steal technology VS Big vein - Какая карта когда выгоднее?
Arcomage. Профитабельное переливание ресурсов и постройка заводов.
Arcomage. Finding exploitable cards & maximizing cards' effects
Arcomage. Maximal effects. Lookup table. (max. сгенерированно автоматически)
Arcomage. Minimal effects. Lookup table. (min. сгенерированно автоматически)

Всё очень ригидно и малоподвижно, т.к. оценки делались в ручную, а не динамично.

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

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


Симуляция

Честно говоря я не сразу увидел, что идёт симуляция игры. А это уже что-то интересное, т.к. всё-таки в конечном итоге есть что-то наподобие динамической оценки карт, хотя и с самим оценщиком карт я далеко не согласен, т.к. слишком много интересных моментов пропущено.
Однако из кода не совсем ясно насколько сильно развит концепт карточных комбинаций, т.е. например:
1. мой ход
2. ход противника
3. мой ход (играем снова)
4. мой ход
5. ход противника
6. мой ход

1-3-4-6 например была какая-то тактика, которую мы успешно смогли реализовать (1-3-4-6 в данном случае комбинация из карт, растянутая на несколько независимых ходов).

Насколько я понял такого там нет.


Вердикт

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

Если у кого есть интерес написать бота и посоревноваться со мной (ещё раз - я не про ГВД. Я лишь пользуюсь здешним блогом и собираю статистику. - На этом и всё) - пишите!

Правила

Ввод данных через JSON (в текстовом файле через командную строку)
Выход: ID карты и action: "turn" / "drop", тоже в консоль

Возможно когда будет проработанна более менее концепция оценки карт и доделаю своего бота (не для ГВД).


Карты

Все карты из таверны. оффициальные ID карт. (english)
Все карты из таверны. (на русском)

Возможность комментировать доступна после регистрации