focus-buddy / src /game_state.py
pocanman's picture
migrate to new repo
3006400
Raw
History Blame Contribute Delete
3.17 kB
from __future__ import annotations
import json
import pathlib
from dataclasses import dataclass, field
_SAVE_FILE = pathlib.Path(__file__).parent.parent / "saved_state.json"
@dataclass
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
@property
def level(self) -> int:
return self.xp // self.XP_PER_LEVEL + 1
@property
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