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.

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 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.

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.

Suche

v
100.123.4.1.6 Spiel-Klasse
Kopieren durch Anklicken

Rückmeldung geben