s n h m r u
i

Universeller Kartenstapel

Schwächen der Modellierung

Die Modellierung unseres Spiels mit den beiden Klassen Endlosstapel und Ablagestapel hat einige Schwächen:

  • Der Endlosstapel liefert zwar immer neue Karten, es können aber auch Karten doppelt vorkommen. Dies entspricht keinem realen Stapel von Karten.
  • Man muss selbst überprüfen, ob eine Karte behalten werden darf. Das Programm hat keine Möglichkeit hier zu unterstützen. Dazu müssten wir auf die oberste Karte des Ablagestapels zugreifen können. Bisher kann man diese zwar ausgeben, aber noch nicht als Objekt zurückgeben.
  • Wir müssen zwei unterschiedliche Arten von Stapeln verwenden. Natürlicher wäre es, wenn alle Kartenstapel vom gleichen Typ, also Objekte der gleichen Klasse wären.

Wir sollten also eine neue Klasse Stapel nutzen, die flexibler in der Anwendung ist, so dass man Karten vom Stapel nehmen und auf den Stapel legen kann. Hier findest du Ausschnitte aus einem Hauptprogramm, das eine universelle Klasse Stapel verwendet. Leider funktioniert keines der Hauptprogramme.

Beispiel 1

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()
    }

    // Kombiniert Safe Call und Elvis für eine saubere Ausgabe
    fun zeigeObersteKarte() {
        karten.lastOrNull()?.druckeKarte() ?: println("Stapel ist leer")
    }
    
    // Legt eine Karte "oben drauf" (ans Ende der Liste)
    fun legeAb(k: Karte) {
        karten.add(k)
    }

    fun druckeAlle() {
        if (karten.isEmpty()) {
            println("Der Stapel ist leer.")
        } else {
            for (karte in karten) {
                karte.druckeKarte()
            }
        }
    }
}
fun main() {
    val stapel: Stapel = Stapel()
    val k = stapel.obersteKarte()
    k.druckeKarte() // Fehler tritt hier auf
}

Beispiel 2

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()
    }

    // Kombiniert Safe Call und Elvis für eine saubere Ausgabe
    fun zeigeObersteKarte() {
        karten.lastOrNull()?.druckeKarte() ?: println("Stapel ist leer")
    }
    
    // Legt eine Karte "oben drauf" (ans Ende der Liste)
    fun legeAb(k: Karte) {
        karten.add(k)
    }

    fun druckeAlle() {
        if (karten.isEmpty()) {
            println("Der Stapel ist leer.")
        } else {
            for (karte in karten) {
                karte.druckeKarte()
            }
        }
    }
}
fun main() {
    val stapel: Stapel = Stapel()
    val k: Karte = stapel.obersteKarte() // Fehler tritt hier auf
    k.druckeKarte()
}

Beispiel 3

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()
    }

    // Kombiniert Safe Call und Elvis für eine saubere Ausgabe
    fun zeigeObersteKarte() {
        karten.lastOrNull()?.druckeKarte() ?: println("Stapel ist leer")
    }
    
    // Legt eine Karte "oben drauf" (ans Ende der Liste)
    fun legeAb(k: Karte) {
        karten.add(k)
    }

    fun druckeAlle() {
        if (karten.isEmpty()) {
            println("Der Stapel ist leer.")
        } else {
            for (karte in karten) {
                karte.druckeKarte()
            }
        }
    }
}
fun main() {
    val stapel: Stapel = Stapel()
    val k: Karte? = stapel.obersteKarte()
    k.druckeKarte() // Fehler tritt hier auf
}

Aufgabe 1 - Fehlerursache

Untersuche die Beispiele, führe sie aus und lies die Fehlermeldungen. Den genauen Wortlaut der Fehlermeldungen kannst du an dieser Stelle noch nicht verstehen. Allerdings deutet der unterschiedliche Code und die Fehlermeldungen in Richtung des eigentlichen Problems. Kannst du dir erklären, wo das Problem bei der Benutzung des Stapels und der Karte liegt?

Welches Problem tritt bei einem leeren Stapel auf? Wie spiegelt sich das im Datentyp Karte? wider?

Funktionierender Code

Eine funktionierende Variante der obigen Beispiele sieht folgendermaßen aus:

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()
    }

    // Kombiniert Safe Call und Elvis für eine saubere Ausgabe
    fun zeigeObersteKarte() {
        karten.lastOrNull()?.druckeKarte() ?: println("Stapel ist leer")
    }
    
    // Legt eine Karte "oben drauf" (ans Ende der Liste)
    fun legeAb(k: Karte) {
        karten.add(k)
    }

    fun druckeAlle() {
        if (karten.isEmpty()) {
            println("Der Stapel ist leer.")
        } else {
            for (karte in karten) {
                karte.druckeKarte()
            }
        }
    }
}
fun main() {
    val stapel: Stapel = Stapel()
    //stapel.erzeugeBlaetter(1)
    stapel.mischen()

    val k: Karte? = stapel.obersteKarte()

    if(k != null) {
        k.druckeKarte()
    } else {
        println("Stapel ist leer")
    }
}

Aufgabe 2 - Analyse des funktionierenden Codes

(a) Führe das obige Programm ein paar mal aus. Entferne den Kommentar und führe das Programm erneut ein paar mal aus. Beschreibe deine Beobachtungen.

(b) Analysiere den Code und erkläre deine Beobachtungen. Gehe dabei insbesondere auf den Datentyp Karte? und das Schlüsselwort null ein.

Benutzung der Stapel-Klasse

Das Klassendiagramm der Stapel-Klasse hat folgende Form:

Klassendiagramm Stapel

Aufgabe 3 - Klassendiagramm

Beschreibe anhand des Klassendiagramms welche Eigenschaften und Fähigkeiten die Klasse Stapel hat. Gehe insbesondere auf den Datentyp Karte? ein. (Hinweis: Der Unterschied zwischen zieheKarte und obersteKarte ist, dass beim Ziehen die oberste Karte nicht nur zurückgegeben, sondern auch vom Stapel entfernt wird).

Aufgabe 4 - Testen

Schreibe ein Hauptprogramm, das einen Stapel erzeugt, ein Blatt von 52 Karten darin erzeugt und 55 Karten abhebt und ausgibt. Nutze dazu das BlueJ-Projekt, das nun statt des Endlosstapels und Ablagestapels den universellen Stapel enthält.

Suche

v
100.123.4.1.4 Universeller Kartenstapel
Kopieren durch Anklicken

Rückmeldung geben