ии противник в игре что такое
Хотя Close Quarters преимущественно является многопользовательской игрой, в ней всё равно должны присутствовать сложные ИИ-боты, чтобы игроки продолжали играть при плохом Интернет-соединении или отсутствии других онлайн-игроков. Кроме того, боты играют важную вспомогательную роль в некоторых режимах игры. Поэтому они должны вести себя правдоподобно и демонстрировать набор сложных поведений, в том числе использование укрытий, применение предметов в подходящее время, обход с флангов, бросание гранат и убегание от них.
Окружение и ограничения
Игровое окружение состоит из полигонов. Большинство полигонов блокирует движение, область видимости и стрельбу, однако есть и «низкие» полигоны, только блокирующие движение. Окружение плотно заставлено препятствиями и укрытиями.
ИИ тоже ограничен несколькими техническими факторами. Самый важный из них: сервер, на котором выполняются боты, когда онлайн находится мало игроков, должен быстро работать на недорогом VPS как минимум при десяти ботах. Кроме того, нагрузка на ЦП должна оставаться достаточно низкой, чтобы можно было выполнять несколько инстансов сервера на одном VPS без превышения лимита ЦП, и не при этом вызвать санкций со стороны поставщика услуг VPS.
Рисунок 1: окружения
Рисунок 2: вид от лица игрока на препятствия, блокирующие обзор (серые области для него невидимы)
Меш навигации и видимости, тактический поиск пути
Боты используют плотный меш навигации, состоящий из дискретных точек. Меш генерируется следующим образом: сначала полигоны, из которых составлено окружение, расширяются и комбинируются. Рядом с углами добавляются дополнительные узлы, потому что эти места с наибольшей вероятностью окажутся подходящими позициями для укрытий. Получившееся пространство для перемещения затем триангулируется для генерации меша.
Рисунок 3: меш навигации
Так как боты должны выполнять тактический поиск пути, меш навигации должен содержать данные видимости, позволяющие нам быстро проверять, содержит ли узел укрытие от выбранного врага. Поэтому каждый узел содержит массив из 24 байтов, каждый из которых представляет собой заранее вычисленное приблизительное расстояние между узлом и ближайшим блокирующим область обзора препятствием в определённом направлении.
Рисунок 4: сохранённые в узле данные видимости (синие линии). Каждая линия представляет однобайтовое значение, определяющее видимость в заданном направлении.
Благодаря этим данным можно выполнять поиск по графам алгоритмом A* на меше навигации, при котором узлы, которые могут быть открыты для известных врагов, получают меньший приоритет без выполнения затратных проверок линии прямой видимости. Чтобы проверить, открыт ли для врага определённый узел, мы определяем направление и расстояние от узла до врага, а затем проверяем, меньше ли это расстояние чем, расстояние, сохранённое в элементе массива, наиболее близко соответствующему этому направлению. Кроме того, мы можем проверить, смотрит ли враг на узел. Затем мы можем применить штрафной коэффициент к затратам на перемещение по открытым для врагов узлам, и получившийся путь будет стремиться избегать таких узлов.
Те же данные видимости, кроме поиска пути между двумя заданными точками, можно использовать для других «тактических» действий. Например, бот ищет укрытие, выполняя поиск в ширину и останавливается, как только найдёт защищённый узел. Для проверки того, действительно ли узел обеспечивает укрытие, используются тесты линии видимости. Аналогично, мы можем генерировать пути атак с флангов, выполняя поиск A* к цели; при этом накладываются высокие штрафы на открытые узлы в пределах конуса стрельбы цели. Поиск останавливается, как только мы достигнем открытого узла за пределами этого конуса стрельбы. (Одна из проблем такого подхода заключается в том, что боты, вышедшие из области видимости, постоянно стремятся приблизиться к цели, а поэтому кажутся чересчур агрессивными; вероятно, это можно исправить настройкой эвристики A* так, чтобы бот двигался не напрямую к цели, а к любым узлам, находящимся на выбранном расстоянии до цели).
Чувства и память ботов
Чтобы боты вели себя убедительно, они не должны казаться «читерами». Другими словами, информация, с которой работает бот, должна быть похожа на информацию, которой обладает игрок. Например, враг за препятствием должен быть невидим для бота так же, как он не виден для игрока.
Существуют два способа, которыми игрок может обнаруживать позицию врага: он может увидеть врага или услышать, как тот движется, стреляет или выполняет какое-то другое действие.
Каждый бот хранит список известных ему «фактов» о позициях и направлении взгляда врагов. Без обновлений эти факты удаляются спустя десять секунд. Факт, относящийся к определённому врагу, обновляется, когда бот может услышать или увидеть этого врага. Когда бот слышит врага, для имитации неопределённости позиция соответствующего факта смещена от истинной позиции врага в случайное направление и расстояние, зависящие от того, как близко к боту был звук (см. видео, 1:28).
Рисунок 5: факты (розовые круги) в памяти бота
Дерево поведений
В более ранней версии Close Quarters ИИ использовал STRIPS — решение, популяризированное игрой F.E.A.R. в 2005 году. В STRIPS отношения между различными поведениями ИИ не задаются программистом заранее. Вместо этого каждое поведение содержит список двоичных предусловий и исходов. Каждый бот имеет состояние задачи в мире и использует поиск по графу A* для нахождения последовательности поведений её достижения. Такое решение хорошо работало, но я чувствовал, что для моего приложения оно было слишком сложным и лучше подходящим для ИИ, которому нужно разрабатывать сложные планы с участием множества разных поведений. В большинстве случаев я уже знал обстоятельства, при которых бот должен был выполнять то или иное поведение, поэтому использование для этого алгоритма A* было ненужной тратой ресурсов ЦП.
Поэтому теперь боты используют простое дерево решений и поведений. В каждом такте бот обходит дерево, начиная с корня, пока не достигнет поведения. Если это поведение такое же, как и уже выполняемое, то бот продолжает это поведение. Если нет, то бот инициирует поведение и начинает его выполнять.
Некоторые поведения могут «блокировать», то есть не позволять боту повторно обходить дерево, пока не будет выполнено определённое условие. Это полезно, например, чтобы гарантировать, что боты добрались до укрытия, прежде чем решат атаковать. Также поведения могут «обнулять» друг друга, вынуждая бота повторно обойти дерево и заново инициировать поведение. Это полезно, например, когда бот убегает от гранаты, и появляется другая граната, ставящая под угрозу выбранные позиции побега.
Рисунок 6: используемое сейчас дерево решений и поведений
Некоторые вторичные поведения закодированы внутри других, более общих поведений. Например, если бот пытается атаковать врага и обнаруживает, что враг не в предпологаемой позиции, то он предполагает, где враг может быть теперь, и вычисляет новый путь атаки, не выходя из поведения атаки.
Распределение нагрузки
Каждого бота необязательно обновлять в каждом кадре физики, то есть, 40 раз в секунду. Для снижения затрат ЦП каждый бот «думает» только 20 раз в секунду (это число при необходимости можно уменьшить). Поэтому в каждом такте физики обновляется ИИ только половины ботов.
Работа с гранатами
Серьёзной проблемой стало осмысленное использование ботами гранат. Работа с гранатами намного сложнее, чем стрельба, потому что гранаты могут отлетать от стен, иметь радиус поражения и время на бросание. К счастью, боты не обязаны использовать гранаты идеально, достаточно убедительности.
Традиционным решением этой задачи является предварительное вычисление траекторий гранат в узлах навигации, но при его реализации к времени загрузки каждой карты добавляется несколько секунд, что противоречит мой цели: Close Quarters должна бросать игроков в битву за считанные секунды после запуска игры.
Рисунок 7: направления, проверяемые ботом в течение одной секунды (бледно-розовые линии) и протестированные траектории (синие круги) вдоль направления, выбранного в текущем такте (ярко-розовая линия).
То есть боты используют гранаты по возможности и не перемещаются в позицию специально для того, чтобы бросить гранату. Однако путь, выбираемый ботом для атаки на врага, часто бывает разумным путём, с которого можно бросить гранату.
Значительным недостатком такой системы является то, что из-за ограниченного количества проверяемых направлений боты упускают возможности бросать гранаты так, чтобы они отскакивали от небольших препятствий. Наиболее заметно это рядом с дверными проёмами, где боты обычно не распознают возможность использования гранаты. Эту проблему можно решить, тестируя в одном кадре несколько направлений и уменьшая таким образом угол между проверяемым направлением и следующим.
Движение к более человеческому поведению
Быстро становится очевидной такая проблема: боты слишком проворно жмут на спусковой крючок, потому их очень сложно победить в схватке один на один. Среднее время реакции человека на визуальные стимулы составляет 250 миллисекунд, но при 20 тактах в секунду, максимальное время реакции бота составит всего 50 миллисекунд!
Чтобы решить эту проблему, я намеренно добавил задержку между моментом, когда бот получает возможность выстрелить, и самим выстрелом, чтобы его время реакции было сравнимо с временем реакции человека.
Разработка хитрого ИИ в тактической игре на основе эвристик и мутаций
В тактических играх ИИ очень важен. Если ИИ видится как «искусственный идиот», то игру может спасти потрясающий мультиплеер, сюжет, атмосфера и графика (это неточно). Решение очевидное: делай хороший ИИ, в чём тут могут быть проблемы?
Краткое описание
В подопытной браузерной игре ИИ основан на генерации множества возможных состояний — результатов выполнения текущего хода. (Из-за игровой специфики и удобства эти результирующие состояния в статье называются то сценариями хода, то стратегиями ИИ — в зависимости от контекста). Затем сценарии хода подвергаются мутациям. По полученным сценариям вычисляются оценки «успешности». Самая успешная и выполняется компьютерным игроком.
Например, генерируются три стратегии:
Ну вроде всё стандартно. Не совсем.
Вся мякотка в том, как генерируются сценарии и как вычисляется коэффициент ценности сценария. Налажаешь в одном из них, и результат тебя опечалит.
Правила игры
У игрока и у ИИ изначально по углам выдаются по 6 одинаковых юнитов. Каждая команда ходит по очереди всеми юнитами сразу. Варианты хода каждого юнита:
Возможные поля на карте:
Игра полностью детерминирована, то есть в ней нет элемента случайности (шанс попадания 100%, никаких критических уронов и т.п.). Также это игра с полной информацией, то есть соперники всё знают о состоянии войск друг друга в любой момент времени. Как в шашках.
ИИ сильнее мясного игрока, но у последнего на первом уровне есть фора в виде одного юнита. На 3-ем у игрока наоборот хандикап в одного юнита и победить гораздо сложнее (у меня около 15% побед на этом этапе). Затем идёт более рандомная версия Игра+.
Игра разработана на нативном javascript: на div-ах и css-стилях, и это было самое неудачное решение из возможных [2]. Это браузерная игра. Движок не использовался. Единственная цель проекта — создать сильного компьютерного игрока «с характером» и возможностью изменения этого характера (расчетливые киборги, агрессивные орки, коварные эльфы, глупые зомби).
Для уменьшения «компьютерного стиля» у противника были применены некоторые хитрости:
И что тут сложного?
Сперва может показаться, что тут все просто: можно просто перебрать все варианты всех ходов и выбрать наилучший. Но очень скоро становится очевидным, что всё очень даже непросто.
Полный перебор невозможен из-за эффекта комбинаторного взрыва [3], который заключается в том, что по мере роста числа проверяемых элементов в сценариях сложность вычислений растет по экспоненте. Далее опишу, что это значит в моей конкретной игре.
Во-первых, т.к. на каждом ходу юниты команды ходят все сразу, то возможна разная их очередность. А при 6 юнитах в команде таких комбинаций становится 720 (1*2*3*4*5*6). Если юнитов будет больше, то комбинаций будет вообще огромное количество (при 7 — 5040, при 8 — 40320. ). Если не учитывать максимального исхода, то игрок рискует распробовать удовольствие в ожидании очередного хода на 5-10 минут (а если он упорный, то задержка дорастёт и до миллионов лет, не каждый вытерпит). Именно из-за этой характеристики мой ИИ в начале боя менее эффективен, чем в конце. Ведь ближе к концу половина команды уже погибла.
Во-вторых, каждый юнит может передвинуться в разные точки карты. Бойцы с дальностью передвижения 4 могут походить на 1-41 разных позиций. У магов и лучников с их передвижением в 3 возможное число ходов равно 1-25. Например, состав команды может быть: 4 бойца, 1 маг и 1 лучник. Итого разных комбинаций ходов по данному пункту мы получаем: 41*41*41*41*25*25 = 1766100625. В действительности из-за взаимных пересечений и непроходимой местности комбинаций будет меньше, но в редкой ситуации «разбегания по карте» число комбинаций будет приближаться к этому числу.
В-третьих, каждый юнит после передвижения может пропустить ход или атаковать в одном из 4 направлений. То есть имеем по 5 возможных завершающих действий на юнита. Всего комбинаций: 5^6 = 15625.
Итого комбинаций: 720 * 1766100625 * 15625 = 19868632031250000.
И в каждой валидной комбинации надо будет рассчитать баллы результирующего состояния. В оценочную функцию входят: эмуляция передвижений, атака, нанесение урона, гибель юнитов и подсчёт оставшихся хитпоинтов у выживших. Конечно, число комбинаций завышено, т.к. в реальных условиях вариативность будет уменьшаться за счёт границ и препятствий на карте, однако это всё равно будет неподъёмное число комбинаций. А всё это происходит ведь в обычном браузере.
Как же сделано?
Чтобы решить подобную задачу, был использован эвристический подход, обобщённый алгоритм которого можно описать так:
Ключевые моменты в этом алгоритме: генерация сценариев, мутации и правильная оценка выгодности состояния. В каждом из этих пунктов используются свои собственные локальные эвристики. Тем не менее, там где можно, использовались алгоритмы с гарантированным оптимальным результатом, например, А* для поиска пути [6].
Использованный мною эволюционный подход нельзя назвать полноценным генетическим [7], т.к. от него я использовал только мутации и выживание «сильнейшего», а коэффициенты влияния отдельных эвристик настраивал вручную. Алгоритмов формирования популяций и скрещиваний не применялось. После мутации выживает только один: либо мутант, либо родитель.
Нейронные сети [8] мною не использовались из-за особенностей задачи. Во-первых, из-за сложности их успешной реализации в условиях постоянно меняющейся среды (появление новых механик, навыков, способностей). Во-вторых, из-за сложности в их контролируемой персонализации (если захочется сделать два поведения: стремительного Суворова и осторожного Кутузова [9]).
Эволюция искусственного идиота в искусственный интеллект
0) Сначала у ИИ были введены только 3 стратегии со случайными ходами. <Сложность игры #0>. Оценка состояния была просто случайным числом. И так как ИИ не единственный элемент разработки, мне пришлось довольно долгое время мириться с поведением сумасшедших рыбок.
1) Затем в расчёты оценки стратегии были добавлены проверки оставшихся юнитов и их жизней у ИИ и у игрока. <Сложность игры #10>. За мертвого юнита команде начислялось 0 баллов. За полностью здорового Х баллов (например, 100 000 за бойца F, 70 000 за лучника A, 85 000 за колдуна W). За раненого начислялись 50% от основной ценности, а оставшиеся 50% пропорционально оставшимся жизням от максимальных. Благодаря этому ИИ было выгоднее добивать врагов, а если он мог только ранить, то он выбирал противников с меньшим числом жизней — более уязвимых.
Случайные ходы стали более осмысленными — ИИ иногда давал сдачи.
2) Затем была добавлена более осмысленная стартовая стратегия:
max_agro — все солдаты бежали максимально ближе к врагам и старались нанести как можно больше урона. <Сложность игры #20>. Одна стратегия использовала изначальный порядок ходов юнита, вторая ходила ими в обратном порядке.
ИИ стал вести себя так, как ведёт себя самый примитивный искусственный идиот в тактических играх. И довольно часто именно такой ИИ в тактических играх и используется. Он популярен из-за своей надежности и простоты. Такой даже может победить — но очень редко.
Именно на это похоже поведение ИИ в провальной игре Master of Monsters – Disciples of Gaia, из-за чего в неё банально скучно играть [10].
3) Дальше были добавлены стратегии, учитывающие возможный урон от врагов при передвижениях, и выбирающие те ходы, которые приводили к наименьшей опасности — желательно нулевой. <Сложность игры #30>. И ИИ сразу же стал сверх трусливым, избегающим любой близости с противником — лучше уж сбежать, чем атаковать и ранить, ведь противник может дать большей сдачи!
Следует отметить, что подобные вычисления возможного урона очень длительны без использования кэша. Один полный просчёт стратегии без оптимизаций изначально занимал 700 миллисекунд. А у меня ведь ограничение на весь ход одним юнитом
4000 мс! После оптимизаций и отработавших кэшей это время уменьшается до 20 миллисекунд при очень похожих стратегиях (к сожалению кэш невозможно просчитать весь заранее из-за эффекта комбинаторного взрыва, поэтому 20 мс достигаются не всегда).
Поэтому когда я внедрял технологию расчета с прогнозированием на несколько ходов вперёд, то время расчетов для глубины только в 2 хода (врага и ИИ) занимало уже +700 миллисекунд. В этом случае применяют оптимизацию с отсечением «слабых» веток. Если для этого пользоваться хоты бы примитивной стратегией max_agro, то увеличение времени было +30 миллисекунд и кэширование эту разницу почти не уменьшало (т.к. позиция на карте была совершенно новой).
В итоге я делал 5 разных заходов к разработке этого подхода, но в конце концов полностью отказался от него, т.к. мутации с эвристиками давали результат лучше и быстрее.
4) Следующие стратегии были направлены на расширение изначального разнообразия стратегий:
far_attack_and_hide — юниты стараются атаковать как можно дальше от противника, а если не атакуют, то прячутся от любой атаки.
close_group_flee — юниты отступают подальше от боя и группируются как можно ближе друг к другу. Если можно при этом безопасно атаковать врага — атаковать.
<Сложность игры #40>.
Это улучшило процесс самого боя, но начало боя все равно было всегда невыгодно для ИИ: он постоянно отступал, но его можно было выманить на атаку и спугнуть так, чтобы группа ИИ разделилась на несколько мелких групп, которые можно было уничтожить по отдельности.
Алгоритм мутаций был очень простой:
Сначала был реализован самый примитивный тип мутации: от 1 до 3 движений заменялись на случайные, порядок ходов оставался прежним. За одну итерацию расчетов в среднем на каждую стратегию создавалось
5-15 мутаций. При этом в среднем каждая пятая мутация была более выгодной и заменяла стратегию родителя.
Эта эвристика повторяла ту тактику, с помощью которой я выманивал ИИ на атаку одним юнитом, чтобы перебить его по одному. Этому трюку удалось научить и ИИ.
Для этого в функции вычисления баллов за состояние стратегии проверяется, соответствует ли текущее состояние ситуации приманки:
7) Потом мне стало бросаться в глаза, что бойцы ИИ постоянно разбегаются как тараканы. <Сложность игры #70>. Также солдаты могли забиться в угол или зайти в тесные тоннели, в которых ИИ сильно терял в своей эффективности перебора возможных атак.
Поэтому в оценочную функцию были добавлены эвристики оценки расстояний между юнитами и рельефа карты со следующими предположениями:
Таким образом добавилось 6 новых стратегий: W_A_F, W_F_A, A_W_F, A_F_W, F_A_W, F_W_A. Они не решили всех проблем, но заметно улучшили качество игры.
9) У меня были мутации, но толку от них было мало. <Сложность игры #90>. В основном они улучшали слабые стратегии, а удачные улучшались редко. Поэтому мутации были доработаны и каждый раз срабатывал один из случайных типов мутации:
10) Затем были добавлены еще полуслучайные стратегии. <Сложность игры #100>. Порядок ходов генерировался случайно, а сами ходы выбирались по следующим принципам (по уменьшению их важности):
11) Мне надоели вопиющие ошибки ИИ, когда он при атаке своим колдуном сильно задевал моих солдат, но при этом ранил своих союзников. <Сложность игры #110>. Хотя перед этим он вообще-то мог походить ими и убрать их с линии огня. Поэтому была создана жёстко сгенерированная стратегия с ручными проверками:
12) Иногда юниты «убегали в кусты» прямо перед началом боевых действий. <Сложность игры #120>. В результате этого, когда начинался обмен атаками, то один или даже два юнита могли оказаться слишком далеко от военных действий и не помогали союзникам. Если это случалось, то я почти гарантированно выигрывал у ИИ. Если не случалось, то я чаще проигрывал. Избавлялся от этого я вводом новой эвристики по оценке результирующих баллов у стратегии. Для каждого юнита проводилась проверка:
Это позволило сильно улучшить «сплоченность» юнитов ИИ. К сожалению, начали появляться случаи «одного дезертира», когда в проигрышной ситуации один из раненых юнитов предпочитал прятаться за спинами своих товарищей вместо того, чтобы внести свою лепту, атаковав врага. Это нелепо выглядело, когда у компьютера остаётся всего 2 юнита, а у игрока 3 или даже больше. Дополнительная исправляющая эвристика представляет собой следующее правило:
13) Под конец ввода стратегий их набралось уже под 25 штук. <Сложность игры #130>.
Мутировать каждую из них стало уж слишком накладно. Поэтому было принято решение удалять самые неудачные и оставлять только 8 штук. С самого начала я не хотел использовать этот подход в расчете на то, что мутация аутсайдеров может привести к неожиданному отличному результату, вместо простого хорошего. Ввод данной обработки в итоге привел к улучшению игры ИИ.
14) Примерно в начале была ещё интересная доработка. Изначально оценка ценности сценария вычислялась как разница сумм баллов:
Но спустя несколько улучшений я вспомнил, что это не самое лучшее решение, т.к. тогда для ИИ будут одинаковыми ситуации «2 солдата против 1 одного солдата» и «4 солдата против 3 солдат». Поэтому баллы стали вычисляться как отношение:
Изменение небольшое, а результат очень серьезный. Без доработки цена ошибки при повышенном риске всегда была одинакова. После доработки ИИ стал меньше безалаберно рисковать к концу сражения, и это заметно усилило его.
Хочу отметить, что все эти доработки вводились постепенно хоть и в указанном порядке, но многие из них улучшались, перерабатывались и исправлялись от багов в более хаотическом порядке. Реальных итераций было больше 100 штук.
ИИ ходит сразу, а не тратит время на раздумья
Для ускорения самих вычислений активно использовались оптимизации алгоритмов в виде разбиений вложенных циклов на последовательные циклы (уменьшение сложности) и внедрение нескольких массивов с кэшированными предварительными вычислениями (и последующей оптимизации еще этих самых кэшей). По моим прикидкам дальнейшие оптимизации смогли бы мне обеспечить еще двойной (или даже больший) прирост к скорости, но это бы привело к неоправданному росту временных затрат и дальнейшей ещё большей потери читаемости кода.
Основная технология быстрого хода — это предварительные вычисления во время простоя. Этот метод заключается в том, чтобы разбить процесс хода на 2 части: сами вычисления и показ анимаций результатов вычислений:
Этим я хотел избавиться от раздражающего окошка ожидания «Компьютер ходит». Такая неприятная плашка есть во многих хороших играх, например, в Xenonauts [12]. Я считаю, что мне удалось справиться с этой проблемой.
Таким образом, ИИ тратит на обдумывание своего хода всегда одинаковое время — независимо от его сложности. Очень любопытная особенность этого подхода в том, что чем сильнее у игрока компьютер, тем большее число мутаций ИИ успеет перебрать, а значит будет тем сильнее, чем мощнее компьютер игрока. Я сначала убрал данный эффект с помощью фиксации времени хода и предварительного подсчета скорости работы компьютера. Однако потом я убрал эту фиксацию, т.к. владельцам мощных компьютеров это позволит сразиться со «своим» компьютером, а не усреднённым.
Каков результат и в чём недостатки
Таким образом, получившийся компьютерный противник умеет достойно сражаться и хорошо пользуется любыми оплошностями игрока, а своих делает не слишком много. Тем не менее, я, зная все особенности его работы, хоть и с напряжением, но побеждаю его почти всегда (при равных условиях). А хотелось бы наоборот: чтобы даже зная о его особенностях, почти всегда ему проигрывать. ИИ далёк от идеала, поскольку используемый мною набор эвристик приводит к синергетическому наложению «ошибок моего восприятия» друг на друга. Вот эти ошибки:
Дополнительные возможности
Подобный способ реализации позволяет добиться дополнительных бонусов в игровой разработке (во многом с точки зрения разработчика и его горящих сроков):
Где пощупать
Вы можете протестировать силу этого ИИ в браузерке «AI tactical rumble. Test subject» бесплатно на площадках типа itch.io [13]. GET параметр ai (значения от 0 до 140 с шагом 10) позволит снизить сложность ИИ.
По моим ожиданиям победить ИИ на равных условиях Вам будет очень и очень сложно. Даже после привыкания к правилам игры. Я рекомендую рассматривать данную игру, как прототип, каковым она по сути и является (музыки, звуков и цены в ней нет).
Пожалуйста, оставляйте своё мнение в комментариях об интересности ИИ, советы и критику о возможной реализации ИИ с помощью различных методов обучения. Если Вам вдруг стали интересны другие мои изыскания, пожалуйста, рассмотрите возможность подписки здесь на мой аккаунт.