HF-Knight / core /orchestrator.py
knightscode139's picture
Upload folder using huggingface_hub
75bb597 verified
Raw
History Blame Contribute Delete
2.58 kB
"""Orchestrator: the FSM that binds quests + state + validation.
The Orchestrator owns one (state, quests) pair and the *decisions* on it --
using quest boundaries it chooses when to advance / complete a stage / win /
lose, and it assembles the player-facing hint (judge hint, else the static
concept_brief, or the reveal on loss). GameState owns its own mutations.
"""
from __future__ import annotations
from config import MAX_ATTEMPT
from core.game_state import GameState
from core.quest_bank import QuestBank
from core.validation import Result, Validator
class Orchestrator:
def __init__(self, bank: QuestBank | None = None, state: GameState | None = None,
validator: Validator | None = None):
self.bank = bank if bank is not None else QuestBank()
self.state = state if state is not None else GameState()
self.validator = validator if validator is not None else Validator()
@property
def current_question(self) -> dict:
"""The full current question (shown + check + reveal)."""
return self.bank.stage(self.state.stage_idx)["questions"][self.state.q_idx]
def quest_view(self) -> dict:
"""Model-safe READ of the current quest: stage flavor + the SHOWN part only.
Drops `check` and `reveal` here, at the core, so the answer never travels
toward the model. Injected into the prompt instead of being a tool.
"""
stage = self.bank.stage(self.state.stage_idx)
return {"concept": stage["concept"], "fiction": stage["fiction"],
**self.current_question["shown"]}
def submit(self, answer: str) -> Result:
"""Validate an answer, drive the state, return (verdict, hint)."""
q = self.current_question
result = self.validator.validate(answer, q["check"])
if result.verdict == "correct":
n_questions = len(self.bank.stage(self.state.stage_idx)["questions"])
if self.state.q_idx + 1 < n_questions:
self.state.advance()
elif self.state.stage_idx + 1 < len(self.bank):
self.state.complete_stage()
else:
self.state.win()
return result # no hint needed on correct
# "wrong" or "partial" -> both count as an attempt toward losing
self.state.strike()
if self.state.attempt >= MAX_ATTEMPT:
self.state.lose()
return Result("lost", q["reveal"])
return Result(result.verdict, result.hint or q["shown"]["concept_brief"])