621 lines
24 KiB
Python
Executable File
621 lines
24 KiB
Python
Executable File
import os
|
|
import uuid
|
|
import json
|
|
import datetime
|
|
from fastapi import FastAPI, Request, HTTPException
|
|
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from starlette.middleware.sessions import SessionMiddleware
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
app = FastAPI(title="Hogwarts Escape Adventure - Die Prüfungen der Zauberei")
|
|
|
|
SECRET_KEY = os.getenv("SECRET_KEY", "alohomora_bootle_potion_secret_2026")
|
|
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, session_cookie="escape_session")
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
|
|
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
|
|
|
# Standard Classic Riddle Solution (Book Accurate):
|
|
CLASSIC_SOLUTION = {
|
|
1: "poison",
|
|
2: "wine",
|
|
3: "ahead",
|
|
4: "poison",
|
|
5: "poison",
|
|
6: "wine",
|
|
7: "back"
|
|
}
|
|
|
|
ALL_ITEMS = {
|
|
"wand": {
|
|
"id": "wand",
|
|
"name": "Zauberstab",
|
|
"icon": "🪄",
|
|
"desc": "Stechpalme und Phönixfeder, 11 Zoll. Liegt warm und vibrationsbereit in deiner Hand.",
|
|
"action": "lumos",
|
|
"action_label": "Lumos / Licht 💡"
|
|
},
|
|
"broom": {
|
|
"id": "broom",
|
|
"name": "Nimbus 2000",
|
|
"icon": "🧹",
|
|
"desc": "Ein pfeilschneller Rennbesen. Perfekt für den rasanten Flug durch schwebende Ringe!",
|
|
"action": "broom_polish",
|
|
"action_label": "Besen polieren ✨"
|
|
},
|
|
"winged_key": {
|
|
"id": "winged_key",
|
|
"name": "Geflügelter Schlüssel",
|
|
"icon": "🗝️",
|
|
"desc": "Ein alter Silberschlüssel mit zarten Libellenflügeln. Er sperrt die Eichentür zur Duellier-Kammer auf!",
|
|
"action": "flutter_key",
|
|
"action_label": "Flügel surren lassen 🪽"
|
|
},
|
|
"duel_badge": {
|
|
"id": "duel_badge",
|
|
"name": "Duellanten-Abzeichen & Snapes Notiz",
|
|
"icon": "⚡",
|
|
"desc": "Ein goldenes Abzeichen mit Snapes handschriftlicher Rezeptur-Notiz für Felix Felicis auf der Rückseite.",
|
|
"action": "read_recipe",
|
|
"action_label": "Snapes Rezeptur lesen 📜"
|
|
},
|
|
"felix_felicis": {
|
|
"id": "felix_felicis",
|
|
"name": "Felix Felicis",
|
|
"icon": "🧪",
|
|
"desc": "Ein Tropfen flüssiges Gold! Verleiht dir unerschütterliches Glück bei deinen magischen Taten.",
|
|
"action": "lucky_aura",
|
|
"action_label": "Glücks-Aura aktivieren 🍀"
|
|
},
|
|
"cryptex_key": {
|
|
"id": "cryptex_key",
|
|
"name": "Schlüssel der Gründer",
|
|
"icon": "🗝️",
|
|
"desc": "Ein schwerer, mit uralten Runen gravierter Messingschlüssel aus dem Tresor von Hogwarts.",
|
|
"action": "inspect_key",
|
|
"action_label": "Runen betrachten 🔍"
|
|
},
|
|
"pixie_jar": {
|
|
"id": "pixie_jar",
|
|
"name": "Wichtelglas",
|
|
"icon": "🧚",
|
|
"desc": "Ein dickbauchiges Glas mit einem gebannten blauen Wichtel. Er schneidet lustige Fratzen!",
|
|
"action": "poke_jar",
|
|
"action_label": "Glas antippen ⚡"
|
|
},
|
|
"snape_scroll": {
|
|
"id": "snape_scroll",
|
|
"name": "Snapes Logik-Rolle",
|
|
"icon": "📜",
|
|
"desc": "Ein vergilbtes Pergament mit Snapes Vierzeilern zur Flammenkammer.",
|
|
"action": "read_scroll",
|
|
"action_label": "Rätsel-Verse lesen 📖"
|
|
},
|
|
"philosophers_stone": {
|
|
"id": "philosophers_stone",
|
|
"name": "Stein der Weisen",
|
|
"icon": "💎",
|
|
"desc": "Der legendäre blutrote Alchemistenstein. Er schenkt unsterbliches Leben und pure Weisheit!",
|
|
"action": "feel_power",
|
|
"action_label": "Macht spüren ⚡"
|
|
}
|
|
}
|
|
|
|
HOUSES = {
|
|
"gryffindor": {"id": "gryffindor", "name": "Gryffindor", "icon": "🦁", "color": "#740001", "accent": "#d3a625"},
|
|
"slytherin": {"id": "slytherin", "name": "Slytherin", "icon": "🐍", "color": "#1a472a", "accent": "#aaaaaa"},
|
|
"ravenclaw": {"id": "ravenclaw", "name": "Ravenclaw", "icon": "🦅", "color": "#0e1a40", "accent": "#946b2d"},
|
|
"hufflepuff": {"id": "hufflepuff", "name": "Hufflepuff", "icon": "🦡", "color": "#ecb939", "accent": "#372e29"}
|
|
}
|
|
|
|
CRYPTEX_SOLUTION = ["🦁", "🌙", "🔥", "🪄"]
|
|
|
|
def get_session_state(request: Request) -> dict:
|
|
if "escape" not in request.session:
|
|
request.session["escape"] = {
|
|
"session_id": str(uuid.uuid4()),
|
|
"house": "gryffindor",
|
|
"inventory": ["wand", "broom", "snape_scroll"],
|
|
"broom_solved": False,
|
|
"broom_score": 0,
|
|
"broom_time": 0,
|
|
"duel_solved": False,
|
|
"brew_solved": False,
|
|
"cryptex_solved": False,
|
|
"pixies_solved": False,
|
|
"bottles_solved": False,
|
|
"sliding_solved": False,
|
|
"started_at": datetime.datetime.utcnow().isoformat(),
|
|
"completed_at": None,
|
|
"lang": "de"
|
|
}
|
|
else:
|
|
state = request.session["escape"]
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if state.get("broom_solved") and "winged_key" not in state["inventory"]:
|
|
state["inventory"].append("winged_key")
|
|
if state.get("duel_solved") and "duel_badge" not in state["inventory"]:
|
|
state["inventory"].append("duel_badge")
|
|
if state.get("brew_solved") and "felix_felicis" not in state["inventory"]:
|
|
state["inventory"].append("felix_felicis")
|
|
if state.get("cryptex_solved") and "cryptex_key" not in state["inventory"]:
|
|
state["inventory"].append("cryptex_key")
|
|
if state.get("pixies_solved") and "pixie_jar" not in state["inventory"]:
|
|
state["inventory"].append("pixie_jar")
|
|
if state.get("bottles_solved") and "philosophers_stone" not in state["inventory"]:
|
|
state["inventory"].append("philosophers_stone")
|
|
if "house" not in state:
|
|
state["house"] = "gryffindor"
|
|
if "duel_solved" not in state:
|
|
state["duel_solved"] = False
|
|
if "brew_solved" not in state:
|
|
state["brew_solved"] = False
|
|
if "cryptex_solved" not in state:
|
|
state["cryptex_solved"] = False
|
|
if "pixies_solved" not in state:
|
|
state["pixies_solved"] = False
|
|
request.session["escape"] = state
|
|
return request.session["escape"]
|
|
|
|
CHAMBER_REQUIREMENTS = {
|
|
"duel": {
|
|
"item_id": "winged_key",
|
|
"item_name": "Geflügelter Schlüssel",
|
|
"item_icon": "🗝️",
|
|
"from_chamber": "Kammer I: Der Ring-Flug",
|
|
"from_url": "/broom",
|
|
"reason": "Die schwere Eichentür zur Duellier-Kammer ist mit einem verzauberten Flügelschloss versiegelt. Fange zuerst den geflügelten Schlüssel im Besenflug!"
|
|
},
|
|
"brew": {
|
|
"item_id": "duel_badge",
|
|
"item_name": "Duellanten-Abzeichen & Kessel-Rezept",
|
|
"item_icon": "⚡",
|
|
"from_chamber": "Kammer II: Das Spruch-Duell",
|
|
"from_url": "/duel",
|
|
"reason": "Snapes Kupferkessel bleibt kalt. Du benötigst die geheime Rezeptur aus der bezwungenen Trainingsrüstung des Duellier-Clubs!"
|
|
},
|
|
"cryptex": {
|
|
"item_id": "felix_felicis",
|
|
"item_name": "Felix Felicis",
|
|
"item_icon": "🧪",
|
|
"from_chamber": "Kammer III: Die Brauküche",
|
|
"from_url": "/brew",
|
|
"reason": "Ein uralter Verwirrungsbann verschleiert die Runen des Gründerschlosses. Braue 'Felix Felicis', um die Symbole der vier Gründer zu durchschauen!"
|
|
},
|
|
"pixies": {
|
|
"item_id": "cryptex_key",
|
|
"item_name": "Schlüssel der Gründer",
|
|
"item_icon": "🗝️",
|
|
"from_chamber": "Kammer IV: Das Runen-Drehschloss",
|
|
"from_url": "/cryptex",
|
|
"reason": "Das Verteidigungs-Klassenzimmer ist mit dem uralten Siegel der vier Gründer verriegelt. Knacke das Drehschloss in Kammer IV!"
|
|
},
|
|
"bottles": {
|
|
"item_id": "cryptex_key",
|
|
"item_name": "Schlüssel der Gründer",
|
|
"item_icon": "🗝️",
|
|
"from_chamber": "Kammer V: Wichtel-Panik",
|
|
"from_url": "/pixies",
|
|
"reason": "Tödliche Flammen und schwere Ketten versperren den Eingang. Schließe zuerst die Wichtel-Prüfung ab und banne die Plagegeister!"
|
|
},
|
|
"finale": {
|
|
"item_id": "philosophers_stone",
|
|
"item_name": "Stein der Weisen",
|
|
"item_icon": "💎",
|
|
"from_chamber": "Kammer VI: Snapes Flaschen",
|
|
"from_url": "/bottles",
|
|
"reason": "Der Spiegel Nerhegeb offenbart sich nur jenem Zauberer, der Snapes schwarzes Feuer erfolgreich durchquert hat!"
|
|
}
|
|
}
|
|
|
|
def get_template_context(request: Request, state: dict, lang: str, chamber_name: str = None) -> dict:
|
|
inv_ids = state.get("inventory", ["wand", "broom", "snape_scroll"])
|
|
inv_items = [ALL_ITEMS[i] for i in inv_ids if i in ALL_ITEMS]
|
|
house_info = HOUSES.get(state.get("house", "gryffindor"), HOUSES["gryffindor"])
|
|
|
|
is_locked = False
|
|
lock_info = None
|
|
has_alohomora = request.query_params.get("alohomora") == "1" or state.get("alohomora_mode")
|
|
if request.query_params.get("alohomora") == "1":
|
|
state["alohomora_mode"] = True
|
|
request.session["escape"] = state
|
|
|
|
req = CHAMBER_REQUIREMENTS.get(chamber_name)
|
|
if req and not has_alohomora:
|
|
has_item = req["item_id"] in inv_ids
|
|
solved_bypass = {
|
|
"duel": state.get("broom_solved"),
|
|
"brew": state.get("duel_solved"),
|
|
"cryptex": state.get("brew_solved"),
|
|
"pixies": state.get("cryptex_solved"),
|
|
"bottles": state.get("pixies_solved"),
|
|
"finale": state.get("bottles_solved")
|
|
}
|
|
if not has_item and not solved_bypass.get(chamber_name, False):
|
|
is_locked = True
|
|
lock_info = req
|
|
|
|
return {
|
|
"state": state,
|
|
"lang": lang,
|
|
"inventory_items": inv_items,
|
|
"all_items": ALL_ITEMS,
|
|
"house": house_info,
|
|
"houses": HOUSES,
|
|
"is_locked": is_locked,
|
|
"lock_info": lock_info,
|
|
"chamber_requirements": CHAMBER_REQUIREMENTS
|
|
}
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def page_index(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="index.html",
|
|
context=get_template_context(request, state, lang)
|
|
)
|
|
|
|
@app.get("/broom", response_class=HTMLResponse)
|
|
async def page_broom(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="broom.html",
|
|
context=get_template_context(request, state, lang, chamber_name="broom")
|
|
)
|
|
|
|
@app.get("/duel", response_class=HTMLResponse)
|
|
async def page_duel(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="duel.html",
|
|
context=get_template_context(request, state, lang, chamber_name="duel")
|
|
)
|
|
|
|
@app.get("/brew", response_class=HTMLResponse)
|
|
async def page_brew(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="brew.html",
|
|
context=get_template_context(request, state, lang, chamber_name="brew")
|
|
)
|
|
|
|
@app.get("/cryptex", response_class=HTMLResponse)
|
|
async def page_cryptex(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="cryptex.html",
|
|
context=get_template_context(request, state, lang, chamber_name="cryptex")
|
|
)
|
|
|
|
@app.get("/pixies", response_class=HTMLResponse)
|
|
async def page_pixies(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="pixies.html",
|
|
context=get_template_context(request, state, lang, chamber_name="pixies")
|
|
)
|
|
|
|
@app.get("/chess")
|
|
async def redirect_chess():
|
|
# Graceful redirect for old links
|
|
return RedirectResponse(url="/duel", status_code=302)
|
|
|
|
@app.get("/bottles", response_class=HTMLResponse)
|
|
async def page_bottles(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="bottles.html",
|
|
context=get_template_context(request, state, lang, chamber_name="bottles")
|
|
)
|
|
|
|
@app.get("/sliding", response_class=HTMLResponse)
|
|
async def page_sliding(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="sliding.html",
|
|
context=get_template_context(request, state, lang, chamber_name="sliding")
|
|
)
|
|
|
|
@app.get("/finale", response_class=HTMLResponse)
|
|
async def page_finale(request: Request):
|
|
state = get_session_state(request)
|
|
lang = request.query_params.get("lang", state.get("lang", "de"))
|
|
state["lang"] = lang
|
|
request.session["escape"] = state
|
|
return templates.TemplateResponse(
|
|
request=request,
|
|
name="finale.html",
|
|
context=get_template_context(request, state, lang, chamber_name="finale")
|
|
)
|
|
|
|
@app.get("/api/state")
|
|
async def api_get_state(request: Request):
|
|
state = get_session_state(request)
|
|
return JSONResponse(state)
|
|
|
|
@app.get("/api/inventory")
|
|
async def api_get_inventory(request: Request):
|
|
state = get_session_state(request)
|
|
inv_ids = state.get("inventory", ["wand", "broom", "snape_scroll"])
|
|
inv_items = [ALL_ITEMS[i] for i in inv_ids if i in ALL_ITEMS]
|
|
house_info = HOUSES.get(state.get("house", "gryffindor"), HOUSES["gryffindor"])
|
|
return JSONResponse({
|
|
"inventory": inv_items,
|
|
"house": house_info,
|
|
"score": state.get("broom_score", 0),
|
|
"broom_solved": state.get("broom_solved", False),
|
|
"duel_solved": state.get("duel_solved", False),
|
|
"brew_solved": state.get("brew_solved", False),
|
|
"cryptex_solved": state.get("cryptex_solved", False),
|
|
"pixies_solved": state.get("pixies_solved", False),
|
|
"bottles_solved": state.get("bottles_solved", False),
|
|
"sliding_solved": state.get("sliding_solved", False)
|
|
})
|
|
|
|
@app.post("/api/player/house")
|
|
async def api_set_house(request: Request):
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
data = {}
|
|
house_id = data.get("house", "gryffindor").lower()
|
|
if house_id not in HOUSES:
|
|
raise HTTPException(status_code=400, detail="Ungültiges Hogwarts-Haus.")
|
|
state = get_session_state(request)
|
|
state["house"] = house_id
|
|
request.session["escape"] = state
|
|
return JSONResponse({"success": True, "house": HOUSES[house_id]})
|
|
|
|
@app.post("/api/reset")
|
|
async def api_reset_state(request: Request):
|
|
request.session["escape"] = {
|
|
"session_id": str(uuid.uuid4()),
|
|
"house": "gryffindor",
|
|
"inventory": ["wand", "broom", "snape_scroll"],
|
|
"broom_solved": False,
|
|
"broom_score": 0,
|
|
"broom_time": 0,
|
|
"duel_solved": False,
|
|
"brew_solved": False,
|
|
"cryptex_solved": False,
|
|
"pixies_solved": False,
|
|
"bottles_solved": False,
|
|
"sliding_solved": False,
|
|
"started_at": datetime.datetime.utcnow().isoformat(),
|
|
"completed_at": None,
|
|
"lang": "de"
|
|
}
|
|
return JSONResponse({"success": True, "message": "Escape progress reset."})
|
|
|
|
@app.post("/api/broom/complete")
|
|
async def api_broom_complete(request: Request):
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
data = {}
|
|
state = get_session_state(request)
|
|
state["broom_solved"] = True
|
|
state["broom_score"] = int(data.get("score", 100))
|
|
state["broom_time"] = int(data.get("time_taken", 30))
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "winged_key" not in state["inventory"]:
|
|
state["inventory"].append("winged_key")
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Parcours gemeistert! Der geflügelte Schlüssel öffnet die Tür zum Zauberspruch-Duell!",
|
|
"new_item": ALL_ITEMS["winged_key"],
|
|
"state": state
|
|
})
|
|
|
|
@app.post("/api/duel/complete")
|
|
async def api_duel_complete(request: Request):
|
|
state = get_session_state(request)
|
|
state["duel_solved"] = True
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "duel_badge" not in state["inventory"]:
|
|
state["inventory"].append("duel_badge")
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Zauberspruch-Duell gewonnen! Die Pforte zur Brauküche steht weit offen!",
|
|
"new_item": ALL_ITEMS["duel_badge"],
|
|
"state": state
|
|
})
|
|
|
|
@app.post("/api/brew/complete")
|
|
async def api_brew_complete(request: Request):
|
|
state = get_session_state(request)
|
|
state["brew_solved"] = True
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "felix_felicis" not in state["inventory"]:
|
|
state["inventory"].append("felix_felicis")
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Goldener Glanz erfüllt den Kessel! Felix Felicis ist meisterhaft gebraut.",
|
|
"new_item": ALL_ITEMS["felix_felicis"],
|
|
"state": state
|
|
})
|
|
|
|
@app.post("/api/cryptex/unlock")
|
|
async def api_cryptex_unlock(request: Request):
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
data = {}
|
|
combo = data.get("combination", [])
|
|
|
|
# Check match against CRYPTEX_SOLUTION ["🦁", "🌙", "🔥", "🪄"]
|
|
is_correct = False
|
|
if combo == CRYPTEX_SOLUTION:
|
|
is_correct = True
|
|
elif combo == [0, 1, 0, 1]:
|
|
is_correct = True
|
|
elif combo == ["lion", "moon", "fire", "wand"]:
|
|
is_correct = True
|
|
|
|
if not is_correct:
|
|
return JSONResponse({
|
|
"success": False,
|
|
"message": "Die Runenzylinder rasten nicht ein. Die Gravur bleibt verschlossen!"
|
|
})
|
|
|
|
state = get_session_state(request)
|
|
state["cryptex_solved"] = True
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "cryptex_key" not in state["inventory"]:
|
|
state["inventory"].append("cryptex_key")
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Das Runenschloss springt mit einem goldenen Glühen auf! Der Schlüssel der Gründer gehört dir.",
|
|
"new_item": ALL_ITEMS["cryptex_key"],
|
|
"state": state
|
|
})
|
|
|
|
@app.post("/api/pixies/complete")
|
|
async def api_pixies_complete(request: Request):
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
data = {}
|
|
caught = int(data.get("caught", 0))
|
|
if caught < 10:
|
|
return JSONResponse({
|
|
"success": False,
|
|
"message": f"Du hast erst {caught}/10 Wichtel gebannt. Halte durch!"
|
|
})
|
|
state = get_session_state(request)
|
|
state["pixies_solved"] = True
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "pixie_jar" not in state["inventory"]:
|
|
state["inventory"].append("pixie_jar")
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Alle frechen Wichtel mit Immobilus gebannt! Das Wichtelglas wandert in deinen Beutel.",
|
|
"new_item": ALL_ITEMS["pixie_jar"],
|
|
"state": state
|
|
})
|
|
|
|
@app.post("/api/bottles/drink")
|
|
async def api_bottles_drink(request: Request):
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
data = {}
|
|
bottle_index = int(data.get("bottle", 0))
|
|
target_gate = data.get("gate", "ahead")
|
|
lang = data.get("lang", "de")
|
|
|
|
if bottle_index not in CLASSIC_SOLUTION:
|
|
raise HTTPException(status_code=400, detail="Ungültige Flaschennummer.")
|
|
|
|
content = CLASSIC_SOLUTION[bottle_index]
|
|
state = get_session_state(request)
|
|
|
|
if content == "ahead":
|
|
if target_gate == "ahead":
|
|
state["bottles_solved"] = True
|
|
if state.get("broom_solved") or state.get("duel_solved"):
|
|
state["completed_at"] = datetime.datetime.utcnow().isoformat()
|
|
if "inventory" not in state:
|
|
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
|
if "philosophers_stone" not in state["inventory"]:
|
|
state["inventory"].append("philosophers_stone")
|
|
request.session["escape"] = state
|
|
msg_de = "Ein eisiger Schauer durchströmt dich! Das schwarze Feuer weicht vor dir zurück. Du trittst siegreich in die Schatzkammer des Steins der Weisen!"
|
|
msg_en = "An icy chill flows through you! The black flames retreat. You step victoriously into the chamber of the Philosopher's Stone!"
|
|
return JSONResponse({
|
|
"success": True,
|
|
"result": "success_ahead",
|
|
"content": content,
|
|
"message": msg_de if lang == "de" else msg_en,
|
|
"new_item": ALL_ITEMS["philosophers_stone"],
|
|
"next_url": "/finale"
|
|
})
|
|
else:
|
|
msg_de = "Dieser Trank schützt dich nur vor dem schwarzen Feuer, nicht vor dem violetten!"
|
|
msg_en = "This potion only protects against black fire, not purple fire!"
|
|
return JSONResponse({"success": False, "result": "wrong_gate", "content": content, "message": msg_de if lang == "de" else msg_en})
|
|
|
|
elif content == "back":
|
|
if target_gate == "back":
|
|
msg_de = "Ein wohliger Kälteschleier hüllt dich ein. Du kannst sicher durch die violetten Flammen zurückweichen."
|
|
msg_en = "A protective cold mist envelops you. You can safely retreat through the purple flames."
|
|
return JSONResponse({
|
|
"success": True,
|
|
"result": "success_back",
|
|
"content": content,
|
|
"message": msg_de if lang == "de" else msg_en,
|
|
"next_url": "/duel"
|
|
})
|
|
else:
|
|
msg_de = "Dieser Trank schützt vor den violetten Flammen, führt dich aber nicht vorwärts durch das schwarze Feuer!"
|
|
msg_en = "This potion protects against purple flames, but does not allow forward passage through black fire!"
|
|
return JSONResponse({"success": False, "result": "wrong_gate", "content": content, "message": msg_de if lang == "de" else msg_en})
|
|
|
|
elif content == "poison":
|
|
msg_de = "💀 Gift! Dein Hals brennt wie Feuer. Ein Zauber bricht die Wirkung gerade noch rechtzeitig ab, doch du musst es erneut versuchen!"
|
|
msg_en = "💀 Poison! Your throat burns. A counter-curse breaks the effect just in time, but you must try again!"
|
|
return JSONResponse({"success": False, "result": "poison", "content": content, "message": msg_de if lang == "de" else msg_en})
|
|
|
|
elif content == "wine":
|
|
msg_de = "🍷 Milder Brennnesselwein! Köstlich erfrischend, aber ohne jegliche magische Schutzwirkung gegen die verzehrenden Flammen."
|
|
msg_en = "🍷 Mild nettle wine! Deliciously refreshing, but offering zero magical protection against the consuming flames."
|
|
return JSONResponse({"success": False, "result": "wine", "content": content, "message": msg_de if lang == "de" else msg_en})
|
|
|
|
@app.post("/api/sliding/complete")
|
|
async def api_sliding_complete(request: Request):
|
|
state = get_session_state(request)
|
|
state["sliding_solved"] = True
|
|
request.session["escape"] = state
|
|
return JSONResponse({
|
|
"success": True,
|
|
"message": "Das Alchemie-Schiebesiegel ist gelöst! Die geheime Essenz wurde freigegeben."
|
|
})
|