Создать список всех уникальных плат Tic Tac Toe

Я хотел бы создать текстовый файл, содержащий все 19,683 Tic-Tac-Toe макеты доски в структуре 0 = Blank, 1 = X и 2 = O. к сожалению, математика не моя сильная сторона, и я не могу найти никаких примеров этого нигде.

Это не для домашней работы, уверяю вас. Я намерен запустить эти данные через калькулятор Minimax, чтобы создать изображение, содержащее значения RGB, представляющие оптимальный ход на основе настройки платы. Я разработка Tic-Tac-Toe для платформы, которая не поддерживает функции (она управляется событиями), поэтому я буду преобразовывать доску в число в своей игре, а затем искать RGB пикселя в изображении, которое указывает, какой лучший ход. Это дерзкий обходной путь, но тот, который требует не больше ОЗУ, чем изображение пикселя 145x145 (145x145 = 21,025, поэтому каждый пиксель представляет рекомендуемый ход на основе платы эффективно). Это также означает, что мне не придется жевать время процессора, что является еще одним плюсом.

6 ответов


Так как вы хотите макеты платы, их только небольшое количество (19683).

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

изменить:

int c = 0;
while (c < 262144){
    bool valid = (c & 3) < 3;
    valid &= ((c >>  2) & 3) < 3;
    valid &= ((c >>  4) & 3) < 3;
    valid &= ((c >>  6) & 3) < 3;
    valid &= ((c >>  8) & 3) < 3;
    valid &= ((c >> 10) & 3) < 3;
    valid &= ((c >> 12) & 3) < 3;
    valid &= ((c >> 14) & 3) < 3;
    valid &= ((c >> 16) & 3) < 3;

    if (valid){
        int i = c;
        int j = 0;
        while (j < 9){
            cout << (i & 3) << " ";
            i >>= 2;
            j++;
        }
        cout << endl;
    }

    c++;
}

это распечатает все 19,683 макетов платы. Я не уверен, какой формат вы хотите, но это должно быть довольно легко извлечь из вывода.


есть 9 позиций и алфавит с 3 буквами (X, O, пустой). Общее количество возможных комбинаций-3^9 = 19683.

for(int i = 0; i < 19683; ++i)
{
    int c = i;
    for (int j = 0; j < 9; ++j)
    {
        cout << (c % 3) << " ";
        c /= 3;
    }

    cout << endl;
}

мой Минимакс для реализации Tic Tac Toe генерирует дерево из 5477 узлов. Каждый узел содержит состояние платы Tic Tac Toe и удовлетворяет следующим условиям:

  • состояние платы действительно согласно правилу Tic Tac Toe, что игроки должны по очереди размещать Xs и Os. т. е. нет таких позиций на доске, как:

    XXX
    XXX
    XXO

  • все листья дерева содержат состояния доски, которые считаются конечными состояниями игры в соответствии с правилами крестики-нолики (игрок 1 выигрывает, игрок 2 выигрывает или ничья). то есть нет такой ветви дерева, как:

    XOX
    Оксо!--7--> X
    |
    |
    XOX
    OXO XO

  • данный узел дерева может иметь несколько родителей (несколько узлов дерева могут иметь одного и того же ребенка).

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

я также нашел книгу, в которой упоминается 5477 уникальных, отличных, действительных состояний доски Tic Tac Toe. :

крестики - нолики имеет 5477 допустимых состояний, исключая пустую позицию


вы можете просто грубая сила свой путь через. Каждый из квадратов равен 0, 1 или 2 так...:

for (int i1 = 0; i1 <= 2; i++) {
    for (int i2 = 0; i2 <= 2; i++) {
        // ...
        // lot's of nested for loops
        // ...
    }
}

или, если вы не можете беспокоиться об этом;), то вы можете написать рекурсивную функцию для него:

int square[9];
void place(int square_num) {
    if (square_num == 9) {
        output the current configuration
    }

    for (int i = 0; i <= 2; i++) {
        square[square_num] = i;
        place(square_num+1);
    }
}

тогда просто сделай:

place(0);

и произойдет волшебство.

Это, кстати, на c++.


генерация всех возможных игровых плат (поиск глубины лучше всего работает) и исключение дубликатов при вращении и зеркальном отображении результатов в 765 платах. 626-средняя игра, 91 игра X выиграл, 44 игры O выиграл и 3 игры ничья.

Если вы только intresetd в оптимальных ходах, вы можете просто использовать https://xkcd.com/832/ как ссылка. Отличный постер.

но все удовольствие в крестики - нолики в реализации его. Поэтому я предоставляю это читателю. Всего несколько советов:

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

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

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

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

  5. Так как вы играете идеально количество доступных досок снова падает. Создайте набор данных для воспроизведения X и один для воспроизведения O, и вы можете сократить этот путь снова.

  6. хотите сделать его еще меньше? Просто запрограммируйте несколько основных правил, таких как: первый O должен быть посередине, если он свободен. С 2 "Мой цвет" в строке завершить строку. С 2 "другим цветом" в строке блокируйте строку и так далее. Википедия имеет список из 8 правил, но я думаю, что у меня было меньше, когда я это сделал путь.

  7. идеальный противник крестики - нолики скучно. Ты никогда не сможешь победить. Почему бы не заставить игру учиться на ошибках? Следите за всеми 626 досками и их возможными ходами. Когда ход приводит к потере, удалите этот ход с доски. Если на доске больше нет ходов, удалите со всех плат, ведущих к этому, ход, который вызывает его (рекурсивно, если это удаляет Последний ход). Ваша игра никогда не будет проигрывать одинаково дважды. Аналогично для ходов, приводящих к победе вы удаляете ход противников из списка возможных, и если их не осталось, вы отмечаете предыдущий ход как верную победу. Таким образом, если вы можете заставить выиграть, вы всегда будете заставлять его с этого момента. Играя X, вы можете заставить его потерять 91 способ? Играя O, вы можете заставить его потерять все 44 способа?


как более раннее решение, но легче читать и в Python.

for i in range(3**9):
     c = i
     for j in range(9):
         if j % 3 == 0:
             print("")
         print(str(c % 3) + " ", end='')
         c //= 3
     print("")