Spaces:
Sleeping
Sleeping
| title: OpenSpiel Environment Server | |
| emoji: ๐ฎ | |
| colorFrom: blue | |
| colorTo: purple | |
| sdk: docker | |
| pinned: false | |
| app_port: 8000 | |
| base_path: /web | |
| tags: | |
| - openenv | |
| # OpenSpiel Environment | |
| Integration of OpenSpiel games with the OpenEnv framework. [OpenSpiel](https://github.com/google-deepmind/open_spiel) is DeepMind's collection of 70+ game environments for RL research. | |
| ## Supported Games | |
| This environment supports 6 games across different categories: | |
| ### Single-Player Games (No Opponent) | |
| 1. **Catch** - Move horizontally to catch a falling ball | |
| 2. **Cliff Walking** - Navigate grid without falling off cliff (Sutton & Barto benchmark) | |
| 3. **2048** - Classic tile-merging puzzle game | |
| 4. **Blackjack** - Simplified blackjack (HIT/STAND only) | |
| ### Multi-Player Games (with Bot Opponent) | |
| 5. **Tic-Tac-Toe** - Classic 3x3 game | |
| 6. **Kuhn Poker** - 2-player simplified poker (game theory benchmark) | |
| ## Quick Start | |
| The simplest way to use the OpenSpiel environment is through the `OpenSpielEnv` class: | |
| ```python | |
| from openspiel_env import OpenSpielEnv, OpenSpielAction | |
| try: | |
| # Create environment from Docker image | |
| env = OpenSpielEnv.from_docker_image("openspiel-env:latest") | |
| # Reset to start a new episode | |
| result = env.reset() | |
| print(f"Initial state: {result.observation.info_state}") | |
| print(f"Legal actions: {result.observation.legal_actions}") | |
| # Play until done | |
| while not result.done: | |
| action_id = result.observation.legal_actions[0] | |
| result = env.step(OpenSpielAction(action_id=action_id)) | |
| print(f"Reward: {result.reward}, Done: {result.done}") | |
| finally: | |
| # Always clean up | |
| env.close() | |
| ``` | |
| That's it! The `OpenSpielEnv.from_docker_image()` method handles: | |
| - Starting the Docker container | |
| - Waiting for the server to be ready | |
| - Connecting to the environment | |
| - Container cleanup when you call `close()` | |
| ## Building the Docker Image | |
| OpenSpiel requires compilation from C++ source. The Docker build uses a **pre-built base image** by default to avoid long build times. | |
| ### Default Build (Recommended) | |
| From the **environment directory** (`envs/openspiel_env/`): | |
| ```bash | |
| # Uses pre-built base image from GHCR (fast, ~1-2 min) | |
| docker build -t openspiel-env:latest -f server/Dockerfile . | |
| ``` | |
| This uses the pre-built `ghcr.io/meta-pytorch/openenv-openspiel-base` image which already contains compiled OpenSpiel. | |
| ### Building Your Own Base Image (Optional) | |
| If you need to customize OpenSpiel or can't access the pre-built image: | |
| ```bash | |
| # Step 1: Build the base image (compiles OpenSpiel, ~30-60 min) | |
| docker build -t openspiel-base:latest -f server/Dockerfile.openspiel-base . | |
| # Step 2: Build the environment using your local base image | |
| docker build -t openspiel-env:latest \ | |
| --build-arg OPENSPIEL_BASE_IMAGE=openspiel-base:latest \ | |
| -f server/Dockerfile . | |
| ``` | |
| ## Deploying to Hugging Face Spaces | |
| You can easily deploy your OpenEnv environment to Hugging Face Spaces using the `openenv push` command: | |
| ```bash | |
| # From the environment directory (envs/openspiel_env/) | |
| openenv push | |
| # Or specify options | |
| openenv push --namespace my-org --private | |
| ``` | |
| The `openenv push` command will: | |
| 1. Validate that the directory is an OpenEnv environment (checks for `openenv.yaml`) | |
| 2. Prepare a custom build for Hugging Face Docker space (enables web interface) | |
| 3. Upload to Hugging Face (ensuring you're logged in) | |
| ### Prerequisites | |
| - Authenticate with Hugging Face: The command will prompt for login if not already authenticated | |
| ### Options | |
| - `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) | |
| - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) | |
| - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) | |
| - `--private`: Deploy the space as private (default: public) | |
| ### Examples | |
| ```bash | |
| # Push to your personal namespace (defaults to username/env-name from openenv.yaml) | |
| openenv push | |
| # Push to a specific repository | |
| openenv push --repo-id my-org/openspiel-env | |
| # Push as a private space | |
| openenv push --private | |
| # Combine options | |
| openenv push --repo-id my-org/openspiel-env --private | |
| ``` | |
| After deployment, your space will be available at: | |
| `https://huggingface.co/spaces/<repo-id>` | |
| The deployed space includes: | |
| - **Web Interface** at `/web` - Interactive UI for exploring the environment | |
| - **API Documentation** at `/docs` - Full OpenAPI/Swagger interface | |
| - **Health Check** at `/health` - Container health monitoring | |
| > **Note**: The default Dockerfile uses a pre-built base image with OpenSpiel already compiled, so deployment is fast and works with standard CPU hardware. If you build your own base image, compilation requires more resources and time. | |
| ## Running Specific Games | |
| ```bash | |
| # Catch (default) | |
| docker run -p 8000:8000 openspiel-env:latest | |
| # Tic-Tac-Toe with random opponent | |
| docker run -p 8000:8000 -e OPENSPIEL_GAME=tic_tac_toe openspiel-env:latest | |
| # Kuhn Poker | |
| docker run -p 8000:8000 -e OPENSPIEL_GAME=kuhn_poker openspiel-env:latest | |
| # 2048 | |
| docker run -p 8000:8000 -e OPENSPIEL_GAME=2048 openspiel-env:latest | |
| # Blackjack | |
| docker run -p 8000:8000 -e OPENSPIEL_GAME=blackjack openspiel-env:latest | |
| # Cliff Walking | |
| docker run -p 8000:8000 -e OPENSPIEL_GAME=cliff_walking openspiel-env:latest | |
| ``` | |
| ## Environment Details | |
| ### Action | |
| **OpenSpielAction**: Contains the action to take | |
| - `action_id` (int) - Action ID to execute | |
| - `game_name` (str) - Game name (default: "catch") | |
| - `game_params` (Dict) - Optional game parameters | |
| ### Observation | |
| **OpenSpielObservation**: Contains the game state | |
| - `info_state` (List[float]) - Agent's information state vector | |
| - `legal_actions` (List[int]) - Legal action IDs | |
| - `game_phase` (str) - "initial", "playing", or "terminal" | |
| - `current_player_id` (int) - Current player (-1 for simultaneous) | |
| - `opponent_last_action` (Optional[int]) - Last opponent action | |
| - `done` (bool) - Whether the episode has ended | |
| - `reward` (Optional[float]) - Reward for the last action | |
| ### State | |
| **OpenSpielState**: Server-side state snapshot | |
| - `episode_id` (str) - Unique identifier for the current episode | |
| - `step_count` (int) - Number of steps taken | |
| - `game_name` (str) - Game name | |
| - `agent_player` (int) - Agent's player ID | |
| - `opponent_policy` (str) - Opponent policy name | |
| - `num_players` (int) - Total players | |
| ## Configuration | |
| ### Environment Variables | |
| - `OPENSPIEL_GAME`: Game name (default: "catch") | |
| - `OPENSPIEL_AGENT_PLAYER`: Player ID for agent (default: 0) | |
| - `OPENSPIEL_OPPONENT_POLICY`: Opponent policy for multi-player games | |
| - `random`: Uniform random (default) | |
| - `first`: Always picks first legal action | |
| - `last`: Always picks last legal action | |
| ### Example: Tic-Tac-Toe with Fixed Opponent | |
| ```bash | |
| docker run -p 8000:8000 \ | |
| -e OPENSPIEL_GAME=tic_tac_toe \ | |
| -e OPENSPIEL_OPPONENT_POLICY=first \ | |
| openspiel-env:latest | |
| ``` | |
| ## Advanced Usage | |
| ### Connecting to an Existing Server | |
| If you already have an OpenSpiel environment server running: | |
| ```python | |
| from openspiel_env import OpenSpielEnv, OpenSpielAction | |
| # Connect to existing server | |
| env = OpenSpielEnv(base_url="http://localhost:8000") | |
| # Use as normal | |
| result = env.reset() | |
| result = env.step(OpenSpielAction(action_id=result.observation.legal_actions[0])) | |
| # Close connection (does NOT stop the server) | |
| env.close() | |
| ``` | |
| ### Connecting to HuggingFace Space | |
| ```python | |
| from openspiel_env import OpenSpielEnv, OpenSpielAction | |
| # Connect to remote Space | |
| env = OpenSpielEnv(base_url="https://your-username-openspiel.hf.space") | |
| result = env.reset() | |
| print(f"Game: {result.observation.game_phase}") | |
| print(f"Legal actions: {result.observation.legal_actions}") | |
| result = env.step(OpenSpielAction(action_id=result.observation.legal_actions[0])) | |
| env.close() | |
| ``` | |
| ## Game-Specific Information | |
| ### 1. Catch | |
| - **Type**: Single-player | |
| - **Action Space**: 3 actions (left, stay, right) | |
| - **Observation**: 5x5 grid flattened (25 dimensions) | |
| - **Reward**: +1 for catching ball, 0 otherwise | |
| - **Episode Length**: ~10 steps | |
| ### 2. Tic-Tac-Toe | |
| - **Type**: 2-player turn-based, perfect information | |
| - **Players**: Agent (X) vs Random Bot (O) | |
| - **Action Space**: 9 positions | |
| - **Observation**: 27 dimensions (3x3 board + game state) | |
| - **Reward**: +1 win, -1 loss, 0 draw/mid-game | |
| ### 3. Kuhn Poker | |
| - **Type**: 2-player turn-based, imperfect information | |
| - **Players**: Agent vs Random Bot | |
| - **Action Space**: 2 actions (pass/fold, bet/call) | |
| - **Observation**: 6 dimensions (card + betting history) | |
| - **Reward**: Pot winnings (typically -1, 0, +1, +2) | |
| - **Notes**: THE benchmark for imperfect-information RL | |
| ### 4. Cliff Walking | |
| - **Type**: Single-player grid world | |
| - **Action Space**: 4 actions (up, down, left, right) | |
| - **Observation**: Position encoding | |
| - **Reward**: -1 per step, -100 for falling off cliff | |
| - **Notes**: Classic RL benchmark from Sutton & Barto | |
| ### 5. 2048 | |
| - **Type**: Single-player puzzle | |
| - **Action Space**: 4 actions (up, down, left, right) | |
| - **Observation**: 4x4 grid with tile values | |
| - **Reward**: Points from merging tiles | |
| - **Notes**: Stochastic tile spawning | |
| ### 6. Blackjack | |
| - **Type**: Single-player vs dealer | |
| - **Action Space**: 2 actions (HIT, STAND) | |
| - **Observation**: Player hand + dealer's visible card | |
| - **Reward**: +1 win, -1 loss, 0 draw | |
| - **Notes**: Simplified version, no double/split | |
| ## Development & Testing | |
| ### Direct Environment Testing | |
| Test the environment logic directly without starting the HTTP server (requires OpenSpiel installed locally): | |
| ```python | |
| from openspiel_env.server.openspiel_environment import OpenSpielEnvironment | |
| from openspiel_env.models import OpenSpielAction | |
| # Create environment directly | |
| env = OpenSpielEnvironment(game_name="catch") | |
| # Test reset | |
| obs = env.reset() | |
| print(f"Info state: {obs.info_state}") | |
| # Test step | |
| obs = env.step(OpenSpielAction(action_id=0)) | |
| print(f"Done: {obs.done}, Reward: {obs.reward}") | |
| ``` | |
| ### Running Locally | |
| Run the server locally for development (requires OpenSpiel installed): | |
| ```bash | |
| # From the environment directory | |
| cd envs/openspiel_env | |
| # Install dependencies | |
| uv venv && source .venv/bin/activate | |
| uv pip install -e . | |
| # Start the server | |
| python -m uvicorn server.app:app --reload | |
| ``` | |
| Or using the CLI entry point: | |
| ```bash | |
| uv run --project . server --port 8000 | |
| ``` | |
| ### Automated Testing (All 6 Games) | |
| ```bash | |
| ./test_docker_all_games.sh | |
| ``` | |
| This script will build and test all 6 supported games in Docker. | |
| ## Project Structure | |
| ``` | |
| openspiel_env/ | |
| โโโ __init__.py # Module exports | |
| โโโ README.md # This file | |
| โโโ openenv.yaml # OpenEnv manifest | |
| โโโ pyproject.toml # Project metadata and dependencies | |
| โโโ client.py # OpenSpielEnv client implementation | |
| โโโ models.py # Action, Observation, and State models | |
| โโโ test_docker_all_games.sh # Automated test script | |
| โโโ server/ | |
| โโโ __init__.py # Server module exports | |
| โโโ openspiel_environment.py # Core OpenSpielEnvironment implementation | |
| โโโ opponent_policies.py # Opponent policies (random, fixed) | |
| โโโ app.py # FastAPI application | |
| โโโ Dockerfile # Environment container (uses pre-built base) | |
| โโโ Dockerfile.openspiel-base # Base image with compiled OpenSpiel | |
| ``` | |
| ## Limitations | |
| - **Simultaneous-move games**: Only agent_player=0 supported | |
| - **Multi-agent training**: Single agent only (no self-play yet) | |
| - **Opponent policies**: Random and fixed only (no MCTS yet) | |
| - **Build time**: Building your own base image takes ~30-60 min (compiles OpenSpiel C++). Using the pre-built image is fast (~1-2 min) and works with standard hardware. | |
| ## References | |
| - [OpenSpiel Paper (2019)](https://arxiv.org/abs/1908.09453) | |
| - [OpenSpiel GitHub](https://github.com/google-deepmind/open_spiel) | |
| - [OpenSpiel Documentation](https://openspiel.readthedocs.io/) | |