C99 - доска 3х3 за 0,084 с
Изменить: я реорганизовал мой код и сделал более глубокий анализ результатов.
Дальнейшие правки: добавлена обрезка по симметрии. Это дает 4 конфигурации алгоритма: с симметрией или без X с или без альфа-бета-отсечения
Дальнейшие правки: добавлено запоминание с использованием хеш-таблицы, что в итоге привело к невозможному: решение доски 3х3!
Основные характеристики:
- простая реализация минимакса с альфа-бета-отсечкой
- очень мало управления памятью (поддерживает dll действительных ходов; O (1) обновлений для каждой ветви в поиске дерева)
- Второй файл с обрезкой по симметрии. По-прежнему достигает O (1) обновлений для каждой ветви (технически O (S), где S - число симметрий. Это 7 для квадратных досок и 3 для неквадратных плат)
- третий и четвертый файлы добавить памятку. Вы можете контролировать размер хеш-таблицы (
#define HASHTABLE_BITWIDTH
). Когда этот размер больше или равен количеству стен, это гарантирует отсутствие столкновений и O (1) обновлений. Меньшие хеш-таблицы будут иметь больше коллизий и будут немного медленнее.
- компилировать с
-DDEBUG
распечатками
Потенциальные улучшения:
исправить небольшую утечку памяти, исправленную в первом редактировании
альфа / бета-обрезка добавлена во втором редактировании
обрезать симметрии, добавленные в третьем редакторе (обратите внимание, что симметрии не обрабатываются с помощью памятки, поэтому это остается отдельной оптимизацией)
памятка добавлена в 4-й редакции
- В настоящее время в памятке используется индикаторный бит для каждой стены. Доска 3х4 имеет 31 стену, поэтому этот метод не может обрабатывать доски 4х4 независимо от временных ограничений. улучшение будет состоять в том, чтобы эмулировать X-битные целые числа, где X, по крайней мере, равно числу стен.
Код
Из-за отсутствия организации количество файлов выросло из-под контроля. Весь код был перемещен в этот репозиторий Github . В редакторе заметок я добавил make-файл и скрипт тестирования.
Результаты
Примечания о сложности
Подходы грубой силы к точкам и коробкам очень быстро взрываются .
Рассмотрим доску со R
строками и C
столбцами. Есть R*C
квадраты, R*(C+1)
вертикальные стены и C*(R+1)
горизонтальные стены. Это всегоW = 2*R*C + R + C
.
Поскольку Лембик попросил нас решить игру с минимаксом, нам нужно пройти к листьям дерева игры. Давайте пока проигнорируем обрезку, потому что важны порядки.
Есть W
варианты для первого хода. Для каждого из них следующий игрок может сыграть на любой из W-1
оставшихся стен и т. Д. Это дает нам пространство для поиска SS = W * (W-1) * (W-2) * ... * 1
или SS = W!
. Факториалы огромны, но это только начало. SS
количество листовых узлов в пространстве поиска. Более важным для нашего анализа является общее количество решений, которые необходимо было принять (т. Е. Количество ветвей B
в дереве). Первый слой веток имеет W
параметры. Для каждого из них есть следующий уровень и W-1
т. Д.
B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!
B = SUM W!/(W-k)!
k=0..W-1
Давайте посмотрим на некоторые небольшие размеры таблицы:
Board Size Walls Leaves (SS) Branches (B)
---------------------------------------------------
1x1 04 24 64
1x2 07 5040 13699
2x2 12 479001600 1302061344
2x3 17 355687428096000 966858672404689
Эти цифры становятся смешными. По крайней мере, они объясняют, почему код грубой силы навсегда висит на доске 2х3. Пространство поиска платы 2х3 в 742560 раз больше, чем 2х2 . Если для завершения 2x2 требуется 20 секунд, консервативная экстраполяция прогнозирует более 100 дней времени выполнения для 2x3. Очевидно, что нам нужно обрезать.
Анализ обрезки
Я начал с добавления очень простого сокращения с использованием алгоритма альфа-бета. По сути, он прекращает поиск, если идеальный противник никогда не даст ему свои текущие возможности. «Эй, смотри - я выиграю много, если мой противник позволит мне получить каждый квадрат!», - подумал ни один ИИ.
редактировать Я также добавил обрезку на основе симметричных плат. Я не использую метод запоминания, просто на случай, если когда-нибудь я добавлю запоминание и хочу оставить этот анализ отдельным. Вместо этого это работает так: большинство линий имеют «симметричную пару» где-то еще в сетке. Существует до 7 симметрий (горизонтальная, вертикальная, 180 вращений, 90 вращений, 270 вращений, диагональ и другие диагонали). Все 7 относятся к квадратным доскам, но последние 4 не относятся к неквадратным доскам. Каждая стена имеет указатель на свою «пару» для каждой из этих симметрий. Если при входе в поворот доска является горизонтально симметричной, то необходимо играть только по одной из каждой горизонтальной пары .
редактировать редактировать запоминание! Каждая стена получает уникальный идентификатор, который я обычно устанавливаю как индикаторный бит; у n-ой стены есть id 1 << n
. Хэш доски - это просто ИЛИ всех сыгранных стен. Это обновляется в каждой ветви за O (1) раз. Размер хеш-таблицы устанавливается в #define
. Все тесты были выполнены с размером 2 ^ 12, потому что почему бы и нет? Когда имеется больше стенок, чем битов, индексирующих хеш-таблицу (в данном случае 12 битов), наименее значимые 12 маскируются и используются в качестве индекса. Столкновения обрабатываются с помощью связанного списка в каждом индексе хеш-таблицы. Следующая таблица - это мой быстрый анализ того, как размер хеш-таблицы влияет на производительность. На компьютере с бесконечной оперативной памятью мы всегда устанавливаем размер таблицы равным числу стен. Доска 3х4 будет иметь хеш-таблицу длиной 2 ^ 31. Увы, у нас нет такой роскоши.
Хорошо, вернемся к обрезке. Остановив поиск высоко в дереве, мы сможем сэкономить много времени, не опускаясь до листьев. «Фактор обрезки» - это часть всех возможных ветвей, которые нам приходилось посещать. У грубой силы фактор обрезки равен 1. Чем он меньше, тем лучше.