Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Haskell für Textanalyse und UNO-Spielregeln: Ein Tutorial zur funktionalen Programmierung

Lerne, wie du mit Haskell funktionale Programme schreibst, die UNO-Spielregeln umsetzen. Dieses Tutorial erklärt Datenstrukturen, Rekursion und Musterabgleich anhand eines Kartenspiels – ideal für Studierende der Textanalyse.

Haskell Tutorial UNO Programmierung funktionale Programmierung Textanalyse Haskell Musterabgleich Rekursion Haskell Spielregeln implementieren Haskell Datenstrukturen KI Chatbot Haskell Social Media Analyse Coding Aufgaben Uni Haskell Übungen Kartenspiel programmieren Haskell für Anfänger funktionale Textanalyse Haskell Projekt UNO

Einführung in Haskell und Textanalyse

Haskell ist eine rein funktionale Programmiersprache, die sich hervorragend für Textanalyse und das Modellieren von Spielregeln eignet. In diesem Tutorial zeigen wir dir, wie du mit Haskell UNO-Spielmechaniken abbildest – von einzelnen Zügen bis zu kompletten Spielrunden. Dabei lernst du zentrale Konzepte wie Musterabgleich, Rekursion und Listenverarbeitung. Obwohl der Fokus auf UNO liegt, sind die Techniken direkt auf Textanalyse-Aufgaben übertragbar, etwa das Parsen von Logdateien oder das Implementieren von regelbasierten Chatbots.

Grundlagen: Datenstrukturen für UNO-Karten

Bevor wir Spielzüge programmieren, brauchen wir eine Repräsentation für Karten. In Haskell bieten sich algebraische Datentypen an:

data Color = Rot | Gruen | Blau | Gelb | Wild deriving (Show, Eq)
data Card = Card Color (Maybe Int) | WildDraw4 | Draw2 deriving (Show, Eq)

Ein Card kann eine farbige Karte mit Symbol (z.B. 5) sein, oder eine Aktionskarte wie WildDraw4. Der Ableitung Show erlaubt uns, Karten auszugeben – nützlich für Debugging. Später könntest du diese Struktur erweitern, um auch Textdokumente zu modellieren, etwa als Liste von Wörtern mit Metadaten.

Spielzustand und erster Zug: onePlayerOneMove

Ein Spielzustand besteht aus Ablagestapel, Nachziehstapel und Hand. Wir definieren einen Typ:

type Deck = [Card]
type Hand = [Card]
type Discard = [Card]
type GameState = (Discard, Deck, Hand)

Die Funktion onePlayerOneMove erhält einen Zustand und gibt den neuen Zustand nach einem Zug zurück. Die Prioritätenliste aus der Aufgabenstellung setzen wir mit Wächterausdrücken um:

onePlayerOneMove :: Discard -> Deck -> Hand -> GameState
onePlayerOneMove disc deck hand
  | kannVerlaengern disc hand = verlaengere disc deck hand
  | hatFarbKarte disc hand   = spieleFarbKarte disc deck hand
  | hatWildDraw4 hand        = spieleWildDraw4 disc deck hand
  | hatSymbolKarte disc hand = spieleSymbolKarte disc deck hand
  | sonst                   = zieheKarte disc deck hand

Jede Bedingung prüft, ob ein bestimmter Zug möglich ist. kannVerlaengern untersucht, ob die oberste Karte des Ablagestapels eine Draw2 oder WildDraw4 ist und der Spieler eine passende Karte hat. Das erinnert an regelbasierte Textanalyse: Ähnlich wie ein Chatbot auf bestimmte Schlüsselwörter reagiert, entscheidet Haskell anhand von Mustern.

Musterabgleich in Aktion

Haskells Musterabgleich ist ideal, um Karten zu untersuchen. Hier ein Beispiel für das Erkennen einer Farbkarte:

hatFarbKarte :: Discard -> Hand -> Bool
hatFarbKarte ((Card col _):_) hand = any (passtFarbe col) hand
  where passtFarbe col (Card c _) = c == col
        passtFarbe _ _ = False

Die Funktion prüft, ob die oberste Karte des Ablagestapels eine Farbe hat und ob der Spieler eine Karte derselben Farbe besitzt. any ist eine Standardfunktion, die testet, ob mindestens ein Element einer Liste eine Bedingung erfüllt. Solche Listenoperationen sind auch in der Textanalyse nützlich, etwa um zu prüfen, ob ein Dokument bestimmte Wörter enthält.

Rekursion für mehrere Züge: onePlayerManyMoves

Nach einem Zug kann der Spieler weitermachen, solange er Karten legen kann. Das modellieren wir rekursiv:

onePlayerManyMoves :: Discard -> Deck -> Hand -> GameState
onePlayerManyMoves disc deck hand =
  let (disc', deck', hand') = onePlayerOneMove disc deck hand
  in if hand' == hand && deck' == deck && disc' == disc
     then (disc', deck', hand')  -- kein Zug möglich
     else onePlayerManyMoves disc' deck' hand'

Die Rekursion endet, wenn sich der Zustand nicht ändert – der Spieler kann nicht mehr ziehen. Dieses Muster ist analog zu iterativen Textanalyse-Algorithmen, die solange wiederholt werden, bis ein bestimmtes Kriterium erfüllt ist (z.B. Konvergenz).

Mehrere Spieler: manyPlayersOneMove

Jetzt wird es komplex: Wir haben eine Liste von Händen und müssen die Reihenfolge der Spieler verwalten. Zusätzlich gibt es Skip und Reverse Karten, die die Zugreihenfolge ändern. Wir definieren eine Richtung:

data Direction = Aufwaerts | Abwaerts deriving (Show, Eq)

Die Funktion manyPlayersOneMove bekommt einen Index des aktuellen Spielers und die Richtung. Sie berechnet den nächsten Spieler nach dem Zug. Hier ein Auszug:

naechsterSpieler :: Int -> Int -> Direction -> Card -> (Int, Direction)
naechsterSpieler n aktuell richtung karte =
  case karte of
    Reverse -> (aktuell, umkehren richtung)
    Skip    -> (ueberspringen n aktuell richtung, richtung)
    _       -> (normal n aktuell richtung, richtung)

Die Implementierung von ueberspringen und normal verwendet modulare Arithmetik, um den Index zu berechnen. Das ist ein klassisches Beispiel für Zustandsverwaltung in funktionalen Sprachen – ähnlich wie bei der Verarbeitung von Token-Strömen in einem Parser.

Vollständiges Spiel: manyPlayersManyMoves

Schließlich simulieren wir ein ganzes Spiel, bis ein Spieler gewinnt oder nicht mehr weitermachen kann. Die Funktion ruft wiederholt manyPlayersOneMove auf:

manyPlayersManyMoves :: Discard -> Deck -> [Hand] -> (Discard, Deck, [Hand])
manyPlayersManyMoves disc deck hands = spieleRunde 0 Aufwaerts disc deck hands
  where
    spieleRunde _ _ disc deck [] = (disc, deck, [])  -- Fehlerfall
    spieleRunde idx richtung disc deck hands
      | null (hands !! idx) = (disc, deck, hands)  -- Spieler hat gewonnen
      | sonst = let (disc', deck', hands') = manyPlayersOneMove idx richtung disc deck hands
                    (idx', richtung') = ... -- nächster Spieler
                in spieleRunde idx' richtung' disc' deck' hands'

Hier wird die Rekursion auf die gesamte Spielschleife angewendet. In der Textanalyse entspricht dies einem Workflow, der mehrere Schritte nacheinander ausführt, z.B. Tokenisierung, Stemming und Klassifikation.

Hilfsfunktionen und Datei-Ein-/Ausgabe

Die bereitgestellte Datei Helpers.hs liest .uno-Dateien in Matrizen ein. Du musst lediglich die Funktionen cardFromString und stringFromCard implementieren, um Karten in Text umzuwandeln. Das ist eine typische Serialisierungsaufgabe, wie sie auch bei der Verarbeitung von CSV-Dateien oder JSON auftritt.

Praktische Tipps und häufige Fehler

  • Vergiss nicht den Basisfall: Rekursion braucht immer eine Abbruchbedingung – sonst endlose Schleifen.
  • Nutze where-Blöcke: Sie machen Code lesbarer, indem du Hilfsfunktionen lokal definierst.
  • Teste mit einfachen Fällen: Beginne mit einer Hand voller Karten und prüfe jeden Zug manuell.
  • Typannotationen helfen: Haskell leitet Typen her, aber explizite Signaturen vermeiden Überraschungen.

Ausblick: Von UNO zur Textanalyse

Die Techniken, die du hier lernst – algebraische Datentypen, Musterabgleich, Rekursion – sind die Grundlage für fortgeschrittene Textanalyse. Stell dir vor, du baust einen KI-Chatbot, der auf Schlüsselwörter reagiert: Die Prioritätenlogik von UNO ist fast identisch mit der Entscheidungslogik eines Chatbots. Oder du analysierst Social-Media-Trends: Jeder Beitrag ist wie eine Karte, und du musst Muster erkennen (z.B. Hashtags). Mit Haskell kannst du solche Systeme sauber und fehlerfrei implementieren.

„Funktionale Programmierung zwingt dich, über Datenflüsse nachzudenken – das ist wie Schach, aber mit Code.“ – Unbekannt

Fazit

In diesem Tutorial hast du gelernt, wie du UNO-Spielregeln in Haskell abbildest. Du hast Datenstrukturen definiert, rekursive Funktionen geschrieben und mit Musterabgleich gearbeitet. Diese Fähigkeiten sind direkt auf Textanalyse-Projekte übertragbar. Probiere es selbst aus: Lade die Vorlage herunter, implementiere die vier Teile und teste sie mit deinen eigenen .uno-Dateien. Viel Erfolg!