Spaces:
Running
Running
| # file: battlewords/local_storage.py | |
| """ | |
| Storage module for BattleWords game. | |
| Provides functionality for: | |
| 1. Saving/loading game results to local JSON files | |
| 2. Managing high scores and leaderboards | |
| 3. Sharing game IDs via query strings | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field, asdict | |
| from typing import List, Dict, Optional, Any | |
| from datetime import datetime | |
| import json | |
| import os | |
| from pathlib import Path | |
| class GameResult: | |
| game_id: str | |
| wordlist: str | |
| game_mode: str | |
| score: int | |
| tier: str | |
| elapsed_seconds: int | |
| words_found: List[str] | |
| completed_at: str | |
| player_name: Optional[str] = None | |
| def to_dict(self) -> Dict[str, Any]: | |
| return asdict(self) | |
| def from_dict(cls, data: Dict[str, Any]) -> "GameResult": | |
| return cls(**data) | |
| class HighScoreEntry: | |
| player_name: str | |
| score: int | |
| tier: str | |
| wordlist: str | |
| game_mode: str | |
| elapsed_seconds: int | |
| completed_at: str | |
| game_id: str | |
| def to_dict(self) -> Dict[str, Any]: | |
| return asdict(self) | |
| def from_dict(cls, data: Dict[str, Any]) -> "HighScoreEntry": | |
| return cls(**data) | |
| class GameStorage: | |
| def __init__(self, storage_dir: Optional[str] = None): | |
| if storage_dir is None: | |
| storage_dir = os.path.join( | |
| os.path.expanduser("~"), | |
| ".battlewords", | |
| "data" | |
| ) | |
| self.storage_dir = Path(storage_dir) | |
| self.storage_dir.mkdir(parents=True, exist_ok=True) | |
| self.results_file = self.storage_dir / "game_results.json" | |
| self.highscores_file = self.storage_dir / "highscores.json" | |
| def save_result(self, result: GameResult) -> bool: | |
| try: | |
| results = self.load_all_results() | |
| results.append(result.to_dict()) | |
| with open(self.results_file, 'w', encoding='utf-8') as f: | |
| json.dump(results, f, indent=2, ensure_ascii=False) | |
| self._update_highscores(result) | |
| return True | |
| except Exception as e: | |
| print(f"Error saving result: {e}") | |
| return False | |
| def load_all_results(self) -> List[Dict[str, Any]]: | |
| if not self.results_file.exists(): | |
| return [] | |
| try: | |
| with open(self.results_file, 'r', encoding='utf-8') as f: | |
| return json.load(f) | |
| except Exception as e: | |
| print(f"Error loading results: {e}") | |
| return [] | |
| def get_results_by_game_id(self, game_id: str) -> List[GameResult]: | |
| all_results = self.load_all_results() | |
| matching = [ | |
| GameResult.from_dict(r) | |
| for r in all_results | |
| if r.get("game_id") == game_id | |
| ] | |
| return sorted(matching, key=lambda x: x.score, reverse=True) | |
| def _update_highscores(self, result: GameResult) -> None: | |
| highscores = self.load_highscores() | |
| entry = HighScoreEntry( | |
| player_name=result.player_name or "Anonymous", | |
| score=result.score, | |
| tier=result.tier, | |
| wordlist=result.wordlist, | |
| game_mode=result.game_mode, | |
| elapsed_seconds=result.elapsed_seconds, | |
| completed_at=result.completed_at, | |
| game_id=result.game_id | |
| ) | |
| highscores.append(entry.to_dict()) | |
| highscores.sort(key=lambda x: x["score"], reverse=True) | |
| highscores = highscores[:100] | |
| with open(self.highscores_file, 'w', encoding='utf-8') as f: | |
| json.dump(highscores, f, indent=2, ensure_ascii=False) | |
| def load_highscores( | |
| self, | |
| wordlist: Optional[str] = None, | |
| game_mode: Optional[str] = None, | |
| limit: int = 10 | |
| ) -> List[HighScoreEntry]: | |
| if not self.highscores_file.exists(): | |
| return [] | |
| try: | |
| with open(self.highscores_file, 'r', encoding='utf-8') as f: | |
| scores = json.load(f) | |
| if wordlist: | |
| scores = [s for s in scores if s.get("wordlist") == wordlist] | |
| if game_mode: | |
| scores = [s for s in scores if s.get("game_mode") == game_mode] | |
| scores.sort(key=lambda x: x["score"], reverse=True) | |
| return [HighScoreEntry.from_dict(s) for s in scores[:limit]] | |
| except Exception as e: | |
| print(f"Error loading highscores: {e}") | |
| return [] | |
| def get_player_stats(self, player_name: str) -> Dict[str, Any]: | |
| all_results = self.load_all_results() | |
| player_results = [ | |
| GameResult.from_dict(r) | |
| for r in all_results | |
| if r.get("player_name") == player_name | |
| ] | |
| if not player_results: | |
| return { | |
| "games_played": 0, | |
| "total_score": 0, | |
| "average_score": 0, | |
| "best_score": 0, | |
| "best_tier": None | |
| } | |
| scores = [r.score for r in player_results] | |
| return { | |
| "games_played": len(player_results), | |
| "total_score": sum(scores), | |
| "average_score": sum(scores) / len(scores), | |
| "best_score": max(scores), | |
| "best_tier": max(player_results, key=lambda x: x.score).tier, | |
| "fastest_time": min(r.elapsed_seconds for r in player_results) | |
| } | |
| def save_json_to_file(data: dict, directory: str, filename: str = "settings.json") -> str: | |
| """ | |
| Save a dictionary as a JSON file with a specified filename in the given directory. | |
| Returns the full path to the saved file. | |
| """ | |
| os.makedirs(directory, exist_ok=True) | |
| file_path = os.path.join(directory, filename) | |
| with open(file_path, "w", encoding="utf-8") as f: | |
| json.dump(data, f, indent=2, ensure_ascii=False) | |
| return file_path | |
| def generate_game_id_from_words(words: List[str]) -> str: | |
| import hashlib | |
| sorted_words = sorted([w.upper() for w in words]) | |
| word_string = "".join(sorted_words) | |
| hash_obj = hashlib.sha256(word_string.encode('utf-8')) | |
| return hash_obj.hexdigest()[:8].upper() | |
| def parse_game_id_from_url() -> Optional[str]: | |
| try: | |
| import streamlit as st | |
| params = st.query_params | |
| return params.get("game_id") | |
| except Exception: | |
| return None | |
| def create_shareable_url(game_id: str, base_url: Optional[str] = None) -> str: | |
| if base_url is None: | |
| base_url = "https://huggingface.co/spaces/Surn/BattleWords" | |
| return f"{base_url}?game_id={game_id}" |