Создать список всех уникальных плат 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 допустимых состояний, исключая пустую позицию
вы можете просто грубая сила свой путь через. Каждый из квадратов равен 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/ как ссылка. Отличный постер.
но все удовольствие в крестики - нолики в реализации его. Поэтому я предоставляю это читателю. Всего несколько советов:
- каждая плитка на доске имеет 3 состояния, поэтому вы можете кодировать доску как номер в базе 3. Для более простой математики я использую base 4 (2 бит на плитку, поэтому мне нужно только сдвинуть). Затем у меня есть хэш-функция, которая генерирует это число для платы при всех возможных поворотах и зеркальном отображении (8 случаев) и возвращает минимальное значение. По этому я могу посмотреть, если я уже играл на этой доске. 
- начиная с пустой доски поместите метку на доска в каждой возможной позиции, проверьте, была ли доска уже сыграна, отметьте ее, проверьте, закончена ли игра, и подсчитайте доску, иначе рекурсивные чередующиеся игроки. 
- первый X может быть установлен только в 3 местах (с учетом вращения и зеркального отображения), а все последующие шаги имеют не более 8 вариантов. Вместо того, чтобы кодировать абсолютную плитку для воспроизведения, вы можете только считать пустые плитки и кодировать их в 3 битах. 
- используя вышеуказанный хэш функция дает нам 626 плат, где мы должны сделать ход (вам просто нужно отменить вращение/зеркальное отображение, чтобы получить реальный ход из данных). Вероятно, существует не намного большее относительное простое число, так что каждая плата помещается в хэш-таблицу без столкновений. Допустим, что число равно 696 (я знаю, а не относительное простое). При 3 битах на плате для хранения наилучшего хода для каждой возможной игры потребуется только 261 байт данных. 
- Так как вы играете идеально количество доступных досок снова падает. Создайте набор данных для воспроизведения X и один для воспроизведения O, и вы можете сократить этот путь снова. 
- хотите сделать его еще меньше? Просто запрограммируйте несколько основных правил, таких как: первый O должен быть посередине, если он свободен. С 2 "Мой цвет" в строке завершить строку. С 2 "другим цветом" в строке блокируйте строку и так далее. Википедия имеет список из 8 правил, но я думаю, что у меня было меньше, когда я это сделал путь. 
- идеальный противник крестики - нолики скучно. Ты никогда не сможешь победить. Почему бы не заставить игру учиться на ошибках? Следите за всеми 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("")
