Мозг же, это просто List с нейронами, для возможности использовать List и методами, такими как запись нейрона, получение веса и запись в файл.
Запись нейрона происходит следующим образом, метод вызывается с параметрами тег и вес, мы пробегаемся по всему листу, если уже имеется нейрон с таким тегом, то к нему просто добавляется вес, иначе мы создаем новый нейрон с указанными тегом и весом и добавляем его в лист.
В получении веса мы, опять же, обращаемся к методу с тегом, пробегаемся по листу, если нашли нейрон с таким тегом, возвращаем его вес, иначе возвращаем 0.
Сохранение в файл происходит с помощью библиотеки "System.IO" и "JsonUtility" которая встроена в Unity3d.
Ну и действие, связывающее нейроны и бота. Этот класс состоит таких параметров, как вес, список тегов, действие, цели, т.к. некоторые действия требуют выбрать их.
Теперь сам бот. В комментариях к прошлой статье был вопрос - "как бот взаимодействует с клиентом", по сути у нас есть 3 линии и возможность перетащить карту на одну из линий, чтобы вызвать её туда. Не зависимо от расположения курсора над линией карта становить вперед своих карт, так что по сути просто вызывается метод у линии "Добавить карту". Вот бот и вызывает этот метод, посылая параметром карту, которую хочет поставить.
В начале хода бота, бот ждет, когда ему дадут карту(как и во многих других ККИ у нас в начале твоего хода дают карту), затем начинает смотреть свою руку, а точнее на карты в ней, просто перебирая очередной List с картами. Если стоимость маны у карты выше, чем у бота маны, то он попросту пропускает ее, а вот если маны хватает, то тут уже происходит анализ. (Маленькое отступление) Концепция обучения этого бота такова, что я даю боту информацию, которую считаю необходимой для него, а он на основании нее уже совершает действия. Т.е. я не написал бота, который в начале даже не знал во что играет, а после долгих опытов пришел к выводу, что это похоже на ККИ.(Конец отступления). Для каждой карты бот смотрит все 3 линии, т.е. он каждую карту анализирует по 3 раза. Сначала бот создает новое действие, затем генерирует первый тег, на основании характеристик самой карты, т.е. это выглядит следующим образом "сыграть_id_*стоимость карты*_*сейчас_маны*"(id - Это идентификатор карты), получает из мозга вес этого тега, и записывает тег и вес в это действие. Затем бот смотрит есть ли дружественный сосед и, если есть, из какой он фракции, т.е. добавляет новый тег типа "фракция1_фракция1" или "фракция1_null"(в случае если на линии нет союзников), ну и все добавление тегов сопровождаются получением и записью веса этого тега в действие. Далее, если у карты есть какое-либо действие, которое воспроизведется с ее появлением на столе, то мы боту говорим результат этих действий.
Просмотр результат действий происходит следующим образом, допустим у нас есть механика убить врага напротив, убить выбранного врага, баффнуть своего союзника и достать еще карту из колоды. В начале всего анализа генерируется тег "использовать_*название механики*", далее, если это, к примеру, убить напротив себя, добавляется ID карты с дополнительным весом, равным сумме характеристик врага напротив , т.е. если напротив стоит существо 2/3 с ID = 2, то в тег добавиться запись "_2_5" и этот тег со своим весом записываеться в действие, если у врага есть способности, то мы за каждую способность так же добавим в действие тег с записью "убить_*название способности*".
В случае, если у карты способность "выбрать цель для убийства", то бот создает отдельное действие для каждой карты на поле, где будет храниться тег убийства и всех способностей этой карты. Затем выберет самое весомое действие и его теги и вес записывает уже в главное действие.
По окончанию анализа каждое действие записывается в лист действий, а так же в самом конце добавляется действие с тегом "nothing_*текущая мана*" и своим весом, что отвечает за пропуск хода. Далее бот пробегается по всему списку и выбирает те действия, которые ему подходят, а точнее самые весомые. НО! У меня, как я уже говорил в прошлом посте, есть коэффициент погрешности, благодаря которому бот может не всегда совершать идеальные, по его мнению, ходы. Т.е перед пробегом по основному листу с действиями, создается дополнительный, изначально пустой лист. Когда мы просматриваем основной лист, мы смотрим его вес, если вес этого действия больше максимального веса в новом списке, то все действия, вес которых ниже чем (макс. вес - погрешность), удаляются, а если у текущего действия сумма (веса + погрешности) >= максимальному весу в новом листе, то просто добавляем его. Далее из полученного списка бот берет случайное действие и совершает его, тратя на это ману, далее он смотрит, может ли он еще что-то сделать, кроме "nothing" и будет ли это хорошим действием. Все разыгранные действия за игру, а именно теги(нейроны) этих действий записываются в отдельный список "задействованных нейронов".
Когда у бота проигрывается действие "nothing", бот передает ход противнику и в этот момент идет просчет урона по себе и по врагу. В зависимости от этого у каждого нейрона в списке "задействованных нейронах" добавляется(в случае нанесения урона) или отнимается(в случае получения урона) вес, где 1 ед. урона = 0.1 ед. веса. Ну и по окончанию игры, если бот выигрывает, мы его хвалим и даем по +10 к каждому сыгранному нейрону, или, в случае проигрыша, отнимает по 10 у каждого нейрона.
Далее я просто добавил в игру 2 бота, один оперировал рукой Игрока1, а другой - Игрока2 и наблюдал за их действиями и логикой. Из интересного, у нас в игре есть механика убить и забрать существо. Так вот, бот не любит убивать хороших существ врага, т.к. он может его попросту забрать себе и из-за этого спустя еще пары десятков игр бот понял, что ставить сильное существо без прикрытия слабым не ок.
Если вам интересны такие статьи, то я бы мог попробовать, для примера, написать простенький Tower Defense, в который так же играл бы бот.