Quais são as formas de programação funcional para implementar o Jogo da Vida de Conway [fechado]

12

Eu recentemente implementei por diversão o Jogo da Vida de Conway em Javascript (na verdade, coffeescript, mas a mesma coisa). Como o javascript pode ser usado como uma linguagem funcional, eu estava tentando permanecer nesse extremo do espectro. Eu não estava feliz com meus resultados. Sou um programador de OO razoavelmente bom e minha solução tem a mesma idade e a mesma idade. Pergunta longa: qual é o estilo funcional (pseudocódigo) de fazê-lo?

Aqui está o Pseudocódigo para minha tentativa:

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)
George Mauer
fonte
@ Oded está deprimente na minha cabeça. Eu reconheço os conceitos básicos, mas apenas por pouco
George Mauer
Muito além da minha cabeça ... Acabei de postá-lo como um exemplo do que um mestre de um idioma especializado pode fazer. Chamá-lo de uma inspiração para todos nós :)
Oded
@GeorgeMauer "coisa realmente CoffeeScript mas mesmo" isso é triste dia
Raynos

Respostas:

11

Bem, algumas idéias. Não sou especialista em FP, mas ...

É bastante claro que deveríamos ter um tipo Boardque represente um estado de jogo. A base da implementação deve ser uma evolvefunção do tipo evolve :: Board -> Board; significando que produz a Boardpartir da aplicação das regras do jogo a a Board.

Como devemos implementar evolve? A Boardprovavelmente deve ser uma matriz nxm de Cells. Poderíamos implementar uma função cellEvolvedo tipo cellEvolve :: Cell -> [Cell] -> Cellque, dado a Celle seus vizinhos, Cellcalcule o Cellestado na próxima iteração.

Também devemos implementar uma getCellNeighborsfunção que extrai os Cellvizinhos de a Board. Não tenho muita certeza da assinatura desse método; dependendo de como você implementa Celle Boardvocê poderia ter, por exemplo getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell], que dado um quadro e duas coordenadas ( CoordElemseria o tipo usado para indexar posições em a Board), fornece uma lista de comprimento variável dos vizinhos (nem todas as células no quadro têm o mesmo número de vizinhos - os cantos têm 3 vizinhos, as fronteiras 5 e todos os demais, 8).

evolveAssim, pode ser implementado combinando cellEvolvee getCellNeighborspara todas as células no quadro, novamente a implementação exata dependerá de como você o implementa Boarde Cell, mas deve ser algo como "para todas as células no quadro atual, obtenha seus vizinhos e use-os para calcular o célula correspondente da nova placa '. Isso deve ser possível com uma aplicação genérica dessas funções em toda a placa usando um "mapa da função da célula da placa".

Outros pensamentos:

  • Você realmente deve implementar cellEvolvepara que tome como parâmetro um tipo GameRulesque codifique as regras do jogo - digamos, uma lista de tuplas (State,[(State,NumberOfNeighbors)],State)que diz um determinado estado e o número de vizinhos em cada estado, que deve ser o estado na próxima iteração . cellEvolveA assinatura decellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • Isso levaria você logicamente a se evolve :: Board -> Boardtransformar evolve :: GameRules -> Board -> Board, para que você pudesse usar evolveinalterado com diferente GameRules, mas você poderia dar um passo adiante e torná-lo cellEvolveplugável em vez de GameRules.

  • Brincar com getCellNeighborsvocê também pode ser evolvegenérico em relação à Boardtopologia da - você pode ter getCellNeighborsque envolver as bordas das placas, placas 3D etc.

alex
fonte
9

Se você está escrevendo uma versão de programação funcional do Life, deve a si mesmo aprender sobre o Algoritmo de Gosper. Ele usa idéias da programação funcional para atingir trilhões de gerações por segundo em quadros trilhões de quadrados de um lado. Parece impossível, eu sei, mas é perfeitamente possível; Eu tenho uma implementação pequena e agradável em C # que lida facilmente com placas quadradas 2 ^ 64 quadrados de um lado.

O truque é tirar proveito da enorme auto-similaridade das placas Life no tempo e no espaço. Memorizando o estado futuro de grandes seções do quadro, você pode avançar rapidamente grandes seções ao mesmo tempo.

Estou planejando publicar uma introdução para o algoritmo de Gosper há muitos anos, mas nunca tive tempo. Se eu terminar, postarei um link aqui.

Observe que você deseja procurar os cálculos do algoritmo de Gosper para a vida , não o algoritmo de Gosper para calcular somas hipergeométricas.

Eric Lippert
fonte
parece interessante - ainda estou esperando por esse link ...;)
jk.
3

Boa coincidência, abordamos esse problema exato em nossa palestra de Haskell hoje. A primeira vez que o vi, mas aqui está um link para o código-fonte que recebemos:

http://pastebin.com/K3DCyKj3

Shane
fonte
você se importaria de explicar mais sobre o que faz e por que o recomenda como resposta à pergunta? "Respostas apenas para links" não são bem-vindas no Stack Exchange
gnat
3

Você pode olhar as implementações no RosettaCode para obter inspiração.

Por exemplo, existem versões funcionais de Haskell e OCaml que criam uma nova placa a cada turno aplicando uma função à anterior, enquanto a versão gráfica do OCaml usa duas matrizes e as atualiza alternadamente para velocidade.

Algumas das implementações decompõem a função de atualização do quadro em funções para contar a vizinhança, aplicar a regra de vida e iterar sobre o quadro. Esses parecem componentes úteis para basear um design funcional. Tente modificar apenas o quadro, mantendo tudo o resto como funções puras.

Toby Kelsey
fonte
1

Aqui está uma versão curta e puramente funcional no Clojure. Todo o crédito é para Christophe Grand, que publicou isso em seu blog: Game of Life, de Conway

(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)))

O jogo pode ser jogado aplicando repetidamente a função "step" a um conjunto de células, por exemplo:

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

A inteligência é a parte (células vizinhas mapcat) - o que isso faz é criar uma lista de oito vizinhos para cada uma das células ativas e concatená-los todos juntos. Em seguida, o número de vezes que cada célula aparece nesta lista pode ser contada (frequências ...) e, finalmente, as que possuem a contagem correta de frequências passam para a próxima geração.

Mikera
fonte