Какой алгоритм для игры в крестики-нолики я могу использовать, чтобы определить лучший ход для ИИ?

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

Какие алгоритмы можно использовать? Я изучаю реализации от простого к сложному. Как мне решить эту часть проблемы?


person praveen chettypally    schedule 24.09.2008    source источник
comment
imgs.xkcd.com/comics/tic_tac_toe.png   -  person Mooing Duck    schedule 21.06.2012
comment
Хотя ответ из Википедии может быть достаточно хорошим, я добавил ниже алгоритм, который вычисляет наилучший возможный ход для каждой заданной доски, проверяя все возможные ходы и оценивая их.   -  person Ben Carp    schedule 06.01.2018
comment
Я задал себе нечто подобное: blog.maxant.co.uk/ галька/2018/04/07/1523086680000.html   -  person Ant Kutschera    schedule 11.04.2018
comment
Вот очень визуальный ответ.   -  person rbinnun    schedule 02.11.2018


Ответы (10)


Стратегия идеальной игры из Википедии (выигрыш или ничья каждый раз) кажется простым псевдокодом:

Цитата из Википедии (Крестики-нолики#Стратегия)

Игрок может сыграть идеальную игру в крестики-нолики (выиграть или, по крайней мере, сыграть вничью), если он каждый ход выбирает первый доступный ход из следующего списка, как это используется в крестиках-ноликах Ньюэлла и Саймона 1972 года. программа.[6]

  1. Выигрыш: Если у вас есть две карты подряд, сыграйте третью, чтобы получить три карты подряд.

  2. Блок: если у противника есть две подряд, сыграйте третью, чтобы заблокировать их.

  3. Вилка: создайте возможность, в которой вы можете выиграть двумя способами.

  4. Блокировать вилку противника:

    Вариант 1: Создайте две подряд, чтобы заставить противника защищаться, если это не приведет к созданию вилки или выигрышу. Например, если у «X» есть угол, у «O» есть центр, а у «X» есть еще и противоположный угол, «O» не должен играть в угол, чтобы выиграть. (Разыгрывание углового в этом сценарии создает развилку для победы «X».)

    Вариант 2: Если есть конфигурация, при которой противник может разветвляться, заблокируйте эту разветвление.

  5. Центр: Играйте в центре.

  6. Противоположный угол: если противник находится в углу, играйте в противоположном углу.

  7. Пустой угол: сыграйте в пустой угол.

  8. Пустая сторона: Играйте за пустую сторону.

Распознавание того, как выглядит ситуация «форка», может быть выполнено методом грубой силы, как было предложено.

Примечание: «идеальный» противник — это хорошее упражнение, но, в конечном счете, с ним не стоит «играть». Однако вы можете изменить указанные выше приоритеты, чтобы дать характерные слабости персонажам противника.

person bkane    schedule 24.09.2008
comment
Как бы вы тогда предложили реализовать разделяющие части стратегии? - person Andrew Szeto; 07.04.2009
comment
Итак, вы говорите: единственный выигрышный ход — не играть. - person tooleb; 15.06.2009
comment
Разве центральная вилка не будет более ценной, чем другие вилки? - person Daniel Kobe; 23.09.2015
comment
@Ник, попробуй сам, здесь немного бесполезен без какой-либо информации о том, как найти контрпример. Существует ли конфигурация, достижимая с помощью этой стратегии, в которой выполнение (6) вместо (7) привело бы, например, к проигрышной игре? Было бы полезно опубликовать больше информации о вашем контрпримере. - person djechlin; 12.04.2016

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

Короче говоря, вам нужно искать не ход, который дает наилучший возможный для вас результат, а ход, в котором наихудший возможный исход является как можно более хорошим. Если вы предполагаете, что ваш оппонент играет оптимально, вы должны исходить из того, что он сделает ход, наихудший для вас, и, следовательно, вы должны сделать ход, который минимизирует его МАКСИМАЛЬНЫЙ выигрыш.

person Nick Johnson    schedule 24.09.2008
comment
Здесь отсутствует важная часть информации: максимизировать следует значение функции оценки, которая, как предполагается, возвращает числовое значение для любой (гипотетической, но особенно достижимой путем размещения следующей фигуры) позиции на доске. Может подойти что-то дешевое, например (фрагмент в центре поля стоимостью 100 очков, углы 30, сторона 5), но в нем будет отсутствовать какая-либо высокоуровневая информация, обсуждавшаяся выше, например, существующая пара, существующая вилка... Так что это не будет моим первым выбор. - person guidot; 06.07.2012
comment
@guidot Пространство поиска крестиков-ноликов настолько мало, что ваша функция оценки тривиальна: +inf, если игра является выигрышным состоянием, -inf, если это проигрышное состояние, 0, если это ничья. - person Nick Johnson; 08.07.2012
comment
Минимакс или альфа-бета, безусловно, не первая идея для такой тривиальной игры (это ограничивает ценность исходного ответа). Однако если вы это сделаете (возможно, с идеей перейти к более сложным играм, таким как го-моку), вам понадобится функция оценки. Такая функция имеет смысл только для заданных алгоритмов, если она дает результат для ЛЮБОЙ промежуточной позиции, предложенная (очень общая) функция, которая ограничена завершенными играми, полезна только для выбора окончательного выигрышного сообщения. - person guidot; 08.07.2012
comment
Напротив, минимакс или альфа-бета с функцией оценки по принципу «все или ничего» применимы к любой игре, которую вы хотите провести исчерпывающим образом. Альфа-бета значительно сокращает пространство поиска по сравнению с грубой силой; минимакс — это просто разумный способ поиска по дереву игры и поиска наилучшего доступного хода. - person Nick Johnson; 09.07.2012
comment
Я согласен, начиная с предложения 2. Ваша картина исчерпывающего поиска, кажется, что возможен анализ до конца игры. Для многих нетривиальных игр это немного оптимистично. В этом (общем) случае требуется оценка для промежуточных позиций, поскольку ее возвращаемое значение является значением сравнения для мини-максинга (см. Википедию, диаграмму альфа-бета-обрезки, числа в узлах). Существенные ссылки (в отличие от общих замечаний) для опровержения этого приветствуются. - person guidot; 09.07.2012
comment
Я не говорил, что вы всегда можете выполнять исчерпывающий поиск, просто когда вы можете — например, в крестиках-ноликах — все, что вам нужно, — это оценщик по принципу «все или ничего». - person Nick Johnson; 09.07.2012
comment
Это вызывает вопрос. Если реализация минимакса требует того, чтобы пространство поиска было небольшим, просто переборщите проблему в первую очередь. - person djechlin; 12.04.2016

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

Как только вы дойдете до этой точки, в древовидной диаграмме будет что-то вроде менее 1 КБ данных для описания результата и, следовательно, лучшего хода для компьютера.

-Адам

person Adam Davis    schedule 24.09.2008
comment
Ну, если вы хотите получить действительно комплекс... ;-) Подход грубой силы, вероятно, лучше, чем моя слабая идея ранжирования, хотя его немного сложнее реализовать. - person Daniel Spiewak; 24.09.2008

Типичный алгоритм для крестиков-ноликов должен выглядеть так:

Board : вектор из девяти элементов, представляющий доску. Мы сохраняем 2 (указывает на пробел), 3 (указывает на X) или 5 (указывает на O). Turn: Целое число, указывающее, какой ход игры будет сыгран. 1-й ход будет обозначен цифрой 1, последний - цифрой 9.

Алгоритм

Основной алгоритм использует три функции.

Make2: возвращает 5, если центральная клетка доски пуста, т. е. если board[5]=2. В противном случае эта функция возвращает любой неугловой квадрат (2, 4, 6 or 8).

Posswin(p): возвращает 0, если игрок p не может выиграть своим следующим ходом; в противном случае возвращается номер клетки, представляющей собой выигрышный ход. Эта функция позволит программе как выигрывать, так и блокировать победу противников. Эта функция работает путем проверки каждой из строк, столбцов и диагоналей. Перемножая значения каждого квадрата вместе для всей строки (или столбца, или диагонали), можно проверить возможность выигрыша. Если продукт 18 (3 x 3 x 2), то X может выиграть. Если произведение равно 50 (5 x 5 x 2), то О может выиграть. Если найден выигрышный ряд (столбец или диагональ), в нем может быть определен пустой квадрат, и эта функция возвращает номер этого квадрата.

Go (n): делает ход на поле n. эта процедура устанавливает для доски [n] значение 3, если ход нечетный, или 5, если ход четный. Это также увеличивает ход на один.

Алгоритм имеет встроенную стратегию для каждого хода. Он делает ход с нечетным номером, если играет X, и ход с четным номером, если играет O.

Turn = 1    Go(1)   (upper left corner).
Turn = 2    If Board[5] is blank, Go(5), else Go(1).
Turn = 3    If Board[9] is blank, Go(9), else Go(3).
Turn = 4    If Posswin(X) is not 0, then Go(Posswin(X)) i.e. [ block opponent’s win], else Go(Make2).
Turn = 5    if Posswin(X) is not 0 then Go(Posswin(X)) [i.e. win], else if Posswin(O) is not 0, then Go(Posswin(O)) [i.e. block win], else if Board[7] is blank, then Go(7), else Go(3). [to explore other possibility if there be any ].
Turn = 6    If Posswin(O) is not 0 then Go(Posswin(O)), else if Posswin(X) is not 0, then Go(Posswin(X)), else Go(Make2).
Turn = 7    If Posswin(X) is not 0 then Go(Posswin(X)), else if Posswin(X) is not 0, then Go(Posswin(O)) else go anywhere that is blank.
Turn = 8    if Posswin(O) is not 0 then Go(Posswin(O)), else if Posswin(X) is not 0, then Go(Posswin(X)), else go anywhere that is blank.
Turn = 9    Same as Turn=7.

Я использовал его. Дайте мне знать, как вы, ребята, себя чувствуете.

person Kaushik    schedule 13.07.2012

Поскольку вы имеете дело только с матрицей возможных местоположений 3x3, было бы довольно легко просто написать поиск по всем возможностям, не нагружая вычислительную мощность. Для каждого открытого пространства вычислите все возможные результаты после того, как отметите это пространство (я бы сказал, рекурсивно), а затем используйте ход с наибольшим количеством шансов на победу.

На самом деле оптимизация этого была бы пустой тратой усилий. Хотя некоторые простые могут быть:

  • Сначала проверьте возможные победы другой команды, заблокируйте первую найденную (если в любом случае закончилось 2 игры).
  • Всегда берите центр, если он открыт (и в предыдущем правиле нет кандидатов).
  • Подавайте углы впереди сторон (опять же, если предыдущие правила пусты)
person billjamesdev    schedule 24.09.2008
comment
На человеческом уровне старт с угла, так как P1 приносит выигрыш гораздо чаще. Ваш противник ошибочно думает, что раз вы не взяли центр, то, может быть, и им не следует по какой-то причине. - person djechlin; 12.04.2016

Вы можете заставить ИИ играть самого себя в некоторых примерах игр, чтобы учиться. Используйте контролируемый алгоритм обучения, чтобы помочь ему.

person J.J.    schedule 24.09.2008

Попытка без использования игрового поля.

  1. выиграть (ваш двойник)
  2. если нет, то не проиграть(дубль соперника)
  3. если нет, то у вас уже есть вилка(есть двойная двойная)
  4. if not, if opponent has a fork
    1. search in blocking points for possible double and fork(ultimate win)
    2. если не искать развилки в блокпоинтах(что дает противнику больше всего проигрышных возможностей)
    3. если не только блокирующие очки(не потерять)
  5. если не искать двойное и вилочное (окончательная победа)
  6. если не искать только те вилки, которые дают противнику больше возможностей для проигрыша
  7. если не искать только дубль
  8. если не тупик, ничья, рандом.
  9. if not(it means your first move)
    1. if it's the first move of the game;
      1. give the opponent the most losing possibility(the algorithm results in only corners which gives 7 losing point possibility to opponent)
      2. или для того, чтобы развеять скуку просто случайным образом.
    2. if it's second move of the game;
      1. find only the not losing points(gives a little more options)
      2. или найдите точки в этом списке, которые имеют наилучшие шансы на победу (это может быть скучно, потому что это приводит только ко всем углам или соседним углам или центру)

Примечание: Когда у вас есть дубль и вилки, проверьте, дает ли ваш дубль противнику дубль. Если он дает, проверьте, включен ли ваш новый обязательный балл в ваш список вилок.

person Community    schedule 19.06.2012
comment
На самом деле я имел в виду попытку без использования дерева игр, что является оптимальным решением для такого рода проблем принятия решений. Только в надежде получить больше информации. - person Mesut Ergul; 12.08.2012

Оцените каждый из квадратов числовыми баллами. Если квадрат взят, переходите к следующему выбору (в порядке убывания ранга). Вам нужно будет выбрать стратегию (есть две основные для первого и три (я думаю) для второго). Технически, вы можете просто запрограммировать все стратегии, а затем выбрать одну наугад. Это сделало бы соперника менее предсказуемым.

person Daniel Spiewak    schedule 24.09.2008
comment
P1 может начаться где угодно. Есть ходы, которые P2 может сделать в ответ на первый ход P1, которые создают выигрышную игру для P1, если оба игрока впоследствии будут играть оптимально для любого возможного первого хода. - person djechlin; 12.04.2016

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

Игра, конечно, должна закончиться вничью, если оба игрока играют оптимально. На человеческом уровне игра P1 в углу приносит гораздо больше выигрышей. По какой-то психологической причине P2 заставляют думать, что игра в центре не так уж важна, что для них неудачно, поскольку это единственный ответ, который не создает выигрышной игры для P1.

Если P2 правильно блокирует в центре, P1 должен сыграть в противоположный угол, потому что опять же, по какой-то психологической причине, P2 предпочтет симметричную игру в углу, что опять-таки приведет к проигрышу для него.

Для любого хода, который P1 может сделать в качестве начального хода, есть ход, который может сделать P2, который создаст выигрыш для P1, если после этого оба игрока будут играть оптимально. В этом смысле P1 может играть где угодно. Краевые ходы являются самыми слабыми в том смысле, что наибольшая часть возможных ответов на этот ход приводит к ничьей, но все же есть ответы, которые обеспечат победу P1.

Эмпирически (точнее, анекдотически) лучшими начальными ходами P1 кажутся первый угол, второй центр и последний край.

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

Я очень веселюсь на вечеринках, я знаю.

person djechlin    schedule 12.04.2016
comment
Я тоже веселюсь на вечеринках - нам нужно собираться... И поэтому я не согласен с вашим утверждением, что P1, играя в углу, приносит гораздо больше выигрышей. У вас есть ссылка на это? Мой анализ показывает, что центр лучше, хотя это зависит от типа игрока: blog.maxant.co.uk/pebble/2018/04/07/1523086680000.html - person Ant Kutschera; 07.04.2018
comment
@AntKutschera не ссылка, просто личный опыт, но я чувствовал себя уверенно, потому что психология / интуиция настолько сильны, что неортодоксальные дебюты требуют неортодоксальных ответов. Если по какой-то другой причине у игрока есть предварительные предположения или он настроен иначе, я думаю, это не сработает. - person djechlin; 09.04.2018

Адаптация крестиков-ноликов к алгоритму минимум-максимум

let gameBoard: [
    [null, null, null],
    [null, null, null],
    [null, null, null]
]

const SYMBOLS = {
    X:'X',
    O:'O'
}

const RESULT = {
    INCOMPLETE: "incomplete",
    PLAYER_X_WON: SYMBOLS.x,
    PLAYER_O_WON: SYMBOLS.o,
    tie: "tie"
}

Нам понадобится функция, которая может проверять результат. Функция проверит последовательность символов. Каким бы ни было состояние доски, результатом будет один из 4 вариантов: либо Незавершенно, либо игрок Х выиграл, либо игрок О выиграл, либо ничья.

function checkSuccession (line){
    if (line === SYMBOLS.X.repeat(3)) return SYMBOLS.X
    if (line === SYMBOLS.O.repeat(3)) return SYMBOLS.O
    return false 
}

function getResult(board){

    let result = RESULT.incomplete
    if (moveCount(board)<5){
        return result
    }

    let lines

    //first we check row, then column, then diagonal
    for (var i = 0 ; i<3 ; i++){
        lines.push(board[i].join(''))
    }

    for (var j=0 ; j<3; j++){
        const column = [board[0][j],board[1][j],board[2][j]]
        lines.push(column.join(''))
    }

    const diag1 = [board[0][0],board[1][1],board[2][2]]
    lines.push(diag1.join(''))
    const diag2 = [board[0][2],board[1][1],board[2][0]]
    lines.push(diag2.join(''))
    
    for (i=0 ; i<lines.length ; i++){
        const succession = checkSuccesion(lines[i])
        if(succession){
            return succession
        }
    }

    //Check for tie
    if (moveCount(board)==9){
        return RESULT.tie
    }

    return result
}

Our getBestMove function will receive the state of the board, and the symbol of the player for which we want to determine the best possible move. Our function will check all possible moves with the getResult function. If it is a win it will give it a score of 1. if it's a loose it will get a score of -1, a tie will get a score of 0. If it is undetermined we will call the getBestMove function with the new state of the board and the opposite symbol. Since the next move is of the oponent, his victory is the lose of the current player, and the score will be negated. At the end possible move receives a score of either 1,0 or -1, we can sort the moves, and return the move with the highest score.

const copyBoard = (board) => board.map( 
    row => row.map( square => square  ) 
)

function getAvailableMoves (board) {
  let availableMoves = []
  for (let row = 0 ; row<3 ; row++){
    for (let column = 0 ; column<3 ; column++){
      if (board[row][column]===null){
        availableMoves.push({row, column})
      }
    }
  }
  return availableMoves
}

function applyMove(board,move, symbol) {
  board[move.row][move.column]= symbol
  return board
}
 
function getBestMove (board, symbol){

    let availableMoves = getAvailableMoves(board)

    let availableMovesAndScores = []

    for (var i=0 ; i<availableMoves.length ; i++){
      let move = availableMoves[i]
      let newBoard = copyBoard(board)
      newBoard = applyMove(newBoard,move, symbol)
      result = getResult(newBoard,symbol).result
      let score
      if (result == RESULT.tie) {score = 0}
      else if (result == symbol) {
        score = 1
      }
      else {
        let otherSymbol = (symbol==SYMBOLS.x)? SYMBOLS.o : SYMBOLS.x
        nextMove = getBestMove(newBoard, otherSymbol)
        score = - (nextMove.score)
      }
      if(score === 1)  // Performance optimization
        return {move, score}
      availableMovesAndScores.push({move, score})
    }

    availableMovesAndScores.sort((moveA, moveB )=>{
        return moveB.score - moveA.score
      })
    return availableMovesAndScores[0]
  }

Алгоритм в действии, Github, Более подробное объяснение процесса

person Ben Carp    schedule 06.01.2018