Spiel-Klasse
Implementierung des fertigen Spiels
Wir können nun das Spiel fertig implementieren.
Theoretisch könnten wir das in einem größeren Hauptprogramm machen,
das dann aber nicht mehr übersichtlich wäre.
Deshalb ist es sinnvoll die Funktionalität in einer Spiel-Klasse auf
mehrere Methoden zu verteilen und dann nur noch ein konkretes
Spiel zu erstellen und zu starten.
Ein Spiel benötigt drei Stapel. Es hat Methoden, um das Spiel
vorzubereiten,
eine Runde des Spiels zu spielen und um das Spiel auszuwerten.
Außerdem soll es eine Methode geben, die das Spiel startet.
Beim Starten des Spiels wird das Spiel vorbereitet, mehrere Runden gespielt
und danach das Spiel ausgewertet.
Aufgabe 1 - Grundstruktur
Erstellen die Grundstruktur der Klasse Spiel mit den drei
Stapeln und den Methoden.
Die Implementierung der Methoden kann momentan noch leer bleiben.
Wir werden diese im weiteren Verlauf Schritt für Schritt implementieren.
Das Spiel vorbereiten
Zu Beginn muss der ziehstapel gefüllt und gemischt werden.
Aufgabe 2 - Spiel vorbereiten
Ergänze in der Methode vorbereiten() die beiden Anweisungen,
um den ziehstapel zu füllen und zu mischen.
Du kannst die Methode testen, indem du ein Spiel erzeugst,
die Methode vorbereiten() aufrufst und dann mit Hilfe
des Objektinspektors überprüfst, ob der ziehstapel gefüllt ist.
Eine Runde spielen
Eine allererste Version der Methode eineRundeSpielen() könnte so
aussehen:
fun eineRundeSpielen() {
val aktuelleKarte = ziehstapel.zieheKarte()
if(aktuelleKarte != null) {
aktuelleKarte.druckeKarte()
}
}
Wir benutzen hier die lokale Variable
aktuelleKarte,
um die gerade gezogene Karte zu speichern.
Der folgende Code enthält zwei Fehler und soll den Unterschied zwischen Attributen
und lokalen Variablen verdeutlichen:
class Karte(
farbe: String, // "Herz", "Karo", "Pik", "Kreuz" (ausgeschrieben, Groß/Kleinschreibung egal)
rang: String // "2"–"10", "B"/"Bube", "D"/"Dame", "K"/"Koenig", "A"/"Ass"
) {
val rang: String = kuerzeRang(rang)
val farbe: String = normalisiereFarbe(farbe)
val wert: Int
get() {
return when (rang) {
"B" -> 11
"D" -> 12
"K" -> 13
"A" -> 14
else -> rang.toInt()
}
}
fun druckeKarte() {
val symbol = when (farbe) {
"herz" -> "♥"
"karo" -> "♦"
"pik" -> "♠"
"kreuz" -> "♣"
else -> "?"
}
val right = rang.padStart(2, ' ')
val left = rang.padEnd(2, ' ')
println("┌─────┐")
println("│$left │")
println("│ $symbol │")
println("│ $right│")
println("└─────┘")
}
private fun kuerzeRang(roherRang: String): String {
val normalisiert = roherRang.trim().lowercase()
return when (normalisiert) {
"a", "ass" -> "A"
"k", "koenig", "könig" -> "K"
"d", "dame" -> "D"
"b", "bube" -> "B"
else -> {
val zahl = normalisiert.toIntOrNull()
?: throw IllegalArgumentException("Unbekannter Rang: $roherRang")
if (zahl in 2..10) zahl.toString()
else throw IllegalArgumentException("Rang ausserhalb des Bereichs: $roherRang")
}
}
}
private fun normalisiereFarbe(roheFarbe: String): String {
return when (val normalisiert = roheFarbe.trim().lowercase()) {
"herz", "karo", "pik", "kreuz" -> normalisiert
else -> throw IllegalArgumentException("Unbekannte Farbe: $roheFarbe")
}
}
}
class Stapel {
private val karten: MutableList<Karte> = mutableListOf()
val anzahlKarten: Int
get() = karten.size
fun erzeugeBlaetter(anzahlBlaetter: Int) {
val raenge = listOf(
"2", "3", "4", "5", "6", "7", "8", "9", "10",
"B", "D", "K", "A"
)
val farben = listOf("Herz", "Karo", "Pik", "Kreuz")
repeat(anzahlBlaetter) {
for (farbe in farben) {
for (rang in raenge) {
karten.add(Karte(farbe, rang))
}
}
}
}
fun mischen() = karten.shuffle()
// entferne die oberste Karte vom Stapel und gib sie zurück
fun zieheKarte(): Karte? {
return karten.removeLastOrNull()
}
// Gibt die oberste Karte zurück, ohne sie zu entfernen
fun obersteKarte(): Karte? {
return karten.lastOrNull()
}
// Legt eine Karte "oben drauf" (ans Ende der Liste)
fun legeAb(k: Karte) {
karten.add(k)
}
// Kombiniert Safe Call und Elvis für eine saubere Ausgabe
fun druckeObersteKarte() {
karten.lastOrNull()?.druckeKarte() ?: println("Stapel ist leer")
}
fun druckeAlle() {
if (karten.isEmpty()) {
println("Der Stapel ist leer.")
} else {
for (karte in karten) {
karte.druckeKarte()
}
}
}
}
class Spiel {
val ziehstapel = Stapel()
fun eineRundeSpielen() {
val aktuelleKarte = ziehstapel.zieheKarte()
if(aktuelleKarte != null) {
aktuelleKarte.druckeKarte()
}
}
fun spielAuswerten() {
println("Letzte Karte war:")
aktuelleKarte.druckeKarte()
if(ziehstapel.anzahlKarten > 0) {
val restkarten = ziehstapel.anzahlKarten
}
println(restkarten)
ziehstapel.druckeAlle()
}
}
fun main() {
}
Aufgabe 3 - Lokale Variablen
(a) Lasse den obigen Code ablaufen und erkläre die Fehlermeldungen. Die Warnung kann hilfreich sein. Wenn du mit der Maus über ein Fehlerzeichen oder Warnzeichen fährst, erscheint die konkrete Fehlermeldung oder Warnung.
(b) Ergänze den Lückentext.
Die Ablauflogik für eine Runde lässt sich auf unterschiedliche Art umsetzen. Hier ist eine mögliche Umsetzung in einer Mischung aus Kotlin und Pseudocode:
fun eineRundeSpielen() {
val neueKarte = ziehe eine neue Karte
if (es gab keine Karte mehr) {
println("Keine Karte mehr im Stapel!")
} else {
drucke die neue Karte
val oberste = schaue die oberste Karte des Keepstapels an
val kannBehaltenWerden = keine oberste ODER oberste kleiner als neue Karte
if (kannBehaltenWerden) {
println("Möchtest du sie behalten? (k/keep)")
val eingabe = readln()
if (eingabe ist "k" ODER eingabe ist "keep") {
behalte die neue Karte
} else {
lege die neue Karte auf den Throwstapel
}
} else {
println("Karte ist zu klein für deinen Stapel und wird weggeworfen.")
lege die neue Karte auf den Throwstapel
// Kurze Pause, damit man die Meldung lesen kann
println("Weiter mit Enter...")
readln()
}
}
print("\u000c") // Löscht die Ausgabe in BlueJ
}
Aufgabe 4 - eine Runde spielen
Implementiere die obige Methode eineRundeSpielen() in der Klasse
Spiel.
Nutze dazu die Vorlage oben oder überlege dir eine eigene Ablauflogik.
Teste deine Implementierung, indem du ein Spiel erzeugst, die Methode
vorbereiten() aufrufst und dann eineRundeSpielen()
mehrfach aufrufst.
Das Spiel auswerten
Nachdem du das Spiel beendet hast, sollte dies ausgewertet werden. Auch dies kann auf unterschiedliche Art erfolgen, z.B.:
- Ausgabe von
keepstapelundthrowstapel - Ausgabe der Anzahl der behaltenen Karten
- Auswertung in der Art:
- bis 3 behaltene Karten: "Das schaffst du besser!"
- bis 6 behaltene Karten: "Das war echt gut!"
- ansonsten: "Wow, das war super!!!"
Aufgabe 5 - Auswertung
Implementiere die Methode auswerten() gemäß der obigen oder eigener Ideen.
Vollständige Spielsteuerung
Nun bleibt nur noch die Methode starten() zu implementieren.
Diese soll die Methode vorbereiten aufrufen, dann in einer Schleife
zehn mal die Methode eineRundeSpielen und anschließend die
Methode auswerten aufrufen.
Aufgabe 6 - Spielsteuerung
Implementiere die Methode starten() und teste sie, also spiele!!!
Optional: Falls du dich im Abschnitt zu Datenkapselung mit dem Zugriffsmodifikator
private beschäftigt hast, dann kannst du die Teile, die nicht außerhalb
der Klasse benutzt werden sollen, entsprechend verstecken.
Optional: Aufgabe 7 - Version für zwei Spieler
Passe dein Programm so an, dass es für zwei Spieler funktioniert. Überlege dir dabei wie viele Kartenstapel es geben soll und überarbeite die Ablauflogik des Programms.