Автор оригинала: Oluwatobi Sofela.
Недавно я задавался вопросом – как я могу запрограммировать компьютер, чтобы быть непобедимым в игру Tic-Tac-Toe?
Ну, я думал, что смогу легко получить ответ на этот вопрос. Но, поскольку я пошел взад и вперед из статьи в видео к серии медитаций кодирования, мне удалось только стать более запутанным.
Тем не менее, мой “ага!” момент пришел, когда я потратил время, чтобы понять, как Минимакс алгоритм работает.
Если вы также находятся на аналогичном пути, позвольте мне взять вас через шаги, чтобы построить непревзойденный AI (искусственный интеллект).
Шаг 1: понять основы алгоритма MiniMax
А Минимакс алгоритм это рекурсивный Программа написана, чтобы найти лучший игровой процесс, который минимизирует любую тенденцию потерять игру при максимизации любой возможности выиграть игру.
Графически мы можем представлять minimax как исследование Игра Дерево узлы Чтобы открыть для себя лучшее движение, чтобы сделать. В таком случае корня дерева – текущее состояние игры – где был вызван алгоритм MiniMax.
Наше внимание в этом руководстве состоит в том, чтобы использовать MiniMax для создания непревзойденного AI для игры Tic-Tac-Tac-Tac-Tace. Тем не менее, Вы также можете использовать его для сложных игр , как шахматы и принятие общего принятия решений для решения любых неопределенностей.
В большинстве случаев игрок, который изначально вызывает MiniMax, называется Максимизирующий плеер Отказ Другими словами, оригинальный invocator of minimax – игрок, который хочет максимизировать любую возможность выиграть игру.
Напротив, оппонент максимизации игрока называется Минимизирующий плеер Отказ Таким образом, минимизирующий проигрыватель – это игрок, чьи шансы на победу должны быть минимизированы.
Короче говоря, алгоритм MiniMax – это рекурсивная функция, созданная для того, чтобы помочь игроку (максимайзеру) принять решение о игровом процессе, который минимизировать Максимум Возможность потерять игру.
Шаг 2: Познакомьтесь с корневым узлом этого учебника
Чтобы сделать этот учебник точным, корневым узлом (текущее состояние игры TIC-TAC-TAC-TAC), которое мы будем использовать, будет ближе ключевой государственной игровой доски – как показано на рисунке 2 ниже.
Кроме того, Х Марк будет представлять знак АИ, а O Марк будет следом человека.
На текущем этапе игры Tic-Tac-Toe (как показано на рисунке 2 выше), это Икс Обратимся в игру (то есть поворот АИ). И поскольку на доске три пустых ячеек, это подразумевает, что Х имеет три возможных варианта воспроизведения – первоклассный, центр или нижний правый.
Но что является лучшим выбором? Какой ход будет лучше всего помочь Х Минимизируйте максимальную возможность потерять игру?
Чтобы сделать лучшее решение, AI должен сделать следующее:
- Храните текущее состояние (значения) доски Tic-Tac-Toe в массиве. (Для любой пустой ячейки индекс ячейки будет храниться как его нынешний контент).
- Получить список массива Только пустые ячейки « индексы.
- Проверьте и подтвердите, выиграл ли конкретный проигрыватель игры.
- Рекурсивно вызывать MiniMax. на каждой из пустых ячеек доски.
- Верните счет для каждого возможного движения для обоих игрок Х и игрок O .
- Из всех возвращенных баллов выберите лучший (самый высокий), который гарантированно минимизирует возможности выигрышей игры человека.
Следовательно, в следующих шагах ниже мы настроим AI для выполнения списка выше. Итак, давайте начнем, сохраняя текущее состояние доски в массиве.
Шаг 3: Храните текущее состояние доски в массиве
Наш следующий шаг состоит в том, чтобы сохранить текущее содержание каждой из ячеек Совета в массиве, как:
const currentBoardState = ["X", 1, "O", "X", 4, "X", "O", "O", 8];
Примечание:
- Текущее состояние нашего доска TIC-TAC-TAC-TAC по-прежнему, как показано на рисунке 2.
- Значения
1
,4
и8
ВCurrentboardState
Массив являются номерами пустых ячеек доски. Другими словами, вместо использования пустых струн, мы решили хранить текущее содержание пустых ячеек в качестве соответствующих индексов.
Главное, прежде чем перейти к следующему шагу, давайте явно определим, чья знак "Х"
А кому владеет «О»
Отказ
const aiMark = "X"; const humanMark = "O";
Приведенные выше заявления обозначают, что Марк AI – Икс Пока Марк человека – O .
Шаг 4: Создайте функцию, чтобы получить индексы всех пустых ячеек
Функция ниже будет фильтровать CurrentboardState
Массив – который будет передан в качестве аргумента параметра функции. Затем он вернет новый массив, содержащий все CurrentboardState
Предметы массива, которые не являются ни "Х"
ни «О»
Отказ
function getAllEmptyCellsIndexes(currBdSt) { return currBdSt.filter(i => i != "X" && i != "O"); }
Примечание: Помните, что CurrentboardState
Массив, который мы создали на шаге 3, содержит только значения "Х"
, «О»
и Индексы пустых ячеек доски Отказ Поэтому getAllememptycellsindexes ()
Функция выше фильтрует любое возникновение индекса в CurrentboardState
множество.
Шаг 5: Создать функцию определителя победителя
Основная цель Функция определения победителя ниже – получить CurrentboardState
Массив и значок конкретного игрока (либо марки «X»
или «O»
) в качестве аргументов его параметров.
Затем он проверяет, формирует ли полученный знак выигрышная комбинация на доске Tic-Tac-Toe. Если это так, логическое значение правда
возвращается – иначе ложь
возвращается.
function checkIfWinnerFound(currBdSt, currMark) { if ( (currBdSt[0] === currMark && currBdSt[1] === currMark && currBdSt[2] === currMark) || (currBdSt[3] === currMark && currBdSt[4] === currMark && currBdSt[5] === currMark) || (currBdSt[6] === currMark && currBdSt[7] === currMark && currBdSt[8] === currMark) || (currBdSt[0] === currMark && currBdSt[3] === currMark && currBdSt[6] === currMark) || (currBdSt[1] === currMark && currBdSt[4] === currMark && currBdSt[7] === currMark) || (currBdSt[2] === currMark && currBdSt[5] === currMark && currBdSt[8] === currMark) || (currBdSt[0] === currMark && currBdSt[4] === currMark && currBdSt[8] === currMark) || (currBdSt[2] === currMark && currBdSt[4] === currMark && currBdSt[6] === currMark) ) { return true; } else { return false; } }
Шаг 6: Создайте алгоритм MiniMax
А Минимакс алгоритм это просто обычная функция, которая содержит операторы, которые должны быть выполнены после того, как функция вызывается. Следовательно, процесс создания алгоритма совпадает с созданием любой другой функции. Итак, давайте создадим один сейчас.
function minimax(currBdSt, currMark) { // Space for the minimax's statements }
Вот и все! Мы создали MiniMax Функция – хотя и пустой. Наш следующий шаг – заполнить функцию с операторами, которые будут выполняться после того, как функция будет вызвана, которую мы сделаем ниже.
Примечание: Функция MiniMax, созданная выше, предназначена для принятия Два аргумента . Первый – массив Список содержимого нынешнего совета – то есть настоящая стоимость CurrentboardState
множество. Пока второй аргумент – Марка игрока в настоящее время управляет алгоритм MiniMax – то есть марки "Х"
или Марк «О»
Отказ
Шаг 7: Первый MiniMax Covocation
Чтобы избежать каких-либо путаницы позже в этом руководстве, давайте вызовем нашу функцию MiniMax в первый раз – при прохождении – в CurrentboardState
Массив и Аймарк
как аргументы функции.
const bestPlayInfo = minimax(currentBoardState, aiMark);
Шаг 8: Храните индексы всех пустых ячеек
На этом шаге мы вызовем getAllemptycellsindexes
Функция, которую мы создали на шаге 4 – во время прохождения CurrentboardState
массив в качестве аргумента функции.
Тогда мы храним Вернулся Массивный список индексов внутри переменной с именем Доступность Cellsindexes
Отказ
const availCellsIndexes = getAllEmptyCellsIndexes(currBdSt);
Шаг 9: Проверьте, есть ли состояние терминала
На этом этапе нам необходимо проверить, есть ли состояние терминала (то есть состояние убытки, состояние выигрыша или состояние ничьей) на доске Tic-Tac-TaC-Toe. Мы сделаем эту проверку, вызвав Функция определения победителя (создан на шаге 5) для каждого из игроков.
Если функция найдет состояние WIN для человека (минимизатор), он вернется -1
(который означает, что человек выиграл человека, и АИ потерял). Но если он найдет государство победы для игрока AI (максимизатор), он вернется +1
(что указывает на то, что ИИ выиграл, и человек проиграл).
Тем не менее, предположим, что функция определения победителя не может найти пустую ячейку на плате или любое состояние Win для любого проигрывателя. В этом случае это вернется 0
(Zero) – что означает, что игра закончилась в галстуке.
Примечание: Оценки ( -1
, +1
и 0
) Указанные выше, являются эвристический Значения – это означает, что мы все равно получим тот же результат, если предпочитаем использовать -25, +25 и 0.
Давайте теперь приступим к выполнению проверки состояния терминала с помощью Если выписка вот так:
if (checkIfWinnerFound(currBdSt, humanMark)) { return {score: -1}; } else if (checkIfWinnerFound(currBdSt, aiMark)) { return {score: 1}; } else if (availCellsIndexes.length === 0) { return {score: 0}; }
Когда существует состояние терминала (проигрыш, выигрыш или розыгрыш), функция Active MiniMax вернет соответствующую оценку состояния терминала ( -1
, +1
или 0
) и положить конец его вызова.
Если активный минимакс заканчивает его вызов здесь, алгоритм переместится на шаг 12.
Однако, когда есть нет Заключительное состояние, функция Active MiniMax выполнит следующее утверждение (шаг 10 ниже).
Шаг 10: будьте готовы проверить результат игры в знак текущего игрока на каждой пустой ячейке
Поскольку шаг 9 не нашел никакого состояния терминала, мы должны разработать способ проверить, что произойдет, если текущий проигрыватель (который должен сделать следующую игру двигаться), играет на каждой пустой ячейке.
Другими словами, если текущий проигрыватель играет на первой доступной ячейке, и противник играет на второй пустой ячейке, будет ли нынешним проигрывателем, проиграл или нарисовать игру? Или все еще не будет обнаружено терминальное состояние?
В качестве альтернативы, что произойдет, если текущий проигрыватель играет на второй доступной ячейке, а противник играет на первой пустой ячейке?
Или, возможно, третья доступная клетка будет лучшим местом для нынешнего игрока, чтобы играть?
Этот тестовый диск – это то, что нам нужно сделать сейчас. Но прежде чем мы начнем, нам нужно место для записи результата каждого теста – так что давайте сделаем это сначала, создав массив с именем alltestplayinfos
Отказ
const allTestPlayInfos = [];
Итак, теперь, когда мы окрепили место для хранения результата каждого тестового диска, давайте начнем испытания, создавая Заключение для петли Это будет петлю через каждый из пустых ячеек, начиная с первого.
for (let i = 0; i < availCellsIndexes.length; i++) { // Space for the for-loop's codes }
В следующих двух шагах мы будем заполнять форум с кодом, оно должно работать для каждой пустой ячейки.
Шаг 11: Pream-Play Марка текущего игрока на пустой ячейке For-Loop в настоящее время обрабатывается
Прежде чем делать что-либо на этом шаге, давайте рассмотрим текущее состояние нашего совета.
Обратите внимание, что вышеуказанная плата по-прежнему такой же, как на рисунке 2, за исключением того, что мы выделили – в красном – ячейку в настоящее время обрабатывается.
Далее будет полезно иметь место для хранения этой оценки терминальной игры Test-Play – поэтому давайте создадим вроде объекта:
const currentTestPlayInfo = {};
Кроме того, перед тестированным воспроизведением знака текущего проигрывателя на красной ячейке давайте сохраним номер индекса клеток – так что будет легко сбросить информацию о ячейке после этой тестовой игры.
currentTestPlayInfo.index = currBdSt[availCellsIndexes[i]];
Давайте теперь будем разместить отметку текущего игрока на красной ячейке (то есть ячейку в настоящее время обрабатывается на петли).
currBdSt[availCellsIndexes[i]] = currMark;
На основании игрового процесса текущего игрока государство Совета изменится, чтобы отразить его последний шаг.
Поэтому, поскольку государство Совета изменилось, нам нужно рекурсивно запустить MiniMax на новой доске – во время прохождения в штате Новой Совета и Следую маркой следующего игрока.
if (currMark === aiMark) { const result = minimax(currBdSt, humanMark); currentTestPlayInfo.score = result.score; } else { const result = minimax(currBdSt, aiMark); currentTestPlayInfo.score = result.score; }
Примечание:
- Рекурсивный вызов MiniMax на этом самом случае будет _____ время, когда мы вызываем функцию. Первый вызов произошел на шаге 7.
- Этот рекурсивный вызов приведет к возобночению шагов от 8 до 11.
- Предположим, что на этапе 9 есть терминальное состояние. В этом случае текущий вызов MiniMax перестанет работать – и хранить возвращенный клеммный объект (например,
{Оценка: 1}
) вРезультат
Переменная. - После того, как есть терминальное состояние, шаг 12 будет следующим шагом.
- Если существует нет терминальное состояние, а Второй для петли начнется для новой доски на шаге 10.
- Если шаг 10 повторяется, замените на рисунке 4 с новой доской на рисунке 5. Тем не менее, ячейка, выделенная красным, теперь будет ячейкой, которая в настоящее время обрабатывается. Поэтому, пожалуйста, отражайте изменения соответственно.
Шаг 12: Сохранить новейший балл терминала
После того, как только что заключенный вызов MiniMax вернул значение своего терминала состояния, активная закрутка сохранит Результат
Оценка переменной в CurrentTestPlayInfo
Объект так:
currentTestPlayInfo.score = result.score;
Затем, поскольку возвращенный балл официально заканчивает текущую тестовую игру, лучше всего сбросить текущую доску обратно в состояние, прежде чем текущий проигрыватель сделал его движение.
currBdSt[availCellsIndexes[i]] = currentTestPlayInfo.index;
Кроме того, нам нужно сохранить результат тестовой игры текущего игрока для будущего использования. Итак, давайте сделаем это, нажав CurrentTestPlayInfo
объект к alltestplayinfos
Массив так:
allTestPlayInfos.push(currentTestPlayInfo);
Примечание:
- Если вы добрались до этого шага с шага 17, пожалуйста, продолжите этот урок на Шаг 18 Отказ В противном случае рассмотрите следующий момент.
- Если активная закрутка заканчивается зацикливаться через все пустые ячейки нынешнего платы, цикл завершится на данной точке, а Шаг 14 будет дальше. В противном случае цикл будет переходить к обработке следующей доступной ячейки (этап 13).
Шаг 13: Запустите активную поддержку на следующей пустой ячейке
Помните, что в настоящее время действующая в настоящее время (которая началась на этапе 10), только закончила свою работу для предыдущих пустых ячейки (ы). Следовательно, цикл будет переходить к тестированию – воспроизвести знак текущего проигрывателя на следующей свободной клетке.
Другими словами, текущая работа MiniMax функция повторяет шаги 11 и 12 Отказ Но, по сути, обратите внимание на следующее:
- Красная ячейка, выделенная на рисунке 4, будет изменяться в клетку для цикла в настоящее время обработка.
- Пожалуйста, помните, что рисунок 5 также изменится. Другими словами, движение текущего проигрывателя теперь будет на ячейке, который в настоящее время обрабатывает.
- После того, как активное для цикла завершила свою работу,
alltestplayinfos
Массив будет содержать определенные объекты для каждой пустой ячейки, обрабатываемой для петли. - Каждый из объектов в
alltestplayinfos
Массив будет содержатьиндекс
Собственность и AОценка
Собственность (предпринять, например:{index: 8, оценка: -1}
). - Если вы добрались до этого шага с шага 20, то На завершение шага 12 Пожалуйста, продолжайте этот учебник на Шаг 18 Отказ
Шаг 14: Запланируйте, как получить объект с лучшим результатом тестового игрока для текущего проигрывателя
Сразу после того, как FOR-LOOP завершил свою работу зацикливания через все пустые ячейки нынешней доски, MiniMax будет:
- Создать пространство Чтобы сохранить ссылочный номер, который позже поможет получить лучший объект тестового воспроизведения.
- Получить ссылочный номер Лучшему тестируемую тестовую игру нынешнего игрока.
- Используйте приобретенный ссылочный номер Чтобы получить объект с лучшими тестовыми играми для текущего проигрывателя.
Без каких-либо дальнейших ADO давайте реализовать этот план в следующие несколько шагов.
Шаг 15: Создайте магазин для лучшей версии Test-Play
Переменная ниже – это место, которое позже будет хранить ссылку на лучший объект Test-Play. (Обратите внимание, что значение NULL
Указывает, что мы сознательно оставили переменную пустую).
let bestTestPlay = null;
Шаг 16: Получите ссылку на лучшую тестовую игру Text-Play
Теперь, когда есть BesttestPlay
Хранить, Active MiniMax функция может продолжить, чтобы получить ссылку на лучшую тестовую игру Teque Flayer так:
if (currMark === aiMark) { let bestScore = -Infinity; for (let i = 0; i < allTestPlayInfos.length; i++) { if (allTestPlayInfos[i].score > bestScore) { bestScore = allTestPlayInfos[i].score; bestTestPlay = i; } } } else { let bestScore = Infinity; for (let i = 0; i < allTestPlayInfos.length; i++) { if (allTestPlayInfos[i].score < bestScore) { bestScore = allTestPlayInfos[i].score; bestTestPlay = i; } } }
Код выше означает, что текущая отметка равна знаке игрока AI:
- Создать
Bestscore
переменная со значением-Infinity
Отказ (Обратите внимание, что это значение – это просто значение заполнителя, которое должно быть меньше, чем все оценки в массивеAlltestPlayInfos
. Поэтому, используя-700
сделаю ту же работу). - Затем для каждого объекта тестового воспроизведения в
alltestplayinfos
Массив, проверьте, является ли тестовая пленка в настоящее время обработка, имеет Высший Оценка, чем текущийлучший результат
. Если это так, запишите эти детали тестирования в обоихBestscore
Переменная иBesttestPlay
Переменная.
В противном случае, если текущий знак – отметку человека:
- Создать
Bestscore
переменная со значением+ Бесконечность
Отказ (Опять же, обратите внимание, что мы получим тот же результат, если бы мы предпочли использовать+300
. Это просто ценность заполнителя, которая должна быть больше чем Все оценки вalltestplayinfos
множество). - Затем для каждого объекта тестового воспроизведения в
alltestplayinfos
Массив, проверьте, является ли тестовая пленка в настоящее время обработка, имеет меньший Оценка, чем текущийлучший результат
. Если это так, запишите эти детали тестирования в обоихBestscore
Переменная иBesttestPlay
Переменная.
Шаг 17: Получить объект с лучшим результатом тестового игрока для текущего игрока
Наконец, в настоящее время запущена вызов MiniMax теперь может завершить свою работу, вернув объект с лучшими тестовой игрой для текущего проигрывателя, который так:
return allTestPlayInfos[bestTestPlay];
Обратите внимание, что MiniMax будет хранить возвращенный объект внутри Результат
Переменная первого для петли, которая началась на этапе 11. Затем он будет повторять шаг 12. Пожалуйста, верните только шаг 12. Затем продолжайте этот учебник ниже.
Шаг 18: Давайте сделаем обзор
Этот этап – отличное время, чтобы просмотреть то, что мы сделали до сих пор наглядно наглядно.
Примечание:
- Если это ваш первый раз на этом шаге, пожалуйста, используйте диаграмму в Шаг 19 Отказ
- Это ваш второй раз на этом шаге? Если это так, диаграмма в Шаг 21 это твое.
- Вы здесь в третий раз? Отличная работа! Проверьте диаграмму в Шаг 23 Отказ
Шаг 19: Отслеживание наших шагов с диаграммой
Диаграмма ниже показывает AI и человеческий игрок Первая тестовая игра Для первого вызова контура, инициированного игроком AI.
Шаг 20: Первый для петли перемещается вперед для обработки следующей пустой ячейки
При заключении, что игра на первой пустой ячейке закончится в состоянии убытков, AI подходит вперед, чтобы проверить результат игры на Вторая свободная клетка повторяя шаг 13.
Шаг 21: Отслеживание наших шагов с диаграммой
Диаграмма ниже показывает AI и человеческий игрок Вторая тестовая игра Для первого вызова контура, инициированного игроком AI.
Шаг 22: Первый для петли перемещается вперед для обработки следующей пустой ячейки
Теперь, когда AI подтвердил, что игра на второй пустой ячейке приведет к завоему государству, он дополнительно проверяет результат игры на третье свободная клетка повторяя шаг 13.
Шаг 23: Отслеживание наших шагов с диаграммой
Диаграмма ниже показывает AI и человеческий игрок Третья тестовая игра Для первого вызова контура, инициированного игроком AI.
Шаг 24: Получить объект с лучшим результатом тестовой игры для игрока AI
На данный момент (после третьей тестовой пьесы) первый для петли обрабатывал бы все три пустых ячеек первой платы (передается в MiniMax на шаге 7).
Следовательно, MiniMax будет предлагать возможность получить объект с лучшими тестовыми играми для игрока AI – повторяя шаги 15-17. Тем не менее, Когда на шаге 17 Пожалуйста, обратите внимание на следующее:
- Возвращенный объект теперь будет храниться в
BestPlayInfo
Переменная, которую мы создали на шаге 7. - MiniMax не повторит шаг 12, потому что оператор For-Loop больше не активен.
Шаг 25: Используйте данные внутри BestPlayInfo
Учитывая совет этого учебника (ближневосточная государственная игровая доска – как показано на рисунке 2 шага 2), объект в BestPlayInfo
Переменная будет {index: 4, оценка: 1}
Отказ Следовательно, AI теперь может использовать свое значение индекса для выбора лучшей ячейки для воспроизведения.
Пример
// Get all the board's cells: const gameCells = document.querySelectorAll(".cell"); // Below is the variable we created at step 3: const aiMark = "X"; // Here is the bestPlayInfo we created at step 7 to contain the best test-play object for the AI player: const bestPlayInfo = minimax(currentBoardState, aiMark); // Play the AI's mark on the cell that is best for it: gameCells[bestPlayInfo.index].innerText = aiMark;
Поэтому игрок AI выиграет игру, а новая доска теперь будет выглядеть так:
Шаг 26: Вид птичьего полета алгоритма этого учебника
Ниже приведен этот минимумный алгоритм этого учебника в одной части. Не стесняйтесь вставить его в свой редактор. Играйте с ним для различных игровых сценариев и используйте консоль, чтобы проверить, тестировать и тестировать его снова, пока вам не удобно построить непревзойденный AI.
И помните, программирование лучше только тогда, когда вы Код сладко – Так есть много веселья с этим!
// Step 3 - Store the board's current state in an array and define each mark's owner: const currentBoardState = ["X", 1, "O", "X", 4, "X", "O", "O", 8]; const aiMark = "X"; const humanMark = "O"; // Step 4 - Create a function to get the indexes of all the empty cells: function getAllEmptyCellsIndexes(currBdSt) { return currBdSt.filter(i => i != "O" && i != "X"); } // Step 5 - Create a winner determiner function: function checkIfWinnerFound(currBdSt, currMark) { if ( (currBdSt[0] === currMark && currBdSt[1] === currMark && currBdSt[2] === currMark) || (currBdSt[3] === currMark && currBdSt[4] === currMark && currBdSt[5] === currMark) || (currBdSt[6] === currMark && currBdSt[7] === currMark && currBdSt[8] === currMark) || (currBdSt[0] === currMark && currBdSt[3] === currMark && currBdSt[6] === currMark) || (currBdSt[1] === currMark && currBdSt[4] === currMark && currBdSt[7] === currMark) || (currBdSt[2] === currMark && currBdSt[5] === currMark && currBdSt[8] === currMark) || (currBdSt[0] === currMark && currBdSt[4] === currMark && currBdSt[8] === currMark) || (currBdSt[2] === currMark && currBdSt[4] === currMark && currBdSt[6] === currMark) ) { return true; } else { return false; } } // Step 6 - Create the minimax algorithm: function minimax(currBdSt, currMark) { // Step 8 - Store the indexes of all empty cells: const availCellsIndexes = getAllEmptyCellsIndexes(currBdSt); // Step 9 - Check if there is a terminal state: if (checkIfWinnerFound(currBdSt, humanMark)) { return {score: -1}; } else if (checkIfWinnerFound(currBdSt, aiMark)) { return {score: 1}; } else if (availCellsIndexes.length === 0) { return {score: 0}; } // Step 10 - Create a place to record the outcome of each test drive: const allTestPlayInfos = []; // Step 10 - Create a for-loop statement that will loop through each of the empty cells: for (let i = 0; i < availCellsIndexes.length; i++) { // Step 11 - Create a place to store this test-play's terminal score: const currentTestPlayInfo = {}; // Step 11 - Save the index number of the cell this for-loop is currently processing: currentTestPlayInfo.index = currBdSt[availCellsIndexes[i]]; // Step 11 - Place the current player's mark on the cell for-loop is currently processing: currBdSt[availCellsIndexes[i]] = currMark; if (currMark === aiMark) { // Step 11 - Recursively run the minimax function for the new board: const result = minimax(currBdSt, humanMark); // Step 12 - Save the result variable's score into the currentTestPlayInfo object: currentTestPlayInfo.score = result.score; } else { // Step 11 - Recursively run the minimax function for the new board: const result = minimax(currBdSt, aiMark); // Step 12 - Save the result variable's score into the currentTestPlayInfo object: currentTestPlayInfo.score = result.score; } // Step 12 - Reset the current board back to the state it was before the current player made its move: currBdSt[availCellsIndexes[i]] = currentTestPlayInfo.index; // Step 12 - Save the result of the current player's test-play for future use: allTestPlayInfos.push(currentTestPlayInfo); } // Step 15 - Create a store for the best test-play's reference: let bestTestPlay = null; // Step 16 - Get the reference to the current player's best test-play: if (currMark === aiMark) { let bestScore = -Infinity; for (let i = 0; i < allTestPlayInfos.length; i++) { if (allTestPlayInfos[i].score > bestScore) { bestScore = allTestPlayInfos[i].score; bestTestPlay = i; } } } else { let bestScore = Infinity; for (let i = 0; i < allTestPlayInfos.length; i++) { if (allTestPlayInfos[i].score < bestScore) { bestScore = allTestPlayInfos[i].score; bestTestPlay = i; } } } // Step 17 - Get the object with the best test-play score for the current player: return allTestPlayInfos[bestTestPlay]; } // Step 7 - First minimax invocation: const bestPlayInfo = minimax(currentBoardState, aiMark);
Полезный ресурс
Оригинал: “https://www.freecodecamp.org/news/minimax-algorithm-guide-how-to-create-an-unbeatable-ai/”