from __future__ import annotations import logging from src.engine import db, tools from src.engine.config import load_from_env from src.engine.game_context import GameContext from src.engine.heuristic_fallback import parse_action as fallback_parse from src.engine.llm import ask as llm_ask, parse_tool_call from src.engine.prompts import SYSTEM_PROMPT, build_prompt log = logging.getLogger(__name__) def _llm_decision(ctx: GameContext, player_action: str, current_area_id: str = "") -> tuple[str, dict, str]: prompt = build_prompt(ctx, player_action, current_area_id=current_area_id) try: raw = llm_ask(prompt, system=SYSTEM_PROMPT) except Exception as e: log.warning("LLM call failed (%s), fallback a heuristic parser", e) tool, params = fallback_parse(player_action, ctx.characters) return tool, params, "" decision = parse_tool_call(raw) tool = decision.get("tool", "") params = decision.get("params", {}) narration = decision.get("narration", "") if not tool: log.info("LLM non ha prodotto tool valido, fallback heuristico") tool, params = fallback_parse(player_action, ctx.characters) return tool, params, narration def dm_cycle(campaign_id: int, player_action: str) -> dict: ctx = GameContext.load(campaign_id) tool_name, tool_params, narration = _llm_decision(ctx, player_action, current_area_id=ctx.current_area_id) result = tools.run(tool_name, **tool_params) mechanic_msg = result.get("message", "") full_msg = f"{narration}\n{mechanic_msg}".strip() turn = db.advance_turn(campaign_id) db.log_turn(campaign_id, turn, player_action, full_msg) return { "turn": turn, "action": player_action, "tool": tool_name, "params": tool_params, "narration": narration, "mechanics": mechanic_msg, "full_output": full_msg, } def interactive(campaign_id: int): cfg = load_from_env() print("=" * 60) print("DCC RPG โ€” AI Dungeon Master") print(f"Provider: {cfg.kind} | Modello: {getattr(cfg, f'{cfg.kind}_model', '?')}") print("Scrivi 'esci' per uscire, 'stato' per vedere la scheda.") print("=" * 60) print() ctx = GameContext.load(campaign_id) print(ctx.summary()) print() while True: try: action = input("โš”๏ธ ยป ").strip() except (EOFError, KeyboardInterrupt): print("\nFine partita.") break if not action: continue if action.lower() in ("esci", "exit", "quit"): print("Alla prossima avventura!") break if action.lower() in ("stato", "stat", "status"): print(GameContext.load(campaign_id).summary()) continue print("๐ŸŽฒ Elaborazione...") out = dm_cycle(campaign_id, action) print(f"\n๐Ÿ“– {out['narration'] or '(azione riconosciuta)'}") if out['mechanics']: print(f"โš™๏ธ {out['mechanics']}") print() if __name__ == "__main__": import sys db.init_db() camp_id = db.create_campaign("Avventura Test", "Tomba del Mago-Guerriero") char_id = db.create_character(camp_id, "Marco il Fidato", "guerriero", "soldato") db.update_character( char_id, level=1, str=15, agi=11, sta=14, per=9, int=8, lck=10, hp_current=12, hp_max=12, ac=15, armor_type="light", ) interactive(camp_id)