#!/usr/bin/env python3 """Mock leaderboard server for local testing. Stores submitted scores in memory, returns rank/total on POST, and serves the top-N on GET. Sends permissive CORS so the web build can talk to it from a different port. Usage: ./tools/mock-leaderboard.py # listens on :8765 ./tools/mock-leaderboard.py 8080 # custom port """ import json import sys from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlparse, parse_qs # In-memory store: {board_name: [score_record, ...]} SCORES = {} def _cors(handler): handler.send_header("Access-Control-Allow-Origin", "*") handler.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") handler.send_header("Access-Control-Allow-Headers", "Content-Type") class Handler(BaseHTTPRequestHandler): def do_OPTIONS(self): self.send_response(204) _cors(self) self.end_headers() def do_GET(self): url = urlparse(self.path) if url.path != "/api/scores": self.send_response(404); _cors(self); self.end_headers(); return q = parse_qs(url.query) board = (q.get("board") or [""])[0] limit = int((q.get("limit") or ["50"])[0]) rows = sorted(SCORES.get(board, []), key=lambda r: r["score"], reverse=True)[:limit] body = json.dumps({ "board": board, "total": len(SCORES.get(board, [])), "scores": [ {"name": r["name"], "score": r["score"], "end_scene": r["end_scene"]} for r in rows ], }).encode() self.send_response(200) self.send_header("Content-Type", "application/json") _cors(self) self.end_headers() self.wfile.write(body) def do_POST(self): if urlparse(self.path).path != "/api/scores": self.send_response(404); _cors(self); self.end_headers(); return length = int(self.headers.get("Content-Length", "0")) try: payload = json.loads(self.rfile.read(length)) except Exception as e: self.send_response(400); _cors(self); self.end_headers() self.wfile.write(f"bad json: {e}".encode()) return board = payload.get("board", "") SCORES.setdefault(board, []).append(payload) # Rank: 1 = best. Sort descending, find position. ranked = sorted(SCORES[board], key=lambda r: r["score"], reverse=True) # Identify *this* submission by reference equality. rank = next((i + 1 for i, r in enumerate(ranked) if r is payload), -1) body = json.dumps({"rank": rank, "total": len(SCORES[board])}).encode() print(f"[POST] board={board} name={payload.get('name')} " f"score={payload.get('score')} -> #{rank}/{len(SCORES[board])}") self.send_response(200) self.send_header("Content-Type", "application/json") _cors(self) self.end_headers() self.wfile.write(body) def log_message(self, fmt, *args): # Keep the noise down; we print our own line on POST. return def main(): port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765 srv = HTTPServer(("127.0.0.1", port), Handler) print(f"mock-leaderboard listening on http://127.0.0.1:{port}") try: srv.serve_forever() except KeyboardInterrupt: pass if __name__ == "__main__": main()