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

Documents pareils