Я создаю простую реализацию MiniMax на функциональном языке программирования Elixir. Поскольку существует множество игр с совершенным знанием (крестики-нолики, connect-four, шашки, шахматы и т. Д.), Эта реализация может стать основой для создания игровых ИИ для любой из этих игр.
Однако одна проблема, с которой я сталкиваюсь, заключается в том, как правильно сохранить состояние игры на функциональном языке. Эти игры в основном имеют дело с двумерными игровыми полями, где часто выполняются следующие операции:
- Прочитайте содержимое определенного места на доске
- Обновить содержимое определенного места на доске (при возврате новой возможности перемещения)
- Рассматривая содержимое одного или нескольких местоположений, которые связаны с текущим местоположением (то есть следующее или предыдущее горизонтальное, вертикальное или диагональное местоположение)
- Учитывая содержимое нескольких подключенных мест в любом направлении.
- Учитывая содержимое целых файлов, рангов и диагоналей.
- Поворот или зеркальное отображение доски (для проверки симметрий, которые дают тот же результат, что и что-то уже рассчитанное).
Большинство функциональных языков используют связанные списки и кортежи в качестве базовых строительных блоков многоэлементных структур данных. Тем не менее, они выглядят очень плохо для работы:
- Связанные списки имеют O (n) (линейное) время поиска. Кроме того, поскольку мы не можем «сканировать и обновлять доску» за один проход по доске, использование списков представляется очень непрактичным.
- Кортежи имеют O (1) (постоянное) время поиска. Однако представление доски в виде кортежа фиксированного размера очень затрудняет итерации по рядам, файлам, диагоналям или другим видам последовательных квадратов. Кроме того, и Elixir, и Haskell (это два известных мне функциональных языка) не имеют синтаксиса для чтения n- го элемента кортежа. Это сделало бы невозможным написание динамического решения, которое работало бы для плат произвольного размера.
Elixir имеет встроенную структуру данных Map (и Haskell Data.Map
), которая позволяет O (log n) (логарифмический) доступ к элементам. Прямо сейчас я использую карту с x, y
кортежами, которые представляют позицию в качестве ключей.
Это «работает», но неправильно использовать карты таким образом, хотя я не знаю точно, почему. Я ищу лучший способ хранения двумерной игровой доски на функциональном языке программирования.
Ответы:
A
Map
является точно правильной базовой структурой данных здесь. Я не уверен, почему это заставит вас чувствовать себя неловко. У него хорошее время поиска и обновления, динамический размер и очень легко создавать производные структуры данных. Например (в haskell):Еще одна вещь, которую часто трудно понять программистам, когда они впервые начинают программировать с полной неизменностью, это то, что вам не нужно придерживаться только одной структуры данных. Обычно вы выбираете одну структуру данных в качестве «источника правды», но вы можете создать столько производных, сколько захотите, даже производных производных, и вы знаете, что они будут синхронизироваться столько, сколько вам нужно.
Это означает, что вы можете использовать
Map
верхний уровень, затем переключиться наLists
илиArrays
для анализа строк, затемArrays
проиндексировать другой способ для анализа столбцов, затем битовые маски для анализа шаблонов, а затемStrings
для отображения. Хорошо разработанные функциональные программы не передают ни одной структуры данных. Это последовательность шагов, в которых используется одна структура данных и создается новая, подходящая для следующего шага.Пока вы можете выйти на другую сторону с переходом в формат, понятный верхнему уровню, вам не нужно беспокоиться о том, насколько сильно вы реструктурируете данные между ними. Он неизменен, поэтому можно найти путь к источнику истины на верхнем уровне.
источник
Я сделал это недавно в F #, и в итоге я использовал одномерный список (в F # это односвязный список). На практике скорость индексации списка O (n) не является узким местом для плат, пригодных для использования человеком. Я экспериментировал с другими типами, такими как 2d массив, но в конце концов это был компромисс либо написания моего собственного кода проверки на равенство значений, либо перевода ранга и файла в индекс и обратно. Последнее было проще. Сначала я бы сказал, чтобы он работал, а затем, при необходимости, оптимизировал тип данных. Это вряд ли будет иметь достаточно большое значение, чтобы иметь значение.
В конце концов, ваша реализация не должна иметь большого значения, если ваши операции с платой должным образом инкапсулированы в зависимости от типа и операций платы. Например, вот как могут выглядеть некоторые из моих тестов для настройки платы:
К этому коду (или к любому вызывающему коду) не имеет значения, как будет представлена доска. Правление представлено операциями над ним больше, чем его основной структурой.
источник