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.
Nutze als Basis wieder das BlueK-Projekt mit dem universellen Kartenstapel.
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: Karte? = 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: String = when (farbe) {
"herz" -> "♥"
"karo" -> "♦"
"pik" -> "♠"
"kreuz" -> "♣"
else -> "?"
}
val right: String = rang.padStart(2, ' ')
val left: String = rang.padEnd(2, ' ')
println("┌─────┐")
println("│$left │")
println("│ $symbol │")
println("│ $right│")
println("└─────┘")
}
private fun kuerzeRang(roherRang: String): String {
val normalisiert: String = roherRang.trim().lowercase()
return when (normalisiert) {
"a", "ass" -> "A"
"k", "koenig", "könig" -> "K"
"d", "dame" -> "D"
"b", "bube" -> "B"
else -> {
val zahl: Int? = 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: String = 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: List<String> = listOf(
"2", "3", "4", "5", "6", "7", "8", "9", "10",
"B", "D", "K", "A"
)
val farben: List<String> = 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 = Stapel()
fun eineRundeSpielen() {
val aktuelleKarte: Karte? = ziehstapel.zieheKarte()
if(aktuelleKarte != null) {
aktuelleKarte.druckeKarte()
}
}
fun spielAuswerten() {
println("Letzte Karte war:")
aktuelleKarte.druckeKarte()
if(ziehstapel.anzahlKarten > 0) {
val restkarten: Int = 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 BlueK
}
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.
Hilfe zu Aufgabe 4
Gehe in drei Schritten vor: Ziehe eine Karte und prüfe, ob der Ziehstapel leer ist. Falls eine Karte gezogen wurde, vergleiche sie mit der obersten Karte des Keepstapels. Ist dieser leer oder ist die neue Karte höher, frage nach der Entscheidung und lege die Karte passend ab. Andernfalls kommt sie direkt auf den Throwstapel.
Ergänze die fehlenden Methoden und Bedingungen. Kopiere den Code danach
in die Methode eineRundeSpielen() deiner Klasse
Spiel.
fun eineRundeSpielen() {
val neueKarte: §§Karte?§§ = ziehstapel.§§zieheKarte::baustein§§()
if (neueKarte == §§null§§) {
println("Keine Karte mehr im Stapel!")
} else {
neueKarte.druckeKarte()
val obersteKarte: §§Karte?§§ = keepstapel.§§obersteKarte::baustein§§()
if (obersteKarte §§==§§ §§null§§ §§||§§ obersteKarte.wert §§<§§ neueKarte.wert) {
println("Möchtest du sie behalten? (k/keep)")
val eingabe: String = §§readln§§()
if (eingabe == §§"k"§"keep"§§ || eingabe == §§"k"§"keep"§§) {
keepstapel.legeAb(neueKarte)
} else {
§§throwstapel.legeAb(neueKarte)§§
}
} else {
println("Karte ist zu klein für deinen Stapel und wird weggeworfen.")
§§throwstapel.legeAb(neueKarte)§§
}
}
}
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.
Hilfe zu Aufgabe 5
Eine einfache Auswertung kann die Zahl der behaltenen Karten ausgeben und dazu eine Rückmeldung anzeigen. Hier ist eine mögliche Variante mit den drei vorgeschlagenen Rückmeldungen.
Ergänze die Lücken und übertrage die Methode in deine Klasse
Spiel.
fun auswerten() {
val behalten: Int = §§keepstapel.anzahlKarten§§
println("Du hast $behalten Karten behalten.")
if (behalten §§<=§§ §§3§§) {
println("Das schaffst du besser!")
} else if (behalten <= §§6§§) {
println("Das war echt gut!")
} else {
println("Wow, das war super!!!")
}
}
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.
Hilfe zu Aufgabe 6
Die Methode starten() ruft zuerst die Vorbereitung auf,
spielt dann zehn Runden und wertet zum Schluss das Spiel aus.
Ergänze die Lücken und übertrage den Code in deine Klasse
Spiel.
fun starten() {
§§vorbereiten§§()
for (runde in §§1..10§§) {
§§eineRundeSpielen§§()
}
§§auswerten§§()
}
Aufgabe 7 - Hauptprogramm
Damit das Spiel durch Klicken auf main starten
gestartet werden kann, benötigen wir eine main-Funktion als Hauptprogramm.
Dort muss einfach nur ein Spiel erzeugt werden und die Methode
starten() aufgerufen werden.
Optional: Aufgabe 8 - 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.