hanabhi/gridworld-env
0
1# OpenEnv: Production RL Made Simple2 3<div align="center">4 5<img src="https://upload.wikimedia.org/wikipedia/commons/1/10/PyTorch_logo_icon.svg" width="200" alt="PyTorch">6 7## From "Hello World" to RL Training in 5 Minutes โจ8 9**What if RL environments were as easy to use as REST APIs?**10 11That's OpenEnv. Type-safe. Isolated. Production-ready. ๐ฏ12 13[](https://colab.research.google.com/github/meta-pytorch/OpenEnv/blob/main/examples/OpenEnv_Tutorial.ipynb)14[](https://github.com/meta-pytorch/OpenEnv)15[](https://opensource.org/licenses/BSD-3-Clause)16[](https://pytorch.org/)17 18Author: [Sanyam Bhutani](http://twitter.com/bhutanisanyam1/)19 20</div>21 22## Why OpenEnv?23 24Let's take a trip down memory lane:25 26It's 2016, RL is popular. You read some papers, it looks promising.27 28But in real world: Cartpole is the best you can run on a gaming GPU.29 30What do you do beyond Cartpole?31 32Fast-forward to 2025, GRPO is awesome and this time it's not JUST in theory, it works well in practise and is really here!33 34The problem still remains, how do you take these RL algorithms and take them beyond Cartpole?35 36A huge part of RL is giving your algorithms environment access to learn.37 38We are excited to introduce an Environment Spec for adding Open Environments for RL Training. This will allow you to focus on your experiments and allow everyone to bring their environments.39 40Focus on experiments, use OpenEnvironments, and build agents that go beyond Cartpole on a single spec.41 42---43 44## ๐ What You'll Learn45 46<table>47<tr>48<td width="50%">49 50**๐ฏ Part 1-2: The Fundamentals**51 52- โก RL in 60 seconds53- ๐ค Why existing solutions fall short54- ๐ก The OpenEnv solution55 56</td>57<td width="50%">58 59**๐๏ธ Part 3-5: The Architecture**60 61- ๐ง How OpenEnv works62- ๐ Exploring real code63- ๐ฎ OpenSpiel integration example64 65</td>66</tr>67<tr>68<td width="50%">69 70**๐ฎ Part 6-8: Hands-On Demo**71 72- ๐ Use existing OpenSpiel environment73- ๐ค Test 4 different policies74- ๐ Watch learning happen live75 76</td>77<td width="50%">78 79**๐ง Part 9-10: Going Further**80 81- ๐ฎ Switch to other OpenSpiel games82- โจ Build your own integration83- ๐ Deploy to production84 85</td>86</tr>87</table>88 89!!! tip "Pro Tip"90 This notebook is designed to run top-to-bottom in Google Colab with zero setup!91 92 โฑ๏ธ **Time**: ~5 minutes | ๐ **Difficulty**: Beginner-friendly | ๐ฏ **Outcome**: Production-ready RL knowledge93 94---95 96## ๐ Table of Contents97 98### Foundation99 100- [Part 1: RL in 60 Seconds โฑ๏ธ](#part-1-rl-in-60-seconds)101- [Part 2: The Problem with Traditional RL ๐ค](#part-2-the-problem-with-traditional-rl)102- [Part 3: Setup ๐ ๏ธ](#part-3-setup)103 104### Architecture105 106- [Part 4: The OpenEnv Pattern ๐๏ธ](#part-4-the-openenv-pattern)107- [Part 5: Example Integration - OpenSpiel ๐ฎ](#part-5-example-integration---openspiel)108 109### Hands-On Demo110 111- [Part 6: Interactive Demo ๐ฎ](#part-6-using-real-openspiel)112- [Part 7: Four Policies ๐ค](#part-7-four-policies)113- [Part 8: Policy Competition! ๐](#part-8-policy-competition)114 115### Advanced116 117- [Part 9: Using Real OpenSpiel ๐ฎ](#part-9-switching-to-other-games)118- [Part 10: Create Your Own Integration ๐ ๏ธ](#part-10-create-your-own-integration)119 120### Wrap Up121 122- [Summary: Your Journey ๐](#summary-your-journey)123- [Resources ๐](#resources)124 125---126 127(part-1-rl-in-60-seconds)=128## Part 1: RL in 60 Seconds โฑ๏ธ129 130**Reinforcement Learning is simpler than you think.**131 132It's just a loop:133 134```python135while not done:136 observation = environment.observe()137 action = policy.choose(observation)138 reward = environment.step(action)139 policy.learn(reward)140```141 142That's it. That's RL.143 144Let's see it in action:145 146```python147import random148 149print("๐ฒ " + "="*58 + " ๐ฒ")150print(" Number Guessing Game - The Simplest RL Example")151print("๐ฒ " + "="*58 + " ๐ฒ")152 153# Environment setup154target = random.randint(1, 10)155guesses_left = 3156 157print(f"\n๐ฏ I'm thinking of a number between 1 and 10...")158print(f"๐ญ You have {guesses_left} guesses. Let's see how random guessing works!\n")159 160# The RL Loop - Pure random policy (no learning!)161while guesses_left > 0:162 # Policy: Random guessing (no learning yet!)163 guess = random.randint(1, 10)164 guesses_left -= 1165 166 print(f"๐ญ Guess #{3-guesses_left}: {guess}", end=" โ ")167 168 # Reward signal (but we're not using it!)169 if guess == target:170 print("๐ Correct! +10 points")171 break172 elif abs(guess - target) <= 2:173 print("๐ฅ Warm! (close)")174 else:175 print("โ๏ธ Cold! (far)")176else:177 print(f"\n๐ Out of guesses. The number was {target}.")178 179print("\n" + "="*62)180print("๐ก This is RL: Observe โ Act โ Reward โ Repeat")181print(" But this policy is terrible! It doesn't learn from rewards.")182print("="*62 + "\n")183```184 185**Output:**186```187๐ฒ ========================================================== ๐ฒ188 Number Guessing Game - The Simplest RL Example189๐ฒ ========================================================== ๐ฒ190 191๐ฏ I'm thinking of a number between 1 and 10...192๐ญ You have 3 guesses. Let's see how random guessing works!193 194๐ญ Guess #1: 2 โ โ๏ธ Cold! (far)195๐ญ Guess #2: 10 โ ๐ Correct! +10 points196 197==============================================================198๐ก This is RL: Observe โ Act โ Reward โ Repeat199 But this policy is terrible! It doesn't learn from rewards.200==============================================================201```202 203---204 205(part-2-the-problem-with-traditional-rl)=206## Part 2: The Problem with Traditional RL ๐ค207 208### ๐ค Why Can't We Just Use OpenAI Gym?209 210Good question! Gym is great for research, but production needs more...211 212| Challenge | Traditional Approach | OpenEnv Solution |213|-----------|---------------------|------------------|214| **Type Safety** | โ `obs[0][3]` - what is this? | โ
`obs.info_state` - IDE knows! |215| **Isolation** | โ Same process (can crash your training) | โ
Docker containers (fully isolated) |216| **Deployment** | โ "Works on my machine" ๐คท | โ
Same container everywhere ๐ณ |217| **Scaling** | โ Hard to distribute | โ
Deploy to Kubernetes โธ๏ธ |218| **Language** | โ Python only | โ
Any language (HTTP API) ๐ |219| **Debugging** | โ Cryptic numpy errors | โ
Clear type errors ๐ |220 221### ๐ก The OpenEnv Philosophy222 223**"RL environments should be like microservices"**224 225Think of it like this: You don't run your database in the same process as your web server, right? Same principle!226 227- ๐ **Isolated**: Run in containers (security + stability)228- ๐ **Standard**: HTTP API, works everywhere229- ๐ฆ **Versioned**: Docker images (reproducibility!)230- ๐ **Scalable**: Deploy to cloud with one command231- ๐ก๏ธ **Type-safe**: Catch bugs before they happen232- ๐ **Portable**: Works on Mac, Linux, Windows, Cloud233 234### The Architecture235 236```237โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ238โ YOUR TRAINING CODE โ239โ โ240โ env = OpenSpielEnv(...) โ Import the client โ241โ result = env.reset() โ Type-safe! โ242โ result = env.step(action) โ Type-safe! โ243โ โ244โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ245 โ246 โ HTTP/JSON (Language-Agnostic)247 โ POST /reset, POST /step, GET /state248 โ249โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ250โ DOCKER CONTAINER โ251โ โ252โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ253โ โ FastAPI Server โ โ254โ โ โโ Environment (reset, step, state) โ โ255โ โ โโ Your Game/Simulation Logic โ โ256โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ257โ โ258โ Isolated โข Reproducible โข Secure โ259โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ260```261 262!!! info "Key Insight"263 You never see HTTP details - just clean Python methods!264 265 ```python266 env.reset() # Under the hood: HTTP POST to /reset267 env.step(...) # Under the hood: HTTP POST to /step268 env.state() # Under the hood: HTTP GET to /state269 ```270 271 The magic? OpenEnv handles all the plumbing. You focus on RL! โจ272 273---274 275(part-3-setup)=276## Part 3: Setup ๐ ๏ธ277 278**Running in Colab?** This cell will clone OpenEnv and install dependencies automatically.279 280**Running locally?** Make sure you're in the OpenEnv directory.281 282```ipython3283# Detect environment284try:285 import google.colab286 IN_COLAB = True287 print("๐ Running in Google Colab - Perfect!")288except ImportError:289 IN_COLAB = False290 print("๐ป Running locally - Nice!")291 292if IN_COLAB:293 print("\n๐ฆ Cloning OpenEnv repository...")294 !git clone https://github.com/meta-pytorch/OpenEnv.git > /dev/null 2>&1295 %cd OpenEnv296 297 print("๐ Installing dependencies (this takes ~10 seconds)...")298 !pip install -q fastapi uvicorn requests299 300 import sys301 sys.path.insert(0, './src')302 print("\nโ
Setup complete! Everything is ready to go! ๐")303else:304 import sys305 from pathlib import Path306 sys.path.insert(0, str(Path.cwd().parent / 'src'))307 print("โ
Using local OpenEnv installation")308 309print("\n๐ Ready to explore OpenEnv and build amazing things!")310print("๐ก Tip: Run cells top-to-bottom for the best experience.\n")311```312 313**Output:**314```315๐ป Running locally - Nice!316โ
Using local OpenEnv installation317 318๐ Ready to explore OpenEnv and build amazing things!319๐ก Tip: Run cells top-to-bottom for the best experience.320```321 322---323 324(part-4-the-openenv-pattern)=325## Part 4: The OpenEnv Pattern ๐๏ธ326 327### Every OpenEnv Environment Has 3 Components:328 329```330src/envs/your_env/331โโโ ๐ models.py โ Type-safe contracts332โ (Action, Observation, State)333โ334โโโ ๐ฑ client.py โ What YOU import335โ (HTTPEnvClient implementation)336โ337โโโ ๐ฅ๏ธ server/338 โโโ environment.py โ Game/simulation logic339 โโโ app.py โ FastAPI server340 โโโ Dockerfile โ Container definition341```342 343Let's explore the actual OpenEnv code to see how this works:344 345```python346# Import OpenEnv's core abstractions347from core.env_server import Environment, Action, Observation, State348from core.http_env_client import HTTPEnvClient349 350print("="*70)351print(" ๐งฉ OPENENV CORE ABSTRACTIONS")352print("="*70)353 354print("""355๐ฅ๏ธ SERVER SIDE (runs in Docker):356 357 class Environment(ABC):358 '''Base class for all environment implementations'''359 360 @abstractmethod361 def reset(self) -> Observation:362 '''Start new episode'''363 364 @abstractmethod365 def step(self, action: Action) -> Observation:366 '''Execute action, return observation'''367 368 @property369 def state(self) -> State:370 '''Get episode metadata'''371 372๐ฑ CLIENT SIDE (your training code):373 374 class HTTPEnvClient(ABC):375 '''Base class for HTTP clients'''376 377 def reset(self) -> StepResult:378 # HTTP POST /reset379 380 def step(self, action) -> StepResult:381 # HTTP POST /step382 383 def state(self) -> State:384 # HTTP GET /state385""")386 387print("="*70)388print("\nโจ Same interface on both sides - communication via HTTP!")389print("๐ฏ You focus on RL, OpenEnv handles the infrastructure.\n")390```391 392**Output:**393```394======================================================================395 ๐งฉ OPENENV CORE ABSTRACTIONS396======================================================================397 398๐ฅ๏ธ SERVER SIDE (runs in Docker):399 400 class Environment(ABC):401 '''Base class for all environment implementations'''402 403 @abstractmethod404 def reset(self) -> Observation:405 '''Start new episode'''406 407 @abstractmethod408 def step(self, action: Action) -> Observation:409 '''Execute action, return observation'''410 411 @property412 def state(self) -> State:413 '''Get episode metadata'''414 415๐ฑ CLIENT SIDE (your training code):416 417 class HTTPEnvClient(ABC):418 '''Base class for HTTP clients'''419 420 def reset(self) -> StepResult:421 # HTTP POST /reset422 423 def step(self, action) -> StepResult:424 # HTTP POST /step425 426 def state(self) -> State:427 # HTTP GET /state428 429======================================================================430 431โจ Same interface on both sides - communication via HTTP!432๐ฏ You focus on RL, OpenEnv handles the infrastructure.433```434 435---436 437(part-5-example-integration---openspiel)=438## Part 5: Example Integration - OpenSpiel ๐ฎ439 440### What is OpenSpiel?441 442**OpenSpiel** is a library from DeepMind with **70+ game environments** for RL research.443 444### OpenEnv's Integration445 446We've wrapped **6 OpenSpiel games** following the OpenEnv pattern:447 448| **๐ฏ Single-Player** | **๐ฅ Multi-Player** |449|---------------------|---------------------|450| 1. **Catch** - Catch falling ball | 5. **Tic-Tac-Toe** - Classic 3ร3 |451| 2. **Cliff Walking** - Navigate grid | 6. **Kuhn Poker** - Imperfect info poker |452| 3. **2048** - Tile puzzle | |453| 4. **Blackjack** - Card game | |454 455This shows how OpenEnv can wrap **any** existing RL library!456 457```python458from envs.openspiel_env.client import OpenSpielEnv459 460print("="*70)461print(" ๐ HOW OPENENV WRAPS OPENSPIEL")462print("="*70)463 464print("""465class OpenSpielEnv(HTTPEnvClient[OpenSpielAction, OpenSpielObservation]):466 467 def _step_payload(self, action: OpenSpielAction) -> dict:468 '''Convert typed action to JSON for HTTP'''469 return {470 "action_id": action.action_id,471 "game_name": action.game_name,472 }473 474 def _parse_result(self, payload: dict) -> StepResult:475 '''Parse HTTP JSON response into typed observation'''476 return StepResult(477 observation=OpenSpielObservation(...),478 reward=payload['reward'],479 done=payload['done']480 )481 482""")483 484print("โ" * 70)485print("\nโจ Usage (works for ALL OpenEnv environments):")486print("""487 env = OpenSpielEnv(base_url="http://localhost:8000")488 489 result = env.reset()490 # Returns StepResult[OpenSpielObservation] - Type safe!491 492 result = env.step(OpenSpielAction(action_id=2, game_name="catch"))493 # Type checker knows this is valid!494 495 state = env.state()496 # Returns OpenSpielState497""")498 499print("โ" * 70)500print("\n๐ฏ This pattern works for ANY environment you want to wrap!\n")501```502 503**Output:**504```505======================================================================506 ๐ HOW OPENENV WRAPS OPENSPIEL507======================================================================508 509class OpenSpielEnv(HTTPEnvClient[OpenSpielAction, OpenSpielObservation]):510 511 def _step_payload(self, action: OpenSpielAction) -> dict:512 '''Convert typed action to JSON for HTTP'''513 return {514 "action_id": action.action_id,515 "game_name": action.game_name,516 }517 518 def _parse_result(self, payload: dict) -> StepResult:519 '''Parse HTTP JSON response into typed observation'''520 return StepResult(521 observation=OpenSpielObservation(...),522 reward=payload['reward'],523 done=payload['done']524 )525 526 527โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ528 529โจ Usage (works for ALL OpenEnv environments):530 531 env = OpenSpielEnv(base_url="http://localhost:8000")532 533 result = env.reset()534 # Returns StepResult[OpenSpielObservation] - Type safe!535 536 result = env.step(OpenSpielAction(action_id=2, game_name="catch"))537 # Type checker knows this is valid!538 539 state = env.state()540 # Returns OpenSpielState541 542โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ543 544๐ฏ This pattern works for ANY environment you want to wrap!545```546 547### Type-Safe Models548 549```python550# Import OpenSpiel integration models551from envs.openspiel_env.models import (552 OpenSpielAction,553 OpenSpielObservation,554 OpenSpielState555)556from dataclasses import fields557 558print("="*70)559print(" ๐ฎ OPENSPIEL INTEGRATION - TYPE-SAFE MODELS")560print("="*70)561 562print("\n๐ค OpenSpielAction (what you send):")563print(" " + "โ" * 64)564for field in fields(OpenSpielAction):565 print(f" โข {field.name:20s} : {field.type}")566 567print("\n๐ฅ OpenSpielObservation (what you receive):")568print(" " + "โ" * 64)569for field in fields(OpenSpielObservation):570 print(f" โข {field.name:20s} : {field.type}")571 572print("\n๐ OpenSpielState (episode metadata):")573print(" " + "โ" * 64)574for field in fields(OpenSpielState):575 print(f" โข {field.name:20s} : {field.type}")576 577print("\n" + "="*70)578print("\n๐ก Type safety means:")579print(" โ
Your IDE autocompletes these fields")580print(" โ
Typos are caught before running")581print(" โ
Refactoring is safe")582print(" โ
Self-documenting code\n")583```584 585**Output:**586```587======================================================================588 ๐ฎ OPENSPIEL INTEGRATION - TYPE-SAFE MODELS589======================================================================590 591๐ค OpenSpielAction (what you send):592 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ593 โข metadata : typing.Dict[str, typing.Any]594 โข action_id : int595 โข game_name : str596 โข game_params : Dict[str, Any]597 598๐ฅ OpenSpielObservation (what you receive):599 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ600 โข done : <class 'bool'>601 โข reward : typing.Union[bool, int, float, NoneType]602 โข metadata : typing.Dict[str, typing.Any]603 โข info_state : List[float]604 โข legal_actions : List[int]605 โข game_phase : str606 โข current_player_id : int607 โข opponent_last_action : Optional[int]608 609๐ OpenSpielState (episode metadata):610 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ611 โข episode_id : typing.Optional[str]612 โข step_count : <class 'int'>613 โข game_name : str614 โข agent_player : int615 โข opponent_policy : str616 โข game_params : Dict[str, Any]617 โข num_players : int618 619======================================================================620 621๐ก Type safety means:622 โ
Your IDE autocompletes these fields623 โ
Typos are caught before running624 โ
Refactoring is safe625 โ
Self-documenting code626```627 628### How the Client Works629 630The client **inherits from HTTPEnvClient** and implements 3 methods:631 6321. `_step_payload()` - Convert action โ JSON6332. `_parse_result()` - Parse JSON โ typed observation6343. `_parse_state()` - Parse JSON โ state635 636That's it! The base class handles all HTTP communication.637 638---639 640(part-6-using-real-openspiel)=641## Part 6: Using Real OpenSpiel ๐ฎ642 643<div style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 15px; margin: 30px 0;">644 645### Now let's USE a production environment!646 647We'll play **Catch** using OpenEnv's **OpenSpiel integration** ๐ฏ648 649This is a REAL environment running in production at companies!650 651**Get ready for:**652 653- ๐ Using existing environments (not building)654- ๐ค Testing policies against real games655- ๐ Live gameplay visualization656- ๐ฏ Production-ready patterns657 658</div>659 660### The Game: Catch ๐ด๐661 662```663โฌ โฌ ๐ด โฌ โฌ664โฌ โฌ โฌ โฌ โฌ665โฌ โฌ โฌ โฌ โฌ Ball666โฌ โฌ โฌ โฌ โฌ667โฌ โฌ โฌ โฌ โฌ falls668โฌ โฌ โฌ โฌ โฌ669โฌ โฌ โฌ โฌ โฌ down670โฌ โฌ โฌ โฌ โฌ671โฌ โฌ โฌ โฌ โฌ672โฌ โฌ ๐ โฌ โฌ673 Paddle674```675 676**Rules:**677 678- 10ร5 grid679- Ball falls from random column680- Move paddle left/right to catch it681 682**Actions:**683 684- `0` = Move LEFT โฌ
๏ธ685- `1` = STAY ๐686- `2` = Move RIGHT โก๏ธ687 688**Reward:**689 690- `+1` if caught ๐691- `0` if missed ๐ข692 693!!! note "Why Catch?"694 - Simple rules (easy to understand)695 - Fast episodes (~5 steps)696 - Clear success/failure697 - Part of OpenSpiel's 70+ games!698 699 **๐ก The Big Idea:**700 Instead of building this from scratch, we'll USE OpenEnv's existing OpenSpiel integration. Same interface, but production-ready!701 702```python703from envs.openspiel_env import OpenSpielEnv704from envs.openspiel_env.models import (705 OpenSpielAction,706 OpenSpielObservation,707 OpenSpielState708)709from dataclasses import fields710 711print("๐ฎ " + "="*64 + " ๐ฎ")712print(" โ
Importing Real OpenSpiel Environment!")713print("๐ฎ " + "="*64 + " ๐ฎ\n")714 715print("๐ฆ What we just imported:")716print(" โข OpenSpielEnv - HTTP client for OpenSpiel games")717print(" โข OpenSpielAction - Type-safe actions")718print(" โข OpenSpielObservation - Type-safe observations")719print(" โข OpenSpielState - Episode metadata\n")720 721print("๐ OpenSpielObservation fields:")722print(" " + "โ" * 60)723for field in fields(OpenSpielObservation):724 print(f" โข {field.name:25s} : {field.type}")725 726print("\n" + "="*70)727print("\n๐ก This is REAL OpenEnv code - used in production!")728print(" โข Wraps 6 OpenSpiel games (Catch, Tic-Tac-Toe, Poker, etc.)")729print(" โข Type-safe actions and observations")730print(" โข Works via HTTP (we'll see that next!)\n")731```732 733**Output:**734```735๐ฎ ================================================================ ๐ฎ736 โ
Importing Real OpenSpiel Environment!737๐ฎ ================================================================ ๐ฎ738 739๐ฆ What we just imported:740 โข OpenSpielEnv - HTTP client for OpenSpiel games741 โข OpenSpielAction - Type-safe actions742 โข OpenSpielObservation - Type-safe observations743 โข OpenSpielState - Episode metadata744 745๐ OpenSpielObservation fields:746 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ747 โข done : <class 'bool'>748 โข reward : typing.Union[bool, int, float, NoneType]749 โข metadata : typing.Dict[str, typing.Any]750 โข info_state : List[float]751 โข legal_actions : List[int]752 โข game_phase : str753 โข current_player_id : int754 โข opponent_last_action : Optional[int]755 756======================================================================757 758๐ก This is REAL OpenEnv code - used in production!759 โข Wraps 6 OpenSpiel games (Catch, Tic-Tac-Toe, Poker, etc.)760 โข Type-safe actions and observations761 โข Works via HTTP (we'll see that next!)762```763 764---765 766(part-7-four-policies)=767## Part 7: Four Policies ๐ค768 769Let's test 4 different AI strategies:770 771| Policy | Strategy | Expected Performance |772|--------|----------|----------------------|773| **๐ฒ Random** | Pick random action every step | ~20% (pure luck) |774| **๐ Always Stay** | Never move, hope ball lands in center | ~20% (terrible!) |775| **๐ง Smart** | Move paddle toward ball | 100% (optimal!) |776| **๐ Learning** | Start random, learn smart strategy | ~85% (improves over time) |777 778**๐ก These policies work with ANY OpenSpiel game!**779 780```python781import random782 783# ============================================================================784# POLICIES - Different AI strategies (adapted for OpenSpiel)785# ============================================================================786 787class RandomPolicy:788 """Baseline: Pure random guessing."""789 name = "๐ฒ Random Guesser"790 791 def select_action(self, obs: OpenSpielObservation) -> int:792 return random.choice(obs.legal_actions)793 794 795class AlwaysStayPolicy:796 """Bad strategy: Never moves."""797 name = "๐ Always Stay"798 799 def select_action(self, obs: OpenSpielObservation) -> int:800 return 1 # STAY801 802 803class SmartPolicy:804 """Optimal: Move paddle toward ball."""805 name = "๐ง Smart Heuristic"806 807 def select_action(self, obs: OpenSpielObservation) -> int:808 # Parse OpenSpiel observation809 # For Catch: info_state is a flattened 10x5 grid810 # Ball position and paddle position encoded in the vector811 info_state = obs.info_state812 813 # Find ball and paddle positions from info_state814 # Catch uses a 10x5 grid, so 50 values815 grid_size = 5816 817 # Find positions (ball = 1.0 in the flattened grid, paddle = 1.0 in the last row of the flattened grid)818 ball_col = None819 paddle_col = None820 821 for idx, val in enumerate(info_state):822 if abs(val - 1.0) < 0.01: # Ball823 ball_col = idx % grid_size824 break825 826 last_row = info_state[-grid_size:]827 paddle_col = last_row.index(1.0) # Paddle828 829 if ball_col is not None and paddle_col is not None:830 if paddle_col < ball_col:831 return 2 # Move RIGHT832 elif paddle_col > ball_col:833 return 0 # Move LEFT834 835 return 1 # STAY (fallback)836 837 838class LearningPolicy:839 """Simulated RL: Epsilon-greedy exploration."""840 name = "๐ Learning Agent"841 842 def __init__(self):843 self.steps = 0844 self.smart_policy = SmartPolicy()845 846 def select_action(self, obs: OpenSpielObservation) -> int:847 self.steps += 1848 849 # Decay exploration rate over time850 epsilon = max(0.1, 1.0 - (self.steps / 100))851 852 if random.random() < epsilon:853 # Explore: random action854 return random.choice(obs.legal_actions)855 else:856 # Exploit: use smart strategy857 return self.smart_policy.select_action(obs)858 859 860print("๐ค " + "="*64 + " ๐ค")861print(" โ
4 Policies Created (Adapted for OpenSpiel)!")862print("๐ค " + "="*64 + " ๐ค\n")863 864policies = [RandomPolicy(), AlwaysStayPolicy(), SmartPolicy(), LearningPolicy()]865for i, policy in enumerate(policies, 1):866 print(f" {i}. {policy.name}")867 868print("\n๐ก These policies work with OpenSpielObservation!")869print(" โข Read info_state (flattened grid)")870print(" โข Use legal_actions")871print(" โข Work with ANY OpenSpiel game that exposes these!\n")872```873 874**Output:**875```876๐ค ================================================================ ๐ค877 โ
4 Policies Created (Adapted for OpenSpiel)!878๐ค ================================================================ ๐ค879 880 1. ๐ฒ Random Guesser881 2. ๐ Always Stay882 3. ๐ง Smart Heuristic883 4. ๐ Learning Agent884 885๐ก These policies work with OpenSpielObservation!886 โข Read info_state (flattened grid)887 โข Use legal_actions888 โข Work with ANY OpenSpiel game that exposes these!889```890 891---892 893(part-8-policy-competition)=894## Part 8: Policy Competition! ๐895 896Let's run **50 episodes** for each policy against **REAL OpenSpiel** and see who wins!897 898This is production code - every action is an HTTP call to the OpenSpiel server!899 900```python901def evaluate_policies(env, num_episodes=50):902 """Compare all policies over many episodes using real OpenSpiel."""903 policies = [904 RandomPolicy(),905 AlwaysStayPolicy(),906 SmartPolicy(),907 LearningPolicy(),908 ]909 910 print("\n๐ " + "="*66 + " ๐")911 print(f" POLICY SHOWDOWN - {num_episodes} Episodes Each")912 print(f" Playing against REAL OpenSpiel Catch!")913 print("๐ " + "="*66 + " ๐\n")914 915 results = []916 for policy in policies:917 print(f"โก Testing {policy.name}...", end=" ")918 successes = sum(run_episode(env, policy, visualize=False)919 for _ in range(num_episodes))920 success_rate = (successes / num_episodes) * 100921 results.append((policy.name, success_rate, successes))922 print(f"โ Done!")923 924 print("\n" + "="*70)925 print(" ๐ FINAL RESULTS")926 print("="*70 + "\n")927 928 # Sort by success rate (descending)929 results.sort(key=lambda x: x[1], reverse=True)930 931 # Award medals to top 3932 medals = ["๐ฅ", "๐ฅ", "๐ฅ", " "]933 934 for i, (name, rate, successes) in enumerate(results):935 medal = medals[i]936 bar = "โ" * int(rate / 2)937 print(f"{medal} {name:25s} [{bar:<50}] {rate:5.1f}% ({successes}/{num_episodes})")938 939 print("\n" + "="*70)940 print("\nโจ Key Insights:")941 print(" โข Random (~20%): Baseline - pure luck ๐ฒ")942 print(" โข Always Stay (~20%): Bad strategy - stays center ๐")943 print(" โข Smart (100%): Optimal - perfect play! ๐ง ")944 print(" โข Learning (~85%): Improves over time ๐")945 print("\n๐ This is Reinforcement Learning + OpenEnv in action:")946 print(" 1. We USED existing OpenSpiel environment (didn't build it)")947 print(" 2. Type-safe communication over HTTP")948 print(" 3. Same code works for ANY OpenSpiel game")949 print(" 4. Production-ready architecture\n")950 951# Run the epic competition!952print("๐ฎ Starting the showdown against REAL OpenSpiel...\n")953evaluate_policies(client, num_episodes=50)954```955 956---957 958(part-9-switching-to-other-games)=959## Part 9: Switching to Other Games ๐ฎ960 961### What We Just Used: Real OpenSpiel! ๐962 963In Parts 6-8, we **USED** the existing OpenSpiel Catch environment:964 965| What We Did | How It Works |966|-------------|--------------|967| **Imported** | OpenSpielEnv client (pre-built) |968| **Started** | OpenSpiel server via uvicorn |969| **Connected** | HTTP client to server |970| **Played** | Real OpenSpiel Catch game |971 972**๐ฏ This is production code!** Every action was an HTTP call to a real OpenSpiel environment.973 974### ๐ฎ 6 Games Available - Same Interface!975 976The beauty of OpenEnv? **Same code, different games!**977 978```python979# We just used Catch980env = OpenSpielEnv(base_url="http://localhost:8000")981# game_name="catch" was set via environment variable982 983# Want Tic-Tac-Toe instead? Just change the game!984# Start server with: OPENSPIEL_GAME=tic_tac_toe uvicorn ...985# Same client code works!986```987 988**๐ฎ All 6 Games:**989 9901. โ
**`catch`** - What we just used!9912. **`tic_tac_toe`** - Classic 3ร39923. **`kuhn_poker`** - Imperfect information poker9934. **`cliff_walking`** - Grid navigation9945. **`2048`** - Tile puzzle9956. **`blackjack`** - Card game996 997**All use the exact same OpenSpielEnv client!**998 999### Try Another Game (Optional):1000 1001```python1002# Stop the current server (kill the server_process)1003# Then start a new game:1004 1005server_process = subprocess.Popen(1006 [sys.executable, "-m", "uvicorn",1007 "envs.openspiel_env.server.app:app",1008 "--host", "0.0.0.0",1009 "--port", "8000"],1010 env={**os.environ,1011 "PYTHONPATH": f"{work_dir}/src",1012 "OPENSPIEL_GAME": "tic_tac_toe", # Changed!1013 "OPENSPIEL_AGENT_PLAYER": "0",1014 "OPENSPIEL_OPPONENT_POLICY": "random"},1015 # ... rest of config1016)1017 1018# Same client works!1019client = OpenSpielEnv(base_url="http://localhost:8000")1020result = client.reset() # Now playing Tic-Tac-Toe!1021```1022 1023**๐ก Key Insight**: You don't rebuild anything - you just USE different games with the same client!1024 1025---1026 1027(part-10-create-your-own-integration)=1028## Part 10: Create Your Own Integration ๐ ๏ธ1029 1030### The 5-Step Pattern1031 1032Want to wrap your own environment in OpenEnv? Here's how:1033 1034### Step 1: Define Types (`models.py`)1035 1036```python1037from dataclasses import dataclass1038from core.env_server import Action, Observation, State1039 1040@dataclass1041class YourAction(Action):1042 action_value: int1043 # Add your action fields1044 1045@dataclass1046class YourObservation(Observation):1047 state_data: List[float]1048 done: bool1049 reward: float1050 # Add your observation fields1051 1052@dataclass1053class YourState(State):1054 episode_id: str1055 step_count: int1056 # Add your state fields1057```1058 1059### Step 2: Implement Environment (`server/environment.py`)1060 1061```python1062from core.env_server import Environment1063 1064class YourEnvironment(Environment):1065 def reset(self) -> Observation:1066 # Initialize your game/simulation1067 return YourObservation(...)1068 1069 def step(self, action: Action) -> Observation:1070 # Execute action, update state1071 return YourObservation(...)1072 1073 @property1074 def state(self) -> State:1075 return self._state1076```1077 1078### Step 3: Create Client (`client.py`)1079 1080```python1081from core.http_env_client import HTTPEnvClient1082from core.types import StepResult1083 1084class YourEnv(HTTPEnvClient[YourAction, YourObservation]):1085 def _step_payload(self, action: YourAction) -> dict:1086 """Convert action to JSON"""1087 return {"action_value": action.action_value}1088 1089 def _parse_result(self, payload: dict) -> StepResult:1090 """Parse JSON to observation"""1091 return StepResult(1092 observation=YourObservation(...),1093 reward=payload['reward'],1094 done=payload['done']1095 )1096 1097 def _parse_state(self, payload: dict) -> YourState:1098 return YourState(...)1099```1100 1101### Step 4: Create Server (`server/app.py`)1102 1103```python1104from core.env_server import create_fastapi_app1105from .your_environment import YourEnvironment1106 1107env = YourEnvironment()1108app = create_fastapi_app(env)1109 1110# That's it! OpenEnv creates all endpoints for you.1111```1112 1113### Step 5: Dockerize (`server/Dockerfile`)1114 1115```dockerfile1116FROM python:3.11-slim1117 1118WORKDIR /app1119COPY requirements.txt .1120RUN pip install --no-cache-dir -r requirements.txt1121 1122COPY . .1123CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]1124```1125 1126### ๐ Examples to Study1127 1128OpenEnv includes 3 complete examples:1129 11301. **`src/envs/echo_env/`**1131 - Simplest possible environment1132 - Great for testing and learning1133 11342. **`src/envs/openspiel_env/`**1135 - Wraps external library (OpenSpiel)1136 - Shows integration pattern1137 - 6 games in one integration1138 11393. **`src/envs/coding_env/`**1140 - Python code execution environment1141 - Shows complex use case1142 - Security considerations1143 1144**๐ก Study these to understand the patterns!**1145 1146---1147 1148(summary-your-journey)=1149## ๐ Summary: Your Journey1150 1151### What You Learned1152 1153<table>1154<tr>1155<td width="50%" style="vertical-align: top;">1156 1157### ๐ Concepts1158 1159โ
**RL Fundamentals**1160 1161- The observe-act-reward loop1162- What makes good policies1163- Exploration vs exploitation1164 1165โ
**OpenEnv Architecture**1166 1167- Client-server separation1168- Type-safe contracts1169- HTTP communication layer1170 1171โ
**Production Patterns**1172 1173- Docker isolation1174- API design1175- Reproducible deployments1176 1177</td>1178<td width="50%" style="vertical-align: top;">1179 1180### ๐ ๏ธ Skills1181 1182โ
**Using Environments**1183 1184- Import OpenEnv clients1185- Call reset/step/state1186- Work with typed observations1187 1188โ
**Building Environments**1189 1190- Define type-safe models1191- Implement Environment class1192- Create HTTPEnvClient1193 1194โ
**Testing & Debugging**1195 1196- Compare policies1197- Visualize episodes1198- Measure performance1199 1200</td>