Add Player Menu with Inventory drawer, house selection, and mobile-first header
This commit is contained in:
+125
-20
@@ -21,13 +21,6 @@ app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), na
|
||||
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
||||
|
||||
# Standard Classic Riddle Solution (Book Accurate):
|
||||
# 1: Poison
|
||||
# 2: Wine
|
||||
# 3: Ahead (Smallest / Dwarf - passes through black fire)
|
||||
# 4: Poison
|
||||
# 5: Poison
|
||||
# 6: Wine
|
||||
# 7: Back (Largest / Giant - passes through purple fire)
|
||||
CLASSIC_SOLUTION = {
|
||||
1: "poison",
|
||||
2: "wine",
|
||||
@@ -38,10 +31,62 @@ CLASSIC_SOLUTION = {
|
||||
7: "back"
|
||||
}
|
||||
|
||||
ALL_ITEMS = {
|
||||
"wand": {
|
||||
"id": "wand",
|
||||
"name": "Zauberstab",
|
||||
"icon": "🪄",
|
||||
"desc": "Stechpalme und Phönixfeder, 11 Zoll. Liegt warm in deiner Hand.",
|
||||
"action": "lumos",
|
||||
"action_label": "Lumos / Licht 💡"
|
||||
},
|
||||
"broom": {
|
||||
"id": "broom",
|
||||
"name": "Nimbus 2000",
|
||||
"icon": "🧹",
|
||||
"desc": "Ein pfeilschneller Rennbesen. Perfekt balanciert für Wendemanöver im Schlüsselraum.",
|
||||
"action": "broom_polish",
|
||||
"action_label": "Besen polieren ✨"
|
||||
},
|
||||
"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 📖"
|
||||
},
|
||||
"silver_key": {
|
||||
"id": "silver_key",
|
||||
"name": "Geknickter Silberschlüssel",
|
||||
"icon": "🗝️",
|
||||
"desc": "Der schwere antike Schlüssel aus der Schlüsselkammer. Sein geknickter Flügel verrät ihn.",
|
||||
"action": "examine_key",
|
||||
"action_label": "Schloss prüfen 🔍"
|
||||
},
|
||||
"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"}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -51,8 +96,33 @@ def get_session_state(request: Request) -> dict:
|
||||
"completed_at": None,
|
||||
"lang": "de"
|
||||
}
|
||||
else:
|
||||
# Ensure new fields exist for existing sessions
|
||||
state = request.session["escape"]
|
||||
if "inventory" not in state:
|
||||
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
||||
if state.get("broom_solved"):
|
||||
state["inventory"].append("silver_key")
|
||||
if state.get("bottles_solved"):
|
||||
state["inventory"].append("philosophers_stone")
|
||||
if "house" not in state:
|
||||
state["house"] = "gryffindor"
|
||||
request.session["escape"] = state
|
||||
return request.session["escape"]
|
||||
|
||||
def get_template_context(request: Request, state: dict, lang: str) -> 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"])
|
||||
return {
|
||||
"state": state,
|
||||
"lang": lang,
|
||||
"inventory_items": inv_items,
|
||||
"all_items": ALL_ITEMS,
|
||||
"house": house_info,
|
||||
"houses": HOUSES
|
||||
}
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def page_index(request: Request):
|
||||
state = get_session_state(request)
|
||||
@@ -62,7 +132,7 @@ async def page_index(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="index.html",
|
||||
context={"state": state, "lang": lang}
|
||||
context=get_template_context(request, state, lang)
|
||||
)
|
||||
|
||||
@app.get("/broom", response_class=HTMLResponse)
|
||||
@@ -74,7 +144,7 @@ async def page_broom(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="broom.html",
|
||||
context={"state": state, "lang": lang}
|
||||
context=get_template_context(request, state, lang)
|
||||
)
|
||||
|
||||
@app.get("/bottles", response_class=HTMLResponse)
|
||||
@@ -86,7 +156,7 @@ async def page_bottles(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="bottles.html",
|
||||
context={"state": state, "lang": lang}
|
||||
context=get_template_context(request, state, lang)
|
||||
)
|
||||
|
||||
@app.get("/sliding", response_class=HTMLResponse)
|
||||
@@ -98,7 +168,7 @@ async def page_sliding(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="sliding.html",
|
||||
context={"state": state, "lang": lang}
|
||||
context=get_template_context(request, state, lang)
|
||||
)
|
||||
|
||||
@app.get("/finale", response_class=HTMLResponse)
|
||||
@@ -110,7 +180,7 @@ async def page_finale(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="finale.html",
|
||||
context={"state": state, "lang": lang}
|
||||
context=get_template_context(request, state, lang)
|
||||
)
|
||||
|
||||
@app.get("/api/state")
|
||||
@@ -118,10 +188,41 @@ 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),
|
||||
"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,
|
||||
@@ -143,28 +244,27 @@ async def api_broom_complete(request: Request):
|
||||
state["broom_solved"] = True
|
||||
state["broom_score"] = int(data.get("score", 100))
|
||||
state["broom_time"] = int(data.get("time_taken", 30))
|
||||
# Add Silver Key to Inventory!
|
||||
if "inventory" not in state:
|
||||
state["inventory"] = ["wand", "broom", "snape_scroll"]
|
||||
if "silver_key" not in state["inventory"]:
|
||||
state["inventory"].append("silver_key")
|
||||
request.session["escape"] = state
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"message": "Der Schlüssel passt ins Schloss! Die Tür zur Flammenkammer steht offen!",
|
||||
"new_item": ALL_ITEMS["silver_key"],
|
||||
"state": state
|
||||
})
|
||||
|
||||
@app.post("/api/bottles/drink")
|
||||
async def api_bottles_drink(request: Request):
|
||||
"""
|
||||
Check the bottle the player decided to drink to pass through the flames.
|
||||
Bottle 3 is Ahead (Black fire -> Success).
|
||||
Bottle 7 is Back (Purple fire -> Escaped back).
|
||||
Bottles 1, 4, 5 are Poison (Fizzle/Defeat).
|
||||
Bottles 2, 6 are Nettle Wine (Harmless wine).
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
data = {}
|
||||
bottle_index = int(data.get("bottle", 0))
|
||||
target_gate = data.get("gate", "ahead") # 'ahead' (black fire) or 'back' (purple fire)
|
||||
target_gate = data.get("gate", "ahead")
|
||||
lang = data.get("lang", "de")
|
||||
|
||||
if bottle_index not in CLASSIC_SOLUTION:
|
||||
@@ -178,6 +278,10 @@ async def api_bottles_drink(request: Request):
|
||||
state["bottles_solved"] = True
|
||||
if state.get("broom_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!"
|
||||
@@ -186,6 +290,7 @@ async def api_bottles_drink(request: Request):
|
||||
"result": "success_ahead",
|
||||
"content": content,
|
||||
"message": msg_de if lang == "de" else msg_en,
|
||||
"new_item": ALL_ITEMS["philosophers_stone"],
|
||||
"next_url": "/finale"
|
||||
})
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user