# 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 @dataclass 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) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "GameResult": return cls(**data) @dataclass 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) @classmethod 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}"