235 lines
8.4 KiB
Python
235 lines
8.4 KiB
Python
"""
|
|
Game service: orchestrates dice rolls, scene progression, and result formatting.
|
|
"""
|
|
|
|
from app.game.dice import resolve_test, _result_text
|
|
from app.game.state import create_game
|
|
from app.data.pixel_assets import get_scene_icon
|
|
|
|
|
|
def start_game(pony_idx, tema_idx, custom_navn=None):
|
|
"""Start a new game. Returns game state dict."""
|
|
return create_game(pony_idx, tema_idx, custom_navn)
|
|
|
|
|
|
def roll_scene(game):
|
|
"""Process a dice roll for the current scene.
|
|
|
|
Args:
|
|
game: current game state dict
|
|
|
|
Returns:
|
|
Updated game state dict
|
|
"""
|
|
if game.get("færdig") or "tema" not in game:
|
|
return game
|
|
|
|
tema = game["tema"]
|
|
scenes = tema.get("scener", [])
|
|
if game["scene"] >= len(scenes):
|
|
game["færdig"] = True
|
|
return game
|
|
|
|
scn = scenes[game["scene"]]
|
|
stat = scn["stat"]
|
|
svaer = scn["svaer"]
|
|
|
|
pony = game["pony"]
|
|
result = resolve_test(pony, stat, svaer)
|
|
|
|
udfald = {
|
|
"aktion": scn["aktion"],
|
|
"stat": stat,
|
|
"svaer": svaer,
|
|
"dice": result["dice"],
|
|
"succes": result["passed"],
|
|
"tekst": scn["succes"] if result["passed"] else scn["fiasko"],
|
|
}
|
|
|
|
game["historie"].append(udfald)
|
|
game["udfald_tekst"] = udfald["tekst"]
|
|
|
|
if result["passed"]:
|
|
game["succeser"] += 1
|
|
else:
|
|
game["fiaskoer"] += 1
|
|
|
|
game["scene"] += 1
|
|
|
|
if game["scene"] >= len(scenes):
|
|
game["færdig"] = True
|
|
|
|
return game
|
|
|
|
|
|
def resolve_scene_interaction(game, selection):
|
|
"""Validate a four-option interaction and progress only when permitted."""
|
|
if game.get("færdig") or "tema" not in game:
|
|
return game, {"accepted": False, "error": "game_finished"}
|
|
scenes = game["tema"].get("scener", [])
|
|
if game["scene"] >= len(scenes):
|
|
return game, {"accepted": False, "error": "scene_missing"}
|
|
scene = scenes[game["scene"]]
|
|
interaction = scene.get("interaction") or {"type": "dice"}
|
|
if interaction.get("type") == "dice":
|
|
return game, {"accepted": False, "error": "dice_required"}
|
|
option = next((item for item in interaction.get("options", []) if item.get("id") == selection), None)
|
|
if not option:
|
|
return game, {"accepted": False, "error": "invalid_selection"}
|
|
|
|
interaction_type = interaction["type"]
|
|
correct = interaction_type == "choice" or selection == interaction.get("target")
|
|
if not correct:
|
|
retries = game.setdefault("interaction_retries", {})
|
|
scene_key = str(game["scene"])
|
|
retries[scene_key] = retries.get(scene_key, 0) + 1
|
|
max_retries = interaction.get("max_retries", 2)
|
|
if retries[scene_key] >= max_retries:
|
|
# After too many wrong tries, reveal the answer and advance with a small penalty
|
|
target = interaction.get("target")
|
|
target_opt = next((o for o in interaction.get("options", []) if o.get("id") == target), None)
|
|
label = target_opt.get("label") if target_opt else (target or "")
|
|
story = f"Du fik det til sidst! {label} var svaret. {scene['succes']}"
|
|
outcome = {
|
|
"aktion": scene["aktion"], "stat": scene["stat"], "svaer": scene["svaer"],
|
|
"dice": [], "succes": True, "tekst": story,
|
|
"interaction_type": interaction_type, "selection": label,
|
|
}
|
|
game["historie"].append(outcome)
|
|
game["udfald_tekst"] = story
|
|
game["scene"] += 1
|
|
if game["scene"] >= len(scenes):
|
|
game["færdig"] = True
|
|
retries.pop(str(game["scene"]), None)
|
|
return game, {
|
|
"accepted": True, "correct": True, "progressed": True,
|
|
"feedback": f"Det var {label}! Du fik det til sidst! ✨",
|
|
"selection": label,
|
|
}
|
|
return game, {
|
|
"accepted": True, "correct": False, "progressed": False,
|
|
"feedback": f"Næsten! Prøv igen. {interaction['prompt']}",
|
|
"selection": option.get("label", selection),
|
|
}
|
|
|
|
if interaction_type == "choice":
|
|
story = f"{option.get('response', '')} {scene['succes']}".strip()
|
|
else:
|
|
story = f"Ja! Du fandt {option.get('label', selection)}. {scene['succes']}"
|
|
outcome = {
|
|
"aktion": scene["aktion"], "stat": scene["stat"], "svaer": scene["svaer"],
|
|
"dice": [], "succeser": 1, "krav": 1, "succes": True, "tekst": story,
|
|
"interaction_type": interaction_type, "selection": option.get("label", selection),
|
|
}
|
|
game["historie"].append(outcome)
|
|
game["udfald_tekst"] = story
|
|
game["succeser"] += 1
|
|
game["scene"] += 1
|
|
if game["scene"] >= len(scenes):
|
|
game["færdig"] = True
|
|
return game, {
|
|
"accepted": True, "correct": True, "progressed": True,
|
|
"feedback": f"Ja! Du valgte {option.get('label', selection)}.",
|
|
"selection": option.get("label", selection),
|
|
}
|
|
|
|
|
|
def format_scene_data(game):
|
|
"""Format game state for JSON API response.
|
|
|
|
Returns a dict suitable for jsonify().
|
|
"""
|
|
pony = game["pony"]
|
|
tema = game.get("tema", {})
|
|
scenes = tema.get("scener", []) if tema else []
|
|
|
|
pony_img_map = {
|
|
"Jordpony": "jordpony.png",
|
|
"Pegasus": "pegasus.png",
|
|
"Enhjørning": "enhjorning.png",
|
|
"Alicorn": "alicorn.png",
|
|
}
|
|
|
|
diff_map = {
|
|
"let": "Nem \U0001f31f",
|
|
"normal": "Lidt sv\u00e6rt \U0001f31f\U0001f31f",
|
|
"svaert": "Sv\u00e6rt \U0001f31f\U0001f31f\U0001f31f",
|
|
}
|
|
|
|
result = {
|
|
"gameId": game.get("game_id"),
|
|
"finished": game.get("færdig", False),
|
|
"ponyName": pony.get("navn", ""),
|
|
"ponyType": pony.get("type", ""),
|
|
"ponyEmoji": pony.get("emoji", ""),
|
|
"ponyImg": f"/static/images/{pony_img_map.get(pony.get('type', ''), 'jordpony.png')}",
|
|
"ponyStats": {
|
|
"krop": pony.get("krop", 2),
|
|
"sind": pony.get("sind", 2),
|
|
"charme": pony.get("charme", 2),
|
|
},
|
|
"talent": pony.get("talent", ""),
|
|
"tema": tema.get("titel", ""),
|
|
"themeIcon": get_scene_icon(tema.get("id", "")),
|
|
"sceneNum": (
|
|
"Eventyret er slut"
|
|
if game.get("færdig")
|
|
else f"Scene {game['scene'] + 1} af {len(scenes)}"
|
|
),
|
|
"progress": f"\U0001f31f {'\u2b50' * game['succeser']} {'\u2606' * (len(scenes) - game['succeser'] - game['fiaskoer'])} \u274c {'\U0001f494' * game['fiaskoer']}",
|
|
"history": [],
|
|
}
|
|
|
|
# Current scene data
|
|
if not game.get("færdig") and game["scene"] < len(scenes):
|
|
scn = scenes[game["scene"]]
|
|
result["sceneText"] = scn.get("tekst", "")
|
|
result["actionText"] = scn.get("aktion", "")
|
|
result["difficulty"] = diff_map.get(scn.get("svaer", ""), "")
|
|
result["voice"] = scn.get("voice")
|
|
# Pass through any clue number from this scene (for memory reminders)
|
|
if scn.get("clueNumber") is not None:
|
|
result["clueNumber"] = scn["clueNumber"]
|
|
interaction = scn.get("interaction", {"type": "dice"})
|
|
result["interaction"] = {
|
|
"type": interaction.get("type", "dice"),
|
|
"prompt": interaction.get("prompt", ""),
|
|
"options": [
|
|
{key: option[key] for key in ("id", "label", "emoji", "color") if key in option}
|
|
for option in interaction.get("options", [])
|
|
],
|
|
}
|
|
else:
|
|
result["sceneText"] = ""
|
|
result["actionText"] = ""
|
|
result["difficulty"] = ""
|
|
|
|
# History
|
|
for h in game.get("historie", []):
|
|
result["history"].append({
|
|
"action": h.get("aktion", ""),
|
|
"dice": h.get("dice", []),
|
|
"success": h.get("succes", False),
|
|
"result": _result_text(h),
|
|
"story": h.get("tekst", ""),
|
|
"interactionType": h.get("interaction_type", "dice"),
|
|
"selection": h.get("selection"),
|
|
})
|
|
|
|
# End-of-game summary
|
|
if game.get("færdig"):
|
|
total = len(scenes)
|
|
wins = game["succeser"]
|
|
result["score"] = f"{wins} succeser ud af {total}"
|
|
if wins >= total:
|
|
result["victory"] = True
|
|
result["endText"] = "Fantastisk! Du reddede Equestria! \U0001f31f"
|
|
elif wins >= total // 2:
|
|
result["mixed"] = True
|
|
result["endText"] = "Flot eventyr! Ponyerne hjalp hinanden hele vejen til slutningen. \U0001f308"
|
|
else:
|
|
result["defeat"] = True
|
|
result["endText"] = "I nåede sikkert gennem eventyret sammen. Det er ægte venskabsmagi! \U0001f308"
|
|
|
|
return result
|