Falkencode · 09.09.2026
Falkencode: app.py — Webserver & WebSocket
app.py ist die Haustür von Falkenmoor: Ein kleiner Flask-Webserver, der die Spiel-Seite, das Impressum und die Statistik-API (/api/stats) ausliefert — und der Kern: die WebSocket-Verbindung /mud für alle Spieler.
Die Anmeldung läuft über die erste WebSocket-Nachricht (LOGIN oder REGISTER), ein bereits angemeldeter Name wirft die alte Verbindung raus (Doppel-Login-Schutz). Die drei Sendefunktionen verteilen Nachrichten an einzelne Spieler, den ganzen Raum oder die ganze Welt — und übersetzen dabei pro Empfänger in dessen Sprache: Ein deutscher Spieler liest „X geht Richtung norden“, ein englischer im selben Raum gleichzeitig „X walks north“.
Am Ende begrüßt der Server den Helden zweisprachig je nach gewählter Sprache und hängt bei neuer Post einen Hinweis auf den Briefkasten an.
#!/usr/bin/env python3
"""Falkenmoor-MUD: Multiuser-Webserver (Flask + WebSocket) auf Port 9003.
Konten mit Passwort (bcrypt), persistenter Spielstand (SQLite)."""
import os, threading
from flask import Flask, send_from_directory
from flask_sock import Sock
import db
import uebersetz
from engine import Engine, Spieler, SPIEL_WELT, ONLINE, _online_lock
BASE = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, static_folder=os.path.join(BASE, "static"), static_url_path="/static")
sock = Sock(app)
db.init()
def sende_an(name, text):
eintrag = ONLINE.get(name)
if eintrag and eintrag.get("ws"):
try:
sprache = getattr(eintrag["spieler"], "sprache", "de")
eintrag["ws"].send(uebersetz.text(text, sprache))
except Exception:
pass
def sende_raum(raum_id, text, ausser=None):
with _online_lock:
namen = [n for n, e in ONLINE.items()
if e["spieler"].raum == raum_id and n != ausser]
for n in namen:
sende_an(n, text)
def sende_alle(text, ausser=None):
with _online_lock:
namen = [n for n in ONLINE if n != ausser]
for n in namen:
sende_an(n, text)
@app.route("/")
def index():
return send_from_directory(os.path.join(BASE, "static"), "index.html")
@app.route("/impressum")
def impressum():
return send_from_directory(os.path.join(BASE, "static"), "impressum.html")
@app.route("/datenschutz")
def datenschutz():
return send_from_directory(os.path.join(BASE, "static"), "datenschutz.html")
@app.route("/doku")
def doku():
return send_from_directory(os.path.join(BASE, "static"), "doku.html")
@app.route("/was-ist-ein-mud")
def was_ist_ein_mud():
return send_from_directory(os.path.join(BASE, "static"), "was-ist-ein-mud.html")
@app.route("/api/stats")
def api_stats():
import sqlite3, json as _json
con = sqlite3.connect(db.DB_PATH)
registriert = con.execute("SELECT COUNT(*) FROM spieler").fetchone()[0]
con.close()
with _online_lock:
online = len(ONLINE)
return {"registriert": registriert, "online": online}
@sock.route("/mud")
def mud_ws(ws):
# --- Anmeldung: erste Nachricht = "LOGIN name pw" / "REGISTER name pw" ---
anmeldung = None
while anmeldung is None:
erste = (ws.receive() or "").strip()
teile = erste.split()
if len(teile) != 3 or teiles_ok(teile):
ws.send("Bitte zuerst: LOGIN <name> <passwort> oder REGISTER <name> <passwort>")
continue
modus, name, pw = teile
if modus == "REGISTER":
name, fehler = db.registrieren(name, pw)
elif modus == "LOGIN":
name, fehler = db.einloggen(name, pw)
else:
ws.send("Bitte zuerst: LOGIN <name> <passwort> oder REGISTER <name> <passwort>")
continue
if fehler:
ws.send(f"✗ {fehler}")
continue
anmeldung = name
# Doppelt eingeloggt? Alte Verbindung rauswerfen.
with _online_lock:
alt = ONLINE.get(anmeldung)
if alt:
try:
alt.get("ws") and alt["ws"].send("Du hast dich von woanders angemeldet. Tschüss!")
alt.get("ws") and alt["ws"].close()
except Exception:
pass
spieler = Spieler(anmeldung, SPIEL_WELT)
spieler.laden(db.laden(anmeldung))
engine = Engine(spieler, sende_raum=sende_raum, sende_alle=sende_alle,
beim_save=lambda: db.speichern(anmeldung, spieler.serialisieren()))
with _online_lock:
ONLINE[anmeldung] = {"spieler": spieler, "ws": ws}
def save():
db.speichern(anmeldung, spieler.serialisieren())
save()
ungezaehlt = db.mail_ungezaehlt(anmeldung)
if spieler.sprache == "en":
gruß = uebersetz.willkommen(anmeldung, SPIEL_WELT.titel, ungezaehlt, "en")
else:
post_hinweis = (f"\n📬 Du hast {ungezaehlt} neue{' Briefe' if ungezaehlt != 1 else 'n Brief'} "
"am Briefkasten auf dem Marktplatz!" if ungezaehlt else "")
gruß = (f"Willkommen zurück in {SPIEL_WELT.titel}, {spieler.name}!\n"
"Dein Fortschritt wird automatisch gespeichert. Tippe HILFE für alle Befehle."
+ post_hinweis)
ws.send(gruß + engine._raum_ansehen())
sende_alle(f"◉ {spieler.name} hat die Welt betreten.", ausser=anmeldung)
try:
while True:
msg = ws.receive()
if msg is None:
break
if msg.strip().lower() in ("quit", "beenden", "ausloggen", "logout", "exit"):
if spieler.sprache == "en":
ws.send(f"Farewell, {spieler.name}! Your progress is saved. "
"See you soon in Falkenmoor!")
else:
ws.send(f"Auf Wiedersehen, {spieler.name}! Dein Fortschritt ist gespeichert. "
"Bis bald in Falkenmoor!")
break
antwort = engine.verarbeite(msg)
if antwort:
ws.send(antwort)
finally:
with _online_lock:
ONLINE.pop(anmeldung, None)
save()
sende_alle(f"◌ {spieler.name} hat sich verabschiedet.")
def teiles_ok(teile):
return len(teile) < 3 or teile[0].upper() not in ("LOGIN", "REGISTER")
if __name__ == "__main__":
print("Falkenmoor-MUD (Multiuser) läuft auf http://localhost:9003")
app.run(host="0.0.0.0", port=9003, debug=False)
Mehr aus der Kategorie „Falkencode“
- Falkencode: engine.py — Die Spiellogik 09.09.2026
- Falkencode: db.py — Konten, Spielstand, Post 09.09.2026
- Falkencode: uebersetz.py — Der DE/EN-Katalog 09.09.2026
- Falkencode: gen_world.py — Der Weltgenerator 09.09.2026