Я написал игру в крестики-нолики на Java, и мой текущий метод определения конца игры учитывает следующие возможные сценарии завершения игры:
- Доска заполнена, победитель еще не объявлен: игра - ничья.
- Кросс победил.
- Круг победил.
К сожалению, для этого он считывает заранее определенный набор этих сценариев из таблицы. Это не обязательно плохо, учитывая, что на доске всего 9 мест, и поэтому таблица несколько мала, но есть ли лучший алгоритмический способ определить, окончена ли игра? Определение того, выиграл ли кто-то или нет, - это суть проблемы, поскольку проверка заполнения 9 пробелов тривиальна.
Табличный метод может быть решением, но если нет, то что? Кроме того, что, если бы плата не была размером n=9
? Что, если бы это была доска гораздо большего размера, скажем n=16
, n=25
и т. Д., В результате чего количество последовательно размещаемых элементов было бы равным x=4
, x=5
и т. Д.? Общий алгоритм для всех n = { 9, 16, 25, 36 ... }
?
источник
3
). Таким образом, вы можете отслеживать счет каждого и начинать проверять выигрыши только в том случае, если они выше.Ответы:
Вы знаете, что выигрышный ход может произойти только после того, как X или O сделали свой последний ход, поэтому вы можете искать только строку / столбец с необязательной диагональю, которая содержится в этом ходу, чтобы ограничить пространство поиска при попытке определить выигрышную доску. Кроме того, поскольку в игре в крестики-нолики с ничьей есть фиксированное количество ходов, после того, как был сделан последний ход, если он не был выигрышным, по умолчанию это игра с ничьей.
изменить: этот код предназначен для доски размером n на n с n в ряду, чтобы выиграть (3x3 доски 3 в ряд и т. д.)
edit: добавлен код для проверки анти-диагноза, я не мог понять, как без цикла определить, была ли точка на антидиаге, поэтому этот шаг отсутствует
источник
вы можете использовать магический квадрат http://mathworld.wolfram.com/MagicSquare.html, если сумма в любой строке, столбце или диагонали равна 15, значит, игрок выиграл.
источник
Как насчет этого псевдокода:
После того, как игрок кладет фишку на позицию (x, y):
Я бы использовал массив char [n, n] с O, X и пробелом для пустого.
источник
i==1
иn==3
,rdiag
необходимо проверить при(1, 3)
и(1, 3-1+1)
равно правильным координатам, но(1, 3-(1+1))
нет.Это похоже на ответ Усамы АЛАССИРИ , но он меняет постоянное пространство и линейное время на линейное пространство и постоянное время. То есть после инициализации не происходит зацикливания.
Инициализируйте пару
(0,0)
для каждой строки, каждого столбца и двух диагоналей (диагональной и антидиагональной). Эти пары представляют собой накопленные(sum,sum)
кусочки в соответствующей строке, столбце или диагонали, гдеКогда игрок кладет фишку, обновите соответствующую пару строк, пару столбцов и диагональные пары (если они находятся на диагоналях). Если какая-либо недавно обновленная пара строк, столбцов или диагоналей равна одному
(n,0)
или,(0,n)
то выигрывает либо A, либо B соответственно.Асимптотический анализ:
Для использования памяти вы используете
4*(n+1)
целые числа.Упражнение: видите ли вы, как проверить ничью за O (1) раз за ход? В таком случае вы можете досрочно завершить игру при ничьей.
источник
O(sqrt(n))
время, но его нужно делать после каждого хода, где n - размер доски. Итак, у вас получаетсяO(n^1.5)
. На это решение вы получаетеO(n)
общее время.Вот мое решение, которое я написал для проекта, над которым я работаю, на javascript. Если вы не возражаете против стоимости памяти нескольких массивов, это, вероятно, самое быстрое и простое решение, которое вы найдете. Предполагается, что вы знаете позицию последнего хода.
источник
Я только что написал это для своего класса программирования C.
Я публикую его, потому что ни один из других примеров здесь не будет работать с прямоугольной сеткой любого размера и любым количеством N -в-ряда последовательных оценок для победы.
Вы найдете мой алгоритм таким, какой он есть, в
checkWinner()
функции. Он не использует магические числа или что-то необычное для проверки победителя, он просто использует четыре цикла for - код хорошо прокомментирован, поэтому я думаю, он говорит сам за себя.источник
Если размер доски n × n, то есть n строк, n столбцов и 2 диагонали. Проверьте каждый из них на все-X или все-O, чтобы найти победителя.
Если потребуется для победы x < n последовательных квадратов, то все немного сложнее. Наиболее очевидное решение - проверить каждый квадрат x × x на победителя. Вот код, демонстрирующий это.
(Я фактически не проверить это * кашель *, но он сделал компиляции с первой попытки, яй меня!)
источник
Я не так хорошо знаю Java, но я знаю C, поэтому я попробовал идею магического квадрата adk (вместе с ограничением поиска Hardwareguy ).
Он хорошо компилируется и тестируется.
Было весело, спасибо!
На самом деле, если задуматься, вам не нужен магический квадрат, просто счет для каждой строки / столбца / диагонали. Это немного проще, чем обобщение магического квадрата на матрицы
n
×n
, поскольку вам просто нужно посчитать доn
.источник
Мне задали тот же вопрос в одном из интервью. Мои мысли: Инициализировать матрицу с 0. Сохраните 3 массива 1) sum_row (размер n) 2) sum_column (размер n) 3) диагональ (размер 2)
Для каждого хода на (X) уменьшайте значение прямоугольника на 1 и для каждого хода на (0) увеличивайте его на 1. В любой момент, если строка / столбец / диагональ, которые были изменены в текущем ходу, имеют сумму либо -3, либо + 3 означает, что кто-то выиграл игру. Для розыгрыша мы можем использовать вышеуказанный подход, чтобы сохранить переменную moveCount.
Вы думаете, я что-то упускаю?
Изменить: то же самое можно использовать для матрицы nxn. Сумма должна быть даже +3 или -3.
источник
Непетлевой способ определить, была ли точка на антидиаге:
источник
Я опаздываю на вечеринку, но я хотел указать на одно преимущество, которое я обнаружил в использовании магического квадрата , а именно на то, что его можно использовать для получения ссылки на квадрат, который вызовет выигрыш или проигрыш на следующем ходу, а не просто используется для расчета, когда игра окончена.
Возьмите этот магический квадрат:
Во-первых, настройте
scores
массив, который увеличивается при каждом перемещении. См. Этот ответ для подробностей. Теперь, если мы незаконно сыграем X дважды подряд в точках [0,0] и [0,1], тогдаscores
массив будет выглядеть так:А плата выглядит так:
Затем все, что нам нужно сделать, чтобы получить ссылку на квадрат, на котором нужно выиграть / заблокировать:
На самом деле для реализации требуется несколько дополнительных приемов, таких как обработка нумерованных ключей (в JavaScript), но мне это показалось довольно простым, и мне понравилась развлекательная математика.
источник
Я сделал некоторую оптимизацию в проверках строк, столбцов, диагоналей. В основном это решается в первом вложенном цикле, если нам нужно проверить конкретный столбец или диагональ. Таким образом, мы избегаем проверки столбцов или диагоналей, экономя время. Это имеет большое значение, когда размер платы больше, а значительное количество ячеек не заполнено.
Вот код Java для этого.
источник
Мне нравится этот алгоритм, поскольку он использует представление платы 1x9 против 3x3.
источник
Другой вариант: сгенерируйте свою таблицу с кодом. С точки зрения симметрии, есть только три способа выиграть: ряд по краям, средний ряд или диагональ. Возьмите эти три и раскрутите их всеми возможными способами:
Эти симметрии могут иметь больше применений в вашем игровом коде: если вы дойдете до доски, которую уже видели повернутую версию, вы можете просто взять кэшированное значение или кэшированный лучший ход из этого (и открутить его обратно). Обычно это намного быстрее, чем оценка поддерева игры.
(Листание влево и вправо также может помочь; здесь в этом не было необходимости, потому что набор поворотов выигрышных паттернов зеркально-симметричен.)
источник
Вот решение, которое я придумал, в нем символы хранятся как символы и используется значение int типа char, чтобы выяснить, выиграл ли X или O (посмотрите код рефери)
Также вот мои модульные тесты, чтобы убедиться, что он действительно работает
Полное решение: https://github.com/nashjain/tictactoe/tree/master/java
источник
Как насчет следующего подхода для 9 слотов? Объявите 9 целочисленных переменных для матрицы 3x3 (a1, a2 .... a9), где a1, a2, a3 представляют строку-1, а a1, a4, a7 образуют столбец-1 (вы поняли). Используйте «1», чтобы указать Игрока-1, и «2», чтобы указать Игрока-2.
Существует 8 возможных выигрышных комбинаций: Win-1: a1 + a2 + a3 (ответ может быть 3 или 6 в зависимости от того, какой игрок выиграл) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7
Теперь мы знаем, что если игрок 1 пересекает a1, нам нужно повторно вычислить сумму трех переменных: Win-1, Win-4 и Win-7. Какой бы ни была победа? переменная достигает 3 или 6 первых выигрышей в игре. Если переменная Win-1 первой достигает 6, то выигрывает Игрок-2.
Я понимаю, что это решение нелегко масштабировать.
источник
Это действительно простой способ проверить.
}
источник
Если у вас, например, граничное поле 5 * 5, я использовал следующий метод проверки:
Думаю, так понятнее, но, наверное, не самый оптимальный.
источник
Вот мое решение с использованием двумерного массива:
источник
Решение с постоянным временем, выполняется за O (8).
Сохраните состояние платы как двоичное число. Наименьший бит (2 ^ 0) - это верхний левый ряд платы. Затем идет вправо, затем вниз.
IE
У каждого игрока есть собственное двоичное число для представления состояния (потому что крестики-нолики) имеют 3 состояния (X, O и пробел), поэтому одно двоичное число не будет работать для представления состояния доски для нескольких игроков.
Например, доска вроде:
Обратите внимание, что биты игрока X не пересекаются с битами игрока O, это очевидно, потому что X не может поставить фишку там, где у O есть фишка, и наоборот.
Чтобы проверить, выиграл ли игрок, нам нужно сравнить все позиции, занятые этим игроком, с позицией, которая, как мы знаем, является выигрышной. В этом случае самый простой способ сделать это - использовать И-стробирование между позициями игрока и выигрышной позицией и проверять, равны ли они.
например.
Примечание:
X & W = W
так что X находится в состоянии победы.Это решение с постоянным временем, оно зависит только от количества выигрышных позиций, потому что применение логического элемента И - это операция с постоянным временем, а количество выигрышных позиций конечно.
Это также упрощает задачу перечисления всех допустимых состояний платы, все их числа представлены 9 битами. Но, конечно, вам нужно дополнительное условие, чтобы гарантировать, что число является допустимым состоянием платы (например,
0b111111111
является допустимым 9-битным числом, но это недопустимое состояние платы, потому что X только что сделал все ходы).Количество возможных выигрышных позиций может быть сгенерировано на лету, но здесь они все равно есть.
Чтобы перечислить все позиции платы, вы можете запустить следующий цикл. Хотя я оставлю определение того, является ли номер допустимым состоянием доски, кому-то другому.
ПРИМЕЧАНИЕ: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)
источник
Не уверен, опубликован ли этот подход. Это должно работать для любой доски m * n, и игрок должен занять последовательную позицию « WinPos ». Идея основана на бегущем окне.
источник
Однажды я разработал алгоритм для этого в рамках научного проекта.
Вы в основном рекурсивно делите доску на кучу перекрывающихся прямоугольников 2x2, проверяя различные возможные комбинации для выигрыша на квадрате 2x2.
Он медленный, но у него есть то преимущество, что он работает на плате любого размера с довольно линейными требованиями к памяти.
Хотел бы я найти свою реализацию
источник