BattleWords / battlewords /local_storage.py
Surn's picture
v0.2.20 - Add Challenge Mode v1.0
507acad
# 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}"