File size: 2,441 Bytes
414dc55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""Session - the stateful controller the UI drives.

Holds the case, the backend, the cached suspect briefs, and the latest immutable
GameState snapshot. Each action advances the snapshot; the snapshots themselves are
never mutated.
"""

from __future__ import annotations

from collections.abc import Iterator

from ..llm.backend import LLMBackend
from ..projections.player_view import PlayerCaseView, build_player_view
from ..projections.suspect_brief import SuspectBrief, build_suspect_brief
from ..schemas.accusation import Accusation, Verdict
from ..schemas.case import CaseFile
from ..schemas.clue import Clue
from .accusation import finalize_accusation
from .evidence_board import available_evidence, search_location
from .game_state import GameState, new_game_state
from .interrogation_loop import InterrogationEvent, interrogate


class Session:
    def __init__(self, case: CaseFile, backend: LLMBackend) -> None:
        self.case = case
        self.backend = backend
        self._briefs: dict[str, SuspectBrief] = {
            s.sus_id: build_suspect_brief(case, s) for s in case.suspects
        }
        self.player_view: PlayerCaseView = build_player_view(case)
        self.state: GameState = new_game_state(
            case.case_id, tuple(s.sus_id for s in case.suspects)
        )

    def brief(self, sus_id: str) -> SuspectBrief:
        return self._briefs[sus_id]

    def interrogate(
        self,
        sus_id: str,
        question: str,
        presented_clue_id: str | None = None,
        seed: int | None = None,
    ) -> Iterator[InterrogationEvent]:
        for event in interrogate(
            self.backend, self.case, self._briefs[sus_id], self.state, sus_id, question,
            presented_clue_id, seed,
        ):
            if event.final is not None:
                self.state = event.final.state
            yield event

    def search(self, loc_id: str) -> tuple[Clue, ...]:
        self.state, found = search_location(self.state, self.case, loc_id)
        return found

    def add_note(self, text: str) -> None:
        from .state_update import add_player_note

        self.state = add_player_note(self.state, text)

    def evidence(self) -> tuple[Clue, ...]:
        return available_evidence(self.state, self.case)

    def accuse(self, accusation: Accusation) -> Verdict:
        self.state, verdict = finalize_accusation(self.state, self.case, accusation)
        return verdict