From fa4521a684b015d3ad1ef53372eed0d5eba129a6 Mon Sep 17 00:00:00 2001 From: DylanBarnes Date: Wed, 15 Jul 2026 15:58:48 +0000 Subject: [PATCH] =?UTF-8?q?Krasses=203-Stufen-R=C3=A4tsel=20im=20KI-Kern,?= =?UTF-8?q?=20persistenter=20Fortschritt,=20Terminal-Log-UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 226 +++++++++++++++++++++++++++++++++++++++++++------ app/init_db.py | 23 +++-- 2 files changed, 217 insertions(+), 32 deletions(-) diff --git a/app/app.py b/app/app.py index 02c3fb7..fe6fd92 100644 --- a/app/app.py +++ b/app/app.py @@ -1,10 +1,11 @@ from flask import Flask, request, session, redirect import html +import random import sqlite3 from pathlib import Path app = Flask(__name__) -app.secret_key = "hier-eigenen-geheimen-text" # für session nötig +app.secret_key = "hier-eigenen-geheimen-text" DB_PATH = Path(__file__).with_name("adventure.db") @@ -14,6 +15,52 @@ def get_db(): return db +DENY_MESSAGES = [ + "ZUGRIFF VERWEIGERT.", + "AUTHENTIFIZIERUNG FEHLGESCHLAGEN.", + "CODE UNGÜLTIG — VERSUCH PROTOKOLLIERT.", +] + +STUFE_HINWEISE = [ + "Tipp: Sichte das Navigationslog auf der Brücke noch einmal genau.", + "Tipp: Der Reaktor im Maschinenraum trägt ein Typenschild mit einer Signatur.", + "Tipp: Das Klemmbrett im Forschungslabor listet den Zustand der Kryokapseln.", +] + + +def parse_stufen(r): + if not r["raetsel_frage"]: + return [] + fragen = r["raetsel_frage"].split("|") + antworten = r["raetsel_antwort"].split("|") + return list(zip(fragen, antworten)) + + +def get_fortschritt(db, spieler, raum_id): + if not spieler: + return session.get(f"stufe_r{raum_id}", 0), session.get(f"fehler_r{raum_id}", 0) + row = db.execute( + "SELECT stufe, fehler FROM raetsel_fortschritt WHERE spieler=? AND raum_id=?", + (spieler, raum_id) + ).fetchone() + if row: + return row["stufe"], row["fehler"] + return 0, 0 + + +def set_fortschritt(db, spieler, raum_id, stufe, fehler): + if not spieler: + session[f"stufe_r{raum_id}"] = stufe + session[f"fehler_r{raum_id}"] = fehler + return + db.execute( + "INSERT INTO raetsel_fortschritt (spieler, raum_id, stufe, fehler) VALUES (?,?,?,?) " + "ON CONFLICT(spieler, raum_id) DO UPDATE SET stufe=excluded.stufe, fehler=excluded.fehler", + (spieler, raum_id, stufe, fehler) + ) + db.commit() + + BASE_CSS = """ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500&display=swap'); @@ -44,7 +91,6 @@ body { overflow-x: hidden; } -/* feine Scanlines über der gesamten Seite */ body::before { content: ""; position: fixed; @@ -61,7 +107,6 @@ body::before { mix-blend-mode: overlay; } -/* Vignette */ body::after { content: ""; position: fixed; @@ -224,8 +269,86 @@ input:focus { border-top: 1px dashed var(--line); } +.terminal-log { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.78rem; + margin-bottom: 1rem; +} +.log-line { margin: 0.15rem 0; } +.log-line.done { color: var(--success); } + +.progress-dots { display: flex; gap: 0.4rem; margin: 0 0 1.2rem; } +.dot-step { + width: 22px; height: 6px; border-radius: 3px; + background: rgba(127,219,182,0.15); + border: 1px solid var(--line); +} +.dot-step.filled { + background: var(--success); + border-color: var(--success); + box-shadow: 0 0 8px var(--success); +} + +.frage { + font-family: 'IBM Plex Mono', monospace; + color: var(--amber); + font-size: 0.95rem; + margin: 0 0 0.4rem; +} + +.denied { + font-family: 'IBM Plex Mono', monospace; + color: var(--danger); + font-size: 0.85rem; + animation: flash 0.4s ease; + border-left: 2px solid var(--danger); + padding-left: 0.6rem; +} +@keyframes flash { + 0% { opacity: 0; transform: translateX(-4px); } + 100% { opacity: 1; transform: translateX(0); } +} + +.hint { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.78rem; + color: var(--text-dim); + border-left: 2px solid var(--amber-dim); + padding-left: 0.6rem; + margin-top: 0.8rem; +} + +.verify-ok { + font-family: 'IBM Plex Mono', monospace; + color: var(--success); + letter-spacing: 0.06em; + font-size: 0.85rem; +} + +.danger-btn { + font-family: 'IBM Plex Mono', monospace; + text-transform: uppercase; + color: #ffe1c9; + background: linear-gradient(135deg, rgba(255,107,94,0.15), rgba(255,180,84,0.15)); + border: 1px solid var(--danger); + border-radius: 6px; + padding: 0.9rem 1.4rem; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.5em; + animation: pulse-glow 1.6s ease-in-out infinite; +} +.danger-btn::before { content: "⚠"; } +@keyframes pulse-glow { + 0%, 100% { box-shadow: 0 0 10px rgba(255,107,94,0.25); } + 50% { box-shadow: 0 0 22px rgba(255,107,94,0.55); } +} + @media (prefers-reduced-motion: reduce) { .card h1::after { animation: none; opacity: 1; } + .danger-btn { animation: none; } + .denied { animation: none; } } """ @@ -336,36 +459,63 @@ def raum(id): eyebrow="FEHLER" ) - eyebrow = f"SEKTOR {id:02d}" + eyebrow_basis = f"SEKTOR {id:02d}" if r["raetsel_frage"]: - if request.method == "POST": - if request.form.get("antwort", "").strip() == r["raetsel_antwort"]: - db.close() - return render_page( - "Notsignal aktiviert", - '

Notsignal erfolgreich aktiviert

Rettung ist unterwegs.

' - '
Zurück zum Start
', - eyebrow="MISSION ERFÜLLT" - ) - db.close() - return render_page( - "Falscher Code", - '

Falscher Code

Der KI-Kern reagiert nicht.

' - '
nochmal versuchen
', - eyebrow=eyebrow - ) + stufen = parse_stufen(r) + spieler = session.get("spieler") + stufe, fehler = get_fortschritt(db, spieler, id) + + deny_msg = None + if request.method == "POST" and stufe < len(stufen): + antwort = request.form.get("antwort", "").strip() + erwartet = stufen[stufe][1] + if antwort.upper() == erwartet.upper(): + stufe += 1 + fehler = 0 + else: + fehler += 1 + deny_msg = random.choice(DENY_MESSAGES) + set_fortschritt(db, spieler, id, stufe, fehler) db.close() + + log_html = "".join( + f'
✓ Stufe {i+1}: {html.escape(f)}
' + for i, (f, a) in enumerate(stufen[:stufe]) + ) + + if stufe >= len(stufen): + body = f""" +
{log_html}
+

IDENTITÄT BESTÄTIGT — ALLE SYSTEME GRÜN

+

{html.escape(r['beschreibung'])}

+
+ Notsignal auslösen +
+ """ + return render_page(html.escape(r["name"]), body, eyebrow=f"{eyebrow_basis} · VERIFIZIERT") + + frage = stufen[stufe][0] + hinweis_html = f'

{html.escape(STUFE_HINWEISE[stufe])}

' if fehler >= 2 else "" + deny_html = f'

{deny_msg}

' if deny_msg else "" + dots = "".join( + f'' for i in range(len(stufen)) + ) + body = f""" -

{html.escape(r['name'])}

+
{log_html}
+

{dots}

{html.escape(r['beschreibung'])}

+

{html.escape(frage)}

+ {deny_html}
- +
+ {hinweis_html} """ - return render_page(html.escape(r["name"]), body, eyebrow=eyebrow) + return render_page(html.escape(r["name"]), body, eyebrow=f"{eyebrow_basis} · STUFE {stufe+1}/{len(stufen)}") ausgaenge = db.execute( "SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", (id,) @@ -380,7 +530,35 @@ def raum(id): for a in ausgaenge: body += f'{html.escape(a["richtung"])}' body += "" - return render_page(html.escape(r["name"]), body, eyebrow=eyebrow) + return render_page(html.escape(r["name"]), body, eyebrow=eyebrow_basis) + + +@app.route("/raum//aktivieren") +@app.route("/app/raum//aktivieren") +def aktivieren(id): + db = get_db() + r = db.execute("SELECT * FROM raeume WHERE id=?", (id,)).fetchone() + if not r: + db.close() + return redirect("/app/") + + stufen = parse_stufen(r) + spieler = session.get("spieler") + stufe, _ = get_fortschritt(db, spieler, id) + db.close() + + if stufe < len(stufen): + return redirect(f"/app/raum/{id}") + + body = """ +

Notsignal aktiviert

+

✓ ALLE DREI SYSTEME VERIFIZIERT

+

Ein greller Alarmton hallt durch die Gänge. Auf dem Hauptschirm leuchtet: „NOTSIGNAL GESENDET — RETTUNG UNTERWEGS.“

+ + """ + return render_page("Notsignal aktiviert", body, eyebrow="MISSION ERFÜLLT") @app.route("/reset") diff --git a/app/init_db.py b/app/init_db.py index 9790559..8ef7007 100644 --- a/app/init_db.py +++ b/app/init_db.py @@ -1,8 +1,5 @@ import sqlite3 -# Die Datenbank entsteht auf dem Server beim einmaligen Aufruf -# und bleibt dort unverändert erhalten. - db = sqlite3.connect("adventure.db") db.executescript(""" @@ -27,13 +24,23 @@ CREATE TABLE IF NOT EXISTS spielstand ( raum_id INTEGER ); +CREATE TABLE IF NOT EXISTS raetsel_fortschritt ( + spieler TEXT, + raum_id INTEGER, + stufe INTEGER DEFAULT 0, + fehler INTEGER DEFAULT 0, + PRIMARY KEY (spieler, raum_id) +); + INSERT INTO raeume (id, name, beschreibung, raetsel_frage, raetsel_antwort) VALUES (1, 'Schlafkapsel', 'Du erwachst aus dem Kryoschlaf. Die Notbeleuchtung flackert und das Schiff wirkt verlassen.', NULL, NULL), - (2, 'Brücke', 'Die Steuerzentrale ist dunkel. Mehrere Monitore zeigen Fehlermeldungen.', NULL, NULL), - (3, 'Maschinenraum', 'Überall verlaufen Rohre und Kabel. Die Hauptenergie scheint instabil zu sein.', NULL, NULL), - (4, 'Forschungslabor', 'Verlassene Arbeitsplätze und zerbrochene Glasbehälter lassen nichts Gutes vermuten.', NULL, NULL), + (2, 'Brücke', 'Die Steuerzentrale ist dunkel. Ein einzelner Bildschirm flackert und zeigt: „NAV-VERIFIKATION — LETZTER KURS: SEKTOR 7-4-2“. Mehrere andere Monitore zeigen nur Fehlermeldungen.', NULL, NULL), + (3, 'Maschinenraum', 'Überall verlaufen Rohre und Kabel. Ein Typenschild am Hauptreaktor liest: „ENERGIESIGNATUR OMEGA-09“. Die Hauptenergie scheint instabil zu sein.', NULL, NULL), + (4, 'Forschungslabor', 'Verlassene Arbeitsplätze und zerbrochene Glasbehälter. Auf einem Klemmbrett steht handschriftlich: „6 Kryokapseln installiert, 4 havariert, 2 stabil.“', NULL, NULL), (5, 'Hangar', 'Die Landebucht ist leer. Nur ein beschädigtes Shuttle steht noch dort.', NULL, NULL), - (6, 'KI-Kern', 'Vor dir befindet sich der zentrale Computer des Schiffs. Nur hier kann das Notsignal aktiviert werden.', 'Gib den Notcode ein, um das Signal zu senden.', 'SOS'); + (6, 'KI-Kern', 'Vor dir befindet sich der zentrale Computer des Schiffs. Ein Drei-Faktor-Protokoll blockiert das Notsignal. Ohne die Daten aus Brücke, Maschinenraum und Forschungslabor kommst du hier nicht durch.', + 'Nenne den Sektor-Code aus dem Navigationslog der Brücke (nur Ziffern).|Wie lautet die Energiesignatur des Reaktors im Maschinenraum?|Wie viele Kryokapseln sind laut den Labornotizen noch stabil?', + '742|OMEGA-09|2'); INSERT INTO ausgaenge (von_raum, richtung, nach_raum) VALUES (1, 'Zur Brücke', 2), @@ -52,4 +59,4 @@ INSERT INTO ausgaenge (von_raum, richtung, nach_raum) VALUES db.commit() db.close() -print("Datenbank adventure.db angelegt.") +print("Datenbank adventure.db angelegt.") \ No newline at end of file