Spaces:
Running on Zero
Running on Zero
| from __future__ import annotations | |
| import json | |
| import pathlib | |
| from dataclasses import dataclass, field | |
| _SAVE_FILE = pathlib.Path(__file__).parent.parent / "saved_state.json" | |
| class GameState: | |
| timer_state: dict = field(default_factory=lambda: { | |
| "running": False, | |
| "mode": "pomodoro", | |
| "duration_minutes": 25, | |
| "phase": "work", | |
| }) | |
| todos: list[dict] = field(default_factory=list) | |
| xp: int = 0 | |
| coins: int = 0 | |
| _next_id: int = field(default=0, repr=False) | |
| XP_PER_TODO = 10 | |
| COINS_PER_TODO = 5 | |
| XP_PER_LEVEL = 100 | |
| def level(self) -> int: | |
| return self.xp // self.XP_PER_LEVEL + 1 | |
| def xp_in_level(self) -> int: | |
| return self.xp % self.XP_PER_LEVEL | |
| def add_xp(self, amount: int) -> int: | |
| self.xp += amount | |
| _save(self) | |
| return self.xp | |
| def add_coins(self, amount: int) -> int: | |
| self.coins += amount | |
| _save(self) | |
| return self.coins | |
| def add_todo(self, task: str) -> dict: | |
| self._next_id += 1 | |
| todo = {"id": self._next_id, "task": task, "done": False} | |
| self.todos.append(todo) | |
| _save(self) | |
| return todo | |
| def complete_todo(self, todo_id: int) -> bool: | |
| """Toggle a todo's done state. Awards XP on the False→True transition.""" | |
| for t in self.todos: | |
| if t["id"] == todo_id: | |
| was_done = t["done"] | |
| t["done"] = not t["done"] | |
| if t["done"] and not was_done: | |
| self.xp += self.XP_PER_TODO | |
| self.coins += self.COINS_PER_TODO | |
| _save(self) | |
| return True | |
| return False | |
| def mark_done(self, todo_id: int) -> bool: | |
| """Force a todo to done (idempotent). Awards XP only on the first completion.""" | |
| for t in self.todos: | |
| if t["id"] == todo_id: | |
| if not t["done"]: | |
| t["done"] = True | |
| self.xp += self.XP_PER_TODO | |
| self.coins += self.COINS_PER_TODO | |
| _save(self) | |
| return True | |
| return False | |
| def remove_todo(self, todo_id: int) -> bool: | |
| before = len(self.todos) | |
| self.todos = [t for t in self.todos if t["id"] != todo_id] | |
| if len(self.todos) < before: | |
| _save(self) | |
| return True | |
| return False | |
| def _save(state: GameState) -> None: | |
| try: | |
| _SAVE_FILE.write_text( | |
| json.dumps( | |
| {"todos": state.todos, "next_id": state._next_id, "xp": state.xp, "coins": state.coins}, | |
| indent=2, | |
| ), | |
| encoding="utf-8", | |
| ) | |
| except OSError: | |
| pass | |
| def _load() -> GameState: | |
| try: | |
| data = json.loads(_SAVE_FILE.read_text(encoding="utf-8")) | |
| s = GameState(todos=data.get("todos", []), xp=data.get("xp", 0), coins=data.get("coins", 0)) | |
| s._next_id = data.get("next_id", 0) | |
| return s | |
| except (OSError, json.JSONDecodeError, KeyError): | |
| return GameState() | |
| _state = _load() | |
| def get_state() -> GameState: | |
| return _state | |