eis-website/app/app.py
2026-07-15 14:18:25 +00:00

233 lines
6.5 KiB
Python

from flask import Flask, request, session, redirect
import html
import sqlite3
from pathlib import Path
app = Flask(__name__)
app.secret_key = "geheimnis-aus-dem-weltall"
DB_PATH = Path(__file__).with_name("adventure.db")
def get_db():
db = sqlite3.connect(DB_PATH)
db.row_factory = sqlite3.Row
return db
BASE_CSS = """
:root {
color-scheme: dark;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #07121d 0%, #102a43 45%, #1e3a5f 100%);
color: #e2e8f0;
min-height: 100vh;
}
.shell {
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem 1rem;
}
.card {
width: min(760px, 100%);
background: rgba(7, 18, 29, 0.88);
border: 1px solid rgba(148, 163, 184, 0.25);
border-radius: 24px;
padding: 2rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(10px);
}
.card h1 {
margin-top: 0;
margin-bottom: 0.75rem;
font-size: 2rem;
color: #f8fafc;
}
.card p {
line-height: 1.7;
color: #cbd5e1;
}
.actions, .links {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.25rem;
}
.btn, .card a, .card button {
display: inline-block;
background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
color: #f8fafc;
border: none;
border-radius: 999px;
padding: 0.8rem 1.1rem;
text-decoration: none;
font-weight: 700;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 10px 25px rgba(6, 182, 212, 0.25);
}
.btn:hover, .card a:hover, .card button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 30px rgba(37, 99, 235, 0.35);
}
.btn.secondary {
background: linear-gradient(135deg, #475569 0%, #64748b 100%);
box-shadow: 0 10px 25px rgba(100, 116, 139, 0.25);
}
form {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
margin-top: 1rem;
}
input {
flex: 1 1 220px;
border: 1px solid rgba(148, 163, 184, 0.35);
border-radius: 999px;
padding: 0.8rem 1rem;
background: rgba(15, 23, 42, 0.9);
color: #f8fafc;
}
input:focus {
outline: 2px solid rgba(6, 182, 212, 0.45);
border-color: transparent;
}
"""
BASE_JS = """
window.addEventListener('DOMContentLoaded', () => {
const firstInput = document.querySelector('input');
if (firstInput) firstInput.focus();
});
"""
def render_page(title, body_html):
return f"""<!doctype html>
<html lang=\"de\">
<head>
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<title>{html.escape(title)}</title>
<style>{BASE_CSS}</style>
</head>
<body>
<main class=\"shell\">
<section class=\"card\">
{body_html}
</section>
</main>
<script>{BASE_JS}</script>
</body>
</html>"""
@app.route("/", methods=["GET", "POST"])
@app.route("/app", methods=["GET", "POST"])
@app.route("/app/", methods=["GET", "POST"])
def start():
if request.method == "POST": # Formular abgeschickt?
name = request.form["name"].strip()
if name != "":
session["spieler"] = name # App merkt sich: das bist du
db = get_db()
db.execute(
"INSERT OR IGNORE INTO spielstand (spieler, raum_id) VALUES (?, 1)",
(name,)
)
db.commit()
db.close()
return redirect("/app/raum/1")
html = '<h1>Verlies</h1>'
if "spieler" in session:
db = get_db()
s = db.execute(
"SELECT raum_id FROM spielstand WHERE spieler=?",
(session["spieler"],)
).fetchone()
db.close()
if s:
html = html + '<p>Willkommen zurück, ' + html.escape(session["spieler"]) + '!</p>' \
+ '<a href="/app/raum/' + str(s["raum_id"]) + '" class="btn">Weiterspielen</a><hr>'
return html + '<form method="post">Dein Name: ' \
+ '<input name="name"> <button>Abenteuer starten</button></form>'
@app.route("/raum/<int:id>", methods=["GET", "POST"])
@app.route("/app/raum/<int:id>", methods=["GET", "POST"])
def raum(id):
db = get_db()
if "spieler" in session: # angemeldet?
db.execute(
"UPDATE spielstand SET raum_id=? WHERE spieler=?",
(id, session["spieler"])
)
db.commit()
r = db.execute("SELECT * FROM raeume WHERE id=?", (id,)).fetchone()
if not r:
db.close()
return render_page(
"Raum nicht gefunden",
'<h1>Raum nicht gefunden</h1><p>Der angeforderte Raum existiert nicht.</p><div class="actions"><a class="btn secondary" href="/app/">Zurück zum Start</a></div>'
)
if r["raetsel_frage"]:
if request.method == "POST":
if request.form.get("antwort", "").strip() == r["raetsel_antwort"]:
db.close()
return render_page(
"Notsignal aktiviert",
'<h1>Notsignal erfolgreich aktiviert</h1><p>Rettung ist unterwegs.</p><div class="actions"><a class="btn" href="/app/">Zurück zum Start</a></div>'
)
db.close()
return render_page(
"Falscher Code",
'<h1>Falscher Code</h1><p>Der KI-Kern reagiert nicht.</p><div class="actions"><a class="btn secondary" href="/app/raum/' + str(id) + '">nochmal versuchen</a></div>'
)
db.close()
body = f"""
<h1>{html.escape(r['name'])}</h1>
<p>{html.escape(r['beschreibung'])}</p>
<form method="post">
<input id="antwort" name="antwort" placeholder="Antwort eingeben" autocomplete="off">
<button type="submit">OK</button>
</form>
"""
return render_page(html.escape(r["name"]), body)
ausgaenge = db.execute(
"SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", (id,)
).fetchall()
db.close()
body = f"""
<h1>{html.escape(r['name'])}</h1>
<p>{html.escape(r['beschreibung'])}</p>
"""
if "spieler" in session:
body = body + '<p><small>Unterwegs als: ' + html.escape(session["spieler"]) + '</small></p>'
body = body + """
<div class="links">
"""
for a in ausgaenge:
body += f'<a class="btn secondary" href="/app/raum/{a["nach_raum"]}">{html.escape(a["richtung"])}</a>'
body += "</div>"
return render_page(html.escape(r["name"]), body)
@app.route("/reset")
@app.route("/app/reset")
def reset():
session.pop("spieler", None)
return redirect("/app/")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=9004)