Feature: Integrate all four Hogwarts mini-games (Duel, Potion Brewing, Cryptex Lock, Cornish Pixies)

This commit is contained in:
Maxi
2026-09-22 19:43:12 +02:00
parent 0e20549a44
commit 2ce944d1cd
13 changed files with 1662 additions and 13 deletions
+165 -3
View File
@@ -56,6 +56,30 @@ ALL_ITEMS = {
"action": "spark_wand",
"action_label": "Funkenregen sprühen ⚡"
},
"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",
@@ -81,6 +105,8 @@ HOUSES = {
"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"] = {
@@ -91,6 +117,9 @@ def get_session_state(request: Request) -> dict:
"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(),
@@ -101,14 +130,26 @@ def get_session_state(request: Request) -> dict:
state = request.session["escape"]
if "inventory" not in state:
state["inventory"] = ["wand", "broom", "snape_scroll"]
if state.get("duel_solved"):
if state.get("duel_solved") and "duel_badge" not in state["inventory"]:
state["inventory"].append("duel_badge")
if state.get("bottles_solved"):
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"]
@@ -161,6 +202,42 @@ async def page_duel(request: Request):
context=get_template_context(request, state, lang)
)
@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)
)
@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)
)
@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)
)
@app.get("/chess")
async def redirect_chess():
# Graceful redirect for old links
@@ -219,6 +296,9 @@ async def api_get_inventory(request: Request):
"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)
})
@@ -247,6 +327,9 @@ async def api_reset_state(request: Request):
"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(),
@@ -283,11 +366,90 @@ async def api_duel_complete(request: Request):
request.session["escape"] = state
return JSONResponse({
"success": True,
"message": "Zauberspruch-Duell gewonnen! Die Pforte zur Flammenkammer steht weit offen!",
"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: