Рубрики
Без рубрики

Tic-Tac-Toe с алгоритмом MiniMax

Простая реализация алгоритма MiniMax для Tic-Tac-Toe в Python. Помечено Python, Tictactoe, MiniMax, алгоритмы.

TIC-TAC-TOE (4 части серии)

В этой статье я хотел бы показать реализацию решателя TIC-TAC-TAC, используя MiniMax. алгоритм. Поскольку это такая простая игра с относительно немногим государствами, я думал, что Tic-Tac-Toe будет удобным тематическим исследованием для машинного обучения и экспериментов AI. Здесь я реализовал простой алгоритм, называемый minimax.

Основная идея MiniMax заключается в том, что мы хотим знать, как играть, когда мы предполагаем, что наш оппонент будет играть лучшие возможные движения. Например, скажем, это X’s поворот и Х играет определенный ход. Какова ценность этого хода? Предположим, что О может ответить одним из двух способов: В первом случае О выигрывает на следующем ходу. Другой ход О приводит к победе от Х на следующем ходу. С О Может выиграть, мы рассмотрим оригинальный ход Х Плохой – это приводит к потере. Мы игнорируем тот факт, что Х мог победить, если О делает ошибку. Мы определим значение 1 Для победы X , -1 для победы О и 0 для розыгрыша. В приведенном выше сценарии, поскольку О Может выиграть на следующем ходу, оригинальный ход Х присваивается значение -1 .

Алгоритм MiniMax применяет эту стратегию рекурсивно из любой данной позиции – мы исследуем игру от данной стартовой позиции, пока мы не достигнем всех возможных состояний в конечном итоге. Мы можем представлять это как дерево, с каждым уровнем дерева, показывающую возможные позиции платы для поворота данного игрока. Когда мы достигаем до конца игрового состояния, нет выбора, чтобы быть сделанным, поэтому значение является результатом игры, то есть 1 Если Х Воина, -1 Если О выиграл, а 0 Если это была ничья. Если это X’s перемена И это не окончательное государство Совета, мы выбираем Максимум Из значений следующих возможных ходов с этого положения в дереве. Это представляет лучший возможный вариант для Икс . Если это О поворот, то мы выбираем Минимальный Из этих ценностей, что является лучшим вариантом для O . Мы продолжаем распространять значения позиции вверх по направлению к корневой позиции, чередуясь между максимальными и минимальными значениями, поскольку мы идем – это, конечно, там, конечно, где алгоритм MiniMax получает свое имя.

Диаграмма ниже показывает пример MiniMax, применяемый к положению платы:

Если позиция является концом игрового состояния, то значение этой позиции является результатом этой игры – Это прекрасное условие для рекурсивных вызовов. После того как мы достигли положений с конца игр, мы можем работать на пути к корневой позиции. Для позиций в Макс Уровни дерева – где это X’s Поверните – мы выбираем движение с максимальным значением из возможных продолжений. Для позиций в мин Уровни дерева – где это О Поверните – мы принимаем минимальное значение. В каждом случае мы ищем наилучшего возможного результата для следующего шага текущего игрока. Как мы видим, лучший Х может сделать на диаграмме (до тех пор, пока О Не допускается ошибкой) – получить ничье, играя в верхнем правом углу доски.

Стоит подчеркивать, что MiniMax работает нормально для игрушечного сценария, такой как Tic-Tac-Toe: есть несколько различных игровых позиций – 765 Если мы воспользуемся вращением и симметрией отражения. Для более сложных сценариев, в том числе игры, такие как шахматы и ход, Minimax, по крайней мере, должны быть объединены с другими методами. Я не реализовал это здесь, но Alpha-Beta Cruning Может использоваться для уменьшения количества позиций, которые необходимо посетить. Общая идея состоит в том, что если мы знаем, что изучение поддерева не приведет к лучшему результату, чем то, что мы уже получили, то нам не нужно с ним беспокоить. Есть также дополнительные эвристики. Мы можем ограничить глубину поиска, и как только мы достигнем этого предела, мы можем использовать эвристический для оценки вероятного значения позиции. Эта идея широко использовалась в шахматных двигателях, таких как Морская рынка Отказ MCTS Другая методика, которая может быть применена к упрощению сценариев, где MiniMax приведет к слишком большому количеству комбинаций, чтобы отслеживать. В наши дни для игр, таких как шахматы и уход, глубокое обучение доказано удивительно эффективным. На самом деле, это гораздо более эффективно, чем любая другая известная техника.

Давайте кратко посмотрим на код, необходимый для реализации MiniMax. Обратите внимание, что этот код не написан для максимальной эффективности, просто иллюстрировать основные концепции. Проект доступен на гадость . Этот код из MiniMax.py файл:

def play_minimax_move(board):
    move_value_pairs = get_move_value_pairs(board)
    move = filter_best_move(board, move_value_pairs)

    return play_move(board, move)

play_minimax_move Определяет, что движется, чтобы играть в данную позицию доски. Сначала он получает значения, соответствующие каждому возможному перемещению, затем воспроизводят движение с максимальным значением, если это X’s поворот или ход с минимальным значением, если это О перемена.

get_move_value_pairs Получает значение для каждого из следующих движений из текущего положения платы:

def get_move_value_pairs(board):
    valid_move_indexes = get_valid_move_indexes(board)

    assert not_empty(valid_move_indexes), "never call with an end-position"

    move_value_pairs = [(m, get_position_value(play_move(board, m)))
                        for m in valid_move_indexes]

    return move_value_pairs

get_position_value Ниже, ниже, либо получает значение текущей позиции из кэша или вычисляет его напрямую, изучая игру для игры. Без кэширования на моем компьютере играют одну игру, занимает около 1,5 минут. Кэширование принимает это до примерно 0,3 секунды! Полный поиск сталкивается с той же позицией снова много раз. Кэширование позволяет нам значительно ускорить этот процесс: если мы видели положение ранее, нам не нужно воспроизвести эту часть дерева игры.

def get_position_value(board):
    cached_position_value, found = get_position_value_from_cache(board)
    if found:
        return cached_position_value

    position_value = calculate_position_value(board)

    put_position_value_in_cache(board, position_value)

    return position_value

calculate_position_value Находит значение для данной доски, когда она еще не в кэше. Если мы в конце игры, мы возвращаем результат игры в качестве значения для позиции. В противном случае мы рекурсивно перезвоним в get_position_value с каждым из действительных возможных ходов. Затем мы либо получаем минимум или максимум всех этих ценностей, в зависимости от того, кто его поворот:

def calculate_position_value(board):
    if is_gameover(board):
        return get_game_result(board)

    valid_move_indexes = get_valid_move_indexes(board)

    values = [get_position_value(play_move(board, m))
              for m in valid_move_indexes]

    min_or_max = choose_min_or_max_for_comparison(board)
    position_value = min_or_max(values)

    return position_value

Мы можем видеть ниже, что Выберите_min_or_max_for_comparison Возвращает мин функция, если это О поворот и Максимум Если это X’s перемена:

def choose_min_or_max_for_comparison(board):
    turn = get_turn(board)
    return min if turn == CELL_O else max

Возвращаясь к кэшированию на мгновение, код кэширования также учитывает позиции, которые эквивалентны. Который включает в себя вращения, а также горизонтальные и вертикальные отражения. Существует 4 эквивалентных позиции под вращением: 0 °, 90 °, 180 ° и 270 °. Есть также 4 размышления: переворачивая исходное положение горизонтально и вертикально, а также вращается на 90 ° Сначала, затем переворачивая горизонтально и вертикально. Отказ от оставшихся вращений является избыточным: переворачивая ротацию 180 °. Переверните вращение 270 °, будут создавать одни и те же позиции, что и переворачивая вращение на 90 °. Не принимая во внимание вращение и отражение, одна игра занимает примерно 0,8 секунды на моем компьютере, по сравнению с 0,3 секунды, когда кэширование также включено для вращений и отражений. get_symmetrical_board_orientations Получает все эквивалентные позиции доски, чтобы их можно было посмотреть в кэш:

def get_symmetrical_board_orientations(board_2d):
    orientations = [board_2d]

    current_board_2d = board_2d
    for i in range(3):
        current_board_2d = np.rot90(current_board_2d)
        orientations.append(current_board_2d)

    orientations.append(np.flipud(board_2d))
    orientations.append(np.fliplr(board_2d))

    orientations.append(np.flipud(np.rot90(board_2d)))
    orientations.append(np.fliplr(np.rot90(board_2d)))

    return orientations

Если вы заинтересованы в том, чтобы поближе взглянуть, REPO Github со всем кодом для этого проекта доступен по адресу:

NestedSoftware/Tictac.

Экспериментируя с различными методами для игры Tic-Tac-Toe

Демо-проект для разных подходов для игры TIC-TAC-TOE.

Код требует python 3, numpy и ptest.

Установить с помощью Pipenv:

  • Pipenv Shell.
  • Pipenv Установить --dev.

Обязательно установите Pythonpath к главному каталогу проекта:

  • В Windows Run путь
  • В Bash Run Исходный путь .sh.

Пробеги тесты и демонстрация:

  • Пробеги тесты: питиш
  • Запустите демонстрацию: python -m tictac.main.

Ниже приведены самые последние демонстрационные результаты. Текущий агент QTable воспроизводится рядом с идеальными играми в зависимости от самого себя, MiniMax и случайных. Получение хорошего результата для X игрока было довольно простым, но для O оно потребовалось довольно много возобновляемого с гиперпараметрами.

Последние результаты:

C:\Dev\python\tictac>python -m tictac.main
Playing random vs random
-------------------------
x wins: 60.10%
o wins: 28.90%
draw  : 11.00%
Playing minimax not random vs minimax random
---------------------------------------------
x wins: 0.00%
o wins: 0.00%
draw  : 100.00%

Playing minimax random vs minimax not random:
---------------------------------------------
x wins: 0.00%
o wins: 0.00%
draw  : 100.00%

Ниже приведены выигрышные проценты для различных комбинаций MiniMax и случайных игроков, с 1000 играми, сыгранными для каждой комбинации:

Мы видим, что если оба игрока играют идеально, возможно только розыгрыш, но Х чаще выиграет, если оба игрока играют случайным образом.

Связанный

  • Альфага: наблюдения о машинном интеллекте
  • Грунтовка нейронных сетей
  • Революционные нейронные сети: интуитивно понятный грунтовка

TIC-TAC-TOE (4 части серии)

Оригинал: “https://dev.to/nestedsoftware/tic-tac-toe-with-the-minimax-algorithm-5988”