TicTacToe AI делает неправильные решения

Немного фона: как способ узнать многоуровневые деревья на С++, я решил создать все возможные платы TicTacToe и сохранить их в дереве, так что ветка, начинающаяся с node, является всеми платами, которые могут следовать из этого node, а дочерние элементы node - это платы, которые следуют за одним шагом. После этого я подумал, что было бы интересно написать AI, чтобы играть в TicTacToe, используя это дерево в качестве дерева решений.

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

Теперь, когда я впервые реализовал AI, я вернулся и добавил два поля для каждого node при генерации: количество раз, которое X будет выигрывать, а количество раз O будет выигрывать у всех детей ниже node. Я решил, что лучшим решением было просто заставить мой ИИ на каждом ходу выбрать и спуститься по поддереву, где он выигрывает больше всего. Затем я обнаружил, что, хотя он играет большую часть времени, я нашел способы, которыми я мог бы победить. Это не проблема с моим кодом, просто проблема с тем, как у меня был AI, выберите его путь.

Затем я решил выбрать дерево с максимальными выигрышами для компьютера или максимальными потерями для человека, в зависимости от того, что больше. Это сделало его ЛУЧШИМ, но все же не идеальным. Я все еще мог победить.

Итак, у меня есть две идеи, и я надеюсь на ввод, который лучше:

1) Вместо того, чтобы максимизировать выигрыши или потери, вместо этого я мог бы присвоить значения 1 для выигрыша, 0 для ничьей и -1 для потери. Тогда выбор дерева с наивысшим значением будет лучшим, потому что следующий node не может быть шагом, приводящим к потере. Это легкое изменение в генерации платы, но оно сохраняет одно и то же пространство поиска и использование памяти. Или...

2) Во время создания платы, если есть доска, в которой X или O победят в следующем шаге, будет создан только ребенок, который предотвратит эту победу. Никакие другие дочерние узлы не будут рассмотрены, и после этого поколение будет продолжать как обычно после этого. Он уменьшает размер дерева, но затем я должен реализовать алгоритм, чтобы определить, есть ли один выигрыш в игре, и я думаю, что это можно сделать только в линейном времени (что делает создание платы намного медленнее, я думаю?)

Что лучше, или есть еще лучшее решение?

Ответ 1

Правильный способ реализации ИИ на основе дерева решений (обычно) заключается в использовании алгоритма Minimax:

  • Присвойте каждому листу node счет (+ 1 = победитель игрока, -1 = проигрыш игрока, 0 = галстук)
  • Пройдитесь по дереву, применяя следующие правила к каждому node:

    • Для четных глубин (когда игрок совершит движение) выберите ребенка с наивысшим баллом и скопируйте его в node.
    • Для нечетных глубин (когда компьютер совершит движение) выберите ребенка с наименьшим баллом и скопируйте его на node.

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

Вы можете узнать больше:

Ответ 2

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

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

Ответ 3

Tic-Tac-Toe можно решить, используя жадный алгоритм и на самом деле не требующий дерева решений.

Если вы хотите продолжить использовать свой текущий алгоритм, сделайте, как предлагает патрос, и свести к минимуму возможность потери при каждом решении.

Если вы хотите, чтобы более простой подход имел AI, выполните следующие действия:

  • Если возможно, завершите победитель Tic-Tac-Toe.
  • Если возможно, заблокируйте противоположный Tic-Tac-Toe.
  • Оцените каждый квадрат по своей желательности, для каждого другого принятого квадрата (по AI) на линии, добавьте одну точку желательности для этого квадрата. Для каждого квадрата, взятого противником, удалите одну точку желательности.

    Например, если в данный момент находится плата:

    _|O|X
    _|X|_
    O| |
    

    Верхний левый угол имеет желательность 0 (1 для X в той же строке и 1 для X в диагонали, но -1 для каждой из Os).

  • Играйте на наиболее желательном квадрате. Нарушение связей произвольно.

    В приведенном выше примере ИИ выберет средний квадрат справа, так как он имеет желательность 2, что приведет к победе в следующем повороте.

  • Если игра только началась, играйте в квадрат центра, если квадрат квадрата берется, выберите угол в случайном порядке.

  • Выиграть (или связать).

Это мой проект класса Visual Basic 10 класса. Невозможно бить и требует гораздо меньше памяти, чем хранение дерева решений.

Ответ 4

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

Каждый раз, когда вы поднимаетесь вверх (один такой шаг обычно называется слоем), в зависимости от того, кто его движет, предположите, что игрок выбирает ход, который лучше для него/нее. Поскольку вы двигаетесь с листьев и вверх, вы всегда будете знать оптимальные возможные результаты для каждого ребенка node.

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