Структура даних для двовимірних настільних ігор на функціональних мовах


9

Я створюю просту реалізацію MiniMax на мові функціонального програмування Elixir. Оскільки існує безліч ігор з бездоганним знаннями (tic tac toe, connect-four, шашки, шахи тощо), ця реалізація може стати основою для створення AI ігор для будь-якої з цих ігор.

Однак одна із проблем, з якими я стикаюся, полягає в тому, як правильно зберігати стан гри на функціональній мові. Ці ігри в основному мають справу з двовимірними ігровими дошками, де такі операції часті:

  • Прочитайте вміст певного місця на дошці
  • Оновіть вміст певного місця на дошці (при поверненні нової можливості переміщення)
  • З урахуванням вмісту однієї або декількох локацій, які підключені до поточного місця розташування (тобто наступного або попереднього горизонтального, вертикального або діагонального розташування)
  • Розгляд вмісту кількох підключених місць у будь-якому напрямку.
  • Враховуючи вміст цілих файлів, рангів та діагоналей.
  • Обертання або дзеркальне відображення дошки (щоб перевірити наявність симетрій, які дають такий же результат, як і щось, що вже розраховано).

Більшість функціональних мов використовують пов'язані списки та кортежі як основні будівельні блоки багатоелементних структур даних. Однак вони здаються дуже поганими для роботи:

  • Пов'язані списки мають O (n) (лінійний) час пошуку. Крім того, оскільки ми не можемо "сканувати та оновлювати дошку" за один проїзд над дошкою, використання списків видається дуже недоцільним.
  • Кортежі мають O (1) (постійний) час пошуку. Однак подання дошки як кортежу фіксованого розміру дуже важко переглядати ранги, файли, діагоналі чи інші види послідовних квадратів. Крім того, і Elixir, і Haskell (які є двома моїми функціональними мовами) не мають синтаксису для читання n- го елемента кортежу. Це унеможливить написання динамічного рішення, яке б працювало для дощок довільного розміру.

Elixir має вбудовану структуру даних карт (І Haskell має Data.Map), яка дозволяє O (log n) (логарифмічний) доступ до елементів. Зараз я використовую карту з x, yкортежами, які представляють позицію як ключі.

Це "працює", але почувається неправильним зловживати картами таким чином, хоча я не знаю точно, чому. Я шукаю кращого способу зберігання двовимірної ігрової дошки на функціональній мові програмування.


1
Я не можу говорити про практику, але дві речі приходять мені на думку з Haskell: блискавки , що дозволяють постійно "крокувати" по структурах даних, і комони, які пов'язані з блискавками деякою теорією, яку я ні пам'ятаю, ні належним чином розумію; )
phipsgabler

Наскільки велика ця ігрова дошка? Великий О характеризує те, як алгоритм масштабується, а не наскільки він швидкий. На невеликій дошці (скажімо, менше 100 у кожному напрямку) O (1) проти O (n) навряд чи матиме велике значення, якщо ви торкаєтесь кожного квадрата лише один раз.
Роберт Харві

@RobertHarvey Він буде різним. Але для прикладу: у Шахах у нас є плата 64х64, але всі обчислення перевіряють, які рухи можливі, і визначають евристичне значення поточної позиції (різниця в матеріалі, король у чеку чи ні, пройшли пішаки тощо) всім потрібно отримати доступ до квадратів дошки.
Qqwy

1
У вас шахів 8х8. У мові, відображеній на пам'ять, на зразок C, ви можете зробити математичний розрахунок, щоб отримати точну адресу комірки, але це не вірно для мов, керованих пам'яттю (де порядкова адресація - це деталь реалізації). Мене не здивує, якщо перехід через (максимум) 14 вузлів займає приблизно стільки ж часу, як адресація елемента масиву мовою, керованою пам'яттю.
Роберт Харві

Дивіться також stackoverflow.com/q/9611904/124319
coredump

Відповіді:


6

А Mapсаме тут є правильна база даних. Я не впевнений, чому це зробить вас неприємно. Він має хороший час пошуку та оновлення, динамічний розмір, і дуже легко створювати з них похідні структури даних. Наприклад (у haskell):

filterWithKey (\k _ -> (snd k) == column) -- All pieces in given column
filterWithKey (\k _ -> (fst k) == row)    -- All pieces in given row
mapKeys (\(x, y) -> (-x, y))              -- Mirror

Інша річ, яку часто важко зрозуміти програмістам, коли вони вперше розпочинають програмування з повною незмінністю, - вам не потрібно дотримуватися лише однієї структури даних. Зазвичай ви вибираєте одну структуру даних як "джерело істини", але ви можете робити скільки завгодно похідних, навіть похідні похідні, і ви знаєте, що вони будуть синхронізовані до тих пір, поки вам потрібно.

Це означає, що ви можете використовувати Mapна найвищому рівні, потім перейти на Listsабо Arraysдля аналізу рядків, Arraysіндексувати іншим способом для аналізу стовпців, потім бітові маски для аналізу шаблонів, потім Stringsдля відображення. Добре розроблені функціональні програми не передають єдиної структури даних навколо. Вони представляють собою низку кроків, які беруть одну структуру даних і випромінюють нову, що підходить для наступного кроку.

Поки ви можете вийти на іншу сторону з переміщенням у форматі, який може зрозуміти верхній рівень, вам не потрібно хвилюватися, наскільки ви реструктуруєте дані між ними. Це незмінне, тому на верхньому рівні можна простежити шлях до джерела істини.


4

Я нещодавно це робив у F #, і в кінцевому підсумку використовував одновимірний список (у F #, це односкладений список). На практиці показник швидкості O (n) списку не є вузьким місцем для зручних для людини розмірів плати. Я експериментував з іншими типами, такими як 2d масив, але врешті-решт це було компромісом або написання мого власного коду перевірки рівності, або перекладу рядків і файлів на індекс та назад. Останнє було простішим. Я б сказав, спочатку працюйте, а потім оптимізуйте свій тип даних, якщо потрібно. Мабуть, це не має значного значення.

Зрештою, ваша реалізація не повинна мати значення, доки операції вашої ради належним чином інкапсульовані за типом та операціями ради. Наприклад, ось, як можуть виглядати деякі мої тести, щоб створити дошку:

let pos r f = {Rank = r; File = f} // immutable record type
// or
let pos r f = OnBoard (r, f) // algebraic type
...
let testBoard =
    Board.createEmpty ()
    |> Board.setPiece p (pos 1 2)
    |> ...

Для цього коду (або будь-якого коду виклику) не має значення, як представлена ​​дошка. Дошка представлена ​​операціями на ній більше, ніж її основна структура.


Здрастуйте, чи є у вас десь опублікований код? Зараз я працюю над шаховою грою в F # (для веселого проекту), і навіть тому я використовую Map <Square, Piece> для представлення дошки, я хотів би побачити, як ви її інкапсулювали в Дошку тип та модуль.
асібахі

Ні, він ніде не публікується.
Спікер Кейсі

тож ви проти заглянути до моєї поточної реалізації та порадити, як я міг би її покращити?
асібахі

Орієнтуючись на типи, я зупинився на дуже подібній реалізації, поки не дійшов до типу Position. Пізніше я зроблю більш ретельний аналіз Code Review.
Спікер Кейсі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.