Quali sono i modi di programmazione funzionale per implementare Conway's Game of Life [chiuso]


12

Recentemente ho implementato per divertimento Conway's Game of Life in Javascript (in realtà coffeescript ma stessa cosa). Dato che javascript può essere usato come linguaggio funzionale, stavo cercando di rimanere fino a quell'estremità dello spettro. Non ero contento dei miei risultati. Sono un programmatore OO abbastanza bravo e la mia soluzione sa di vecchio-stesso-vecchio-vecchio. Quindi lunga domanda breve: qual è lo stile funzionale (pseudocodice) di farlo?

Ecco Pseudocode per il mio tentativo:

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)


@Oded che è deprimente sopra la mia testa. Riconosco i concetti di base ma solo a malapena
George Mauer,

Anche oltre la mia testa ... l'ho appena pubblicato come esempio di ciò che un maestro di una lingua specializzata può fare. Chiamalo fonte d'ispirazione per tutti noi :)
Oded,

@GeorgeMauer "in realtà coffeescript ma stessa cosa" questo è un giorno triste
Raynos

Risposte:


11

Bene, un paio di idee. Non sono un esperto di FP, ma ...

È abbastanza chiaro che dovremmo avere un tipo Boardche rappresenta uno stato di gioco. La base dell'attuazione dovrebbe essere una evolvefunzione di tipo evolve :: Board -> Board; nel senso che produce a Boarddall'applicazione delle regole del gioco a Board.

Come dovremmo implementare evolve? A Boarddovrebbe probabilmente essere una matrice nxm di Cells. Potremmo implementare una funzione cellEvolvedi tipo cellEvolve :: Cell -> [Cell] -> Cellche ha dato Cellae Celli suoi vicini calcolano lo Cellstato nella prossima iterazione.

Dovremmo anche implementare una getCellNeighborsfunzione che estrae Celli vicini di casa da a Board. Non sono del tutto sicuro della firma di questo metodo; a seconda di come si implementa Celle Boardsi potrebbe avere, ad esempio getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell], che ha dato una tavola e due coordinate ( CoordElemsarebbe il tipo usato per indicizzare le posizioni in a Board), ti dà un elenco di lunghezza variabile dei vicini (non tutte le celle nella tavola hanno il lo stesso numero di vicini: gli angoli hanno 3 vicini, i confini 5 e tutti gli altri, 8).

evolvepuò quindi essere implementato combinando cellEvolvee getCellNeighborsper tutte le celle nella scheda, ancora una volta l'implementazione esatta dipenderà da come si implementa Boarde Cell, ma dovrebbe essere qualcosa di simile a "per tutte le celle nella scheda corrente, ottenere i loro vicini e usarli per calcolare il la cella corrispondente della nuova scheda ". Ciò dovrebbe essere possibile con un'applicazione generica di tali funzioni sull'intera scheda utilizzando una" funzione cella sulla mappa della scheda ".

Altri pensieri:

  • Dovresti davvero implementare in cellEvolvemodo che prenda come parametro un tipo GameRulesche codifichi le regole del gioco, ad esempio un elenco di tuple (State,[(State,NumberOfNeighbors)],State)che dice per un dato stato e il numero di vicini in ogni stato, che dovrebbe essere lo stato nella prossima iterazione . cellEvolveLa firma potrebbe quindi esserecellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • Ciò logicamente ti porterebbe a evolve :: Board -> Boardtrasformarti in evolve :: GameRules -> Board -> Board, in modo da poter usare evolveinvariato con diversi GameRules, ma potresti fare un passo ulteriore e rendere cellEvolvecollegabile anziché GameRules.

  • Giocare con getCellNeighborste potrebbe anche rendere evolvegenerico per quanto riguarda la Boardtopologia - potresti avere getCellNeighborsche avvolgono i bordi di una delle schede, le schede 3d, ecc.


9

Se stai scrivendo una versione di programmazione funzionale di Life, devi a te stesso conoscere l'Algoritmo di Gosper. Usa idee dalla programmazione funzionale per raggiungere trilioni di generazioni al secondo su schede trilioni di quadrati su un lato. Sembra impossibile, lo so, ma è del tutto possibile; Ho una piccola implementazione in C # che gestisce facilmente quadrati quadrati 2 ^ 64 quadrati su un lato.

Il trucco è trarre vantaggio dalla massiccia auto-somiglianza delle schede Life attraverso il tempo e lo spazio. Memorizzando lo stato futuro di ampie sezioni del tabellone è possibile avanzare rapidamente in enormi sezioni contemporaneamente.

Ho intenzione di blog un'introduzione per principianti all'algoritmo di Gosper per molti anni ormai, ma non ho mai avuto il tempo. Se finisco per farlo, posterò un link qui.

Si noti che si desidera cercare i calcoli dell'algoritmo di Gosper per la vita , non l'algoritmo di Gosper per il calcolo delle somme ipergeometriche.


sembra interessante - sto ancora aspettando quel link ...;)
jk.

3

Bella coincidenza, abbiamo affrontato questo esatto problema nella nostra lezione di Haskell oggi. La prima volta che l'ho visto, ma ecco un link al codice sorgente che ci è stato dato:

http://pastebin.com/K3DCyKj3


ti dispiacerebbe spiegare di più su ciò che fa e perché lo consigli come rispondere alla domanda posta? Le "risposte solo link" non sono del tutto benvenute allo Stack Exchange
moscerino del

3

Potresti voler dare ispirazione alle implementazioni su RosettaCode .

Ad esempio, esistono versioni funzionali Haskell e OCaml che creano una nuova scheda ogni turno applicando una funzione alla precedente, mentre la versione grafica OCaml utilizza due array e li aggiorna alternativamente per la velocità.

Alcune implementazioni decompongono la funzione di aggiornamento della scheda in funzioni per il conteggio del vicinato, l'applicazione della regola di vita e l' iterazione sulla lavagna. Sembrano componenti utili su cui basare un design funzionale. Prova a modificare solo la scheda, mantenendo tutto il resto come funzioni pure.


1

Ecco una breve versione puramente funzionale in Clojure. Tutto il merito va a Christophe Grand che lo ha pubblicato nel suo post sul 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)))

Il gioco può quindi essere giocato applicando ripetutamente la funzione "step" a un set di celle, ad es .:

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

L'intelligenza è la parte (mapcat neighbors cell) - ciò che fa è creare un elenco di otto vicini per ciascuna delle celle attive e concatenarli tutti insieme. Quindi il numero di volte in cui ogni cella appare in questo elenco può essere contato con (frequenze ....) e infine quelli che hanno i conteggi di frequenza giusti passano alla generazione successiva.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.