"Spiel des Lebens"

Wang

Bekanntes Mitglied
Hallo,

irgendwie komme ich bei diesem Projekt auf keinen grünen Zweig. Es heißt zwar "lassen sie sich im Internet von verschiedenen Implementierungen inspirieren", aber ich finde, es ist besser, wenn sich Lösungsansätze aus der Diskussion ergeben (Idealfall: man kommt selbst drauf). Sonst heißt es nach der Abgabe noch "Plagiat!".

picez.jpg


Es wird ja ein besonderer Wert darauf gelegt, strikt zwischen Model, View und Control zu unterscheiden.
Ist es richtig, dass die Teilaufgabe b) dem Model und die Teilaufgabe c) dem Control entspricht, also zwei Klassen her müssen?

In Teilaufgabe b) heißt es, dass die Welt wahlweise begrenzt oder unbegrenzt sein soll, dass also der Anwender des Programms das bestimmen kann.
Mir ist klar, dass da mit Modulo gearbeitet werden muss, falls die Welt unbegrenzt sein soll, aber ich weiß nicht, wo und wie ich das implementieren soll?

Java:
// Datei: Welt.java

public class Welt
{
	private int [][] feld;
	
	public void setWelt(int x, int y)
	{
		feld = new int [x][y];
	}
	
	
}


Ich denke das genügt erstmal, denn vielleicht erschließt sich mir der Rest, wenn ich das geklärt habe.

Vielen Dank für die Hilife.

Gruß
Wang
 

Marco13

Top Contributor
Es empfiehlt sich, das Modell erstmal als Interface zu definieren. Die Implementierung könnte dann ggf. ein "DefaultModel" sein.

Für Model-View-Controller braucht man i.a. drei Klassen ;) Die "View" wäre für das CLI vielleicht einfach ein Ding, das das Modell mit System.out.printlns ausgibt, und der Controller der, der von der Konsole Eingaben liest.

Das mit dem Modulo kommt erst zum Tragen, wenn man einzelne Zellen setzt oder die neue generation berechnet
Code:
public void set(int x, int y, int state)
{
    x %= width;
    y %= height;
    if (x<0) x+=width;
    if (y<0) y+=height;
    feld[x][y] = state;
}
 

Wang

Bekanntes Mitglied
Danke Marco,
ohne dich und dem Forum hier, hätte ich das erste Semester damals wohl nicht bestanden. ;)

Wenn ich es richtig verstanden habe, soll es also folgende Methoden geben:
- eine zum Anlegen der Welt
- eine zum Einfügen von Elementen in eine unbegrenzte Welt
- eine zum Einfügen von Elementen in eine begrenzte Welt
- eine zum Berechnen der neuen Generation

Ich verstehe aber nicht was mit "Eine unbegrenzte Welt besteht aus der gleichen endlichen Anzahl an Zellen, ist aber nicht flach, sondern bildet einen Torus, so dass jede Zelle genau 8 Nachbarn hat." gemeint ist, denn wenn es z.B. für x=100 und für y=40 heißt, dann hat doch jede Zelle mehr als 8 Nachbarn?

Im Code den du gepostet hast, verstehe ich leider nicht, wo das height und width herkommt bzw. was es meint (ist es dazu gedacht, wenn man die Welt verkleinert oder vergrößert)?
 

Marco13

Top Contributor
Das Anlegen der Welt wird nicht durch eine ("richtige") Methode gemacht, sondern durch den Konstruktor. Es bietet sich an, im Konstruktor die Größe (width und height) zu übergeben- zumindest würde ich das mit dem "...initial festgesetzt" so interpretieren.

Man KÖNNTE auch noch ein ein boolean-flag übergeben, das sagt, ob die Welt unbegrenzt ist. Das braucht man aber evtl. nicht: Man könnte theoretisch auch zwei unterschiedliche Implementierungen für das "Welt"-Interface erstellen (im Sinne von "BoundedWorld implements World" und "UnboundedWorld implements World"). Allerdings hätten diese beiden Klassen sehr viele Gemeinsamkeiten, d.h. WENN man das so machen würde, könnte man beide von einer "AbstractWorld implements World" erben lassen, die schonmal alles enthält, was für beide Arten gleich ist).

Aber das sind eher Architekturfragen. Wichtig ist erstmal, dass das interface passt, die Implementierungen kann man später noch erstellen. Was du bisher an Methoden aufgelistet hast, reicht nicht, um die in der Aufgabenstellung genannten Anforderungen zu erfüllen. Spontan würde ich auch sagen, dass es beim Einfügen neuer Zellen keinen Unterschied machen sollte, ob die Welt begrenzt ist oder nicht.

wenn es z.B. für x=100 und für y=40 heißt, dann hat doch jede Zelle mehr als 8 Nachbarn?

Hast du mal ein Stück Karopapier da? ;) (Es geht um die direkten Nachbarn, falls du das meintest...)


EDIT: Nebenbei: Das mit dem Interface steht auch explizit in der Aufgabenstellung.
 
Zuletzt bearbeitet:

Wang

Bekanntes Mitglied
Hier soweit die Schnittstelle:

Java:
interface WeltSchnittstellen
{
	public void setWelt (int x, int y);
	public void insertInTorus (int x, int y, int state);
	public void insertInLimitedWorld (int x, int y, int state);
	public void berechneNeueGeneration ();
	public void changeWorldSize();
}

Für die Arbeitsanweisung "Das Modell ermöglicht die Änderung des Zustandes einer beliebigen Zelle." müsste es doch reichen, wenn man einfach die zweite oder dritte Methode aufruft?

Das mit dem "Ändern der Größe der Welt" ergibt sich wohl, wenn man den Algorithmus zum Berechnen der Folgegeneration entwickelt hat.
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Was soll "setWelt" machen?
Warum sind "insertInTorus " und "insertInLimitedWorld" unterschiedliche Methoden?
Braucht "changeWorldSize" nicht noch Parameter?
 

Wang

Bekanntes Mitglied
Was soll "setWelt" machen?

Ich habe inzwischen erkannt, dass das mehr als überflüssig ist, denn diese Aufgabe übernimmt ja der Konstruktor mit den beiden Übergabeparametern.

Warum sind "insertInTorus " und "insertInLimitedWorld" unterschiedliche Methoden?

Ich hatte mir das so vorgestellt:
Wenn der User eine begrenzte Welt haben möchte, nutzt das Programm die Methode "insertInLimitedWorld" und bei einer unbegrenzten Welt nutzt das Programm die Methode "insertInTorus".
Inzwischen habe ich aber erkannt, dass das schwachsinnig ist, denn dein Code mit dem Modulo-Operator deckt ja auch den Fall für eine begrenzte Welt problemlos ab.

Code:
public void set(int x, int y, int state)
{
    x %= width;
    y %= height;
    if (x<0) x+=width;
    if (y<0) y+=height;
    feld[x][y] = state;
}

Ich erkenne aber nicht, wann die Fälle
Code:
x < 0
bzw.
Code:
y < 0
eintreten, denn x bzw. y sind doch immer größer gleich 0?

Braucht "changeWorldSize" nicht noch Parameter?

Code:
public void changeWorldSize(int newWidth, int newHeight);


P.S. Sorry, wenn ich mich etwas blöd anstelle, aber es ist ehrlich gesagt mein erstes "größeres Projekt". Auf jeden Fall vielen Dank für deine Hilfe und Geduld. ;)

EDIT:
Ich habe leider eine weitere Schwierigkeit und zwar bei der Berechnung der Folgegeneration.
In einer begrenzten Welt interessiert es ja nicht, was auf der anderen Seite steht, bei einer unbegrenzten Welt, muss das aber mitberücksichtigt werden.
Muss ich da zwei Methoden für die Berechnung der Folgegeneration schreiben?
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Bei Java kommen, wenn man eine negative Zahl modulo einer positiven rechnet, negative Zahlen raus, z.B. -4%3==-1. Deswegen am Ende noch sicherstellen, dass sie positiv ist.

Die Sache mit der Folgegeneration ... ja... da kommt's jetzt drauf an, wie man die überhaupt berechnen will. Ein einfach-pragmatischer Ansatz ist ja, die Anzahl der lebenden Zellen in der Umgebung zu zählen. Es gibt aber auch "gepimpte" Versionen vom Game Of Life, mit unterschiedlichen Zellenfarben und tollen "Lebendig-Oder-Tot"-Regeln, ... ... das Game Of Life ist nur die verspielte Spitze eines Eisberges von Forschung... (ein binärer totalistischer zellulärer Automat mit einer Moore-Nachbarschaftsweite von 1.... wie auf Cellular Automaton -- from Wolfram MathWorld beschrieben ;) )

Für die einfachsten Standardregeln, die nur auf der Anzahl der lebenden Zellen aufbauen, spielt die Begrenztheit aber IMHO keine Rolle (müßte nochmal genauer prüfen, aber ich glaube nicht...). In diesem Fall würden die Nachbarfelder, die außerhalb des Spielfeldes liegen, ja einfach als "tot" angesehen. Das ist entweder eine if-Abfrage in der get(x,y)-Methode, oder eben eine geeignete Implementierung dieser Methode, falls du wie oben angedeutet verschiedene Implementierungen machen willst.
 

Wang

Bekanntes Mitglied
Ich denke ich habe jetzt das Model endlich fertig, der Code macht aber 306 Zeilen aus. :lol:

Wirken sich viele if-Abfragen eigentlich sehr negativ auf die Laufzeit aus?
Bei for-Schleifen weiß ich, dass das der Fall ist, davon existieren aber in zwei Methoden jeweils nur zwei (einen Weg mit einer for-Schleife habe ich leider nicht gefunden).

Ich muss den Code nochmal durchgehen und Kommentare einfügen, was ich aber wohl erst morgen machen werde, dann kann ich euch den Code zumuten. ;)
 

Andi_CH

Top Contributor
if Abfragen sind extrem schnell - die Frage ist nur wie kompliziert die Berechnung des jeweilligen boolean Wertes ist. Wenn der als Variable vorliegt kannst du die Zeit vernachlässigen

For Schleifen sind auch extrem schnell (ich meinen damit die Kontrollstruktur - inkrement und Vergleich) aber auch da könnte schon innerhalb des for - Statements ein beliebig komplizierter Ausdruck stehen und was innerhalb der Schleife ausgeführt wird .... aber das ist ja wohl klar

Was viel Zeit braucht sind Exceptions sowie deren Handling und das Allozieren von Memory ( new MeineRiesenKlasse() wenn dann das BS swappen muss ;-) )
 

Marco13

Top Contributor
Eigentlich wollte ich noch auf den Code warten, aber... If-Abfragen sind schnell. Ein Beispiel: Für diese angedeutete set-Methode
Java:
public void set(int x, int y, int state)
{
    x %= width;
    y %= height;
    if (x<0) x+=width;
    if (y<0) y+=height;
    feld[x][y] = state;
}
wäre es mit Sicherheit schneller, wenn man stattdessen einfach
Java:
public void set(int x, int y, int state)
{
    if (x >= w) x-=w;
    else if (x<0) x+=w;
    if (y >= h) y-=h;
    else if (y<0) y+=h;
    feld[x][y] = state;
}
schreiben würde: Modulo ist deutlich langsamer als ein 'if', und es werden in dieser Methode ja immer (!?!) nur die Nachbarn eines gültigen Feldes untersucht. D.h. bei einem 4x4-Feld ruft man diese Methode höchstens mit x=4, aber nicht mit x=1423 auf.

Aber umsonst sind if-Abfragen natürlich auch nicht. Wenn sie bei einem 10000x10000-Feld pro Generation 100 Million mal gemacht werden müssen, könnte es theoretisch (!!! und nur um den Grundgedanken zu verdeutlichen !!!) schneller sein, (bei einem begrenzten Feld einen leeren Rand einzuführen, oder) die "inneren" Felder gesondert zu behandeln, GROB im Stil von
Java:
for (x=1; x<w-1; x++)
{
    for (y=1; y<h-1; y++)
    {
        berechneNeueGeneration_OHNE_ifAbfrage(x,y);
    }
}
for (x=1; x<w-1; x++) berechneNeueGenerationFürY0(x);
for (y=1; y<h-1; y++) berechneNeueGenerationFürX0(y);
...

Aber das sind Sachen, die man sich erst überlegen sollte, wenn man ein Programm hat, dass 100% so funktioniert, wie es sollte...
 

Marco13

Top Contributor
(Nur mal eine Querverbindung zu http://www.java-forum.org/allgemeine-java-themen/108681-java-performanzwerte.html#post696728 )

Ein Vergleich ist mit Sicherheit schneller als ein Modulo. Und selbst wenn man nur von positiven x ausgeht, ist die Frage, ob
if (x >= w) x%=w;
oder ein pauschales
x%=w
schneller ist, sicher von Faktoren abhängig, die jenseits dessen liegen, worüber man sich bei Java noch sinnvoll Gedanken machen kann.

Analog dazu, für Werte zwischen -w und +unendlich mag sowas wie
x = (x+w)%w;
sicher sinnvoll sein, aber vermutlich(!) ist es weniger effizient als ein
if (x<0) x+=w; else x%=w;
Auch das ist so allgemein schwer zu sagen. (Ganz zu schweigen von der Frage, wo sich das wirklich bemerkbar macht...)

Der Fall für Zahlen zwischen -unendlich und +unendlich ist mit
x = (x+w)%w;
nicht abgedeckt. Aber das jetzt zur Vermeidung von if's mit
x = ((x%w)+w)%w;
abzuhandeln wäre wohl nicht empfehlenswert ;)
 

Wang

Bekanntes Mitglied
Danke für die Hilfe soweit,

um die Länge des Beitrags nicht ad absurdum zu führen, habe den Code als Anhang hochgeladen.

Wäre super, wenn jemand bei Gelegenheit und Motivation mal einen Blick reinwerfen könnte.

Gruß
Wang
 

Anhänge

  • Welt.java
    6,3 KB · Aufrufe: 28
  • WeltSchnittstellen.java
    301 Bytes · Aufrufe: 14

ARadauer

Top Contributor
Ich hab mir das jetzt nicht im Detail durchgelesen, was ihr da oben diskutiert habt und ich hab auch das Spiel des Lebens noch nie programmiert... Ich geb nur allgemein meinen Senf mal dazu...

1. berechneNeueGenerationUnlimited hat du sehr veile überprüfungen drinnen, ob du mit deinen koordinaten drinnen oder draussen bist... ich würde auf die überprüfung verzichten und einfach immer modulo rechnen, dann kannst dir die überprüfung sparen...
was passiert, wenn man rechts raus geht? kommt man links wieder rein, oder was passiert wenn man links raus kommt, geht man rechts wieder rein... stimmt das

2. Ich würde das zählen in einer schleife machen und in eine methode auslagern... also

so ungefähr....


Java:
public class Test1 {

   static int[][] data = { { 0, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0 }, {1, 1, 1, 1, 1 }, { 0, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0 } };

   public static void main(String[] args) {
      
      for(int x = 0; x < data.length; x++){
         for(int y = 0; y < data[x].length; y++){
            System.out.print(getCountNeighbors(x, y)+" ");
         }
         System.out.println();         
      }      
      
   }

   public static int getCountNeighbors(int x, int y) {
      int count = 0;
      for (int i = -1; i < 2; i++) {
         for (int j = -1; j < 2; j++) {
            if (i == 0 && j == 0)
               continue; // bin ich selber
            int tmpX = (x + i);
            tmpX = tmpX < 0 ? tmpX + data.length : tmpX % data.length;
            int tmpY = (y + j);
            tmpY = tmpY < 0 ? tmpY + data[0].length : tmpY % data[0].length;
            
            if(data[tmpX][tmpY] ==1)
               count++;
         }
      }
      return count;

   }
}

ich weiß ein paar mögen den tenären operator nicht so gerne wie ich...
man könnte statt
Java:
tmpX = tmpX < 0 ? tmpX + data.length : tmpX % data.length;
auch
Java:
if(tmpX < 0){
               tmpX += data.length;
            }else if(tmpX >= data.length){
               tmpX %= data.length;
            }
schreiben, dann würde man sich das modulo sparen, wenn man gar nicht draussen ist... wobei ich denke, dass das keinene performance unterschied macht...


Noch eine Anmerkung zu meinem Code.. habs eigentlich nicht getest.. könnte sein, dass das Müll ist ;-)
 
Zuletzt bearbeitet:

Marco13

Top Contributor
changeWorldSize Tut nicht, was es soll. Es würde sich anbieten, darin "System.arraycopy" zu verwenden.

Ich wiederhole nochmal: Ich denke, der Unterschied zwischen "begrenzt" und "unbegrenzt" sollte NICHT an der Schnittstelle sichtbar sein. Die "insertInWorld"-Methode könnte auch set(x,y,state) heißen, und wenn man noch eine get(x,y) dazumacht wird auch klar, warum man den Unterschied zwischen "begrenzt" und "unbegrenzt" von außen nicht sieht: An der Berechnung der neuen Generation besteht bei einer begrenzten oder nicht begrenzten Welt KEIN Unterschied. Der Unterschied ist eigentlich nur, ob
get(-1,0) eine 0 liefert, oder ob
get(-1,0) den Wert liefert, der an der Position (breite-1-1,0) steht.

Dann sollten beide Varianten sowas verwenden können wie
Java:
    private int count(int state, int cx, int cy)
    {
        int counter = 0;
        for (int x=cx-1; x<=cx+1; x++)
        {
            for (int y=cy-1; y<=cy+1; y++)
            {
                if (get(x,y) == state)
                {
                    counter++;
                }
            }
        }
        return counter;
    }
 

Wang

Bekanntes Mitglied
changeWorldSize Tut nicht, was es soll. Es würde sich anbieten, darin "System.arraycopy" zu verwenden.

Argh! Schande über mein Haupt. :oops:

Hoffe, ich konnte deinen Tip richtig umsetzen:

Java:
	public void changeWorldSize(int newWidth, int newHeigth)
	{
		int[][] tempField = new int[newWidth][newHeigth];
		System.arraycopy(field, 0, tempField, 0, tempField.length);
		field = tempField;
	}

Ich wiederhole nochmal: Ich denke, der Unterschied zwischen "begrenzt" und "unbegrenzt" sollte NICHT an der Schnittstelle sichtbar sein. Die "insertInWorld"-Methode könnte auch set(x,y,state) heißen, und wenn man noch eine get(x,y) dazumacht wird auch klar, warum man den Unterschied zwischen "begrenzt" und "unbegrenzt" von außen nicht sieht: An der Berechnung der neuen Generation besteht bei einer begrenzten oder nicht begrenzten Welt KEIN Unterschied. Der Unterschied ist eigentlich nur, ob
get(-1,0) eine 0 liefert, oder ob
get(-1,0) den Wert liefert, der an der Position (breite-1-1,0) steht.

Dann sollten beide Varianten sowas verwenden können wie
Java:
    private int count(int state, int cx, int cy)
    {
        int counter = 0;
        for (int x=cx-1; x<=cx+1; x++)
        {
            for (int y=cy-1; y<=cy+1; y++)
            {
                if (get(x,y) == state)
                {
                    counter++;
                }
            }
        }
        return counter;
    }


Hier zunächst die geänderte Schnittstelle unter Berücksichtigung deiner Tips:

Java:
public interface WeltSchnittstelle
{
    public void setZelle (int x, int y, int state); // ehemals public void insertInWorld (int x, int y, int state)
	public int getZelle (int x, int y);
    public void berechneNeueGeneration();
	private int count(int state, int cx, int cy);
    public void changeWorldSize(int newWidth, int newHeight);
}

Leider kann ich das
An der Berechnung der neuen Generation besteht bei einer begrenzten oder nicht begrenzten Welt KEIN Unterschied. Der Unterschied ist eigentlich nur, ob
get(-1,0) eine 0 liefert, oder ob
get(-1,0) den Wert liefert, der an der Position (breite-1-1,0) steht.
noch nicht nachvollziehen.
Wenn ich z.B. die Position [0][0] betrachte, dann spielt es in einer begrenzten Welt ja keine Rolle, was oben rechts, oben, oben links, links und links unten von dieser Position für eine Zelle "existiert".
In einer unbegrenzten Welt, muss ich doch aber einen Algorithmus implementieren, der checkt, ob dann auf der "anderen Seite" eine tote oder lebende Zelle vorhanden ist?

Ich hoffe du weißt was ich meine und möchte mich nochmals für deine starke Hilfe bedanken. ;)

Gruß
Wang
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Hoffe, ich konnte deinen Tip richtig umsetzen:
Nicht so ganz... ein 2D-Array ist ein Array aus Arrays. Man muss also mit einer for-Schleife über den äußeren Array laufen, und für die inneren Arrays dann System.arraycopy verwenden. Du wirst aber ein bißchen nachdenken müssen, wenn du einigermaßen geschickt die Anforderung umsetzen willst, dass man das Spielfeld sowohl vergrößern als auch verkleinern kann...


Hier zunächst die geänderte Schnittstelle unter Berücksichtigung deiner Tips:
Interface-Methoden dürfen nicht private sein. Die count-Methode ist ein Implementierungsdetail. Niemand sollte diese Methode von außen aufrufen können. Im Interface steht nur das, was nach außen hin sichtbar sein soll. Und das so klar und so allgemeingültig wie möglich ... oder nötig...

Leider kann ich das noch nicht nachvollziehen.
Wenn ich z.B. die Position [0][0] betrachte, dann spielt es in einer begrenzten Welt ja keine Rolle, was oben rechts, oben, oben links, links und links unten von dieser Position für eine Zelle "existiert".
In einer unbegrenzten Welt, muss ich doch aber einen Algorithmus implementieren, der checkt, ob dann auf der "anderen Seite" eine tote oder lebende Zelle vorhanden ist?

Ich hoffe du weißt was ich meine und möchte mich nochmals für deine starke Hilfe bedanken. ;)

Hmjaa... das zielte auf mögliche ... Vereinfachungen oder Verallgemeinerungen ab. Im Moment hast du ja für die begrenzte Welt so eine if-Kaskade, die auf sowas rausläuft wie
Code:
for (alle Zellen (x,y))
{
    if (x,y) == (0,0) zähleNurDieZellenRechtsUndUnten();
    else if (x == 0) zähleNurDieZellenRechts();
    else if (y == 0) zähleNurDieZellenUnten();
    else if ((x,y) == (w-1,h-1)) zähleNurDieZellenObenLinks();
    else if (x == w-1) zähleNurDieZellenLinks();
    else if (y == h-1) zähleNurDieZellenOben();
    else zähleAlleZellen();
}
und für den Fall der unbegrenzten Welt eine noch viel üblere wie
Code:
for (alle Zellen (x,y))
{
    if (x,y) == (0,0) {
        zähleDieZellenLinksUndUnten();
        geheAufDieAndereSeiteUndZählDaNochWas();
    }
    else if (x == 0) { ... }
    else if (y == 0) { ... }
    ...
}

Das kann man sich aber sparen, wenn man die "get(x,y)"-Methode so implementiert, dass sie diese ganzen "if's" und das "gehe mit % auf die andere Seite" abhandelt. Wie oben schon angedeutet könnte man DA dann zwei Implementierungen machen, oder eine if-Abfrage, die nach begrenzt oder unbegrenzt unterscheidet:
Java:
public int get(int x, int y)
{
    if (begrenzt)
    {
        if (x<0) return 0; // Das Feld gibt's nicht 
        if (x>=w) return 0; // Das Feld gibt's nicht 
        if (y<0) return 0; // Das Feld gibt's nicht 
        if (y>=h) return 0; // Das Feld gibt's nicht 
    }
    else
    {
        if (x >= w) x-=w; // Gehe falls nötig auf die andere Seite
        else if (x<0) x+=w; // Gehe falls nötig auf die andere Seite
        if (y >= h) y-=h; // Gehe falls nötig auf die andere Seite
        else if (y<0) y+=h; // Gehe falls nötig auf die andere Seite
    }
    return array[x][y];
}


Aber nochmal: Natürlich hat man da etliche Freiheiten. Man könnte genausogut deine bisherigen Methoden verwenden, und die (um das noch zu verstecken) so aufrufen:
Java:
public void nextGeneration()
{
    if (begrenzt) berechneNeueGenerationLimited();
    else berechneNeueGenerationUnlimited();
}
Aber diese riesen-ifs sind schon ziemlich unübersichtlich und fehleranfällig. Von "fiesen" Sachen mal ganz abgesehehen: Wenn du das nun erweitern solltest, dass nicht nur die direkten Nachbarn, sondern auch die
Code:
o o o o o
o o o o o
o o X o o
o o o o o
o o o o o
in der 2-Nachbarschaft berücksichtigt würden, wärst du gescrewed ;)
 

Wang

Bekanntes Mitglied
Nicht so ganz... ein 2D-Array ist ein Array aus Arrays. Man muss also mit einer for-Schleife über den äußeren Array laufen, und für die inneren Arrays dann System.arraycopy verwenden. Du wirst aber ein bißchen nachdenken müssen, wenn du einigermaßen geschickt die Anforderung umsetzen willst, dass man das Spielfeld sowohl vergrößern als auch verkleinern kann...

Java:
	public void changeWorldSize(int newWidth, int newHeight)
	{
		int[][] tempField = new int[newWidth][newHeight];
		
		if(newWidth > width)
		{
			if(newHeight > height)
			{
				for (int i = 0; i < field.length; i++)
				{
					System.arraycopy(field[i], 0, tempField[i], 0, field[i].length);
				}
			}
			else
			{
				for (int i = 0; i < field.length; i++)
				{
					System.arraycopy(field[i], 0, tempField[i], 0, newHeight);
				}
			}
		}
		else
		{
			if(newHeight > height)
			{
				for (int i = 0; i < newWidth; i++)
				{
					System.arraycopy(field[i], 0, tempField[i], 0, field[i].length);
				}
			}
			else
			{
				for (int i = 0; i < newWidth; i++)
				{
					System.arraycopy(field[i], 0, tempField[i], 0, newHeight);
				}
			}
		}
		
		field = tempField;
	}

Interface-Methoden dürfen nicht private sein. Die count-Methode ist ein Implementierungsdetail. Niemand sollte diese Methode von außen aufrufen können. Im Interface steht nur das, was nach außen hin sichtbar sein soll. Und das so klar und so allgemeingültig wie möglich ... oder nötig...

Java:
public interface WeltSchnittstelle
{
	public void setZelle (int x, int y, int state); // ehemals public void insertInWorld (int x, int y, int state)
	public int getZelle (int x, int y);
	public void berechneNeueGeneration();
	public void changeWorldSize(int newWidth, int newHeight);
}

Das kann man sich aber sparen, wenn man die "get(x,y)"-Methode so implementiert, dass sie diese ganzen "if's" und das "gehe mit % auf die andere Seite" abhandelt. Wie oben schon angedeutet könnte man DA dann zwei Implementierungen machen, oder eine if-Abfrage, die nach begrenzt oder unbegrenzt unterscheidet:

Kurz zusammengefasst/nachgefragt, ob ich alles richtig verstanden habe: ich habe also die Methode
Code:
public void berechneNeueGeneration();
, die sowohl für eine begrenzte wie auch für eine unbegrenzte Welt nutzbar sein soll.
Diese Methode verwendet die Methode
Code:
private int count(int state, int cx, int cy)
, welche wiederum von der Methode
Code:
public int getZelle (int x, int y);
Gebrauch macht.

In der Methode berechneNeueGeneration brauche ich eine for-Schleife wie for (int i, j = 0; (i < width) && (j < height); i++, j++), sodass dann in jedem Schleifendurchlauf über count(0/1, i, j) die Anzahl der lebenden oder toten Zellen aufgerufen wird.

Vor dem nächsten Schleifendurchlauf wird dann noch so etwas aufgerufen wie (das muss ich natürlich noch entsprechend anpassen, nur für's Prinzip):

Java:
				// Prüfe, welche Regel (Wikipedia) erfüllt wird und wende sie an
				if ((field [i][j] == 0) && (counter == 3))
				{
					newField [i][j] = 1;
				}
				else if ((field [i][j] == 1) && (counter < 2))
				{
					newField [i][j] = 0;
				}
				else if ((field [i][j] == 1) && ((counter == 2) || (counter == 3)))
				{
					newField [i][j] = 1;
				}
				else if ((field [i][j] == 0) && (counter > 3))
				{
					newField [i][j] = 0;
				}

Aber nochmal: Natürlich hat man da etliche Freiheiten. Man könnte genausogut deine bisherigen Methoden verwenden, und die (um das noch zu verstecken) so aufrufen

Ich gehe besser deinen Weg. ;)


EDIT3:
Marco, kann es sein, dass deine count-Methode auch den Status der aktuellen Zelle betrachtet?
Beispielsweise wenn uns die Anzahl der lebenden Nachbarn der ebenfalls lebenden Zelle an der Position [x][y] interessiert, so wird diese Zelle in der Mitte mitgezählt...?
Vielleicht irre ich mich auch, aber das darf doch laut den Regeln (Wikipedia) nicht sein...?
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Java:
	public void changeWorldSize(int newWidth, int newHeight)

Ja, zum Beispiel. Alternativ
Java:
    private static int[][] copyB(int input[][], int newSizeX, int newSizeY)
    {
        int result[][] = new int[newSizeX][newSizeY];
        int sx = Math.min(input.length, newSizeX);
        for (int i=0; i<sx; i++)
        {
            int sy = Math.min(newSizeY, input[i].length);
            System.arraycopy(input[i], 0, result[i], 0, sy);
        }
        return result;
    }



Java:
public interface WeltSchnittstelle
Sollte passen. Methoden für Listener fehlen noch (soll ja MVC sein)


Kurz zusammengefasst/nachgefragt, ob ich alles richtig verstanden habe:
Ja, aber um das nochmal zu betonen: Man hat sehr viele Freiheiten und kann unterschiedlichste Designentscheidungen treffen. Ich persönlich würde sowas machen wie eine "AbstractWorld", die schonmal alle Methoden enhält außer setState und getState. In Klassen BoundedWorld und UnboundedWorld würden dann die noch fehlenden Methoden passend implementiert. Das hat den Vorteil der Wiederverwendung von existierendem Code: Bei beiden Welt-Arten ist fast alles gleich: Das Berechnen der Generation, das Ändern der Größe, das Verwalten der Listener ... der einzige Unterschied liegt in der Frage, ob bei "get(-1,0)" nun "tot" zurückgegeben wird, oder der Wert der Zelle auf der gegenüberliegenden Seite.
Alternativ dazu kann man eben in der get-Methode diese if-Abfrage machen. Oder, wie angedeutet, die Abfrage in der nextGeneration-Methode im Stil von
Code:
if (bounded) computeForBounded();
else computeForUnbounded();
machen. Der Vorteil bei der abstrakten Basisklasse und den unterschiedlichen Implementierungen mag im ersten Moment "akademisch" wirken: Das ist ja nur eine kleine Fingerübungs-Aufgabe, das kann man ja schnell irgendwie hin-hacken, ist ja wurscht. Ich denke aber, dass man daran schon gut bestimmte Designprinzipien erkennt, die "in der Realität" angewendet werden können. Deutlich wird das vielleicht bei einem etwas konstruiert wirkenden Beispiel: Angenommen, dein Programm ist dann "fertig". Und dann kommt die Aufgabe: Implementiere eine Welt, die horizontal beschränkt, aber vertikal unendlich ist, und dann noch eine, die vertikal beschränkt, aber horizontal unendlich ist. Bei den bisherigen Ansätzen würde man dann irgendwelche if-Abfragen im existierenden Code durch zusätzliche Fälle erweitern, und die Wahrscheinlichkeit wäre hoch, dass man dadurch die Fälle "kaputt" macht, die vorher schonmal funktioniert haben. Bei der Abstraken Welt wäre die Aufgabe mit sowas wie
Java:
class HorizontallyBoundedWorld extends AbstractWorld
{
    @Override
    public void get(int x, int y)
    {
        if (x<0 || x>=w) return 0;
        if (y<0) y+=h;
        if (y>=h) y-=h;
        return array[x][y];
    }
    ...
}

...
    //private World world = new BoundedWorld(); // Weg
    private World world = new HorizontallyBoundedWorld(); // Hin
erledigt. Weiter darüber hinaus gehende Erweiterungen wären denkbar: Vielleicht sollen irgendwann mal gelten
0 = tot
2 = lebendig
1 = rand
und darauf aufbauend werden neue Regeln für den neuen Zustand der Zelle definiert. Aber das kann man belieibig weiterspinnen, da würde man dann erstmal You Arent Gonna Need It dagegen halten...

Man könnte das ganze als "Faktorisierung" (im mathematischen Sinn, aber nich in dem eingeschränken Sinn wie es auf Wikipedia erklärt wird) verstehen. Theoretisch (!) könnte man das ganze noch weitertreiben, in dem Sinne, dass getState und setState ja auch in jeder Implementierung das gleiche machen, und nur die Interpretation der Koordinaten jeweils unterschiedlich abläuft. Aber das wäre wohl in diesem Fall auch ein Overkill.

In der Methode berechneNeueGeneration brauche ich eine for-Schleife wie for (int i, j = 0; (i < width) && (j < height); i++, j++), sodass dann in jedem Schleifendurchlauf über count(0/1, i, j) die Anzahl der lebenden oder toten Zellen aufgerufen wird.
Ich nehme an, dass du zwei verschachtelte Schleifen meinst. Es soll ja nicht nur die Diagonale abgearbeitet werden. Ansonsten geisterten in meinem Hinterkopf noch mögliche abstraktionen der Entschiedung über Leben und Tod herum, aber das ist wohl erstmal nicht so wichtig.

Ich gehe besser deinen Weg. ;)

Wenn du keine eigenen Fußabdrücke hinterlassen willst :bahnhof: ;)


EDIT: "Marco, kann es sein, dass deine count-Methode auch den Status der aktuellen Zelle betrachtet?"
Ja, das kann man entsprechend ändern, oder dadurch behandeln, dass man die aktuelle Zelle vorher pauschal auf 0 setzt. Ersteres wäre vermutlich besser.
 

Wang

Bekanntes Mitglied

Wow! Mein Kompliment! ;)
Warum die Methode private sein soll, ist mir klar. Warum trägt sie aber das Schlüsselwort "static"?

Sollte passen. Methoden für Listener fehlen noch (soll ja MVC sein)

Ich denke ich habe noch ein paar Schwierigkeiten mit den Begriffen:
In der Teilaufgabe c) heißt es ja, man soll ein CLI implementieren. Wäre das nach dem MVC-Prinzip die Control?

Ja, aber um das nochmal zu betonen: Man hat sehr viele Freiheiten und kann unterschiedlichste Designentscheidungen treffen. Ich persönlich würde sowas machen wie eine "AbstractWorld", die schonmal alle Methoden enhält außer setState und getState. In Klassen BoundedWorld und UnboundedWorld würden dann die noch fehlenden Methoden passend implementiert. Das hat den Vorteil der Wiederverwendung von existierendem Code: Bei beiden Welt-Arten ist fast alles gleich: Das Berechnen der Generation, das Ändern der Größe, das Verwalten der Listener ... der einzige Unterschied liegt in der Frage, ob bei "get(-1,0)" nun "tot" zurückgegeben wird, oder der Wert der Zelle auf der gegenüberliegenden Seite. Alternativ dazu kann man eben in der get-Methode diese if-Abfrage machen. Oder, wie angedeutet, die Abfrage in der nextGeneration-Methode im Stil von
Code:
if (bounded) computeForBounded();
else computeForUnbounded();
machen. Der Vorteil bei der abstrakten Basisklasse und den unterschiedlichen Implementierungen mag im ersten Moment "akademisch" wirken: Das ist ja nur eine kleine Fingerübungs-Aufgabe, das kann man ja schnell irgendwie hin-hacken, ist ja wurscht. Ich denke aber, dass man daran schon gut bestimmte Designprinzipien erkennt, die "in der Realität" angewendet werden können.

Meinst du das so?
"abstract AbstractWorld implements WeltSchnittstelle" wobei die Methoden schon implementiert sind
"BoundedWorld extends AbstractWorld" bzw. "UnboundedWorld extends AbstractWorld"


Ich habe deine Methode bzw. deinen Code verwendet:

Java:
public int getZelle (int x, int y)
	{
		if (begrenzt)
...

Ich denke mal, es wäre sinnvoll, ein boolean Datenfeld zu schreiben (true für begrenzt, false für unbegrenzt), aber ich weiß leider nicht, ob es sinnvoll ist, das im Konstruktor
Code:
public Welt (int width, int heigth)
zu initialisieren (+ einem zusätzlichen Übergabeparameter).
Mein Problem ist immer noch, dass ich leider keine Vorstellung habe, wie der CLI (Control?) mit dem Model interagieren soll...

Das Model dürfte nach Klärung dieser Probleme dann aber (endlich) fertig sein.

Wenn du keine eigenen Fußabdrücke hinterlassen willst :bahnhof: ;)

Ehrlich gesagt möchte ich die Aufgabe lieber dazu nutzen, meinen Horizont zu erweitern. ;)
Auf die Methode
Code:
private static int[][] copyB(int input[][], int newSizeX, int newSizeY)
wäre ich wohl nie gekommen und jetzt habe ich wieder was dazugelernt.
 

Marco13

Top Contributor
Wow! Mein Kompliment! ;)
Warum die Methode private sein soll, ist mir klar. Warum trägt sie aber das Schlüsselwort "static"?

Als Daumenregel: Methoden macht man 'static', wenn man sie "ohne Krämpfe" static machen kann. Insbesondere für solche "Utility-Methoden" bietet sich das an: Dass diese Methode in diesem speziellen Fall verwendet wird, um die Welt beim GameOfLife zu verändern, ändert nichts daran, dass sie nichts anderes macht, also einen 2D-Array in einen anderen (größeren oder kleineren) reinzukopieren. Insbesondere ist sie nicht vom Zustand eines Objektes der Klasse abhängig ist, in der sie steht. Sie erledigt ganz isoloert und eigenständig eine sehr allgemeine Aufgabe. Man könnte sie auch als "public static" Methode in eine Klasse "ArrayUtilities" packen und dann von überall aus aufrufen. Aber die Frage http://www.java-forum.org/allgemein...embermethoden-statische-utility-methoden.html ist nicht immer leicht zu beantworten ;)



Ich denke ich habe noch ein paar Schwierigkeiten mit den Begriffen:
In der Teilaufgabe c) heißt es ja, man soll ein CLI implementieren. Wäre das nach dem MVC-Prinzip die Control?
Dazu steht schon was in meiner ersten Antwort zu diesem Thread.


Meinst du das so?
"abstract AbstractWorld implements WeltSchnittstelle" wobei die Methoden schon implementiert sind
"BoundedWorld extends AbstractWorld" bzw. "UnboundedWorld extends AbstractWorld"

Ja. Aber es ist nur eine Option, wie gesagt.



Ich denke mal, es wäre sinnvoll, ein boolean Datenfeld zu schreiben (true für begrenzt, false für unbegrenzt), aber ich weiß leider nicht, ob es sinnvoll ist, das im Konstruktor
Code:
public Welt (int width, int heigth)
zu initialisieren (+ einem zusätzlichen Übergabeparameter).
Mein Problem ist immer noch, dass ich leider keine Vorstellung habe, wie der CLI (Control?) mit dem Model interagieren soll...
Entweder das boolean, das im Konstruktor übergeben wird, oder die beiden verschiedenen Implementierungen. Erstellt wird dann das passende Modell entweder direkt vom Controller, oder von dem Ding, das die Konfigurationsdatei einliest. Macht in so einem einfachen Fall nicht sooo einen großen Unterschied.
 

Wang

Bekanntes Mitglied
Vielen Dank, Marco. ;)

Ich bin gerade dabei, den Controller zu schreiben, habe da aber noch eine Schwierigkeit:
Soll die main-Methode, die das Ganze zum Laufen bringen soll, im Controller stehen oder in einer separaten Datei?

Denke wenn das geklärt ist, müsste ich noch heute fertig werden.

EDIT:
Hier soweit der aktuelle Stand des Controllers.
Gut möglich, dass das falsch ist, denn ich habe hier eine main-Methode eingefügt. Außerdem gibt es eine Klasse Input (nicht im Anhang), die in der Lage ist, einen String und eine Ganzzahl einzulesen.

Mein Problem ist wirklich noch, dass ich nicht weiß, ob ich eine extra Klasse mit einer main-Methode schreiben soll, oder ob das im Controller geschehen soll.
Außerdem bin ich noch etwas am Rätseln, wie das Zusammenspiel Controller<->View aussehen soll (denke mal, die View soll vom Modell gar nichts wissen?).

Thanks!

EDIT2:
Am Ende der Teilaufgabe c) heißt es ja, man soll im CLI wann immer es möglich ist, von der Schnittstelle Gebrauch machen. Ist das nicht irgendwo sinnfrei, wenn man im CLI die Methoden der Schnittstelle nochmal implementieren soll?

Bei der folgenden Teilaufgabe aus einem späteren Anweisungsblatt kann ich den Hintergedanken nicht ganz nachvollziehen, warum diese Klasse verwendet werden soll bzw. was daran so besonders sein soll

teilaufgabe.jpg
 

Anhänge

  • Controller.java
    1,5 KB · Aufrufe: 9
Zuletzt bearbeitet:

Marco13

Top Contributor
Ja, was genau der Controller macht, dazu gibt's hier im Forum schon einige Threads. In diesem Fall KÖNNTE man ganz pragmatisch auf sowas hinarbeiten wie
Java:
class Controller
{
    private Model model;
    private View view;

    public void start(....)
    {
         vielleichtDieConfigDateiLesen();

         model = new Model();
         view = new View(model);
         model.addSomeListener(view);
         while (!beenden)
         {
             leiteBenutzereingabenAnsModellWeiter();
         }
    }

Ob die start-Methode die Config-Datei liest, oder die nötigen Parameter übergeben bekommt, kann man sich noch überlegen. Allgemein gilt es als "guter Stil", in der main-Methode so wenig wie möglich zu machen. Irgendwelches Parameter- oder Startkonfigurationslesen gehört zwar zu den wenigen Dingen, die man dort machen kann, aber vielleicht noch irgendwie mit ein paar aufgeräumten Methoden.

Das "Zusammenspiel" zwischen View und Controller ist bei einem CLI so eine Sache. Eigentlich ist MVC ja für Graphische Anwendungen gedacht, da wirkt einiges mit einem CLI vielleicht ein bißchen krampfig. Wie oben schonmal gesagt: Ich würde das so interpetieren, dass der Controller Eingaben von der Konsole liest und ans Modell weiterreicht, und die View als Listener am Modell hängt, und es auf der Konsole ausgibt.

Wie du auf die Idee kommst, dass im CLI nochmal irgendwelche Methoden aus dem Interface implementiert werden sollen, ist mir nicht ganz klar. Es geht darum, dass das Modell überall nur über das Interface bekannt sein soll. Alle Methoden, die irgendwas mit dem Modell machen, kennen NUR das Interface, also z.B.
public void gibtDasModellAufDerKonsoleAus(ModellInterface m)
und eben NICHT
public void gibtDasModellAufDerKonsoleAus(ModellImplementierung m)
Auch da geht es wieder um die Flexibilität.

Das mit dem "späteren Anweisungsblatt" ist ja mies :D Es geht da wohl nur zum Teil um die Klasse BitSet an sich. Die Klasse hat schon einen Vorteil: Bei einer Welt mit 1000x1000 Zellen hat man, wenn man sie als int-Array speichert, ca. 1000x1000x4 byte = 4MB Speicher belegt. Bei einem BitSet wird das bißchen Information, das man braucht (tot oder lebendig) in einzelnen Bits gespeichert, und damit braucht man dann nur ca. 130KB. Viel interessanter ist aber die Frage, wie viel man ändern muss um diese Anforderung zu erfüllen. Ein heißer Tipp: Diejenigen, die dort gaaanz viele Sachen in der Form
Code:
for (int i=0; i<[B]array[/B].length; i++) { if ([B]array[/B][i]==0) [B]array[/B][i] = 1; }
drin haben, und NICHT krampfhaft auf Abstraktionen geachtet haben wie
Code:
for (int i=0; i<[B]getWidth()[/b]; i++) { if ([b]get(i)[/b]==0) [b]set(i,1)[/b]; }
werden einiges mehr an Arbeit haben ;)

Trotzdem ist das bisher angedeutete natürlich noch nicht "perfekt" dafür geeignet. Du wirst ein paar Dinge anpassen müssen. Aber sowas wie die count-Methode bleibt gleich - egal ob da ein BitSet oder ein Array drunterliegt (und darum ging es ja...).
 

Wang

Bekanntes Mitglied
Wie du auf die Idee kommst, dass im CLI nochmal irgendwelche Methoden aus dem Interface implementiert werden sollen, ist mir nicht ganz klar. Es geht darum, dass das Modell überall nur über das Interface bekannt sein soll. Alle Methoden, die irgendwas mit dem Modell machen, kennen NUR das Interface, also z.B.
public void gibtDasModellAufDerKonsoleAus(ModellInterface m)
und eben NICHT
public void gibtDasModellAufDerKonsoleAus(ModellImplementierung m)
Auch da geht es wieder um die Flexibilität.

Vielen Dank, Marco. Nach deiner Erklärung und ein Blick ins "Schlaue Buch" ist es mir wieder klar geworden, dass überall, wo ein Übergabeparameter vom Typ einer Schnittstelle erwartet wird, natürlich auch ein aktueller Parameter vom Typ einer Klasse stehen kann, die diese Schnittstelle implementiert.
Irgendwie hatte ich die Arbeitsanweisung total falsch interpretiert...

Ich habe das Einlesen der Konfigurationsdatei in die start-Methode integriert. Denke mal es ist gut, wenn alles fertig ist und funktioniert, dann sollte es mir von alleine dämmern, wo man in eine weitere Methode ausgliedern kann bzw. vereinfachen kann.

Zwei Punkte, die mir noch große Schwierigkeiten bereiten:
- Die main-Methode kommt doch in den Controller und darin steht dann nur so etwas wie "Controller control = new Controller();" und dann müsste das Ganze dann laufen?
- Ich habe heute versucht, mit den Listenern auf einen grünen Zweig zu kommen, weil ich damit aber noch nie gearbeitet habe, ist es mir leider nicht gelungen, die letzte Hürde "Listener <-> View" zu nehmen.
Irgendwie kann ich es mir auch gar nicht vorstellen, wie ich die View implementieren kann.

Es wäre echt sehr nett und ich wäre dir sehr dankbar, wenn du etwas "Red Bull" übrig hättest. ;)

Trotzdem ist das bisher angedeutete natürlich noch nicht "perfekt" dafür geeignet. Du wirst ein paar Dinge anpassen müssen. Aber sowas wie die count-Methode bleibt gleich - egal ob da ein BitSet oder ein Array drunterliegt (und darum ging es ja...).

Dieses Mini-Projekt bzw. deine starke Hilfe haben mir echt gezeigt, was man unter richtiger Abstraktion versteht. Bin da echt froh, deinen Weg gegangen zu sein und nicht meinen "if-Pfad-des-Verderbens"... ;)
 

Anhänge

  • Controller.java
    2 KB · Aufrufe: 8

Marco13

Top Contributor
Zwei Punkte, die mir noch große Schwierigkeiten bereiten:
- Die main-Methode kommt doch in den Controller und darin steht dann nur so etwas wie "Controller control = new Controller();" und dann müsste das Ganze dann laufen?
Ja, man könnte auch überlegen, eine Art "Main-Klasse" zu machen, um sich selbst ein bißchen dazu zu "zwingen", jede der "richtigen" Klassen nur das enthalten zu lassen, was wirklich reingehört, aber vorerst ist die main im Controller wohl OK.

- Ich habe heute versucht, mit den Listenern auf einen grünen Zweig zu kommen, weil ich damit aber noch nie gearbeitet habe, ist es mir leider nicht gelungen, die letzte Hürde "Listener <-> View" zu nehmen.
Irgendwie kann ich es mir auch gar nicht vorstellen, wie ich die View implementieren kann.

Naja, das "Muster" ist ja recht eingängig:
Java:
class SomeModel implements Model
{
    private List<Listener> listeners = ...
    public void add/removeListener(Listener listener) { ... }
    protected void notifyAllListeners() { ... listener.somethingWasChanged(vielleichtNochEinenEventDazu); ... }
    public void changeSomething()
    {
    ....
        notifyAllListeners();
    }
}

class View implements Listener
{
    private Model model = ...;

    public void somethingWasChanged(vielleichtNochEinenEventDazu)
    {
        gibAus(model);
    }
}
Beschreib' ggf. nochmal wo's hakt.
 

Wang

Bekanntes Mitglied
Naja, das "Muster" ist ja recht eingängig:
Java:
class SomeModel implements Model
{
    private List<Listener> listeners = ...
    public void add/removeListener(Listener listener) { ... }
    protected void notifyAllListeners() { ... listener.somethingWasChanged(vielleichtNochEinenEventDazu); ... }
    public void changeSomething()
    {
    ....
        notifyAllListeners();
    }
}

Kommt dieser Code in meinen Controller rein (Anhang im vorigen Beitrag)?

Java:
class View implements Listener
{
    private Model model = ...;

    public void somethingWasChanged(vielleichtNochEinenEventDazu)
    {
        gibAus(model);
    }
}

Ist das dann Bestandteil der View?

P.S. Tut mir echt Leid, dass ich mich mit den Listenern so dumm anstelle, aber das ist leider komplettes Neuland für mich. Wenn ich das jetzt verstehe, sollte ich im Hauptprojekt ("Die Sielder von Catan") aber keine Probleme haben.
 

Marco13

Top Contributor
Der richtige Code, der diesem Pseudocode aus dem ersten Block entspricht, steht in deinem Modell - also in einer Welt-Implementierung. Naheliegenderweise in einer AbstractWorld, kann aber auch in der finalen Implementierung liegen wenn man es zweimal schreiben will ;)

Das zweite sollte nur Verdeutlichen, dass es eine Klasse "View" gibt, die das Listener-Interface implementiert. Diese View hängt als Listener am Modell, und immer wenn sich das Modell ändert, werden alle Listener (also auch die View) benachrichtigt. Die View kann daraufhin das Modell auf dem Bildschirm ausgeben.

EDIT: Was hast du dir zu MVC und Observer/Observable bisher so durchgelesen?
 

Wang

Bekanntes Mitglied
EDIT: Was hast du dir zu MVC und Observer/Observable bisher so durchgelesen?

Insgesamt ziemlich viel, sehr hilfreich war aber dieser Link

Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 7.2 Design-Pattern (Entwurfsmuster)

auch wenn ich da noch ein paar Verständnisschwierigkeiten habe.
Das mit der Schnittstelle Observer und der Klasse Observable ist mir inzwischen klar geworden und ich weiß, dass sie Bestandteil vom package java.util sind, weshalb man implementieren bzw. erweitern kann/soll.

Allerdings bereiten mir die Listener Verständnisschwierigkeiten, denn ich verstehe den Sinn davon nicht?
Laut der "Insel" muss ich hierfür java.util.EventObject erweitern und dann javax.swing.event.EventListenerList; importieren.

Ich sehe das jetzt so in der Art Observer/Observable vs. Listener - also zwei Konzepte, die konkurrieren - und weiß nicht, welches Konzept für mich in Frage kommt.

Hoffe ich konnte mich verständlich ausdrücken und du weißt wo der Schuh drückt. ;)

Vielen Dank für deine Hilfe und Mühe.
 

Marco13

Top Contributor
Observer/Observable ist eigentlich nur ein "Teil", aus dem MVC aufgebaut ist: Dabei ist das Model Observable, und die View ist Observer. Dass es bei Java zufällig Klassen mit diesen Namen gibt, ... sorgt IMHO für mehr Verwirrung, als sie Nutzen bringen (ich wüßte kaum, wo man sie verwenden sollte). Auch muss man nicht mit den Swing Events rumhantieren (und sollte das auch in diesem Fall nicht). Die Grundidee ist eigentlich die ganz allgemeine: Auf der einen Seite ist das beobachtbare Objekt (Observable, irgendein Model), und da können sich beliebig viele Beobacher (Observer, Listener) registrieren, die informiert werden, wenn sich etwas ändert. Da ist nichts magisches dabei. Der Vorteil und Sinn bei Listenern ist ...

...


... joa, die Algemeingültigkeit mal wieder ;)

Man könnte ja sowas machen wie
Java:
class MeineWelt
{
    private CommandLineInterface commandLineInterface = ...

    public void neueGenerationErstellen();
    {
        erstelle();
        commandLineInterface.gibDasModellHierAufDerKonsoleAus();
    }
}

Das hätte die Nachteile, dass das Modell
- zwingend ein CLI bräuchte, um selbst funktionierten zu können
- nur EIN CLI als ausgabe Verwendet werden kann
- nur GENAU die Klasse "CommandLineInterface" für die Ausgabe verwendet werden kann

Wenn man diesen Punkten entgegen wirken will, kann man das eben mit einer
List<InterfaceDasAlleImplementierenDieBenachrichtigtWerdenWollenWennSichWasÄndert> list = ..
erreichen, die im Modell liegt:
- Die Liste kann auch leer sein
- es können mehrere CLIs verwendet werden
- es könnte auch ein Graphisches GUI dort drin liegen, oder irgendeine Netwerkschnittstelle, oder, oder oder... solange das Interface implementiert ist, kann es da rein.
 

Wang

Bekanntes Mitglied
Sorry Marco für die sehr späte Rückmeldung, aber mich haben die anderen Vorlesungen voll im Griff gehabt.

Dass es bei Java zufällig Klassen mit diesen Namen gibt, ... sorgt IMHO für mehr Verwirrung, als sie Nutzen bringen (ich wüßte kaum, wo man sie verwenden sollte).

Es wäre sehr nett, wenn du kurz einen Blick auf die verlinkte Seite werfen könntest. Der Autor hat am Anfang anscheinend von diesen beiden Klassen abgeleitet und geht dann weiter unten auf die Listener ein, verwendet dort dann aber auch SWING-Elemente, was mein Verständnis jetzt nicht gerade erleichtert...

Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 7.2 Design-Pattern (Entwurfsmuster)

Ich habe die beiden "Code-Skelette" von dir mal in meinen bisherigen Zwischenstand eingefügt und falls du eine freie Minute und die Motivation dafür hast wäre es sehr nett, wenn du am Anfang der betroffenen Dateien eintippen würdest, was ich eigentlich importieren muss. Dann sollte bei mir hoffentlich endlich der Groschen fallen, sodass ich es endlich über die Bühne bringe. ;)

Mich verwirrt auch diese Code-Zeile:
Code:
private List<Listener> listeners = ...

Der Grund: der Autor der Java-Insel hat am Anfang Strings verwendet, ich habe aber leider keine Ahnung, was in meine Liste reingehört.

Nochmals vielen Dank für deine große Mühe und deinen starken Einsatz.
 

Anhänge

  • zwischenstand.zip
    8,9 KB · Aufrufe: 8

Marco13

Top Contributor
Es wäre sehr nett, wenn du kurz einen Blick auf die verlinkte Seite werfen könntest. Der Autor hat am Anfang anscheinend von diesen beiden Klassen abgeleitet und geht dann weiter unten auf die Listener ein, verwendet dort dann aber auch SWING-Elemente, was mein Verständnis jetzt nicht gerade erleichtert...

Wie gesagt: In beiden Fällen gibt es etwas "Beobachtbares" und einen "Beobachter". Die Unterscheidung zwischen Observer/Observer und Listenern bezieht sich in diesem Fall darauf, dass bei Listenern auch noch Event-Objekte verwendet werden. (Bei einem Observer ist das Event-Objekt vom Typ "Void" ;) ) .
Ich vermute, mit "Swing-Elemente" meinst du die ListenerList? Das kann auch eine ganz normale ArrayList sein (besser eine CopyOnWriteArrayList).

Ich habe die beiden "Code-Skelette" von dir mal in meinen bisherigen Zwischenstand eingefügt...

Hmja, aber auch nicht viel mehr. Praktisch das gesamte Modell besteht aus Schnipseln, die 1:1 aus diesem Thread zusammenkopiert sind. Auch mein Einsatz hat Grenzen.
 

Neue Themen


Oben