Niedawno zaimplementowałem dla zabawy Game of Life Conwaya w Javascript (właściwie coffeescript, ale to samo). Ponieważ javascript może być używany jako język funkcjonalny, starałem się pozostać na tym końcu spektrum. Nie byłem zadowolony z moich wyników. Jestem dość dobrym programistą OO, a moje rozwiązanie zostało spryskane tym samym-starym-tym samym-starym. Tak długie pytanie: jaki jest funkcjonalny (pseudokod) styl działania?
Oto Pseudokod dla mojej próby:
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
źródło
źródło
Odpowiedzi:
Cóż, kilka pomysłów. Nie jestem ekspertem od FP, ale ...
Jest całkiem jasne, że powinniśmy mieć typ
Board
reprezentujący stan gry. Podstawą wdrożenia powinna byćevolve
funkcja typuevolve :: Board -> Board
; co oznacza, że powoduje toBoard
zastosowanie reguł gry doBoard
.Jak powinniśmy wdrożyć
evolve
?Board
Powinien prawdopodobnie matryca nXM odCell
s. Możemy zaimplementować funkcjęcellEvolve
typu,cellEvolve :: Cell -> [Cell] -> Cell
która dla danego aCell
i jego sąsiadaCell
s obliczaCell
stan w następnej iteracji.Powinniśmy także zaimplementować
getCellNeighbors
funkcję, która wyodrębniaCell
sąsiadów zBoard
. Nie jestem całkowicie pewien podpisu tej metody; w zależności od tego, jak się zaimplementujesz,Cell
iBoard
na przykład możesz miećgetCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell]
, który z tablicą i dwiema współrzędnymi (CoordElem
byłby typem używanym do indeksowania pozycji w aBoard
), daje listę sąsiadów o zmiennej długości (nie wszystkie komórki na tablicy mają taka sama liczba sąsiadów - rogi mają 3 sąsiadów, granice 5 i wszystkich innych, 8).evolve
można więc zaimplementować łącząccellEvolve
igetCellNeighbors
dla wszystkich komórek na planszy, ponownie dokładna implementacja będzie zależeć od sposobu implementacjiBoard
iCell
, ale powinno to być coś w rodzaju „dla wszystkich komórek na bieżącej planszy, zdobądź ich sąsiadów i użyj ich do obliczenia nowa komórka odpowiadająca komórce ”. Powinno to być możliwe w przypadku ogólnego zastosowania tych funkcji na całej płycie przy użyciu„ mapy funkcji komórki na tablicy ”.Inne przemyślenia:
Naprawdę powinieneś zaimplementować,
cellEvolve
aby jako parametrGameRules
przyjmował typ kodujący reguły gry - powiedz listę krotek,(State,[(State,NumberOfNeighbors)],State)
która mówi o danym stanie i liczbę sąsiadów w każdym stanie, który powinien być stanem w następnej iteracji .cellEvolve
Podpis mógłby wtedy byćcellEvolve :: GameRules -> Cell -> [Cell] -> Cell
To logicznie doprowadziłoby cię do zmiany
evolve :: Board -> Board
wevolve :: GameRules -> Board -> Board
, abyś mógł używaćevolve
niezmienionych z innymiGameRules
, ale możesz pójść o krok dalej i uczynićcellEvolve
wtykowym zamiastGameRules
.Zabawa z
getCellNeighbors
tobą może również byćevolve
ogólna w odniesieniu doBoard
topologii - możesz mieć,getCellNeighbors
które owijają się wokół krawędzi planszy, plansz 3d itp.źródło
Jeśli piszesz funkcjonalną wersję programistyczną Life, musisz jej dowiedzieć się o algorytmie Gospera. Wykorzystuje pomysły z programowania funkcjonalnego, aby osiągnąć biliony pokoleń na sekundę na tablicach bilionów kwadratów na boku. Wiem, że to brzmi niemożliwe, ale jest całkowicie możliwe; Mam fajną małą implementację w języku C #, która z łatwością radzi sobie z kwadratowymi planszami 2 ^ 64 kwadratów z boku.
Sztuką jest wykorzystanie ogromnego samopodobieństwa plansz Życia zarówno w czasie, jak i przestrzeni. Zapamiętując przyszły stan dużych sekcji planszy, możesz szybko przejść do dużych sekcji jednocześnie.
Zamierzałem blogować wprowadzenie dla początkujących do Algorytmu Gospera od wielu lat, ale nigdy nie miałem czasu. Jeśli to zrobię, opublikuję link tutaj.
Zauważ, że chcesz sprawdzić obliczenia algorytmu Gospera dla życia , a nie algorytmu Gospera do obliczania sum hipergeometrycznych.
źródło
Niezły zbieg okoliczności, dokładnie omawialiśmy ten problem w naszym dzisiejszym wykładzie w Haskell. Pierwszy raz go widziałem, ale oto link do kodu źródłowego, który otrzymaliśmy:
http://pastebin.com/K3DCyKj3
źródło
Inspiracje możesz znaleźć w implementacjach RosettaCode .
Na przykład istnieją funkcjonalne wersje Haskell i OCaml, które tworzą nową tablicę w każdej turze poprzez zastosowanie funkcji do poprzedniej, podczas gdy wersja graficzna OCaml wykorzystuje dwie tablice i aktualizuje je naprzemiennie dla prędkości.
Niektóre implementacje rozkładają funkcję aktualizacji planszy na funkcje zliczania sąsiedztwa, stosowania reguły życia i iteracji po planszy. Wydają się być przydatnymi komponentami, na których można oprzeć funkcjonalny projekt. Spróbuj zmodyfikować tylko kartę, zachowując wszystko inne jako czyste funkcje.
źródło
Oto krótka, czysto funkcjonalna wersja Clojure. Wszystkie podziękowania należą się Christophe'owi Grandowi, który opublikował to na swoim blogu: Conway's Game of Life
W grę można następnie grać, stosując wielokrotnie funkcję „krok” do zestawu komórek, np .:
Spryt to część (komórki sąsiadów mapcat) - tworzy to listę ośmiu sąsiadów dla każdej z aktywnych komórek i łączy je wszystkie razem. Następnie liczbę zliczeń każdej komórki na tej liście można policzyć (częstotliwości ....) i wreszcie te, które mają prawidłowe liczenie częstotliwości, przechodzą do następnej generacji.
źródło