Files
dcc-rpg/src/engine/dm.py
T
truetype cb45172717 refactor: remediation plan Fase 0-4 completata
- Fase 0: env var allineate, SYSTEM_PROMPT deduplicato, .gitignore pulito
- Fase 1: GameContext isolato, ciclo DM unificato, provider thread-safe (contextvars)
- Fase 2: DB schema esteso (current_area_id, combat_state, character_items)
- Fase 3: thread-local connection pool + WAL + busy_timeout
- Fase 4: test suite (32 test, 100% pass)
- heuristic_fallback come parser di riserva (non secondo pilota)
2026-06-27 18:00:03 +02:00

112 lines
3.4 KiB
Python

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)