s n h m r u

Minimallogo des digitalen Schulbuchs inf-schule.de. Schriftzug in Zustandsübergangsdiagramm eines endlichen Automaten.

s n h m r u
i

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.

Klassendiagramm Spiel

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 keepstapel und throwstapel
  • 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.

Suche

v
100.123.4.1.6 Spiel-Klasse
Kopieren durch Anklicken

Rückmeldung geben