¿Cuáles son las formas de programación funcional de implementar Conway's Game of Life [cerrado]

12

Recientemente implementé por diversión el Juego de la vida de Conway en Javascript (en realidad coffeescript pero lo mismo). Como javascript se puede usar como un lenguaje funcional, estaba tratando de mantenerme en ese extremo del espectro. No estaba contento con mis resultados. Soy un programador de OO bastante bueno y mi solución parecía la misma de siempre. Tan larga pregunta corta: ¿cuál es el estilo funcional (pseudocódigo) de hacerlo?

Aquí está el pseudocódigo para mi intento:

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
fuente
3
Obligatorio: youtube.com/watch?v=a9xAKttWgP4
Finalizado el
@Oded eso es deprimentemente sobre mi cabeza. Reconozco los conceptos básicos pero solo apenas
George Mauer
Muy por encima de mi cabeza ... Acabo de publicarlo como un ejemplo de lo que puede hacer un maestro de un idioma especializado. Llámalo una inspiración para todos nosotros :)
Falleció el
@GeorgeMauer "en realidad coffeescript pero lo mismo" este es un día triste
Raynos

Respuestas:

11

Bueno, un par de ideas. No soy un experto en FP, pero ...

Está bastante claro que deberíamos tener un tipo Boardque represente un estado de juego. La base de la implementación debe ser una evolvefunción de tipo evolve :: Board -> Board; lo que significa que produce a a Boardpartir de la aplicación de las reglas del juego a a Board.

¿Cómo debemos implementar evolve? A Boardprobablemente debería ser una matriz nxm de Cells. Podríamos implementar una función cellEvolvede tipo cellEvolve :: Cell -> [Cell] -> Cellque, dado que ay Cellsus Cells vecinas calculan el Cellestado en la próxima iteración.

También deberíamos implementar una getCellNeighborsfunción que extraiga Celllos vecinos de s de a Board. No estoy completamente seguro de la firma de este método; dependiendo de cómo implemente Celly Boardpodría tener, por ejemplo getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell], que dado un tablero y dos coordenadas ( CoordElemsería el tipo utilizado para indexar posiciones en a Board), le da una lista de longitud variable de los vecinos (no todas las celdas en el tablero tienen mismo número de vecinos: las esquinas tienen 3 vecinos, las fronteras 5 y todos los demás, 8).

evolvepor lo tanto, se puede implementar combinando cellEvolvey getCellNeighborspara todas las celdas en el tablero, nuevamente la implementación exacta dependerá de cómo se implemente Boardy Cell, pero debería ser algo así como "para todas las celdas en el tablero actual, obtener sus vecinos y usarlos para calcular el celda correspondiente de la nueva placa '. Esto debería ser posible con una aplicación genérica de esas funciones en toda la placa utilizando un "mapa sobre la función de celda de la placa".

Otros pensamientos:

  • Realmente deberías implementar de cellEvolvemanera que tome como parámetro un tipo GameRulesque codifique las reglas del juego, digamos una lista de tuplas (State,[(State,NumberOfNeighbors)],State)que dice para un estado dado y el número de vecinos en cada estado, que debería ser el estado en la próxima iteración . cellEvolvela firma de entonces podría sercellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • Esto lógicamente lo llevaría a evolve :: Board -> Boardconvertirse en evolve :: GameRules -> Board -> Board, para que pueda usar evolvesin cambios con diferentes GameRules, pero podría ir un paso más allá y hacer que se pueda cellEvolveenchufar en lugar de GameRules.

  • Jugar getCellNeighborscontigo también podría ser evolvegenérico en lo que respecta a la Boardtopología de la misma, podrías tener getCellNeighborsque envolver los bordes de cada tablero, tableros 3d, etc.

alex
fuente
9

Si está escribiendo una versión de programación funcional de Life, se lo debe a usted mismo para aprender sobre el Algoritmo de Gosper. Utiliza ideas de la programación funcional para lograr billones de generaciones por segundo en tableros, billones de cuadrados de un lado. Eso suena imposible, lo sé, pero es completamente posible; Tengo una pequeña implementación agradable en C # que maneja fácilmente tableros cuadrados 2 ^ 64 cuadrados en un lado.

El truco consiste en aprovechar la enorme autosimilitud de los tableros de vida en el tiempo y el espacio. Al memorizar el estado futuro de grandes secciones del tablero, puede avanzar rápidamente grandes secciones a la vez.

He tenido la intención de bloguear una introducción para principiantes al Algoritmo de Gosper durante muchos años, pero nunca he tenido el tiempo. Si termino haciéndolo, publicaré un enlace aquí.

Tenga en cuenta que desea buscar los cálculos del algoritmo de Gosper para la vida , no el algoritmo de Gosper para calcular sumas hipergeométricas.

Eric Lippert
fuente
parece interesante, aunque todavía estoy esperando ese enlace ...;)
jk.
3

Buena coincidencia, cubrimos este problema exacto en nuestra conferencia de Haskell hoy. La primera vez que lo vi, pero aquí hay un enlace al código fuente que nos dieron:

http://pastebin.com/K3DCyKj3

Shane
fuente
¿Le importaría explicar más sobre lo que hace y por qué lo recomienda como respuesta a la pregunta que se hace? Las "respuestas de solo enlace" no son bienvenidas en Stack Exchange
mosquito
3

Es posible que desee ver las implementaciones en RosettaCode para inspirarse.

Por ejemplo, hay versiones funcionales de Haskell y OCaml que crean una nueva placa cada turno aplicando una función a la anterior, mientras que la versión gráfica de OCaml usa dos matrices y las actualiza alternativamente para mayor velocidad.

Algunas de las implementaciones descomponen la función de actualización del tablero en funciones para contar el vecindario, aplicar la regla de vida e iterar sobre el tablero. Esos parecen componentes útiles para basar un diseño funcional. Intente modificar solo el tablero, manteniendo todo lo demás como funciones puras.

Toby Kelsey
fuente
1

Aquí hay una versión corta puramente funcional en Clojure. Todo el crédito es para Christophe Grand, quien publicó esto en su blog: Conway's Game of Life

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

El juego se puede jugar aplicando repetidamente la función "paso" a un conjunto de celdas, por ejemplo:

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

La inteligencia es la parte (celdas vecinas de mapcat): lo que esto hace es crear una lista de ocho vecinos para cada una de las celdas activas y concatenarlos todos juntos. Luego, se puede contar el número de veces que cada celda aparece en esta lista (frecuencias ...) y, finalmente, las que tienen los recuentos de frecuencia correctos pasan a la siguiente generación.

mikera
fuente