Murdle: Ein tägliches Logikrätsel — Devlog V0.1
Wie ich ein tägliches Mordfall-Logikrätsel mit KI-gestützter Generierung baue — Architektur, erste Schritte und was dabei schief geht.
Seit ein paar Wochen arbeite ich an einem kleinen Nebenprojekt: Murdle — ein tägliches Mordfall-Logikrätsel, eingebettet in eine fortlaufende Detektivgeschichte.
Die Idee kommt von der gleichnamigen Rätselbuchreihe von G.T. Karber. Das Prinzip ist simpel: drei Verdächtige, drei Tatwaffen, drei Tatorte — und durch logische Deduktion herausfinden wer der Täter war, womit, und wo. Ich wollte das als tägliches Webformat umsetzen, mit einer eigenen Detektivin und einer Geschichte die sich von Fall zu Fall weiterspinnt.
Die Detektivin
Sierra ist eine Hexe — irgendwo zwischen Harry Potter und Hercule Poirot. Das Setting ist heutige Zeit mit einem magischen Overlay im Stil von Fantastic Beasts: Magie existiert, aber unauffällig, im Hintergrund. Die ersten Fälle spielen noch in Deutschland während Sierras Abschlussprüfung an der Akademie. Danach reist sie — zunächst durch Deutschland, später weiter.
Jeder Fall ist in sich abgeschlossen und lösbar, aber die Hinweise die Sierra sammelt spinnen sich zu einem größeren Handlungsfaden zusammen.
Der technische Aufbau
Das ganze läuft auf meinem Strato VPS als Teil von diezunddaz.xyz. Das Frontend ist in Astro gebaut, das Backend besteht aus einem Python-Generator und zwei OpenClaw-KI-Agenten die über OpenRouter laufen.
Die Pipeline sieht so aus:
Cron 02:00 Uhr
→ Narrator-Agent (Claude Sonnet) generiert Stadtkontext + Charaktere
→ Python-Generator erzeugt das Logikrätsel (rein algorithmisch, keine KI)
→ Puzzle-Agent (Claude Haiku) formuliert die Hinweise als atmosphärische Texte
→ Narrator-Agent verwebt alles zu Intro und Outro
→ Finales JSON landet unter /murdle/[tag]
Die Rätsel-JSONs liegen als day-N.json auf dem Server, das Astro-Frontend rendert sie serverseitig. Gitter-Zustand wird im localStorage gespeichert, die Lösungsprüfung passiert serverseitig per Hash.
Der Generator — ein längerer Weg
Der interessanteste Teil war der Rätsel-Generator. Die Anforderung klingt einfach: drei 3×3-Gitter, in jedem genau ein Haken pro Zeile und Spalte, und alle drei müssen untereinander konsistent sein (wenn Person A Waffe B hat und Person A an Ort C war, muss Waffe B auch an Ort C gewesen sein).
Mein erster Ansatz war: Hinweise generieren und validieren. Das hat nicht funktioniert — 742 von 1000 generierten Rätseln hatten eine falsche Lösung, weil indirekte Kreuz-Propagation die beabsichtigte Lösung ausschließen konnte ohne dass der Validator es bemerkte.
Nach mehreren Iterationen mit immer ausgefeilteren Validierungen (_solution_still_valid(), _state_has_contradiction(), vollständige Konsistenzprüfungen) landete ich schließlich beim richtigen Ansatz:
Erst das vollständige Gitter aufbauen, dann Hinweise ableiten.
# 1. Zufällige Permutation für Waffe→Person
wp = shuffle([0, 1, 2]) # wp[w] = p
# 2. Zufällige Permutation für Waffe→Ort
wo = shuffle([0, 1, 2]) # wo[w] = o
# 3. Person→Ort folgt automatisch (immer konsistent)
po[wp[w]] = wo[w] # für jede Waffe w
# 4. Zufälligen Drilling als Mord wählen
sol = (w, wp[w], wo[w]) # Täter, Waffe, Tatort
# 5. Hinweise aus dem Gitter ableiten bis eindeutig lösbar
Widersprüche sind damit strukturell unmöglich. Das Gitter ist von Anfang an mathematisch korrekt, und Hinweise werden direkt aus dem Gitter abgeleitet — sie können die Lösung nicht verletzen weil sie aus ihr hervorgehen.
Ein kleines mathematisches Detail: in einem 3×3-Latin-Square-System gibt es immer genau drei gültige Drillinge — nicht nur den Mord, sondern auch die anderen beiden Kombinationen die das Gitter konsistent halten. Deshalb braucht es am Ende immer einen murder_clue der atmosphärisch auf Tatwaffe oder Tatort hinweist und damit den Täter eindeutig identifiziert.
Das Ergebnis: 0 Widersprüche, 0 Fehler, 0,1ms pro Rätsel bei 1000 generierten Testrätseln.
Das Deduktionsgitter
Das Frontend zeigt drei 3×3-Gitter in einer L-Form:
- Links oben: Verdächtige × Waffen
- Links unten: Verdächtige × Tatorte
- Rechts: Waffen × Tatorte
Klicken wechselt zwischen leer / ✓ (möglich) / ✗ (ausgeschlossen) / ? (unsicher). Der Zustand wird im localStorage gespeichert. Wenn alle drei Gitter korrekt ausgefüllt sind und man den Fall abschließt, prüft der Server den Lösungs-Hash.
Hinweis-Qualität
Ein großer Teil der Arbeit floss in die Formulierung der Hinweise. Statt generischer Sätze wie “X hatte nichts mit Y zu tun” greifen die Hinweise die Beschreibungen der Charaktere und Orte auf:
“Der nervöse Bibliothekar mit seinen schwarzgefärbten Fingernägeln schien mit der eleganten Mineralwasserflasche sehr vertraut zu sein — als hätte er sie oft genug gefüllt.”
Das war ein Prompt-Engineering-Problem: der Puzzle-Agent bekommt jetzt explizit die Beschreibungen der beteiligten Elemente zusammen mit dem Hinweis-Typ und soll daraus einen atmosphärischen Satz bauen.
Stand heute
Aktuell laufen 30 Tage Testdaten — von Lübeck bis Bad Doberan, quer durch Norddeutschland. Das Rätsel generiert sich jede Nacht um 2 Uhr automatisch. Die Logik ist stabil, das Frontend funktioniert.
Es ist aber noch V0.1. Die Testdaten werden vor dem echten Start gelöscht. Sierra hat noch keinen Begleiter. Und einiges am Design und an der Spielerfahrung kann noch besser werden.
Demnächst mehr — wenn die erste echte Staffel startet.