"""Game state: progress data + the mutations on that data. Every method here touches ONLY its own fields (no quest content). The orchestrator decides *when* and *which* to call (using quest boundaries); GameState owns the actual state changes. """ from __future__ import annotations from dataclasses import dataclass RANK_LADDER = [ "Squire", "Knight-Errant", "Knight Bachelor", "Knight Banneret", "Knight Commander", "Knight Champion", "Grandmaster", ] @dataclass class GameState: stage_idx: int = 0 q_idx: int = 0 attempt: int = 0 status: str = "playing" # "playing" | "won" | "lost" @property def rank(self) -> str: """Derived from progress alone.""" if self.status == "won": return RANK_LADDER[-1] # Grandmaster return RANK_LADDER[self.stage_idx] # Squire, Knight-Errant, etc. # --- mutations (pure: own fields only) --- def advance(self) -> None: """Move to the next question in the current stage.""" self.q_idx += 1 self.attempt = 0 def complete_stage(self) -> None: """Move to the next stage (rank up follows from stage_idx).""" self.stage_idx += 1 self.q_idx = 0 self.attempt = 0 def strike(self) -> None: self.attempt += 1 def lose(self) -> None: """Game over: too many strikes ends the run.""" self.status = "lost" def win(self) -> None: self.status = "won"