Conway's Game of Life Logarithmiusfehler

Salmo Salar

Mitglied
*Logarithmusfehler
Hallo allerseits, nachdem ich nun schon nicht den ersten Tag rumgrübel, habe ich beschlossen mich hier anzumelden.
Ich habe versucht das Spiel "Conways Spiel des Lebens" (siehe https://de.wikipedia.org/wiki/Conways_Spiel_des_Lebens) zu programmieren und es läuft auch soweit - Fenster wird angezeigt, keine Kompilierungsfehler etc.
Aber irgendwas scheint in der Programmlogik durcheinander geraten zu sein - das Spiel folgt nicht mehr den Regeln, dass eine Zelle neben zwei oder drei Nachbarn überlebt und eine tote Zelle neben drei Nachbarn geboren wird.
Da alle Zellen gleichzeitig aktualisiert werden sollen, habe ich einen boolean[][] altesFeld und einen boolean[][] neuesFeld. Die Nachbarn einer Zelle auf dem alten Feld werden abgesucht, und dementsprechend wird dann die Stelle im neuen Feld auf true(lebt) oder false(lebt nicht) gesetzt und in schwarzweiß ausgegeben.
Nach einigem Durchrechnen per Hand habe ich verstanden, dass die neuen Zellen direkt in das alte Feld eingefügt werden, kann allerdings im Quellcode nicht die Stelle finden, an der das passiert.
Ich habe das Programm mit dem OpenSource-Programm BlueJ geschrieben, dass ohne Main()-Methode arbeitet, aber man könnte einfach in die Klasse Logik die Main()-Methode spielen(50,1); reinschreiben- man sieht das Problem bereits nach einem Schritt.

Java:
public class Logik
{
    private JFrame frame;
    private boolean[][] altesFeld;
    private boolean[][] neuesFeld;
    private Welt welt;
    public Logik(){
        frame = new JFrame("Game of Life");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(515,540);
        frame.setVisible(true);
        altesFeld = new boolean[100][100];
        neuesFeld = new boolean[100][100];
        neuZeichnen(neuesFeld);
    }
  
    public void spielen(int prozent, int anzahlSchritte){
        boolean[][] altesFeld = feldZufälligInitialisieren(prozent);
        neuZeichnen(altesFeld);
        int n = 0;
        while(n < anzahlSchritte){
            try{
                Thread.sleep(150);
            }
            catch(Exception e){
                System.out.println("Error in Logik.spielen.Thread.sleep();");
            }
            boolean[][] neuesFeld = nächsteGeneration(altesFeld);
            neuZeichnen(neuesFeld);
            n++;
        }
    }
  
    public boolean[][] feldZufälligInitialisieren(int p){
        int prozent = p;
        Random rand = new Random();
        neuesFeld = new boolean[100][100];
        for (int i = 0; i<100; i++){
            for(int j = 0; j<100; j++){
                int zufallszahl = rand.nextInt(100);
                if(zufallszahl < prozent){
                    neuesFeld[i][j] = true;
                }
                else{
                    neuesFeld[i][j] = false;
                }
            }
        }
        return neuesFeld;
    }
  
    private boolean[][] nächsteGeneration(boolean[][] altesFeld){
        boolean[][] neuesFeld = new boolean[100][100];
        for (int i = 0; i<100; i++){
            for (int j = 0; j < 100; j++){
                int nachbarn = 0;
                nachbarn = getAnzahlNachbarn(altesFeld, i, j);
                if(altesFeld[i][j] == true){     //Zelle lebt
                    if((nachbarn == 2) || (nachbarn == 3)){
                        neuesFeld[i][j] = true;       //Zelle bleibt am Leben
                    }
                    else {
                        neuesFeld[i][j] = false;      //Zelle stirbt.
                    }
                }
                else {                                 //Zelle ist tot
                    if(nachbarn == 3){
                        neuesFeld[i][j] = true;    //Zelle wird geboren
                    }
                    else {
                        neuesFeld[i][j] = false;   //Zelle bleibt tot
                    }
                }
            }
        }
        return neuesFeld;
    }
  
    public int getAnzahlNachbarn(boolean[][] altesFeld, int x, int y){
        int anzahlNachbarn = 0;
        if(x == 0 || x == 99 || y == 0 || y == 99) {
            anzahlNachbarn = 0; //Die Randfelder sollen leer bleiben
        }
        else {
            for(int i  = x-1; i<= x+1; i++) {
                for(int j = y-1; j<= y+1; j++) {
                    if(altesFeld[i][j] == true) {
                        anzahlNachbarn++;
                    }
                }  
            }
        }
        if(altesFeld[x][y] == true){
            anzahlNachbarn --;      //Die Zelle an sich rechnen wir raus.
        }
        return anzahlNachbarn; 
    }

Es existiert noch die Methode neuZeichnen() und die Klasse Welt(), die aber nur noch die Darstellung übernehmen. Wenn sie jemandem nützlich sind, kann ich sie natürlich auch noch dranhängen.
Das Bittere ist, dass es einen Abend funktioniert hat, bis ich irgendwas daran geändert habe- aber ich weiß nicht mehr was.

Liebe Grüße und vielen Dank im Voraus für die Hilfe!
 

mrBrown

Super-Moderator
Mitarbeiter
Ich kann den Fehler nicht nachvollziehen, kann den Code aber aufgrund der fehlenden Teile nicht nachvollziehen.

Deine Instanzattribute altesFeld und neuesFeld sind allerdings überflüssig und können gelöscht werden, die werden aktuell nur im Konstruktor und sonst nirgends benutzt.
 

Salmo Salar

Mitglied
Hallo mrBrown, vielen Dank für eine so schnelle Rückfrage!
Ich würde gerne den Quellcode ergänzen und aktualisieren, kann aber irgendwie nichts mehr an der Frage bearbeiten. Ich sehe nur die Optionen "Thema nicht weiter beobachten", "Melden" und "Zitieren". Ich hatte ihn nicht komplett hochgeladen, weil es in den FAQ so rüberkam, als wären komplette Projekte nicht erwünscht...
Sonst hier nochmal die komplette Klasse Logik:
Java:
import javax.swing.JFrame;
import java.util.Random;

public class Logik
{
    private JFrame frame;
    private Welt welt;
    public Logik(){
        frame = new JFrame("Game of Life");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(515,540);
        frame.setVisible(true);
        boolean[][] altesFeld = new boolean[100][100];
        boolean[][] neuesFeld = new boolean[100][100];
        neuZeichnen(neuesFeld);
    }
   
    public void spielen(int prozent, int anzahlSchritte){
        boolean[][] altesFeld = feldZufälligInitialisieren(prozent);
        neuZeichnen(altesFeld);
        int n = 0;
        while(n < anzahlSchritte){
            try{
                Thread.sleep(150);
            }
            catch(Exception e){
                System.out.println("Error in Logik.spielen.Thread.sleep();");
            }
            boolean[][] neuesFeld = nächsteGeneration(altesFeld);
            neuZeichnen(neuesFeld);
            n++;
        }
    }
   
    public boolean[][] feldZufälligInitialisieren(int p){
        int prozent = p;
        Random rand = new Random();
        boolean[][] neuesFeld = new boolean[100][100];
        for (int i = 0; i<100; i++){
            for(int j = 0; j<100; j++){
                int zufallszahl = rand.nextInt(100);
                if(zufallszahl < prozent){
                    neuesFeld[i][j] = true;
                }
                else{
                    neuesFeld[i][j] = false;
                }
            }
        }
        return neuesFeld;
    }
   
    private boolean[][] nächsteGeneration(boolean[][] altesFeld){
        boolean[][] neuesFeld = new boolean[100][100];
        for (int i = 0; i<100; i++){
            for (int j = 0; j < 100; j++){
                int nachbarn = 0;
                nachbarn = getAnzahlNachbarn(altesFeld, i, j);
                if(altesFeld[i][j] == true){     //Zelle lebt
                    if((nachbarn == 2) || (nachbarn == 3)){
                        neuesFeld[i][j] = true;       //Zelle bleibt am Leben
                    }
                    else {
                        neuesFeld[i][j] = false;      //Zelle stirbt.
                    }
                }
                else {                                 //Zelle ist tot
                    if(nachbarn == 3){
                        neuesFeld[i][j] = true;    //Zelle wird geboren
                    }
                    else {
                        neuesFeld[i][j] = false;   //Zelle bleibt tot
                    }
                }
            }
        }
        return neuesFeld;
    }
   
    public int getAnzahlNachbarn(boolean[][] altesFeld, int x, int y){
        int anzahlNachbarn = 0;
        if(x == 0 || x == 99 || y == 0 || y == 99) {
            anzahlNachbarn = 0; //Die Randfelder sollen leer bleiben
        }
        else {
            for(int i  = x-1; i<= x+1; i++) {
                for(int j = y-1; j<= y+1; j++) {
                    if(altesFeld[i][j] == true) {
                        anzahlNachbarn++;
                    }
                }   
            }
        }
        if(altesFeld[x][y] == true){
            anzahlNachbarn --;      //Die Zelle an sich rechnen wir raus.
        }
        return anzahlNachbarn;  
    }
   
    private void neuZeichnen(boolean[][] f){
        Welt welt = new Welt(f);
        welt.repaint();
        frame.add(welt);
        frame.setVisible(true);
    }
}
Und hier nochmal die Zeichenklasse "Welt"
Java:
import java.awt.*;
import javax.swing.*;

public class Welt extends JPanel
{
    private boolean[][] feld;
    /**
     * Constructor for objects of class Welt
     */
    public Welt(boolean[][] f){
        // initialise instance variables
        feld = f;
    }

    @Override public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //Das Feld
        for(int i = 0; i<100; i++){
            for (int j = 0; j<100; j++){
                if(feld[i][j] == true) {
                    g.setColor(java.awt.Color.BLACK);
                    g.fillRect(i*5, j*5, 5, 5);
                }
                else {
                    g.setColor(java.awt.Color.WHITE); //ja, ist eigentlich unnötig, wenn man das super hat
                    g.fillRect(i*5, j*5, 5,5);
                }
            }
        }
    }
}

Ich weiß, das Programm ist bisher auch nicht weiter lauffähig als einen Schritt, da das Umpacken vom neuen Feld zum alten Feld für die nächste Runde fehlt - ich habe es als potentielle Fehlerursache rausgelassen und den Code minimalistisch gehalten.
 

Salmo Salar

Mitglied
Aber wenn ich das Programm ablaufen lasse tauchen viele "falsche" Figuren auf, einzelne Punkte, wie sie eigentlich nicht überlebensfähig sein sollten, oder eine Raute als "Oszillator", in deren Mitte abwechselnd eine Zelle entsteht und verschwindet, obwohl sie vier Nachbarn hat. Es hat ja schonmal funktioniert, da sah es genauso aus wie online, und dann wollte ich irgendwas umbauen und nun ist es zerstört : (
 

JCODA

Top Contributor
verfolge mal die Zuweisungen von "altesFeld" innerhalb deiner spielen Methode. Du initialisierst es zwar einmal, allerdings setzt du das "alteFeld" nicht mehr auf das "neueFeld".

Ich hab mal einige Dinge geändert, vielleicht möchtest du dich inspirieren lassen, wie du magst:
Java:
import java.awt.*;
import javax.swing.*;

public class Welt extends JPanel {
    private boolean[][] feld = new boolean[100][100];

    /**
     * Constructor for objects of class Welt
     */
    public Welt() {
    }

    public void setWelt(boolean[][] feld) {
        this.feld = feld;
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        // Das Feld
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                if (feld[i][j]) {
                    g.setColor(java.awt.Color.BLACK);
                } else {
                    g.setColor(java.awt.Color.WHITE);
                }
                g.fillRect(i * 5, j * 5, 5, 5);
            }
        }
    }
}

Java:
import javax.swing.JFrame;
import java.util.Random;

public class Logik {
    private JFrame frame;
    private Welt welt = new Welt();

    public static void main(String args[]) throws InterruptedException {
        new Logik().spielen(50, 1000);
    }

    public Logik() {
        frame = new JFrame("Game of Life");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(515, 540);
        frame.setContentPane(welt);
        frame.setVisible(true);
    }

    public void spielen(int prozent, int anzahlSchritte) throws InterruptedException {
  
        boolean[][] feld = feldZufälligInitialisieren(prozent);

        for (int n = 0; n < anzahlSchritte; n++) {
            welt.setWelt(feld);         
            Thread.sleep(150);      
            feld = nächsteGeneration(feld);
        }

    }

    public boolean[][] feldZufälligInitialisieren(int p) {
        Random rand = new Random();
        boolean[][] neuesFeld = new boolean[100][100];
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                neuesFeld[I][j] = rand.nextInt(100) < p;
            }
        }
        return neuesFeld;
    }

    private boolean[][] nächsteGeneration(boolean[][] altesFeld) {
        boolean[][] neuesFeld = new boolean[100][100];
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                int nachbarn = getAnzahlNachbarn(altesFeld, i, j);

                if (altesFeld[I][j]) {
                    neuesFeld[I][j] = (nachbarn == 2) || (nachbarn == 3);
                } else {
                    neuesFeld[I][j] = nachbarn == 3;

                }
            }
        }
        return neuesFeld;
    }

    public int getAnzahlNachbarn(boolean[][] altesFeld, int x, int y) {
     
        if (x == 0 || x == 99 || y == 0 || y == 99) {
            return 0; // Die Randfelder sollen leer bleiben
        }
        int anzahlNachbarn = 0;
        for (int i = x - 1; i <= x + 1; i++) {
            for (int j = y - 1; j <= y + 1; j++) {
                if (altesFeld[I][j]) {
                    anzahlNachbarn++;
                }
            }
        }

        if (altesFeld[x][y]) {
            anzahlNachbarn--; // Die Zelle an sich rechnen wir raus.
        }
        return anzahlNachbarn;
    }
}

Hinweis: Eigentlich hätte man hier noch mit Threads oder Timern arbeiten sollen, denn das sleep ist an dieser Stelle nicht gerade schön, aber ich hab es jetzt einmal dabei belassen.
 
Zuletzt bearbeitet:

Salmo Salar

Mitglied
Hallo JCODA, vielen Dank für die ausführliche Antwort! Die Änderungen, die ich bisher ausmachen konnte erscheinen mir sinnvoll, nur die Main()-Methode brauche ich wie gesagt in BlueJ nicht, das pack ich dann nachher einfach in den Konstruktor. Mit deiner Genehmigung teste ich deine Version erst morgen, nach dem Schlafen - aber hast du sie schon ausprobiert? Oder tritt das komische Spielfeld nur bei mir auf?
Und die Threads sollte ich vielleicht tatsächlich mal angucken, oft lernt man aber erst Neues wenn das Alte nicht mehr greift: )
 

Salmo Salar

Mitglied
Hallo JCODA, vielen, vielen Dank, es funktioniert wieder:) Was hast du gemacht? Ich habe gesehen, das du die Abgleiche in nächsteGeneration() so gemacht hast:
Java:
neuesFeld[j] = (nachbarn == 2) || (nachbarn == 3);
Ich habe sowas zwar noch nie gesehen, aber kann man anscheinend ja so machen. Lag dort der Fehler?
@Meniskusschaden : natürlich spricht nichts gegen eine Main()-Methode, ich habe sie auch dagelassen. Am Ende mache ich wohl sowieso einen Dialog rein, wo der Benutzer die Prozent- und Schrittzahl aussucht. Aber vielen Dank, dass du dir die Zeit genommen hast dich dem Problem zu widmen!
 
X

Xyz1

Gast
Zitieren wir mal:
Siehe oben hat gesagt.:
Code:
Die von Conway zu Anfang verwendeten Regeln sind:

    Eine tote Zelle mit genau drei lebenden Nachbarn wird in der Folgegeneration neu geboren.

    Lebende Zellen mit weniger als zwei lebenden Nachbarn sterben in der Folgegeneration an Einsamkeit.

    Eine lebende Zelle mit zwei oder drei lebenden Nachbarn bleibt in der Folgegeneration am Leben.

    Lebende Zellen mit mehr als drei lebenden Nachbarn sterben in der Folgegeneration an Überbevölkerung.

Java:
int anzahl = getAnzahl(zelle);
if (zelle.tot()) {
  if (anzahl == 3) {
    zelle.neuBoren();
  }
} else {
  if (anzahl < 2 || anzahl > 3) {
    zelle.neuSterben();
  }
}

Das wäre die Spiellogik, wenn ich mich nicht vertue. :D

Ehhh, kleiner Vorteil, wenn die Zelle schon lebt.
 

JCODA

Top Contributor
Was hast du gemacht?
Wie gesagt, in der spielen-Methode hast du das "neueFeld" nicht weiterverwendet, sondern immer vom Alten die "nächste" Generation berechnet.
Ich habe gesehen, das du die Abgleiche in nächsteGeneration() so gemacht hast:
Java:
neuesFeld[j] = (nachbarn == 2) || (nachbarn == 3);
Ich habe sowas zwar noch nie gesehen, aber kann man anscheinend ja so machen. Lag dort der Fehler?
Nein ich denke nicht, dass es falsch war, aber diese Variante gefällt mir besser.
Diese Vergleiche liefern Wahrheitswerte, also booleans, dann muss man nicht extra nochmal Abfragen (mit if), ob sie wahr sind und dann auf wahr setzen, sondern kann explizit sofort den Wert übernehmen.
 

mrBrown

Super-Moderator
Mitarbeiter
Das ist doch genau das, was er mit Absicht raus genommen hat, weil der Fehler schon bei einem Schritt kam?
Ich weiß, das Programm ist bisher auch nicht weiter lauffähig als einen Schritt, da das Umpacken vom neuen Feld zum alten Feld für die nächste Runde fehlt - ich habe es als potentielle Fehlerursache rausgelassen und den Code minimalistisch gehalten.
 

Salmo Salar

Mitglied
Bin gerade verwirrt (wer ist TE?), aber na ja, hauptsache, jetzt funktioniert der Code in JCODAs Version, auch über mehrere "Generationen" hinweg.
Vielen Dank an alle, die sich die Mühe gemacht haben, sich in den Code einzulesen und nach einer Lösung zu suchen. Ich füge nun ein paar Buttons mit Listener und Dialogen hinzu und mache mal ein richtiges Programm daraus.
Insofern könnte man dieses Thema als beendet betrachten.
Liebe Grüße, Salmo Salar
 

Meniskusschaden

Top Contributor
Der Thread-Ersteller. Also du.;)

auch über mehrere "Generationen" hinweg.
Das verwirrt mich jetzt. Hast du nicht einen Fehler vom Übergang der ersten Generation (altesFeld) zur zweiten Generation (neuesFeld) erwähnt? Ich habe es nämlich auch so gesehen wie @mrBrown es in Post #12 erläutert hat. Demnach gab es keinen Logik-Fehler, mit Ausnahme des Nicht-Fortschreibens der neuen Generationen, was von dir laut Post #3 aber auch so beabsichtigt war.:confused:
 

Salmo Salar

Mitglied
Irrungen Wirrungen... Der Fehler war, dass die Darstellung im Frame nicht den Regeln entsprach, z.B. freischwebende lebendige Zellen ohne Nachbarn. Also war doch die Logik durcheinander und da der Fehler bereits nach einem Schritt auftrat, habe ich die Schleife für mehrere Generationen damals weggemacht, um möglichst "nackten" Code hier reinzustellen, damit man den Fehler vielleicht schneller findet.
Nun funktioniert die gekürzte Version von JCODA, nun kämpfe ich mit den Layout-Managern... aber ich versuch's erstmal alleine.
 

Ähnliche Java Themen


Oben