Übungen zu Softwareentwicklung 1, WS 2011/12 Übung 9 Name: ________________________________________ Abzugeben bis: Mi, 18.01.2012 (12:00) Matrikelnummer: _______________________________ Bearbeitungsdauer in Stunden: _______ Nummer d. Übungsgruppe: ________________________ Name des Tutors: _________________ Name d. Übungsleiters: ___________________________ Punkte: _________________________ Aufgabe 1: Spezifische Simulation 12 Punkte In dieser Aufgabe soll ein Java Programm implementiert werden, welches ein Ökosystem von Pflanzen simuliert. Die in diesem Beispiel betrachteten systemischen Regeln sind frei erfunden und entsprechen somit in keinster Weise naturwissenschaftlichen Tatsachen. Als Grundgerüst Ihrer Implementierung finden Sie im Downloadbereich der aktuellen Übung die Klassen Simulation und Environment sowie die abstrakte Klasse Plant. Die Simulation soll auf dem Konzept eines sogenannten zellulären Automaten (http://en.wikipedia.org/wiki/Cellular_automaton) basieren. Man verwendet eine 2D-Matrix als Simulationsumgebung (Environment), wobei jedes Element bzw. jede Zelle dieser Matrix entweder leer sein kann oder eine Pflanze (Plant) enthält. Die Entwicklung der Zellen hängt in jedem Simulationsschritt vom derzeitigen Zustand bestimmter Nachbarzellen ab. Pflanzen haben dabei die Möglichkeit zu wachsen (grow), sich zu vermehren (spawn) oder zu sterben (die). In diesem einfachen Szenario sollen die beiden Pflanzenarten Blume (Flower) und Baum (Tree) modelliert werden, die sich durch folgende Charakteristika voneinander unterscheiden. • Pflanzenart (getTypeId() in Plant.java): Integer-Wert der Blumen von Bäumen unterscheidet; Blume: 0 / Baum: 1 • Maximalgröße (getMaxSize() in Plant.java): bei Wachstum über diesen Wert hinaus stirbt die Pflanze; Blume: 30 / Baum: 300 • Wachstumsrate (getMaxGrowthRate() in Plant.java): maximales Wachstum pro Simulationsschritt; Blume: 5 / Baum: 10 • Vermehrungsdistanz (getMaxSeedDistance() in Plant.java): maximaler Radius innerhalb dessen sich die Pflanze vermehren kann; Blume: 3 / Baum: 5 • Darstellungssymbol (getRenderChar() in Plant.java): einzelner Buchstabe, der zur textuellen Darstellung der Simulationsumgebung dient; Blume: * / Baum: # Implementieren Sie die Klassen Flower und Tree als nicht-abstrakte Subklassen von Plant derart, dass die update-Methode bei jedem Simulationsschritt alle regelbasierten Aktionen der jeweiligen Pflanze initiiert. Die Regeln sind wie folgt definiert: • Blume: vermehrt sich falls es insgesamt <= 16 Blumen im Umkreis von 6 Zellen gibt oder sich im Umkreis der eigenen Vermehrungsdistanz >= 3 Bäume befinden; stirbt falls es insgesamt >= 10 Blumen im Umkreis der eigenen Vermehrungsdistanz gibt • Baum: vermehrt sich falls es insgesamt <= 10 Bäume im Umkreis von 8 Zellen gibt oder sich im Umkreis der eigenen Vermehrungsdistanz >= 12 Blumen befinden; stirbt falls es insgesamt >= 8 Bäume im Umkreis der eigenen Vermehrungsdistanz gibt • Alle Pflanzen wachsen bei jedem Simulationsschritt und sterben sobald sie ihre jeweilige Maximalgröße überschritten haben Benutzen Sie dabei die Methode getNumPlantsInRange der Klasse Environment um die Anzahl bestimmter Pflanzenarten in der Umgebung zu evaluieren. Implementieren Sie in Ihren speziellen Pflanzenklassen außerdem jeweils die clone-Methode, welche eine exakte Kopie des Objekts zurückgibt und als Grundlage der Vermehrungsfunktionalität dient. Die Klasse Simulation ist ein Testprogramm, das eine Simulationsumgebung der Dimension 40 x 20 anlegt und diese mit jeweils 5 zufällig platzierten Blumen und Bäumen ausstattet. Bei jedem Druck der Eingabetaste wird ein Simulationsschritt ausgeführt bzw. mit q gefolgt von der Eingabetaste das Programm beendet. Die ersten 4 Simulationsschritte könnten bei einer Umgebungsdimension von 10 x 5 beispielsweise folgendermaßen aussehen: # # *## * * # * * # # * # * *## * *# * * # # # * ** *## * *# *# * # * * Aufgabe 2: Generische Simulation **# # * *** *## * *# *# *** #** * 12 Punkte Anders als bei der spezifischen Simulation soll es hier ermöglicht werden neben Blumen und Bäumen auch beliebige andere Pflanzenarten zu modellieren und zwar ohne dafür jeweils eine spezielle Klasse anlegen zu müssen. Implementieren Sie dafür die Klasse GenericPlant als nichtabstrakte Subklasse von Plant, sodass diese sämtliche Pflanzen-Charakteristika (Pflanzenart, Maximalgröße, etc.) als Memberattribute enthält, welche im Konstruktor definiert werden können. Implementieren Sie in dieser Klasse die Methoden addSpawnRule und addDeathRule um das dynamische Anlegen des Simulationsregelwerks zu ermöglichen. Um in weiterer Folge die Entscheidung für Vermehrung bzw. Sterben treffen zu können sollen beide Methoden folgende Parameter entgegennehmen: int int int int range neighborPlantTypeId minNeighborPlants maxNeighborPlants // // // // für die Entscheidung relevanter Radius gesuchter Pflanzentyp innerhalb des Radius min. Zahl gefundener Pflanzen für pos. Entsch. max. Zahl gefundener Pflanzen für pos. Entsch. Legen Sie diese Regeldaten in geeigneten Datenstrukturen des Objekts ab, um sie in der updateMethode auslesen zu können. Dort sollen auf Basis der abgelegten Regeldaten Aktionen wie Vermehren oder Sterben richtig ausgelöst werden. Die Regeln sind mit einem logischen Oder verknüpft, d.h. sobald eine zutrifft wird die entsprechende Aktion bereits ausgelöst und keine weitere Regel für die gleiche Aktion mehr geprüft. Auch die generische Pflanze muss bei der Vermehrung die clone-Methode zur Verfügung stellen. Vergessen Sie bei deren Implementierung nicht darauf, dass neben dem Kopieren der Pflanzenattribute auch das Kopieren aller Regeldaten notwendig ist. Adaptieren Sie die Testklasse Simulation derart, dass sie statt Flower und Tree nun die Klasse GenericPlant dazu benutzt um Blumen und Bäume mit der gleichen Funktionalität wie vorher nachzubilden. Legen Sie insbesondere auf das richtige Anlegen des Simulationsregelwerks Wert. Hinweise zu Aufgabe 2: Ein generisches Pflanzenobjekt kann natürlich nicht nur die Funktionalität der zuvor spezifizierten Pflanzenarten Blume und Baum nachbilden, sondern auch beliebige neue Pflanzenarten definieren. Der Grundgedanke von generischer Programmierung ist es, die Funktionalität so allgemein zu entwerfen, dass Programmteile oder Klassen für unterschiedliche Spezialfälle einer bestimmten Problemklasse wiederverwendet werden können. Je mehr unterschiedliche Spezialfälle es gibt desto sinnvoller ist es eine generische Lösung anzustreben, anstatt jeden Fall spezifisch zu lösen. Beispielsweise könnte man mittels GenericPlant die Pflanzenart Strauch wie folgt implementieren: ... ... GenericPlant p = new GenericPlant(env, x, y, typeBush, 60, 8, 4, '~'); p.addSpawnRule(4, typeBush, 0, 5); ... ... Das Ergebnis wäre eine Pflanze, die in der Simulationsumgebung an der Position x/y platziert ist, die Typ ID von der Variable typeBush übernimmt und eine Maximalgröße von 60, eine Wachstumsrate von 8 eine Vermehrungsdistanz von 4 sowie ein Darstellungssymbol von ~ aufweist. Darüber hinaus würde sich diese Pflanze in der Simulation genau dann vermehren, wenn es im Umkreis von 4 Zellen maximal 5 Pflanzen dieser Art gibt. Wichtige Anmerkungen zur Übung: Wir empfehlen auch weiterhin für Benutzereingaben (read-Operationen) die Input-Klasse zu verwenden. Formatieren Sie Ihre Ausgabe der Programme so, dass diese den gegebenen Auszügen aus den Programmabläufen aus der Übungsangabe entsprechen. Geben Sie für jede Aufgabe folgendes ab: a) Lösungsidee (textuell) b) Quelltext (Java-Programm inklusive Kommentaren) c) Testplan (zum Testen der Grenzen und Sonderfälle des Programms) d) Ausgabe des Programms für die Testfälle aus dem Testplan Achten Sie bei der Implementierung besonders auf die Verwendung adäquater Datentypen, die Überprüfung der Benutzereingaben und die Ausgabe von Fehlermeldungen.