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)
functional-programming
George Mauer
fuente
fuente
Respuestas:
Bueno, un par de ideas. No soy un experto en FP, pero ...
Está bastante claro que deberíamos tener un tipo
Board
que represente un estado de juego. La base de la implementación debe ser unaevolve
función de tipoevolve :: Board -> Board
; lo que significa que produce a aBoard
partir de la aplicación de las reglas del juego a aBoard
.¿Cómo debemos implementar
evolve
? ABoard
probablemente debería ser una matriz nxm deCell
s. Podríamos implementar una funcióncellEvolve
de tipocellEvolve :: Cell -> [Cell] -> Cell
que, dado que ayCell
susCell
s vecinas calculan elCell
estado en la próxima iteración.También deberíamos implementar una
getCellNeighbors
función que extraigaCell
los vecinos de s de aBoard
. No estoy completamente seguro de la firma de este método; dependiendo de cómo implementeCell
yBoard
podría tener, por ejemplogetCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell]
, que dado un tablero y dos coordenadas (CoordElem
sería el tipo utilizado para indexar posiciones en aBoard
), 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).evolve
por lo tanto, se puede implementar combinandocellEvolve
ygetCellNeighbors
para todas las celdas en el tablero, nuevamente la implementación exacta dependerá de cómo se implementeBoard
yCell
, 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
cellEvolve
manera que tome como parámetro un tipoGameRules
que 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 .cellEvolve
la firma de entonces podría sercellEvolve :: GameRules -> Cell -> [Cell] -> Cell
Esto lógicamente lo llevaría a
evolve :: Board -> Board
convertirse enevolve :: GameRules -> Board -> Board
, para que pueda usarevolve
sin cambios con diferentesGameRules
, pero podría ir un paso más allá y hacer que se puedacellEvolve
enchufar en lugar deGameRules
.Jugar
getCellNeighbors
contigo también podría serevolve
genérico en lo que respecta a laBoard
topología de la misma, podrías tenergetCellNeighbors
que envolver los bordes de cada tablero, tableros 3d, etc.fuente
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.
fuente
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
fuente
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.
fuente
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
El juego se puede jugar aplicando repetidamente la función "paso" a un conjunto de celdas, por ejemplo:
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.
fuente