JavaFX: automaty komórkowe
W książce Matematyka dla programistów JavaScript w rozdziale 21 zostały opisane automaty komórkowe z przykładami języku JavaScript.
W książce Matematyka dla programistów Java w rozdziale 24 zostały opisane automaty komórkowe z przykładami języku Java.
Tutaj zamieszczam bonus dla programistów JavaFX. Są to kody przykładów odnoszących się do automatów komórkowych.
Jedna z klas została przygotowana wyłącznie na potrzeby tego wpisu. Opis znajduje się poniżej.
Gra w życie Conway’a (Conway’s Game of Life) – modyfikacje reguł.
W literaturze przyjęło się zapisywać nowe reguły dla Gry w Życie w poniższy sposób:
- przed ukośnikiem umieszcza się te liczby komórek w sąsiedztwie, dla których żywe komórki przeżywają (dla reguły Conwaya będzie to 23). Ten zapis czasami przyjmuje formę B23, gdzie 'B’ pochodzi od słowa 'birth’
- następnie umieszcza się ukośnik: '/’
- po ukośniku umieszcza się te liczby komórek w sąsiedztwie, dla których martwe komórki ożywają (dla reguły Conwaya będzie to 3). Ten zapis czasami przyjmuje formę S3, gdzie 'S’ pochodzi od słowa 'survive’.
Tak więc reguła dla Gry w Życie może być zapisana albo jako ’23/3′ albo jako 'B23/S3′.
W niektórych źródłach przyjmuje się odwrotną kolejność np. 'S3/B23′
W poniższej klasie kolejność zapisu jest zawsze 23/3. Należy pamiętać, aby reguły dotyczące urodzin były zawsze umieszczane jako pierwsze.
Gdy wyniki działania klasy nie będą zgodne z twoimi oczekiwaniami – poeksperymentuj z prawdopodobieństwem, które ma decydujący wpływ na otrzymywane wyniki.
Klasa LifeCanvas2.java
package autosfx; import javafx.animation.AnimationTimer; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import java.util.Arrays; public class LifeCanvas2 extends Canvas { private static final int w = 800; private static final int h = 800; private static final int liczbaPol = 100; private static final int szer = 8; private static final int len = szer * liczbaPol; private boolean[][] autos1;//komorki aktualne private final boolean[][] autos2;//komorki nastepnej generacji private final Color kolorKomorek; private final Ksztalt ksztalt; private final GraphicsContext gc; private final AnimationTimer timer; private final long millis; private final int[] birthRule; private final int[] survRule; public LifeCanvas2(Ksztalt ksztalt, Color kolorKomorek, double prawdopodobienstwo, long millis, String rule) { this.kolorKomorek = kolorKomorek; //prawdopodobienstwo żywej komórki this.ksztalt = ksztalt; this.millis = millis; this.prefWidth(w); this.prefHeight(h); this.setWidth(w); this.setHeight(h); this.autos1 = new boolean[liczbaPol][liczbaPol]; this.autos2 = new boolean[liczbaPol][liczbaPol]; this.birthRule = getBirthRule(rule); this.survRule = getSurvRule(rule); //clear(autos1); //clear(autos2); for (int i = 0; i < liczbaPol; i++) { for (int j = 0; j < liczbaPol; j++) { this.autos1[i][j] = Math.random() < prawdopodobienstwo; } } this.gc = this.getGraphicsContext2D(); this.timer = new LifeTimer(); this.timer.start(); } public void paint(GraphicsContext gc) { gc.clearRect(0, 0, w, h); gc.setStroke(Color.rgb(200, 200, 200)); int z = 0;//polozenie linii for (int i = 0; i <= liczbaPol + 1; i++) { gc.strokeLine(0, z, len, z); gc.strokeLine(z, 0, z, len); z += szer; } gc.setFill(kolorKomorek); for (int i = 0; i < liczbaPol; i++) { for (int j = 0; j < liczbaPol; j++) { if (autos1[i][j]) { int x = i * szer; int y = j * szer; switch (ksztalt) { case KOLO -> gc.fillOval(x, y, szer, szer); case KWADRAT -> gc.fillRect(x, y, szer - 1, szer - 1); } } } } } private int liczbaSasiadow(boolean[][] autos, int p, int q) { int x1 = p; int x2 = p; int y1 = q; int y2 = q; int licznik = 0; if (p > 0) { x1--; } if (p < liczbaPol - 1) { x2++; } if (q > 0) { y1--; } if (q < liczbaPol - 1) { y2++; } for (int i = x1; i <= x2; i++) { for (int j = y1; j <= y2; j++) { licznik += (autos[i][j]) ? 1 : 0; } } if (autos[p][q]) { licznik--; } return licznik; } public static boolean[][] clone(boolean[][] array) { int r = array.length; int c = array[0].length; boolean[][] array1 = new boolean[r][c]; for (int i = 0; i < r; i++) { array1[i] = Arrays.copyOf(array[i], c); } return array1; } private void clear(boolean[][] array) { for (int j = 0; j < array.length; j++) { for (int k = 0; k < array[0].length; k++) { array[j][k] = false; } } } public void putStructure(boolean[][] array) { clear(this.autos1); for (int m1 = 0; m1 < array.length; m1++) { for (int j1 = 0; j1 < array[0].length; j1++) { this.autos1[j1 + 25][m1 + 25] = array[m1][j1]; } } } private void putStructure(boolean[] array, int cols) { clear(this.autos1); boolean[][] arr = oneToTwo(array, cols); for (int m1 = 0; m1 < arr.length; m1++) { System.arraycopy(arr[m1], 0, this.autos1[m1 + 25], 25, arr[0].length); } } private boolean[][] oneToTwo(boolean[] tab, int cols) { int rows = tab.length / cols; boolean[][] temparr = new boolean[rows][cols]; for (int i = 0; i < tab.length; i++) { int x = (i / cols); int y = i % cols; temparr[x][y] = tab[i]; } return temparr; } private class LifeTimer extends AnimationTimer { @Override public void handle(long now) { for (int i = 0; i < liczbaPol; i++) { for (int j = 0; j < liczbaPol; j++) { boolean ld = autos1[i][j]; int nb = liczbaSasiadow(autos1, i, j); if (ld) { int sr = Arrays.binarySearch(survRule, nb); if (sr >= 0) { autos2[i][j] = autos1[i][j]; } } else { int br = Arrays.binarySearch(birthRule, nb); if (br >= 0) { autos2[i][j] = true; } } } } autos1 = LifeCanvas2.clone(autos2); clear(autos2); paint(gc); try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } } public static int[] getSurvRule(String str) { String[] strs = str.split("/"); int[] tabl = new int[strs[0].length()]; for (int i = 0; i < tabl.length; i++) { tabl[i] = Integer.parseInt(strs[0].substring(i, i + 1)); } return tabl; } public static int[] getBirthRule(String str) { String[] strs = str.split("/"); int[] tabl = new int[strs[1].length()]; for (int i = 0; i < tabl.length; i++) { tabl[i] = Integer.parseInt(strs[1].substring(i, i + 1)); } return tabl; } public AnimationTimer getTimer() { return timer; } }
Klasa Autos21fx.java
package autosfx; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class Autos21fx extends Application { public static void main(String[] args) { Application.launch(args); } @Override public void init() { } @Override public void start(Stage stage) { try { Pane root = new Pane(); BackgroundFill bf = new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, new Insets(0)); Background bg = new Background(bf); root.setBackground(bg); Canvas cv = new LifeCanvas2(Ksztalt.KWADRAT, Color.BLUE, 0.2, 250, "2345/45678"); root.getChildren().add(cv); Scene scene = new Scene(root); stage.setScene(scene); stage.setOnCloseRequest(e -> Platform.exit()); stage.show(); } catch (Exception e) { e.printStackTrace(); } } @Override public void stop() { } }
Po uruchomieniu klasy zobaczymy wynik (za każdym razem inny):