""" app.py ────── HuggingFace Spaces entry point. For Docker-based Spaces (sdk: docker), HF looks for this file but does not run it — the actual server is started by the Dockerfile CMD. This file serves as a discoverable Python client that users can copy/paste to interact with the environment from their own code. Usage: from app import ChessEconClient env = ChessEconClient() obs, info = env.reset() obs, reward, done, truncated, info = env.step("e2e4") """ import httpx from typing import Any SPACE_URL = "https://adaboostai-chessecon.hf.space" class ChessEconClient: """ OpenEnv 0.1 client for the ChessEcon environment. Compatible with any RL trainer that expects: reset() → (observation, info) step() → (observation, reward, terminated, truncated, info) state() → StateResponse dict """ def __init__(self, base_url: str = SPACE_URL, timeout: float = 30.0): self.base = base_url.rstrip("/") self._client = httpx.Client(timeout=timeout) def reset(self, seed: int | None = None) -> tuple[dict[str, Any], dict[str, Any]]: """Start a new episode. Returns (observation, info).""" payload: dict[str, Any] = {} if seed is not None: payload["seed"] = seed r = self._client.post(f"{self.base}/env/reset", json=payload) r.raise_for_status() data = r.json() return data["observation"], data.get("info", {}) def step(self, action: str) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]: """ Apply a chess move (UCI e.g. 'e2e4' or SAN e.g. 'e4'). Returns (observation, reward, terminated, truncated, info). """ r = self._client.post(f"{self.base}/env/step", json={"action": action}) r.raise_for_status() data = r.json() return ( data["observation"], data["reward"], data["terminated"], data["truncated"], data.get("info", {}), ) def state(self) -> dict[str, Any]: """Return current episode state (read-only).""" r = self._client.get(f"{self.base}/env/state") r.raise_for_status() return r.json() def env_info(self) -> dict[str, Any]: """Return environment metadata.""" r = self._client.get(f"{self.base}/env/env_info") r.raise_for_status() return r.json() def health(self) -> dict[str, Any]: r = self._client.get(f"{self.base}/health") r.raise_for_status() return r.json() def close(self): self._client.close() def __enter__(self): return self def __exit__(self, *_): self.close() # ── Quick demo ──────────────────────────────────────────────────────────────── if __name__ == "__main__": import json with ChessEconClient() as env: print("Environment info:") print(json.dumps(env.env_info(), indent=2)) print("\nResetting …") obs, info = env.reset() print(f" FEN: {obs['fen']}") print(f" Turn: {obs['turn']}") print(f" Wallet W={obs['wallet_white']} B={obs['wallet_black']}") print("\nPlaying e2e4 …") obs, reward, done, truncated, info = env.step("e2e4") print(f" Reward: {reward}") print(f" Done: {done}") print(f" FEN: {obs['fen']}")