JavaFX z Arią - powrót do strony głównej

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):

Jeden z wyników dla reguły 2345/45678
Jeden z wyników dla reguły 2345/45678