Каковы функционально-программные способы реализации игры жизни Конвея [закрыто]


12

Недавно я для забавы реализовал «Игру жизни» Конвея в Javascript (на самом деле coffeescript, но то же самое). Поскольку javascript можно использовать в качестве функционального языка, я пытался придерживаться этого конца спектра. Я не был доволен моими результатами. Я довольно хороший ОО-программист, и мое решение попахивает все тем же, старым и старым. Короткий вопрос: каков функциональный стиль (псевдокод) для этого?

Вот псевдокод для моей попытки:

class Node
  update: (board) ->
    get number_of_alive_neighbors from board
    get this_is_alive from board
    if this_is_alive and number_of_alive_neighbors < 2 then die
    if this_is_alive and number_of_alive_neighbors > 3 then die
    if not this_is_alive and number_of_alive_neighbors == 3 then alive

class NodeLocations
  at: (x, y) -> return node value at x,y
  of: (node) -> return x,y of node

class Board
  getNeighbors: (node) -> 
   use node_locations to check 8 neighbors 
   around node and return count

nodes = for 1..100 new Node
state = new NodeState(nodes)
locations = new NodeLocations(nodes)
board = new Board(locations, state)

executeRound:
  state = clone state
  accumulated_changes = for n in nodes n.update(board)
  apply accumulated_changes to state
  board = new Board(locations, state)


@ Сказал, что это печально по моей голове. Я узнаю основные понятия, но едва-едва
Джордж Мауэр

Также и над головой ... Я просто опубликовал это как пример того, что может сделать мастер специализированного языка. Назовите это вдохновением для всех нас :)
Одед

@GeorgeMauer "на самом деле coffeescript, но то же самое", это печальный день
Рэйнос

Ответы:


11

Ну, пара идей. Я не эксперт в FP, но ...

Ясно, что у нас должен быть тип, Boardпредставляющий игровое состояние. Основой реализации должна быть evolveфункция типа evolve :: Board -> Board; Это означает, что Boardприменение правил игры к Board.

Как мы должны реализовать evolve? A, Boardвероятно, должно быть nxm-матрицей Cells. Мы могли бы реализовать функцию cellEvolveтипа, cellEvolve :: Cell -> [Cell] -> Cellкоторая с учетом a Cellи соседних Cellс s вычисляет Cellсостояние на следующей итерации.

Мы также должны реализовать getCellNeighborsфункцию, которая извлекает Cellсоседей s из a Board. Я не совсем уверен в подписи этого метода; в зависимости от того, как вы реализуете, Cellи Board, например getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell], у вас может быть , что при задании доски и двух координат ( CoordElemэто будет тип, используемый для индексирования позиций в a Board), выдает список соседей переменной длины (не все ячейки на доске имеют такое же количество соседей-углов имеют 3 соседей, границы 5 и все остальные, 8).

evolveтаким образом, может быть реализовано путем объединения cellEvolveи getCellNeighborsдля всех ячеек на плате, опять же, точная реализация будет зависеть от того, как вы реализуете, Boardи Cell, но это должно быть что-то вроде «для всех ячеек на текущей плате, найдите их соседей и используйте их для вычисления соответствующая ячейка новой доски ». Это должно быть возможно сделать с общим применением этих функций по всей доске, используя« карту по ячейке доски ».

Другие мысли:

  • Вы должны действительно реализовать cellEvolveтак, чтобы он принимал в качестве параметра тип, GameRulesкоторый кодирует правила игры - скажем, список кортежей, (State,[(State,NumberOfNeighbors)],State)который говорит для данного состояния и количество соседей в каждом состоянии, которое должно быть состоянием в следующей итерации , cellEvolveподпись может бытьcellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • Это логически приведет вас к evolve :: Board -> Boardпревращению в evolve :: GameRules -> Board -> Board, так что вы сможете использовать evolveбез изменений с другим GameRules, но вы можете пойти еще дальше и сделать cellEvolveподключаемым вместо GameRules.

  • Игра с getCellNeighborsвами может также сделать evolveобщий характер в отношении Boardтопологии - вы можете иметь такие, getCellNeighborsкоторые будут оборачиваться вокруг краев доски, 3d доски и т. Д.


9

Если вы пишете функциональную версию Life, вы должны узнать об алгоритме Госпера. Он использует идеи функционального программирования для достижения триллионов поколений в секунду на досках триллионов квадратов на стороне. Это звучит невозможно, я знаю, но это вполне возможно; У меня есть хорошая маленькая реализация в C #, которая легко обрабатывает квадратные доски 2 ^ 64 квадратов на стороне.

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

Я много лет собирался написать новичку в введение в Алгоритм Госпера, но у меня никогда не было времени. Если я это сделаю, я опубликую ссылку здесь.

Обратите внимание, что вы хотите посмотреть алгоритм Gosper для вычислений Life , а не алгоритм Gosper для вычисления гипергеометрических сумм.


выглядит интересно - все еще жду эту ссылку, хотя ...;)
JK.

3

Хорошее совпадение, мы рассмотрели эту проблему в нашей лекции на Хаскеле сегодня. В первый раз я видел это, но вот ссылка на исходный код, который нам дали:

http://pastebin.com/K3DCyKj3


Вы не могли бы объяснить больше о том, что он делает, и почему вы рекомендуете ответить на заданный вопрос? «Ответы только на ссылки» не очень приветствуются на Stack Exchange
gnat

3

Возможно, вы захотите взглянуть на реализации RosettaCode для вдохновения.

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

Некоторые из реализаций разбивают функцию обновления платы на функции для подсчета окрестностей, применения правила жизни и итераций по доске. Они кажутся полезными компонентами, на которых строится функциональный дизайн. Попробуйте изменить только доску, оставив все остальное как чистые функции.


1

Вот короткая чисто функциональная версия в Clojure. Вся заслуга принадлежит Кристофу Гранду, который опубликовал это в своем блоге: Игра жизни Конвея

(defn neighbours [[x y]]
  (for [dx [-1 0 1] 
        dy (if (zero? dx) [-1 1] [-1 0 1])]
    [(+ dx x) (+ dy y)]))

(defn step [cells]
  (set (for [[loc n] (frequencies (mapcat neighbours cells))
             :when (or (= n 3) (and (= n 2) (cells loc)))]
         loc)))

Затем в игру можно играть, многократно применяя функцию «шаг» к набору ячеек, например:

(step #{[1 0] [1 1] [1 2]})
=> #{[2 1] [1 1] [0 1]}

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.