Tutoraufgabe 1 (Verifikation): Lösung:
Transcription
Tutoraufgabe 1 (Verifikation): Lösung:
Programmierung WS12/13 Lösung - Übung 3 M. Brockschmidt, F. Emmes, C. Otto, T. Ströder Prof.aa Dr. J. Giesl Tutoraufgabe 1 (Verifikation): Gegeben sei folgendes Java-Programm P : h a.length > 0 i (Vorbedingung) res = a[0]; i = 1; while (i < a.length) { if (a[i] > res) { res = a[i]; } i = i + 1; } h res = max{ a[j] | 0 ≤ j < a.length } i (Nachbedingung) a) Vervollständigen Sie die folgende Verifikation des Algorithmus im Hoare-Kalkül, indem Sie die unterstrichenen Teile ergänzen. Hierbei dürfen zwei Zusicherungen nur dann direkt untereinander stehen, wenn die untere aus der oberen folgt. Hinter einer Programmanweisung darf nur eine Zusicherung stehen, wenn dies aus einer Regel des Hoare-Kalküls folgt. Beachten Sie bei der Anwendung der “Bedingungsregel 1” mit Vorbedingung ϕ und Nachbedingung ψ, dass ϕ ∧ ¬B =⇒ ψ gelten muss. D. h. die Nachbedingung der if-Anweisung ψ muss aus der Vorbedingung der if-Anweisung ϕ und der Bedingung B selbst folgen. Geben Sie beim Verwenden der Regel einen entsprechenden Beweis an. Hinweise: • Sie dürfen beliebig viele Zusicherungs-Zeilen ergänzen oder streichen. In der Musterlösung werden allerdings genau die angegebenen Zusicherungen benutzt. • Bedenken Sie, dass die Regeln des Kalküls syntaktisch sind, weshalb Sie semantische Änderungen (beispielsweise von x+1 = y+1 zu x = y) nur unter Zuhilfenahme der Konsequenzregeln vornehmen dürfen. b) Untersuchen Sie den Algorithmus P auf seine Terminierung. Für einen Beweis der Terminierung muss eine Variante angegeben werden und unter Verwendung des Hoare-Kalküls die Terminierung unter der Voraussetzung a.length > 0 bewiesen werden. Geben Sie auch bei dieser Teilaufgabe einen Beweis für die Aussage ϕ ∧ ¬B =⇒ ψ bei der Anwendung der “Bedingungsregel 1” an. Lösung: a) Mit Hilfe der Schleife wird das Array a von der zweiten Stelle (a[1]) bis zur letzten Stelle a.length − 1 durchlaufen. Die Variable res wird mit dem Wert der ersten Stelle des Arrays initialisiert und somit gilt res = max{ a[j] | 0 ≤ j < 1 }. Im Schleifenkörper wird der Wert von res so angepasst, dass der nächstgrößere Index des Arrays mit betrachtet wird. So kann man die Aussage zu res = max{ a[j] | 0 ≤ j < i } verallgemeinern (alternativ lässt sich die obere Grenze a.length in der Nachbedingung durch die Laufvariable i ersetzen). Um mit der negierten Schleifenbedingung ¬(i < a.length) die Nachbedingung folgern zu können, brauchen wir zusätzlich noch die Aussage, dass nach dem Schleifendurchlauf i = a.length gilt. Dies können wir folgern, wenn wir zu der bisherigen Invariante i ≤ a.length hinzufügen. So ergibt sich die Schleifeninvariante i ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i } (diese Begründung war nicht gefordert und dient nur zur Erklärung der Musterlösung). 1 Programmierung WS12/13 Lösung - Übung 3 ha.length > 0i ha.length > 0 ∧ a[0] = a[0] ∧ 1 = 1i res = a[0]; ha.length > 0 ∧ res = a[0] ∧ 1 = 1i i = 1; ha.length > 0 ∧ res = a[0] ∧ i = 1i hi ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i }i while (i < a.length) { hi ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i } ∧ i < a.lengthi hi + 1 ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i }i if (a[i] > res) { hi + 1 ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i } ∧ a[i] > resi hi + 1 ≤ a.length ∧ a[i] = max{ a[j] | 0 ≤ j < i + 1 }i res = a[i]; hi + 1 ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i + 1 }i } hi + 1 ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i + 1 }i i = i + 1; hi ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i }i } hi ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i } ∧ ¬(i < a.length)i hres = max{ a[j] | 0 ≤ j < a.length }i Damit die Bedingungsregel angewendet werden darf, muss überprüft werden, dass aus i+1 ≤ a.length∧ res = max{ a[j] | 0 ≤ j < i } ∧ ¬(a[i] > res) die Aussage i + 1 ≤ a.length ∧ res = max{ a[j] | 0 ≤ j < i + 1 } folgt. Dies ist allerdings offensichtlich: Da ¬(a[i] > res) gilt, ändert sich die Größe des max-Terms nicht, falls das Element a[i] zusätzlich berücksichtigt wird. Für diese Teilaufgabe würde es insgesamt 8 Punkte geben. Das nachfolgende Punkteschema erläutert eine mögliche Bewertung von Teilleistungen und soll einen Eindruck vermitteln, worauf bei Verifikationsaufgaben (insbesondere in Prüfungen) zu achten ist. • vor der Schleife und am Ende der Schleife steht die gleiche Zusicherung: 1 Punkt • mindestens eine der beiden Zusicherungen vor oder am Ende der Schleife enthalten eine Schleifeninvariante: 0,5 Punkte • mindestens eine der beiden Zusicherungen vor oder am Ende der Schleife impliziert zusammen mit der negierten Schleifenbedingung die Nachbedingung: 0,5 Punkte • korrekte Anwendung der Zuweisungsregeln: je 0,5 Punkte • die Zusicherung nach dem Schleifenkopf entsteht aus der Zusicherung vor der Schleife, indem die Schleifenbedingung hinzugefügt wurde: 0,5 Punkte • die Zusicherung nach der Schleife entsteht aus der Zusicherung am Ende der Schleife, indem die negierte Schleifenbedingung hinzugefügt wurde: 0,5 Punkte • korrekte Anwendung der Bedingungsregel 1: 1 Punkt • alle Konsequenzregeln korrekt: insgesamt 2 Punkte 2 Programmierung WS12/13 Lösung - Übung 3 b) Eine gültige Variante für die Terminierung ist V = a.length − i, denn die Schleifenbedingung B = i < a.length impliziert a.length − i ≥ 0 und es gilt: ha.length − i = m ∧ i < a.lengthi ha.length − (i + 1) < mi if (a[i] > res) { ha.length − (i + 1) < m ∧ a[i] > resi ha.length − (i + 1) < mi res = a[i]; ha.length − (i + 1) < mi } ha.length − (i + 1) < mi i = i + 1; ha.length − i < mi Damit die Bedingungsregel angewendet werden darf, muss überprüft werden, dass aus a.length − (i + 1) < m ∧ ¬(a[i] > res) die Aussage a.length − (i + 1) < m folgt. Dies ist allerdings offensichtlich. Damit ist die Terminierung der einzigen Schleife in P gezeigt. Für diese Teilaufgabe würde es insgesamt 2 Punkte geben. Das nachfolgende Punkteschema erläutert eine mögliche Bewertung von Teilleistungen und soll einen Eindruck vermitteln, worauf bei Verifikationsaufgaben (insbesondere in Prüfungen) zu achten ist. • geeignete und korrekte Variante: 0,5 Punkte • Begründung für Korrektheit der Variante: 0,5 Punkte • korrekte Anwendung des Hoare-Kalküls: 1 Punkt Aufgabe 2 (Verifikation): (7 + 2 = 9 Punkte) Gegeben sei folgendes Java-Programm P : h a.length > 0 i (Vorbedingung) i = 0; res = 0; while (i < a.length) { res = res + 2 * a[i]; i = i + 1; } a.length−1 h res = 2∗ X a[j] i (Nachbedingung) j=0 a) Vervollständigen Sie die folgende Verifikation des Algorithmus im Hoare-Kalkül, indem Sie die unterstrichenen Teile ergänzen. Hierbei dürfen zwei Zusicherungen nur dann direkt untereinander stehen, wenn die untere aus der oberen folgt. Hinter einer Programmanweisung darf nur eine Zusicherung stehen, wenn dies aus einer Regel des Hoare-Kalküls folgt. Hinweise: • Sie dürfen beliebig viele Zusicherungs-Zeilen ergänzen oder streichen. In der Musterlösung werden allerdings genau die angegebenen Zusicherungen benutzt. • Bedenken Sie, dass die Regeln des Kalküls syntaktisch sind, weshalb Sie semantische Änderungen (beispielsweise von x+1 = y+1 zu x = y) nur unter Zuhilfenahme der Konsequenzregeln vornehmen dürfen. 3 Programmierung WS12/13 Lösung - Übung 3 • Für n < m und einen beliebigen mathematischen Term t gilt n X t = 0. i=m b) Untersuchen Sie den Algorithmus P auf seine Terminierung. Für einen Beweis der Terminierung muss eine Variante angegeben werden und unter Verwendung des Hoare-Kalküls die Terminierung unter der Voraussetzung a.length > 0 bewiesen werden. Lösung: Pa.length−1 a) Die Nachbedingung res = 2 ∗ j=0 a[j] und die Schleifenbedingung i < a.length liefern Hinweise auf die Schleifeninvariante. Die Schleife benutzt die Variable i als Laufvariable, welche gegen die Grenze a.length läuft. Durch Substitution dieserPGrenze mit der Laufvariablen erhalten wir aus der i−1 Nachbedingung die Schleifeninvariante res = 2 ∗ j=0 a[j]. Diese Invariante ist allerdings noch nicht stark genug, um zusammen mit der negierten Schleifenbedingung die Nachbedingung zu implizieren. Deshalb muss die Schleifenbedingung selbst in abgeschwächter Form zur Invariante hinzugefügt werden. Abgeschwächt bedeutet in diesem Fall, dass die strikte Ungleichung i < a.length (die ja spätestens am Ende des letzten Schleifendurchlaufs nicht gilt) zur nicht-strikten Ungleichung i ≤ a.length wird (diese ist tatsächlich eine Invariante). Pi−1 So ergibt sich die Schleifeninvariante res = 2∗ j=0 a[j]∧i ≤ a.length (dass es sich hierbei tatsächlich um eine Invariante handelt, zeigt die erfolgreiche Anwendung des Hoare-Kalküls, da alle dort auftretenden Folgerungen korrekt sind). ha.length > 0i ha.length > 0 ∧ 0 = 0 ∧ 0 = 0i i = 0; ha.length > 0 ∧ i = 0 ∧ 0 = 0i res = 0; ha.length > 0 ∧ i = 0 ∧ res = 0i Pi−1 hres = 2 ∗ j=0 a[j] ∧ i ≤ a.lengthi while (i < a.length) { Pi−1 hres = 2 ∗ j=0 a[j] ∧ i ≤ a.length ∧ i < a.lengthi Pi+1−1 hres + 2 ∗ a[i] = 2 ∗ j=0 a[j] ∧ i + 1 ≤ a.lengthi res = res + 2 * a[i]; Pi+1−1 hres = 2 ∗ j=0 a[j] ∧ i + 1 ≤ a.lengthi i = i + 1; Pi−1 hres = 2 ∗ j=0 a[j] ∧ i ≤ a.lengthi } Pi−1 hres = 2 ∗ j=0 a[j] ∧ i ≤ a.length ∧ ¬(i < a.length)i Pa.length−1 hres = 2 ∗ j=0 a[j]i b) Eine gültige Variante für die Terminierung ist V = a.length − i, denn die Schleifenbedingung B = i < a.length impliziert a.length − i ≥ 0 und es gilt: ha.length − i = m ∧ i < a.lengthi ha.length − (i + 1) < mi res = res + 2 * a[i]; ha.length − (i + 1) < mi i = i + 1; ha.length − i < mi Damit ist die Terminierung der einzigen Schleife in P gezeigt. 4 Programmierung WS12/13 Lösung - Übung 3 Tutoraufgabe 3 (Schiffe versenken): In dieser Tutoraufgabe und in der nachfolgenden Hausaufgabe soll das bekannte Spiel “Schiffe versenken” als Java-Programm implementiert werden. Dazu müssen Sie sich die Dateien Schiffe.java und SchiffeSecret.class von unserer Webseite herunterladen und im selben Verzeichnis ablegen. “Schiffe versenken” ist ein Spiel für zwei Spieler. Jeder Spieler zeichnet dazu verdeckt zwei Quadrate, welche jeweils 10x10 Kästchen enthalten. Die Zeilen dieser Quadrate sind mit den Buchstaben A bis J markiert, die Spalten mit den Ziffern 0 bis 9. Dadurch ergeben sich also zwei einfache Koordinatensysteme pro Spieler. In einem dieser Koordinatensysteme trägt jeder Spieler vier “Schiffe” unterschiedlicher Länge ein, indem er so viele aneinander angrenzende Kästchen ausmalt, wie die jeweilige Länge des Schiffs angibt. Dabei müssen die Kästchen entweder in waagerechter oder senkrechter Folge aneinander grenzen (nicht diagonal oder mit “Knicken”). Darüber hinaus dürfen zwei verschiedene Schiffe nicht aneinander grenzen (auch nicht diagonal). Die Längen der Schiffe betragen 5, 4, 3 und 2 Kästchen. Nachdem beide Spieler ihre Schiffe positioniert haben, nennen sie sich abwechselnd Koordinaten, auf die sie einen Schuss abgeben. Der jeweils andere Spieler antwortet mit “Wasser”, falls an den genannten Koordinaten kein Schiff von ihm liegt. Liegt doch ein Schiff von ihm dort, so antwortet er stattdessen mit “Treffer”, falls das Schiff noch nicht getroffene Teile besitzt, und ansonsten mit “versenkt” (dadurch weiß der schießende Spieler, dass sich um die angrenzenden Trefferfelder keine gegnerischen Schiffsteile mehr befinden können und welches der gegnerischen Schiffe er versenkt hat). In dem Koordinatensystem, in welchem der jeweilige Spieler nicht seine eigenen Schiffe positioniert hat, kann er seine Schussversuche und Treffer aufzeichnen, während er im Koordinatensystem mit seinen Schiffen die Schussversuche und Treffer seines Gegners notieren kann. Das Spiel endet, sobald alle Schiffe eines Spielers versenkt wurden (dieser Spieler hat das Spiel verloren). Nachfolgend ist exemplarisch eine Ausgangssituation für einen Spieler nach Positionierung seiner Schiffe angegeben: |0|1|2|3|4|5|6|7|8|9| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ A|S| | | | | | | | |S| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ B|S| | | | | | | | |S| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ C|S| | | | | | | | |S| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ D|S| | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ E|S| | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ F| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ G| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ H| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ I| | | | | | | | | |S| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ J|S|S|S|S| | | | | |S| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ |0|1|2|3|4|5|6|7|8|9| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ A| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ B| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ C| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ D| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ E| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ F| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ G| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ H| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ I| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ J| | | | | | | | | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ In unserem Programm soll ein menschlicher Spieler gegen einen Computerspieler antreten. Außerdem verallgemeinern wir das Spiel auf eine durch den Spieler einzugebende Seitenlänge fieldsize der Quadrate (zwischen 10 und 26), der Länge biggestShip des längsten Schiffs (zwischen 1 und der Hälfte von fieldsize) und der Länge smallestShip des kürzesten Schiffs (zwischen 1 und biggestShip). Es wird nach wie vor jeweils ein Schiff aller Längen zwischen biggestShip und smallestShip positioniert. Die Datei Schiffe.java enthält bereits eine main Methode sowie einige Hilfsmethoden, von denen manche jedoch noch nicht implementiert sind. Die Datei SchiffeSecret.class stellt einige fertig implementierte Methoden zur Verfügung, welche einen Computerspieler realisieren. Für diese Tutoraufgabe lassen wir zuerst zwei Computerspieler gegeneinander spielen. a) Implementieren Sie die Methode boolean gewonnen(boolean[][] schiffe, boolean[][] schuesse) 5 Programmierung WS12/13 Lösung - Übung 3 in der Datei Schiffe.java. Die Tabelle (das zweidimensionale Array) schiffe enthält die Schiffe eines Spielers und die Tabelle schuesse die Schüsse des Gegners, welche auf die Schiffe des Spielers abgegeben wurden. Diese Methode soll true zurück liefern, falls für alle Koordinaten (z,s), für welche schiffe[z][s] gilt, auch schuesse[z][s] gilt. Sonst soll sie false zurückgeben. b) Implementieren Sie die Methode void ausgabe ( boolean [][] schiffe , boolean [][] schuesse , boolean schiffeSichtbar ) in der Datei Schiffe.java. Diese Methode soll einen durch die beiden zweidimensionalen Arrays schiffe und schuesse spezifizierten Spielzustand auf der Konsole ausgeben. Die Bedeutung dieser Arrays ist wie folgt: Wir betrachten die Koordinaten (z,s). Gilt schiffe[z][s], so ist im Kästchen in der Zeile z und in der Spalte s ein Schiffsteil ausgemalt (und sonst nicht). Gilt schuesse[z][s], so hat der Gegner auf das Kästchen in Zeile z und Spalte s geschossen (und sonst nicht). Die Methode soll nun ein von einem Spieler gezeichnetes Quadrat zeilenweise ausgeben. Falls Schiffe angezeigt werden sollen (schiffeSichtbar = true), soll ein S ausgegeben werden, falls an den aktuellen Koordinaten ein Schiffsteil liegt, der noch nicht getroffen wurde. Sollen Schiffe nicht angezeigt werden (schiffeSichtbar = false), soll in diesem Fall stattdessen ein Leerzeichen ausgegeben werden. Unabhängig vom Wert der schiffeSichtbar Variablen soll ein X ausgegeben werden, falls sich an den aktuellen Koordinaten ein Schiffsteil befindet, das getroffen wurde, und ein O, falls auf die aktuellen Koordinaten geschossen wurde, dort aber kein Schiffsteil liegt. In allen anderen Fällen soll ein Leerzeichen ausgegeben werden. Die Ausgabe kann dadurch verbessert werden, dass die einzelnen Koordinaten durch Trennsymbole (|) und Trennzeilen auseinandergehalten werden. Außerdem kann eine Zeilen- und Spaltenbeschriftung hinzugefügt werden, wie die folgende Beispielausgabe zeigt: |0|1|2|3|4|5|6|7|8|9| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ A| | | |O| | |O|O|O|O| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ B| | | | |O|O|O| | | | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ C| |O| |O| | | |O|X| | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ D| | |O| | |O| | |X|O| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ E| | |O|O| |O| |O| |O| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ F|O| | | |O|O| | |O| | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ G| | |S|O|O|X|X|X|O| | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ H| |O|S| |O| |O| |O|O| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ I| |O|S|O| |X|X|X|X|X| -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ J| |O|S|O|O|O| | |O| | -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Lösung: public class Schiffe { // Laenge des laengsten Schiffs im Spiel public static int biggestShip = 5; 6 Programmierung WS12/13 Lösung - Übung 3 // Fehlermeldung bei Kollision public static String f eh l er Ko l li si o n = " Ungueltige Eingabe ! Das Schiff kollidiert mit einem bereits " + " vorhandenen Schiff ! " ; // Fehlermeldung bei S c h i f f e i n t r a g u n g ausserhalb des Spielfeldes public static String f e h l e r P a s s t N i c h t = " Ungueltige Eingabe ! Das Schiff passt nicht ins Spielfeld ! " ; // Seitenlaenge des quadratischen Spielfeldes public static int fieldsize = 10; // Laenge des kuerzesten Schiffs im Spiel public static int smallestShip = 2; // Gibt das Spielfeld aus . public static void ausgabe ( boolean [][] schiffe , boolean [][] schuesse , boolean sc h if fe S ic h tb ar ) { // 0 Indizes werden fuer Beschriftung genutzt , Arrayindizes sind // um 1 verschoben for ( int i = 0; i <= Schiffe . fieldsize ; i ++) { for ( int j = 0; j <= Schiffe . fieldsize ; j ++) { if ( i > 0) { if ( j > 0) { // wir sind im Spielfeld if ( schuesse [ i - 1][ j - 1]) { System . out . print ( schiffe [ i - 1][ j - 1] ? " X " : " O " ); } else { System . out . print ( s ch if f eS i ch tb a r && schiffe [ i - 1][ j - 1] ? "S" : " " ); } } else { // wir sind in der Z e i l e n b e s c h r i f t u n g ( j == 0) System . out . print (( char ) (64 + i )); } } else { // wir sind in der S p a l t e n b e s c h r i f t u n g ( i == 0) // fuer j == 0 wird ein Leerzeichen ausgegeben System . out . print ( j > 0 ? j - 1 : " " ); } // nach jedem Eintrag kommt ein Trennsymbol System . out . print ( " | " ); } // nach jeder Zeile kommt ein Zeilenumbruch System . out . println (); // und anschliessend eine Trennzeile Schiffe . linie ( Schiffe . fieldsize ); } // zur Abgrenzung weiterer Ausgaben wird ein zusaetzlicher // Zeilenumbruch am Ende hinzugefuegt System . out . println (); } // Prueft , ob alle Schiffe versenkt wurden . public static boolean gewonnen ( boolean [][] schiffe , boolean [][] schuesse ) { for ( int i = 0; i < Schiffe . fieldsize ; i ++) { for ( int j = 0; j < Schiffe . fieldsize ; j ++) { if ( schiffe [ i ][ j ] && ! schuesse [ i ][ j ]) { return false ; } } } return true ; } // Gibt eine Zeile mit length + 1 vielen " -+" - en aus . public static void linie ( int length ) { for ( int i = 0; i <= length ; i ++) { System . out . print ( " -+ " ); } System . out . println (); } // Fuehrt das Programm aus . 7 Programmierung WS12/13 Lösung - Übung 3 public static void main ( String [] args ) { int feld ; do { System . out . print ( " Geben Sie die Groesse des Spielfelds ein " + " ( zwischen 10 und 26): " ); feld = Integer . parseInt ( System . console (). readLine ()); } while ( feld < 10 || feld > 26); int big ; do { System . out . print ( " Geben Sie die Laenge des laengsten Schiffes ein " + " ( zwischen 1 und " + ( feld / 2) + " ): " ); big = Integer . parseInt ( System . console (). readLine ()); } while ( big < 1 || big > feld / 2); int small ; do { System . out . print ( " Geben Sie die Laenge des kuerzesten Schiffes ein " + " ( zwischen 1 und " + big + " ): " ); small = Integer . parseInt ( System . console (). readLine ()); } while ( small < 1 || small > big ); Schiffe . fieldsize = feld ; Schiffe . biggestShip = big ; Schiffe . smallestShip = small ; // werden alle mit false initialisiert boolean [][] schiffe1 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schiffe2 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schuesse1 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schuesse2 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; // Fuer Hausaufgabe ersetzen durch // Schiffe . s c h i f f e E i n t r a g e n ( schiffe1 , schuesse1 ); SchiffeSecret . s c h i f f e E i n t r a g e n ( schiffe1 ); SchiffeSecret . s c h i f f e E i n t r a g e n ( schiffe2 ); boolean gewonnen ; do { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); // Fuer Hausaufgabe ersetzen durch // Schiffe . sc hussEi ngeben ( schiffe2 , schuesse2 ); SchiffeSecret . sch ussEin geben ( schiffe2 , schuesse2 ); gewonnen = Schiffe . gewonnen ( schiffe2 , schuesse2 ); if ( gewonnen ) { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); System . out . println ( " Spieler 1 hat gewonnen ! " ); } else { SchiffeSecret . sch ussEin geben ( schiffe1 , schuesse1 ); gewonnen = Schiffe . gewonnen ( schiffe1 , schuesse1 ); if ( gewonnen ) { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); System . out . println ( " Spieler 2 hat gewonnen ! " ); } } } while (! gewonnen ); } // Gibt die Anzahl Kaestchen zurueck , auf die noch nicht geschossen // wurde . public static int nochFrei ( boolean [][] schuesse ) { // Hausaufgabe return Schiffe . fieldsize * Schiffe . fieldsize ; } // Fordert den Benutzer zur Eingabe von Koordinaten und Ausrichtung // der Schiffe auf , liest diese ein und traegt schliesslich die // Schiffe ins Spielfeld ein . Dabei wird kontrolliert , ob die // eingegebene Po sition ierung gueltig ist und im Falle einer // ungueltigen Po sition ierung wird die E i n g a b e a u f f o r d e r u n g wiederholt . public static void s c h i f f e E i n t r a g e n ( boolean [][] schiffe , boolean [][] schuesse ) 8 Programmierung WS12/13 Lösung - Übung 3 { int zeile ; int spalte ; char ausrichtung ; for ( int i = Schiffe . biggestShip ; i >= Schiffe . smallestShip ; i - -) { do { Schiffe . ausgabe ( schiffe , schuesse , true ); System . out . println ( " Geben Sie die oberen linken Koordinaten des Schiffs " + " mit der Laenge " + i + " ein : " ); // bestimmt aus der S p i e l f e l d g r o e s s e den le t zt mo e gl ic h en // Buchstaben System . out . print ( " Zeile (A - " + (( char ) (64 + Schiffe . fieldsize )) + " ): " ); // wandelt Buchstaben in Zahlen um ( A = 0 , B = 1 , usw .) zeile = System . console (). readLine (). toUpperCase (). charAt (0) - 65; System . out . print ( " Spalte (0 - " + ( Schiffe . fieldsize - 1) + " ): " ); spalte = Integer . parseInt ( System . console (). readLine ()); do { System . out . print ( " Soll das Schiff ( w ) aagerecht oder ( s ) enkrecht " + " positioniert werden ? " ); ausrichtung = System . console () . readLine () . toUpperCase () . charAt (0); } while ( ausrichtung != ’S ’ && ausrichtung != ’W ’ ); } while ( ! Schiffe . sc h if fE i nt r ag en ( schiffe , zeile , spalte , ausrichtung == ’S ’ ? true : false , i ) ); } } // Prueft , ob die Po sition ierung eines Schiffs mit den oberen linken // Koordinaten ( zeile , spalte ) , der Ausrichtung senkrecht ( true ) oder // waagerecht ( false ) und der angegebenen Laenge laenge gueltig ist . public static boolean s c hi ff E in tr a ge n ( boolean [][] schiffe , int zeile , int spalte , boolean senkrecht , int laenge ) { // Hausaufgabe return true ; } // Fordert den Benutzer zur Eingabe der naechsten S c h u s s k o o r d i n a t e n // auf und liest diese ein . Sind die Koordinaten gueltig ( also // innerhalb des Spielfeldes ) , werden der Schuss und eventuell // zusaetzlich inferiertes Wissen eingetragen . Ansonsten wird die // E i n g a b ea u f f o r d e r u n g wiederholt . public static void sch ussEin geben ( boolean [][] schiffe , boolean [][] schuesse ) { // Hausaufgabe // Fuer Hausaufgabe am Ende der Methode ei nk o mm en t ie re n // ( zeile , spalte ) sind die Koordinaten des Schusses : // SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , zeile , spalte ); } } 9 Programmierung WS12/13 Lösung - Übung 3 (2 + 8 + 2 + 5∗ = 12 + 5∗ Punkte) Aufgabe 4 (Schiffe versenken): Bitte beachten Sie den Hinweis zum Workshop vorne auf dem ersten Blatt! In dieser Aufgabe soll das Programm für das “Schiffe versenken” Spiel aus der vorherigen Tutoraufgabe so erweitert werden, dass ein menschlicher Spieler gegen den Computer spielen kann. Außerdem soll die künstliche Intelligenz des Computers verbessert werden. a) Der Computerspieler nutzt die Methode int nochFrei(boolean[][] schuesse) in der Datei Schiffe.java, um zu berechnen, wieviele Kästchen es gibt, auf die er noch nicht geschossen hat. In der vorgegebenen Implementierung ist diese Zahl konstant die Anzahl aller Kästchen, sodass die Berechnungen der künstlichen Intelligenz fehlerhaft sind. Schreiben Sie diese Methode so um, dass die Anzahl der Arrayeinträge im zweidimensionalen Array schuesse zurückgegeben wird, die false sind. Sie dürfen in dieser Teilaufgabe weder Rekursion noch irgendeine andere Schleife als foreach-Schleifen nutzen. b) Nun soll der menschliche Spieler seine Schiffe positionieren können. Implementieren Sie dazu die Methode boolean schiffEintragen ( boolean [][] schiffe , int zeile , int spalte , boolean senkrecht , int laenge ) in der Datei Schiffe.java, welche überprüfen soll, ob die geplante Positionierung des nächsten Schiffes gültig ist. Die Positionierung wird dabei durch die oberen linken Koordinaten (zeile, spalte) des Schiffes, die laenge des Schiffes und die Ausrichtung des Schiffes definiert (senkrecht = true bedeutet eine senkrechte Ausrichtung, senkrecht = false eine waagerechte). Die Positionierung ist gültig, wenn das Schiff innerhalb des Spielfeldes liegt und nicht mit einem bereits vorhandenen Schiff (solche Schiffe sind im schiffe Array eingetragen) kollidiert, wobei auch eine Berührung durch angrenzende Kästchen als Kollision gilt. Ist die Positionierung nicht gültig, soll eine entsprechende Fehlermeldung auf der Konsole ausgegeben werden (die Fehlermeldungen sind bereits in den Variablen fehlerPasstNicht und fehlerKollision vorgegeben) und die Methode soll false zurück liefern, ohne das schiffe Array zu modifizieren. Ist die Positionierung gültig, sollen die entsprechenden Positionen im schiffe Array auf true gesetzt und true zurückgegeben werden. Schließlich soll die Zeile SchiffeSecret.schiffeEintragen(schiffe1); in der main Methode durch einen entsprechenden Aufruf der Methode void schiffeEintragen ( boolean [][] schiffe , boolean [][] schuesse ) in der Datei Schiffe.java ersetzt werden. c) Als Letztes soll der menschliche Spieler nun auch die Schüsse selber eingeben können. Implementieren Sie dazu die Methode void schussEingeben(boolean[][] schiffe, boolean[][] schuesse) in der Datei Schiffe.java. Diese Methode soll den Benutzer so lange zur Eingabe einer Zeile als Buchstabe und einer Spalte als Zahl auffordern und seine Eingaben einlesen, bis der Benutzer gültige Koordinaten eingibt (Koordinaten sind gültig, wenn sie sich innerhalb des Spielfelds befinden). Zum Einlesen der Zeilenkoordinate sollen Sie den Aufruf System.console().readLine().toUpperCase().charAt(0) - 65; verwenden, welcher einen int Wert zurück liefert, der dem eingegebenen Buchstaben entspricht (A = 0, B = 1, usw.). Sobald gültige Koordinaten eingelesen wurden, soll der entsprechende Eintrag im schuesse Array auf true gesetzt und die Methode void z usa et zl ich eF el der ( boolean [][] schiffe , boolean [][] schuesse , int zeile , int spalte ) 10 Programmierung WS12/13 Lösung - Übung 3 aus der Datei SchiffeSecret.class mit passenden Argumenten aufgerufen werden (der Kommentar in dieser Methode muss lediglich einkommentiert werden). Schließlich soll die Zeile SchiffeSecret.schussEingeben(schiffe2, schuesse2); in der main Methode durch einen entsprechenden Aufruf der Methode void schussEingeben ( boolean [][] schiffe , boolean [][] schuesse ) in der Datei Schiffe.java ersetzt werden. d)∗ Sie können auch selbst eine künstliche Intelligenz entwickeln, die gegen den menschlichen Spieler spielt. Dazu müssen Sie lediglich zwei Methoden void schiffeEintragenComputer(boolean[][] schiffe) und void schussEingebenComputer(boolean[][] schiffe, boolean[][] schuesse) implementieren und die Zeilen SchiffeSecret.schiffeEintragen(schiffe2); und SchiffeSecret.schussEingeben(schiffe1, schuesse1); in der main Methode durch entsprechende Aufrufe Ihrer Methoden ersetzen. Die erste Methode nimmt dabei die Positionierung der Schiffe vor, während die zweite den Eintrag des nächsten Schusses übernimmt. Sie sollten natürlich nicht das sichtbare Wissen um die Position der gegnerischen Schiffe für Ihre künstliche Intelligenz nutzen. Um Zufallszahlen zu benutzen, können Sie den Aufruf SchiffeSecret.random.nextInt(limit) verwenden, welcher eine zufällige ganze Zahl zwischen 0 und limit - 1 zurück liefert. Lösung: public class Schiffe { // Laenge des laengsten Schiffs im Spiel public static int biggestShip = 5; // Fehlermeldung bei Kollision public static String f eh l er Ko l li si o n = " Ungueltige Eingabe ! Das Schiff kollidiert mit einem bereits vorhandenen Schiff ! " ; // Fehlermeldung bei S c h i f f e i n t r a g u n g ausserhalb des Spielfeldes public static String f e h l e r P a s s t N i c h t = " Ungueltige Eingabe ! Das Schiff passt nicht ins Spielfeld ! " ; // Seitenlaenge des quadratischen Spielfeldes public static int fieldsize = 10; // Laenge des kuerzesten Schiffs im Spiel public static int smallestShip = 2; // Gibt das Spielfeld aus . public static void ausgabe ( boolean [][] schiffe , boolean [][] schuesse , boolean s ch if f eS ic h tb a r ) { // 0 Indizes werden fuer Beschriftung genutzt , Arrayindizes sind um 1 verschoben for ( int i = 0; i <= Schiffe . fieldsize ; i ++) { for ( int j = 0; j <= Schiffe . fieldsize ; j ++) { if ( i > 0) { if ( j > 0) { // wir sind im Spielfeld if ( schuesse [ i - 1][ j - 1]) { System . out . print ( schiffe [ i - 1][ j - 1] ? " X " : " O " ); } else { System . out . print ( sc h if f eS ic h tb ar && schiffe [ i - 1][ j - 1] ? " S " : " " ); } } else { // wir sind in der Z e i l e n b e s c h r i f t u n g ( j == 0) System . out . print (( char ) (64 + i )); } } else { // wir sind in der S p a l t e n b e s c h r i f t u n g ( i == 0) // fuer j == 0 wird ein Leerzeichen ausgegeben System . out . print ( j > 0 ? j - 1 : " " ); } // nach jedem Eintrag kommt ein Trennsymbol System . out . print ( " | " ); } // nach jeder Zeile kommt ein Zeilenumbruch System . out . println (); // und anschliessend eine Trennzeile Schiffe . linie ( Schiffe . fieldsize ); } 11 Programmierung WS12/13 Lösung - Übung 3 // zur Abgrenzung weiterer Ausgaben wird ein zusaetzlicher // Zeilenumbruch am Ende hinzugefuegt System . out . println (); } // Prueft , ob alle Schiffe versenkt wurden . public static boolean gewonnen ( boolean [][] schiffe , boolean [][] schuesse ) { for ( int i = 0; i < Schiffe . fieldsize ; i ++) { for ( int j = 0; j < Schiffe . fieldsize ; j ++) { if ( schiffe [ i ][ j ] && ! schuesse [ i ][ j ]) { return false ; } } } return true ; } // Gibt eine Zeile mit length + 1 vielen " -+" - en aus . public static void linie ( int length ) { for ( int i = 0; i <= length ; i ++) { System . out . print ( " -+ " ); } System . out . println (); } // Fuehrt das Programm aus . public static void main ( String [] args ) { int feld ; do { System . out . print ( " Geben Sie die Groesse des Spielfelds ein ( zwischen 10 und 26): " ); feld = Integer . parseInt ( System . console (). readLine ()); } while ( feld < 10 || feld > 26); int big ; do { System . out . print ( " Geben Sie die Laenge des laengsten Schiffes ein " + " ( zwischen 1 und " + ( feld / 2) + " ): " ); big = Integer . parseInt ( System . console (). readLine ()); } while ( big < 1 || big > feld / 2); int small ; do { System . out . print ( " Geben Sie die Laenge des kuerzesten Schiffes ein ( zwischen 1 und " + big + " ): " ); small = Integer . parseInt ( System . console (). readLine ()); } while ( small < 1 || small > big ); Schiffe . fieldsize = feld ; Schiffe . biggestShip = big ; Schiffe . smallestShip = small ; // werden alle mit false initialisiert boolean [][] schiffe1 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schiffe2 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schuesse1 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; boolean [][] schuesse2 = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; Schiffe . s c h i f f e E i n t r a g e n ( schiffe1 , schuesse1 ); SchiffeSecret . s c h i f f e E i n t r a g e n ( schiffe2 ); boolean gewonnen ; do { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); Schiffe . sc hussEi ngeben ( schiffe2 , schuesse2 ); gewonnen = Schiffe . gewonnen ( schiffe2 , schuesse2 ); if ( gewonnen ) { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); System . out . println ( " Spieler 1 hat gewonnen ! " ); } else { SchiffeSecret . sch ussEin geben ( schiffe1 , schuesse1 ); gewonnen = Schiffe . gewonnen ( schiffe1 , schuesse1 ); if ( gewonnen ) { Schiffe . ausgabe ( schiffe1 , schuesse1 , true ); Schiffe . ausgabe ( schiffe2 , schuesse2 , false ); System . out . println ( " Spieler 2 hat gewonnen ! " ); } } } while (! gewonnen ); } // Gibt die Anzahl Kaestchen zurueck , auf die noch nicht geschossen // wurde . public static int nochFrei ( boolean [][] schuesse ) { int res = 0; for ( boolean [] line : schuesse ) { 12 Programmierung WS12/13 Lösung - Übung 3 for ( boolean schuss : line ) { if (! schuss ) { res ++; } } } return res ; } // Fordert den Benutzer zur Eingabe von Koordinaten und Ausrichtung // der Schiffe auf , liest diese ein und traegt schliesslich die // Schiffe ins Spielfeld ein . Dabei wird kontrolliert , ob die // eingegebene Po sition ierung gueltig ist und im Falle einer // ungueltigen Po sition ierung wird die E i n g a b e a u f f o r d e r u n g wiederholt . public static void s c h i f f e E i n t r a g e n ( boolean [][] schiffe , boolean [][] schuesse ) { int zeile ; int spalte ; char ausrichtung ; for ( int i = Schiffe . biggestShip ; i >= Schiffe . smallestShip ; i - -) { do { Schiffe . ausgabe ( schiffe , schuesse , true ); System . out . println ( " Geben Sie die oberen linken Koordinaten des Schiffs mit der Laenge " + i + " ein : " ); // bestimmt aus der S p i e l f e l d g r o e s s e den le t zt mo e gl ic h en // Buchstaben System . out . print ( " Zeile (A - " + (( char ) (64 + Schiffe . fieldsize )) + " ): " ); // wandelt Buchstaben in Zahlen um ( A = 0 , B = 1 , usw .) zeile = System . console (). readLine (). toUpperCase (). charAt (0) - 65; System . out . print ( " Spalte (0 - " + ( Schiffe . fieldsize - 1) + " ): " ); spalte = Integer . parseInt ( System . console (). readLine ()); do { System . out . print ( " Soll das Schiff ( w ) aagerecht oder ( s ) enkrecht positioniert werden ? " ); ausrichtung = System . console (). readLine (). toUpperCase (). charAt (0); } while ( ausrichtung != ’S ’ && ausrichtung != ’W ’ ); } while ( ! Schiffe . sc h if fE i nt r ag en ( schiffe , zeile , spalte , ausrichtung == ’S ’ ? true : false , i ) ); } } // Prueft , ob die Po sition ierung eines Schiffs mit den oberen linken // Koordinaten ( zeile , spalte ) , der Ausrichtung senkrecht ( true ) oder // waagerecht ( false ) und der angegebenen Laenge laenge gueltig ist . public static boolean s c hi ff E in tr a ge n ( boolean [][] schiffe , int zeile , int spalte , boolean senkrecht , int laenge ) { // berechne untere rechte Koordinaten int zeileUnten ; int spalteRechts ; if ( senkrecht ) { zeileUnten = zeile + laenge - 1; spalteRechts = spalte ; } else { zeileUnten = zeile ; spalteRechts = spalte + laenge - 1; } // pruefe , ob Schiff innerhalb des Spielfelds liegt if ( zeile < 0 || spalte < 0 || zeileUnten >= Schiffe . fieldsize || spalteRechts >= Schiffe . fieldsize ) { System . out . println ( Schiffe . f e h l e r P a s s t N i c h t ); return false ; } // wenn wir diesen Teil erreichen , liegt das Schiff innerhalb des Spielfeldes // berechne obere linke und untere rechte Koordinaten des Bereichs , in welchem Kollisionen // auftreten koennen - beachte dabei Spielfeldrand int zeileOben = zeile == 0 ? zeile : zeile - 1; int spalteLinks = spalte == 0 ? spalte : spalte - 1; int zeileUnterst = zeileUnten == Schiffe . fieldsize - 1 ? zeileUnten : zeileUnten + 1; int spalte Recht est = spalteRechts == Schiffe . fieldsize - 1 ? spalteRechts : spalteRechts + 1; // pruefe , ob Kollision vorliegt for ( int i = zeileOben ; i <= zeileUnterst ; i ++) { for ( int j = spalteLinks ; j <= spal teRech test ; j ++) { if ( schiffe [ i ][ j ]) { System . out . println ( Schiffe . f e hl er K ol li s io n ); return false ; 13 Programmierung WS12/13 Lösung - Übung 3 } } } // wenn wir diesen Teil erreichen , ist keine Kollision aufgetreten // trage Schiff ein for ( int i = zeile ; i <= zeileUnten ; i ++) { for ( int j = spalte ; j <= spalteRechts ; j ++) { schiffe [ i ][ j ] = true ; } } return true ; } // Fordert den Benutzer zur Eingabe der naechsten S c h u s s k o o r d i n a t e n // auf und liest diese ein . Sind die Koordinaten gueltig ( also // innerhalb des Spielfeldes ) , werden der Schuss und eventuell // zusaetzlich inferiertes Wissen eingetragen . Ansonsten wird die // E i n g a b ea u f f o r d e r u n g wiederholt . public static void sch ussEin geben ( boolean [][] schiffe , boolean [][] schuesse ) { int zeile ; int spalte ; do { System . out . println ( " Geben Sie die Koordinaten Ihres naechsten Schusses ein : " ); System . out . print ( " Zeile (A - " + (( char ) (64 + Schiffe . fieldsize )) + " ): " ); zeile = System . console (). readLine (). toUpperCase (). charAt (0) - 65; System . out . print ( " Spalte (0 - " + ( Schiffe . fieldsize - 1) + " ): " ); spalte = Integer . parseInt ( System . console (). readLine ()); } while ( zeile < 0 || spalte < 0 || zeile >= Schiffe . fieldsize || spalte >= Schiffe . fieldsize ); schuesse [ zeile ][ spalte ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , zeile , spalte ); } } import java . util .*; public class SchiffeSecret { // Z u f a l l s z a h l e n g e n e r a t o r public static Random random = new Random ( System . c u r r e n t T i m e M i l l i s ()); // Gibt die Anzahl Felder zurueck , die von der uebergebenen Position aus in die durch die beiden // boolean Variablen spezifizierte Richtung liegen und bereits getroffen wurden ( inklusive der // uebergebenen Position ). public static int a n za hl G et ro f fe n ( boolean [][] schiffe , boolean [][] schuesse , int zeile , int spalte , boolean senkrecht , boolean positiv ) { // setze Summanden fuer Zeilen und Spalten abhaengig von der Richtung int zPlus ; int sPlus ; if ( senkrecht ) { sPlus = 0; if ( positiv ) { zPlus = 1; } else { zPlus = -1; } } else { zPlus = 0; if ( positiv ) { sPlus = 1; } else { sPlus = -1; } } int z = zeile ; int s = spalte ; // solange wir innerhalb des Feldes und auf Treffern bleiben , gehen wir weiter while ( z >= 0 && s >= 0 && z < Schiffe . fieldsize && s < Schiffe . fieldsize && schiffe [ z ][ s ] && schuesse [ z ][ s ]) { z += zPlus ; s += sPlus ; } 14 Programmierung WS12/13 Lösung - Übung 3 // das Ergebnis ist die absolute Differenz zwischen der ur s pr ue n gl ic h en und der erreichten // Position return Math . abs ( z - zeile + s - spalte ); } // berechnet die oberen linken und unteren rechten Koordinaten eines Schiffes an der gegebenen // Position in der Reihenfolge links , oben , rechts , unten public static int [] b e r e c h n e S c h i f f s b e r e i c h ( boolean [][] schiffe , int zeile , int spalte ) { int [] res = new int [4]; res [0] = SchiffeSecret . g r e n z e D e s S c h i f f s ( schiffe , zeile , spalte , false , false ); res [1] = SchiffeSecret . g r e n z e D e s S c h i f f s ( schiffe , zeile , spalte , true , false ); res [2] = SchiffeSecret . g r e n z e D e s S c h i f f s ( schiffe , zeile , spalte , false , true ); res [3] = SchiffeSecret . g r e n z e D e s S c h i f f s ( schiffe , zeile , spalte , true , true ); return res ; } // Bestimmt zu dem Schiff an der Position ( zeile , spalte ) die oberste Zeile ( senkrecht && ! positiv ) , // rechteste Spalte (! senkrecht && positiv ) , unterste Zeile ( senkrecht && positiv ) oder linkeste // Spalte (! senkrecht && ! positiv ) , in der ein Teil desselben Schiffs liegt . public static int g r e n z e D e s S c h i f f s ( boolean [][] schiffe , int zeile , int spalte , boolean senkrecht , boolean positiv ) { if (! schiffe [ zeile ][ spalte ]) { // Fehlerwert , da an der gegebenen Position gar kein Schiff liegt return -1; } int res = senkrecht ? zeile : spalte ; if ( positiv ) { while ( res < Schiffe . fieldsize && schiffe [ senkrecht ? res : zeile ][ senkrecht ? spalte : res ]) { res ++; } // wir sind einen Schritt zu weit gelaufen res - -; } else { while ( res >= 0 && schiffe [ senkrecht ? res : zeile ][ senkrecht ? spalte : res ]) { res - -; } // wir sind einen Schritt zu weit gelaufen res ++; } return res ; } // laesst den Computer die Schiffe zufaellig positionieren public static void s c h i f f e E i n t r a g e n ( boolean [][] schiffe ) { // damit die Posi tionie rung sicher terminiert , merken wir uns , wo wir bereits eine Positi onierung // versucht haben boolean [][] versucht = new boolean [ Schiffe . fieldsize ][ Schiffe . fieldsize ]; // speichert die Anzahl noch unversuchter Felder fuer den Z u f a l l s z a h l e n g e n e r a t o r int limit = Schiffe . fieldsize * Schiffe . fieldsize ; // wir gehen die Schiffe vom kleinsten zum groessten durch , damit bereits versuchte Felder fuer // kleinere Schiffe auch als versuchte Felder fuer groessere Schiffe gelten koennen for ( int laenge = Schiffe . smallestShip ; laenge <= Schiffe . biggestShip ; laenge ++) { int zeile = -1; int spalte = -1; boolean senkrecht ; do { // wir laufen eine zufaellige Anzahl an unversuchten Feldern durch das Spielfeld und // positionieren dort das Schiff int wert = SchiffeSecret . random . nextInt ( limit ); outerLoop : for ( int i = 0; i < Schiffe . fieldsize ; i ++) { for ( int j = 0; j < Schiffe . fieldsize ; j ++) { if ( wert == 0) { // wir versuchen die naechste Positionierung , falls der Zufallswert 0 ist zeile = i ; spalte = j ; versucht [ i ][ j ] = true ; limit - -; break outerLoop ; } else if (! versucht [ i ][ j ]) { // ansonsten reduzieren wir den Zufallswert , falls wir auf einem noch nicht // besuchten Feld sind wert - -; } } } // ob wir zuerst eine senkrechte oder waagerechte Posit ionier ung versuchen , entscheidet 15 Programmierung WS12/13 Lösung - Übung 3 // auch der Zufall senkrecht = SchiffeSecret . random . nextBoolean (); // wir brechen die Schleife ab , sobald eine der beiden P o s i t i o n i e r u n g s m o e g l i c h k e i t e n // ( waagerecht oder senkrecht ) erfolgreich ist } while (! SchiffeSecret . sc h if fE i nt r ag en ( schiffe , zeile , spalte , senkrecht , laenge ) && ! SchiffeSecret . s c hi ff E in t ra ge n ( schiffe , zeile , spalte , ! senkrecht , laenge )); } } // analog zur Hausaufgabe , nur ohne F e hl er m el d un ge n public static boolean s c hi ff E in tr a ge n ( boolean [][] schiffe , int zeile , int spalte , boolean senkrecht , int laenge ) { // berechne untere rechte Koordinaten int zeileUnten ; int spalteRechts ; if ( senkrecht ) { zeileUnten = zeile + laenge - 1; spalteRechts = spalte ; } else { zeileUnten = zeile ; spalteRechts = spalte + laenge - 1; } // pruefe , ob Schiff innerhalb des Spielfelds liegt if ( zeile < 0 || spalte < 0 || zeileUnten >= Schiffe . fieldsize || spalteRechts >= Schiffe . fieldsize ) { return false ; } // wenn wir diesen Teil erreichen , liegt das Schiff innerhalb des Spielfeldes // berechne obere linke und untere rechte Koordinaten des Bereichs , in welchem Kollisionen // auftreten koennen - beachte dabei Spielfeldrand int zeileOben = zeile == 0 ? zeile : zeile - 1; int spalteLinks = spalte == 0 ? spalte : spalte - 1; int zeileUnterst = zeileUnten == Schiffe . fieldsize - 1 ? zeileUnten : zeileUnten + 1; int spalte Recht est = spalteRechts == Schiffe . fieldsize - 1 ? spalteRechts : spalteRechts + 1; // pruefe , ob Kollision vorliegt for ( int i = zeileOben ; i <= zeileUnterst ; i ++) { for ( int j = spalteLinks ; j <= spal teRech test ; j ++) { if ( schiffe [ i ][ j ]) { return false ; } } } // wenn wir diesen Teil erreichen , ist keine Kollision aufgetreten // trage Schiff ein for ( int i = zeile ; i <= zeileUnten ; i ++) { for ( int j = spalte ; j <= spalteRechts ; j ++) { schiffe [ i ][ j ] = true ; } } return true ; } // laesst den Computer einen Schuss eingeben public static void sch ussEin geben ( boolean [][] schiffe , boolean [][] schuesse ) { for ( int zeile = 0; zeile < Schiffe . fieldsize ; zeile ++) { for ( int spalte = 0; spalte < Schiffe . fieldsize ; spalte ++) { if ( schuesse [ zeile ][ spalte ] && schiffe [ zeile ][ spalte ]) { // hier haben wir schonmal getroffen int [] bereich = SchiffeSecret . b e r e c h n e S c h i f f s b e r e i c h ( schiffe , zeile , spalte ); if (! SchiffeSecret . versenkt ( schiffe , schuesse , bereich )) { // aber das Schiff ist noch nicht versenkt - suche um bisherige Treffer // wieviele Felder sind nach links schon getroffen ? int links = spalte - SchiffeSecret . a nz ah l Ge tr o ff en ( schiffe , schuesse , zeile , spalte , false , false ); if ( links >= 0 && ! schuesse [ zeile ][ links ]) { // hier koennen wir noch hinschiessen schuesse [ zeile ][ links ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , zeile , links ); 16 Programmierung WS12/13 Lösung - Übung 3 return ; } // wieviele Felder sind nach oben schon getroffen ? int oben = zeile - SchiffeSecret . a nz ah l Ge tr o ff en ( schiffe , schuesse , zeile , spalte , true , false ); if ( oben >= 0 && ! schuesse [ oben ][ spalte ]) { // hier koennen wir noch hinschiessen schuesse [ oben ][ spalte ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , oben , spalte ); return ; } // wieviele Felder sind nach rechts schon getroffen ? int rechts = spalte + SchiffeSecret . an za h lG et r of f en ( schiffe , schuesse , zeile , spalte , false , true ); if ( rechts < Schiffe . fieldsize && ! schuesse [ zeile ][ rechts ]) { // hier koennen wir noch hinschiessen schuesse [ zeile ][ rechts ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , zeile , rechts ); return ; } // wieviele Felder sind nach unten schon getroffen ? int unten = zeile - SchiffeSecret . a nz ah l Ge tr o ff e n ( schiffe , schuesse , zeile , spalte , true , true ); if ( unten < Schiffe . fieldsize && ! schuesse [ unten ][ spalte ]) { // hier koennen wir noch hinschiessen schuesse [ unten ][ spalte ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , unten , spalte ); return ; } } } } } // laufe eine zufaellige Anzahl noch nicht beschossener Felder entlang und schiesse dorthin int wert = SchiffeSecret . random . nextInt ( Schiffe . nochFrei ( schuesse )); for ( int zeile = 0; zeile < Schiffe . fieldsize ; zeile ++) { for ( int spalte = 0; spalte < Schiffe . fieldsize ; spalte ++) { if (! schuesse [ zeile ][ spalte ]) { if ( wert == 0) { schuesse [ zeile ][ spalte ] = true ; SchiffeSecret . z u s a e t z l i c h e F e l d e r ( schiffe , schuesse , zeile , spalte ); return ; } else { wert - -; } } } } } // testet , ob das Schiff im uebergebenen Bereich versenkt wurde ( der Bereich enthaelt die Koordinaten // des Schiffes in der Reihenfolge links , oben , rechts , unten ) public static boolean versenkt ( boolean [][] schiffe , boolean [][] schuesse , int [] bereich ) { if ( bereich [1] == bereich [3]) { // das Schiff liegt waagerecht for ( int i = bereich [0]; i <= bereich [2]; i ++) { if (! schuesse [ bereich [1]][ i ]) { return false ; } } } else { // das Schiff liegt senkrecht for ( int i = bereich [1]; i <= bereich [3]; i ++) { 17 Programmierung WS12/13 Lösung - Übung 3 if (! schuesse [ i ][ bereich [0]]) { return false ; } } } return true ; } // traegt zusaetzliches Wissen ein als haette man bereits auf Felder geschossen , von denen man sicher // sein kann , dass sich dort kein Schiff befindet public static void z u s a e t z l i c h e F e l d e r ( boolean [][] schiffe , boolean [][] schuesse , int zeile , int spalte ) { if ( schiffe [ zeile ][ spalte ]) { // wir muessen nur etwas tun , wenn der Schuss getroffen hat boolean bigSpalte = spalte > 0; boolean smallSpalte = spalte < Schiffe . fieldsize - 1; if ( zeile > 0) { if ( bigSpalte ) { schuesse [ zeile - 1][ spalte - 1] = true ; } if ( smallSpalte ) { schuesse [ zeile - 1][ spalte + 1] = true ; } } if ( zeile < Schiffe . fieldsize - 1) { if ( bigSpalte ) { schuesse [ zeile + 1][ spalte - 1] = true ; } if ( smallSpalte ) { schuesse [ zeile + 1][ spalte + 1] = true ; } } // bereich enthaelt Koordinaten des Schiffes in der Reihenfolge links , oben , rechts , unten int [] bereich = SchiffeSecret . b e r e c h n e S c h i f f s b e r e i c h ( schiffe , zeile , spalte ); if ( SchiffeSecret . versenkt ( schiffe , schuesse , bereich )) { // erweitere Bereich auf den K o l l i s i o n s b e r e i c h des Schiffes bereich [0] = bereich [0] > 0 ? bereich [0] - 1 : bereich [0]; bereich [1] = bereich [1] > 0 ? bereich [1] - 1 : bereich [1]; bereich [2] = bereich [2] < Schiffe . fieldsize - 1 ? bereich [2] + 1 : bereich [2]; bereich [3] = bereich [3] < Schiffe . fieldsize - 1 ? bereich [3] + 1 : bereich [3]; // im K o l l i s i o n s b e r e i c h kann kein weiteres Schiff liegen , also trage dort Schuesse ein for ( int i = bereich [1]; i <= bereich [3]; i ++) { for ( int j = bereich [0]; j <= bereich [2]; j ++) { schuesse [ i ][ j ] = true ; } } } } } } Tutoraufgabe 5 (Punkt): Schreiben Sie eine Klasse Punkt, welche zwei Attribute vom Typ double enthält. Ein Objekt vom Typ Punkt soll einen Punkt im zweidimensionalen Raum repräsentieren. a) Erweitern Sie die Klasse Punkt um eine Methode distanz, welche ein Argument vom Typ Punkt erhält und die euklidische Distanz zwischen dem aktuellen und dem übergebenen Punkt als double Wert zurück liefert. D.h. für zwei Objekte p und q vom Typ Punkt berechnet der Aufruf p.distanz(q) die euklidische Distanz zwischen p und q. Die euklidische Distanz d zweier Punkte (x1 , y1 ) und (x2 , y2 ) wird gemäß der folgenden Formel berechnet: p d = (x2 − x1 )2 + (y2 − y1 )2 b) Erweitern Sie die Klasse Punkt um eine Methode eingabe, welche den Benutzer zur Eingabe zweier Koordinaten auffordert, diese einliest und die Attribute des aktuellen Objekts mit diesen Koordinaten belegt. c) Schreiben Sie eine ausführbare main-Methode, welche den Benutzer auffordert, zwei (double-) Koordinaten für einen Punkt einzugeben. Anschließend soll der Benutzer die Koordinaten eines weiteren Punktes 18 Programmierung WS12/13 Lösung - Übung 3 eingeben. Danach soll das Programm die Distanz der beiden Punkte ausgeben. Benutzen Sie zur Lösung dieser Aufgabe Objekte vom Typ Punkt. Beispiel: Der Benutzer gibt als Koordinaten für den ersten Punkt 5.0 und 5.0 ein. Anschließend gibt er die Koordinaten 3.0 und 1.0 für den zweiten Punkt ein. Das Programm gibt daraufhin aus, dass die Punkte eine Distanz von 4.47213595499958 haben. Hinweise: • Um einen double Wert einzulesen, können Sie die Methode Double.parseDouble() verwenden, die einen String als Argument erhält und den entsprechenden double Wert zurück liefert. Lösung: public class Punkt { double x ; double y ; public double distanz ( Punkt p ) { return Math . sqrt (( p . x - x ) * ( p . x - x ) + ( p . y - y ) * ( p . y - y )); } public void eingabe () { System . out . print ( " Geben Sie die X - Koordinate ein : " ); x = Double . parseDouble ( System . console (). readLine ()); System . out . print ( " Geben Sie die Y - Koordinate ein : " ); y = Double . parseDouble ( System . console (). readLine ()); } public static void main ( String [] args ) { Punkt p1 = new Punkt (); System . out . println ( " Geben Sie die Koordinaten eines Punktes ein . " ); p1 . eingabe (); System . out . print ( " Geben Sie die Koordinaten eines weiteren Punktes ein : " ); Punkt p2 = new Punkt (); p2 . eingabe (); double dist = p1 . distanz ( p2 ); System . out . print ( " Der erste Punkt hat eine Entfernung von " ); System . out . println ( dist + " zum zweiten Punkt . " ); } } Aufgabe 6 (Pilzverbrechen): (2 + 3 + 3 = 8 Punkte) Bitte beachten Sie den Hinweis zum Workshop vorne auf dem ersten Blatt! In dieser Aufgabe beschäftigen wir uns mit dem berühmten Gaunerpärchen Bonnie und Clyde. Wenn die beiden nicht gerade Banken ausrauben, gehen sie gerne im Wald Pilze sammeln (bzw. klauen). Wir verwenden hier die Klassen Main, Mensch und Pilz, die Sie auf der Homepage herunterladen können. Jeder dieser (beiden) Menschen hat einen Korb, in den eine feste Anzahl von Pilzen passt. Weiterhin hat jeder Mensch einen Namen. Zusätzlich merken wir uns für jeden Menschen, wie viel Gewicht (in Gramm) er maximal tragen kann. Wie Sie in der Klasse Mensch sehen können, gibt es hierfür vier Attribute. Das Attribut anzahl gibt hierbei an, wie viele Pilze bereits im Korb enthalten sind. Zu jedem Pilz kennen wir den Namen und das Gewicht (in Gramm). a) Vervollständigen Sie die Klasse Main wie folgt: • Ergänzen Sie an den mit TODO a.1) markierten Stellen den Code so, dass die Variablen steinpilz, champignon, pfifferling auf Pilz-Objekte mit den folgenden Gewichten und passenden Namen verweisen. Ein Steinpilz soll 100g wiegen, ein Champignon wiegt 200g und ein Pfifferling wiegt 150g. Hinweis: Diese Aufgabe wurde von einem Informatiker gestellt, der keine Ahnung von Biologie hat. • Ergänzen Sie an den mit TODO a.2) markierten Stellen den Code so, dass die Variablen bonnie und clyde auf passende Mensch-Objekte zeigen. Setzen Sie hierfür jeweils den passenden Namen und 19 Programmierung WS12/13 Lösung - Übung 3 sorgen Sie dafür, dass in Bonnies Korb maximal 3 Pilze Platz haben und sie maximal 400g tragen kann. Bei Clyde passen 4 Pilze in den Korb und sein Maximalgewicht ist 500g. b) Gehen Sie in dieser Teilaufgabe davon aus, dass die Attribute bereits alle auf vernünftige Werte gesetzt sind. • Erweitern Sie die Klasse Mensch um eine Methode hatPlatz(), die true genau dann zurückgibt, wenn im Korb Platz für einen weiteren Pilz ist. Anderenfalls wird false zurückgegeben. • Erweitern Sie die Klasse Mensch nun um eine Methode gewicht(), die das Gesamtgewicht aller Pilze im Korb berechnet und zurückgibt. Beachten Sie hierbei, dass das Array auch null-Einträge enthalten kann. • Verwenden Sie die Methode gewicht() um eine Methode gewichtOK(int zusatzGewicht) in der Klasse Mensch zu schreiben. Diese Methode gibt true zurück, genau dann wenn die Summe von zusatzGewicht und dem Gewicht der bereits im Korb vorhandenen Pilze das Maximalgewicht der Person nicht übersteigt. • Schreiben Sie für die Klasse Mensch eine Methode ausgabe(). Diese gibt kein Ergebnis zurück, aber gibt den Namen und eine lesbare Übersicht der von der Person gesammelten Pilze aus. Geben Sie in der ersten Zeile den Namen der Person gefolgt von einem Doppelpunkt („:“) aus. Schreiben Sie pro Pilz im Korb eine weitere Zeile, in der (nur) der Name des jeweiligen Pilzes steht. Eine Beispielausgabe von einem Menschen mit Namen „Gustav“ und einem Korb, der einen Pilz mit Namen „Morchel“ und einen Pilz mit Namen „Steinpilz“ enthält: Gustav: Morchel Steinpilz c) In der Klasse Main sehen Sie eine Variable wald. Schreiben Sie an die mit TODO c) markierte Stelle eine Schleife, die die Pilze im wald-Array nach und nach abarbeitet. Hierbei wird zuerst überprüft, ob Bonnie Platz für einen weiteren Pilz hat. Wenn dies der Fall ist, wird (mittels gewichtOK) überprüft, ob Bonnie das zusätzliche Gewicht des aktuellen Pilzes tragen kann. Wenn beides der Fall ist, wird der Pilz in Bonnies Korb hinzugefügt. Benutzen Sie hierfür auch das Attribut anzahl und passen Sie dieses entsprechend an. Nur wenn der Pilz bei Bonnie nicht hinzugefügt werden konnte (weil eine oder beide der Bedingungen nicht erfüllt sind), probieren wir den Pilz bei Clyde hinzuzufügen. Überprüfen Sie also in diesem Fall beide Bedingungen für Clyde und den aktuellen Pilz und fügen Sie diesen ggf. hinzu. Wenn ein Pilz weder bei Bonnie noch bei Clyde hinzugefügt werden kann, ist das egal. Rufen Sie am Ende einer jeden Schleifeniteration die Methode ausgabe() zuerst für Bonnie und anschließend für Clyde auf. Geben Sie anschließend eine Zeile aus, in der nur „—“ (drei Bindestriche) steht. Damit sollte sich folgende Ausgabe ergeben: Bonnie: Steinpilz Clyde: --Bonnie: Steinpilz Champignon Clyde: --Bonnie: Steinpilz Champignon Clyde: Champignon 20 Programmierung WS12/13 Lösung - Übung 3 --Bonnie: Steinpilz Champignon Clyde: Champignon Pfifferling --Bonnie: Steinpilz Champignon Steinpilz Clyde: Champignon Pfifferling --Bonnie: Steinpilz Champignon Steinpilz Clyde: Champignon Pfifferling Pfifferling --Bonnie: Steinpilz Champignon Steinpilz Clyde: Champignon Pfifferling Pfifferling --- Lösung: public class Main { public static void main ( String [] args ) { Pilz steinpilz = new Pilz (); steinpilz . name = " Steinpilz " ; steinpilz . gewicht = 100; Pilz champignon = new Pilz (); champignon . name = " Champignon " ; champignon . gewicht = 200; Pilz pfifferling = new Pilz (); pfifferling . name = " Pfifferling " ; pfifferling . gewicht = 150; Mensch bonnie = new Mensch (); bonnie . name = " Bonnie " ; bonnie . korb = new Pilz [3]; bonnie . maxGewicht = 400; Mensch clyde = new Mensch (); clyde . name = " Clyde " ; clyde . korb = new Pilz [4]; clyde . maxGewicht = 500; Pilz [] wald = new Pilz [] { steinpilz , champignon , champignon , pfifferling , steinpilz , pfifferling , champignon }; 21 Programmierung WS12/13 Lösung - Übung 3 for ( Pilz pilz : wald ) { if ( bonnie . hatPlatz () && bonnie . gewichtOK ( pilz . gewicht )) { bonnie . korb [ bonnie . anzahl ] = pilz ; bonnie . anzahl ++; } else if ( clyde . hatPlatz () && clyde . gewichtOK ( pilz . gewicht )) { clyde . korb [ clyde . anzahl ] = pilz ; clyde . anzahl ++; } bonnie . ausgabe (); clyde . ausgabe (); System . out . println ( " ---" ); } } } public class Mensch { String name ; Pilz [] korb ; int anzahl = 0; int maxGewicht ; public boolean hatPlatz () { return anzahl < korb . length ; } public int gewicht () { int ergebnis = 0; for ( Pilz pilz : korb ) { if ( pilz != null ) { ergebnis = ergebnis + pilz . gewicht ; } } return ergebnis ; } public boolean gewichtOK ( int zusatzGewicht ) { return gewicht () + zusatzGewicht <= maxGewicht ; } public void ausgabe () { System . out . println ( name + " : " ); for ( Pilz pilz : korb ) { if ( pilz != null ) { System . out . println ( pilz . name ); } } } } Tutoraufgabe 7 (Lehrveranstaltung): In dieser Aufgabe soll ein Programm betrachtet werden, das bei der Organisation des Übungsbetriebs einer Lehrveranstaltung hilft. Zu einer Lehrveranstaltung gehören verschiedene Tutorien, wobei ein Tutorium aus mehreren Studenten zusammengesetzt ist. Jeder Student hat einen Namen und eine Matrikelnummer. Der Einfachheit halber verwenden wir in dieser Aufgabe genau zwei Tutorien, die als Arrays von Studenten dargestellt sind. Diese Zusammenhänge erkennt man an den entsprechenden Teilen der Klassendeklarationen: public class Veranstaltung { Student [] tutoriumEins ; Student [] tutoriumZwei ; ... } public class Student { String name ; int matrikelNummer ; ... } Startet man die main-Methode in der Klasse Veranstaltung, werden zu Beginn in der Methode init() die beiden Tutorien-Arrays vom Typ Student[] mit neuen Student-Objekten gefüllt. Jedem Studenten wird dabei ein Name und eine Matrikelnummer zugewiesen. Anschließend wird die Methode tauschen() aufgerufen, mit der man die Tutorien zweier Studenten einfach tauschen kann. Hierfür wird der Benutzer pro Tutorium nach einem Studenten gefragt, der tauschen möchte. 22 Programmierung WS12/13 Lösung - Übung 3 Tutorium 1: 1: Hans Wurst (356789) 2: Lisa Lachs (346879) Tutorium 2: 1: Simone Schnitzel (361030) 2: Peter Pute (357001) Welcher Student aus Tutorium 1 möchte tauschen? (0 zum Beenden) 2 Welcher Student aus Tutorium 2 möchte tauschen? 1 Sobald der Benutzer die letzte Eingabe (im Beispiel „1“) bestätigt, werden die beiden Studierenden (im Beispiel Lisa Lachs aus Gruppe 1 und Simone Schnitzel aus Gruppe 2) getauscht und das Ergebnis ausgegeben. Anschließend ist ein weiterer Tauschvorgang möglich. Sie finden die benötigten Dateien Veranstaltung.java und Student.java auf der Webseite zur Vorlesung. a) Wir betrachten den Zeitpunkt, zu dem das erste der beiden Tutorien initialisiert und mit Studenten gefüllt wurde. In der Klasse Veranstaltung ist in der Methode init() eine entsprechende Markierung als Kommentar vorhanden. Visualisieren Sie zu diesem Zeitpunkt den internen Zustand des Programms, bestehend aus beiden Arrays und allen Objekte der Klasse Student. b) Das Programm arbeitet fehlerhaft. Wir betrachten die Situation, in der der Benutzer zum Tauschen die beiden Studenten Lisa Lachs (aus Gruppe 1) und Simone Schnitzel (aus Gruppe 2) ausgewählt hat. Visualisieren Sie den internen Zustand des Programs, nachdem die entsprechende Tauschoperation durchgeführt wurde. In der Klasse Veranstaltung ist in der Methode tauschen() eine entsprechende Markierung als Kommentar vorhanden. Beschreiben Sie, warum das Programm fehlerhaft ist und wie der Fehler behoben werden könnte. Lösung: a) Der Speicher sieht nach Initialisierung des Arrays tutoriumEins wie folgt aus: Objekt Student String name Student[] tutoriumEins Hans Wurst int matrikelNummer 356789 [0] [1] Objekt Student String name int matrikelNummer Student[] tutoriumZwei [0] [1] 23 Lisa Lachs 346879 Programmierung WS12/13 Lösung - Übung 3 b) Nach dem Tausch der beiden Studenten sieht der Speicher wie folgt aus: Objekt Student String name Student[] tutoriumEins Hans Wurst int matrikelNummer 356789 [0] [1] Objekt Student String name Lisa Lachs int matrikelNummer 346879 Objekt Student Student[] tutoriumZwei [0] String name Simone Schnitzel int matrikelNummer [1] 361030 Objekt Student String name int matrikelNummer Peter Pute 357001 Das Programm arbeitet fehlerhaft, was man daran erkennt, dass nach dem Tauschvorgang der Student Lisa Lachs sowohl im ersten als auch im zweiten Tutorium ist, der angegebene Tauschpartner Simone Schnitzel allerdings keinem Tutorium mehr zugeordnet ist. Der Fehler wird dadurch verursacht, dass der Tauschpartner aus der zweiten Gruppe mit dem Studenten aus der ersten Gruppe überschrieben wird, weshalb der Tausch von der zweiten in die erste Gruppe nicht wie gewünscht durchgeführt wird. Das Problem kann beispielsweise dadurch gelöst werden, dass der Student in der zweiten Gruppe in einer Variable zwischengespeichert wird, bevor der Eintrag mit dem Studenten aus der ersten Gruppe überschrieben wird. Hierfür bietet es sich an, die Variable ausB entsprechend früher zu benutzen. // hole Student // hole Student Studenten aus erster Gruppe ausA = tutoriumEins [ indexA -1]; Studenten aus zweiter Gruppe ausB = tutoriumZwei [ indexB -1]; // speichere Studenten tutoriumZwei [ indexB -1] // speichere Studenten tutoriumEins [ indexA -1] in zweiter Gruppe = ausA ; in erster Gruppe = ausB ; Aufgabe 8 (Bank): (3 Punkte) Wir betrachten ein sehr kleines Programm Bank, das Bankkonten verwaltet. Ein Bankkonto hat für diese Aufgabe eine Kontonummer und einen Betrag. Diese Eigenschaften sind als Attribute in der Klasse Konto abgebildet. Der Einfachheit halber betrachten wir in dieser Aufgabe eine sehr kleine Bank, die nur zwei Konten hat. Diese Zusammenhänge erkennt man an den entsprechenden Teilen der Klassendeklarationen: public class Bank { Konto [] konten ; ... } public class Konto { int kontonummer ; int betrag ; } 24 Programmierung WS12/13 Lösung - Übung 3 Startet man die main-Methode in der Klasse Bank, werden zu Beginn die beiden Konten vom Typ Konto sowie ein Bank-Objekt erzeugt. Jedem Konto wird dabei eine Kontonummer und ein Betrag zugewiesen. Anschließend wird im Bank-Objekt ein Array vom Typ Konto[] angelegt und mit den beiden Konto-Objekten gefüllt. Nach dieser Initialisierung erhält eines der Konten einen Bonuszins. Anschließend wird die Methode zinsen() aufgerufen, mit der die Jahreszinsen für jedes Konto berechnet und eingezahlt werden. Sie finden die benötigten Dateien Bank.java und Konto.java auf der Webseite zur Vorlesung. a) Wir betrachten den Zeitpunkt, zu dem die beiden Konto-Objekte initialisiert wurden, aber das Konto[]Array im Bank-Objekt noch nicht angelegt wurde. In der Klasse Bank existiert in der Methode main für diesen Zeitpunkt eine Markierung „a)“. Visualisieren Sie zu diesem Zeitpunkt den internen Zustand des Programms. Diese Visualisierung soll alle Konto-Objekte zeigen und deutlich machen, worauf die Variablen k1 und k2 zeigen. b) Visualisieren Sie den internen Zustand des Programms zu dem Zeitpunkt nach Auszahlung der Sonderzinsen, aber vor Berechnung der Jahreszinsen (Markierung „b)“). Zeigen Sie hier zusätzlich zu den Konto-Objekten und den Variablen das Konto[]-Array. c) Nach Berechnung der Jahreszinsen schenkt der Besitzer des zweiten Kontos das Konto dem Besitzer des ersten Kontos. Der Programmierer der Bank hat hierfür die Zuweisung k1 = k2; programmiert. Visualisieren Sie den internen Zustand des Programms (wie in Aufgabenteil b) nach Ende aller Berechnungen (Markierung „c)“). Lösung: a) Objekt Konto int kontonummer 8234 int betrag 100 Objekt Konto int kontonummer 7119 int betrag 300 k1 k2 25 Programmierung WS12/13 Lösung - Übung 3 b) Objekt Konto int kontonummer Konto[] konten int betrag 8234 110 [0] [1] Objekt Konto int kontonummer 7119 int betrag 300 k1 k2 c) Objekt Konto int kontonummer Konto[] konten int betrag 8234 115 [0] [1] Objekt Konto int kontonummer 7119 int betrag 315 k1 k2 26