Spaces:
Running
Running
| """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", | |
| ] | |
| class GameState: | |
| stage_idx: int = 0 | |
| q_idx: int = 0 | |
| attempt: int = 0 | |
| status: str = "playing" # "playing" | "won" | "lost" | |
| 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" | |