Natknęłam się ostatnio w sieci na wpis o implementacji maszyny stanów w Groovy’m i postanowiłam wykorzystać ten pomysł w praktyce. Do implementacji wybrałam Grę w Życie. Algorytm nie jest skomplikowany, każda komórka może przyjąć jeden z dwóch stanów, a i przejść między stanami nie jest za dużo. W skrócie: istnieje dwuwymiarowa plansza z komórkami. Każda komórka może być albo 'żywa’ albo 'martwa’. Stan komórki zależy od jej 'sąsiadów’ – 8 stykających się z nią komórek. W każdym kroku sprawdza się stan komórki i zmienia się go zgodnie z następującymi zasadami:
- jeśli żywą komórkę otacza mniej niż 2 żywych sąsiadów – komórka umiera z samotności
- jeśli żywą komórkę otacza więcej niż 3 żywych sąsiadów – komórka umiera z „przeludnienia”
- jeśli martwą komórkę otacza dokładnie 3 żywych sąsiadów – komórka ożywa
- w pozostałych przypadkach, komórka nie zmienia stanu
Cały kod programu tu: github
Implementacja takiej maszyny wg wpisu to mapa, gdzie kluczem jest obecny stan komórki, a jako wartość występuje lista możliwych przejść:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static Map<State, List> state_machine_definition = [ | |
(State.ALIVE): [ | |
[event: Event.OVER_POPULATION, to: State.DEAD], | |
[event: Event.UNDER_POPULATION, to: State.DEAD], | |
[event: Event.STABLE, to: State.ALIVE, afterAction: 'increment'], | |
], | |
(State.DEAD) : [ | |
[event: Event.REPRODUCTION, to: State.ALIVE, afterAction: 'reset'], | |
[event: Event.STABLE, to: State.DEAD] | |
] | |
] |
Kod przejścia jest równie nieskomplikowany – wyciągamy z maszyny stanów możliwe przejścia dla obecnego stanu komórki. Sprawdzamy, czy jest możliwe przejście wyzwalane eventem, który został przekazany jako parametr. Jeśli wszystko jest ok, tworzymy nową komórkę z nowym stanem wynikającym z przejścia i ewentualnie wykonujemy dodatkowe akcje związane z przejściem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static Cell transition(event, Cell cell) { | |
List states = state_machine_definition[cell.state] | |
def transition = states.find { it.event == event } | |
if (!transition) { | |
throw new Exception("invalid event $event for state ${cell.state}") | |
} | |
Cell tempCell = new Cell(transition.to, cell.lifeLong) | |
if (transition.afterAction) { | |
tempCell."${transition.afterAction}"() | |
} | |
return tempCell | |
} |
Definicja komórki jest dość prosta. Każda komórka ma stan i dodatkowo 'długość życia’, którą wykorzystuję później do kolorowania komórek na ekranie. Używam też klasy Board, w której przechowuję dwuwymiarową tablicę z komórkami najważniejszą metodę step(), która przechodząc komórka po komórce, przy użyciu maszyny stanów ustawia kolejny stan na podstawie tego ilu żywych sąsiadów ją otacza (getEvent()).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void step() { | |
Board tmpBoard = new Board(this.board) | |
for (int x = 0; x < this.x; x++) { | |
for (int y = 0; y < this.y; y++) { | |
Cell cell = board[x][y] | |
Event event = getEvent(x, y, cell.state) | |
tmpBoard.board[x][y] = StateMachineDefinition.transition(event, cell) | |
} | |
} | |
this.board = tmpBoard.board | |
} |
Wstępnie wyjście wyrzucałam na konsolę w postaci kropek i gwiazdek:
ale zachciało mi się przedstawić efekt działania programu w postaci animacji. Nie chciałam się bawić w Swinga i AWT, JavaFX byłaby do tego za ciężka, więc zadałam pytanie na grupie „Programuj, dziewczyno!” czy nie znają czegoś odpowiedniego. I tak poznałam Processing, który zaskoczył mnie tym jak mało kodu wymaga przedstawienie tablicy jako pikseli i zrobienie z nim animacji: kod tu
Good post. I study one thing more difficult on totally different blogs everyday. It will always be stimulating to learn content from other writers and follow a little one thing from their store. I’d favor to use some with the content on my blog whether you don’t mind. Natually I’ll provide you with a hyperlink on your web blog. Thanks for sharing.