Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Dockerfile +21 -0
- README.md +50 -5
- __init__.py +30 -0
- client.py +98 -0
- models.py +69 -0
- openenv.yaml +5 -0
- openenv_connect4.egg-info/PKG-INFO +17 -0
- openenv_connect4.egg-info/SOURCES.txt +19 -0
- openenv_connect4.egg-info/dependency_links.txt +1 -0
- openenv_connect4.egg-info/entry_points.txt +2 -0
- openenv_connect4.egg-info/requires.txt +13 -0
- openenv_connect4.egg-info/top_level.txt +1 -0
- pyproject.toml +37 -0
- server/__init__.py +15 -0
- server/app.py +27 -0
- server/connect4_environment.py +89 -0
- uv.lock +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1.6
|
| 2 |
+
|
| 3 |
+
FROM python:3.11-slim AS runtime
|
| 4 |
+
|
| 5 |
+
WORKDIR /app/env
|
| 6 |
+
|
| 7 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 8 |
+
git \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
COPY . .
|
| 12 |
+
|
| 13 |
+
RUN pip install --upgrade pip && \
|
| 14 |
+
pip install --no-cache-dir -e .
|
| 15 |
+
|
| 16 |
+
EXPOSE 8000
|
| 17 |
+
|
| 18 |
+
ENV PYTHONUNBUFFERED=1 \
|
| 19 |
+
ENABLE_WEB_INTERFACE=true
|
| 20 |
+
|
| 21 |
+
CMD ["python", "-m", "uvicorn", "connect4_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"]
|
README.md
CHANGED
|
@@ -1,10 +1,55 @@
|
|
| 1 |
---
|
| 2 |
-
title: Connect4
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Connect4 Environment Server
|
| 3 |
+
emoji: 🔴
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 8000
|
| 9 |
+
base_path: /web
|
| 10 |
+
tags:
|
| 11 |
+
- openenv
|
| 12 |
+
- games
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# Connect4 Environment
|
| 16 |
+
|
| 17 |
+
This repository packages the classic Connect4 board game as a standalone OpenEnv
|
| 18 |
+
environment. It exposes a FastAPI server compatible with the OpenEnv CLI and
|
| 19 |
+
provides a Python client for interacting with the environment programmatically.
|
| 20 |
+
|
| 21 |
+
## Quick Start
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
# Install dependencies (editable mode for local development)
|
| 25 |
+
uv pip install -e .
|
| 26 |
+
|
| 27 |
+
# Launch the server locally
|
| 28 |
+
uv run server
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Once running, visit `http://localhost:8000/docs` to explore the OpenAPI schema.
|
| 32 |
+
|
| 33 |
+
## Python Usage
|
| 34 |
+
|
| 35 |
+
```python
|
| 36 |
+
from connect4_env import Connect4Env, Connect4Action
|
| 37 |
+
|
| 38 |
+
env = Connect4Env.from_docker_image("connect4-env:latest")
|
| 39 |
+
result = env.reset()
|
| 40 |
+
print(result.observation.board)
|
| 41 |
+
|
| 42 |
+
result = env.step(Connect4Action(column=3))
|
| 43 |
+
print(result.reward, result.done)
|
| 44 |
+
|
| 45 |
+
env.close()
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## Deploy
|
| 49 |
+
|
| 50 |
+
- **Validate:** `openenv validate`
|
| 51 |
+
- **Build Docker image:** `openenv build`
|
| 52 |
+
- **Push to Hugging Face / Docker Hub:** `openenv push`
|
| 53 |
+
|
| 54 |
+
Customize the Docker build or deployment metadata through environment variables
|
| 55 |
+
as needed. The default server listens on port `8000`.
|
__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Connect4 Environment for OpenEnv.
|
| 9 |
+
|
| 10 |
+
This module provides OpenEnv integration for the classic Connect4 board game.
|
| 11 |
+
|
| 12 |
+
Example:
|
| 13 |
+
>>> from connect4_env import Connect4Env, Connect4Action
|
| 14 |
+
>>>
|
| 15 |
+
>>> # Connect to a running server or start via Docker
|
| 16 |
+
>>> env = Connect4Env.from_docker_image("connect4-env:latest")
|
| 17 |
+
>>>
|
| 18 |
+
>>> # Reset and interact
|
| 19 |
+
>>> result = env.reset()
|
| 20 |
+
>>> result = env.step(Connect4Action(column=2))
|
| 21 |
+
>>> print(result.reward, result.done)
|
| 22 |
+
>>>
|
| 23 |
+
>>> # Cleanup
|
| 24 |
+
>>> env.close()
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from .client import Connect4Env
|
| 28 |
+
from .models import Connect4Action, Connect4Observation, Connect4State
|
| 29 |
+
|
| 30 |
+
__all__ = ["Connect4Env", "Connect4Action", "Connect4Observation", "Connect4State"]
|
client.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Connect4 Environment HTTP Client.
|
| 9 |
+
|
| 10 |
+
This module provides the client for connecting to a Connect4 Environment server
|
| 11 |
+
over HTTP.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
from typing import TYPE_CHECKING, Any, Dict
|
| 17 |
+
|
| 18 |
+
from openenv_core.http_env_client import HTTPEnvClient, StepResult
|
| 19 |
+
|
| 20 |
+
from .models import Connect4Action, Connect4Observation, Connect4State
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING:
|
| 23 |
+
from openenv_core.containers.runtime import ContainerProvider
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Connect4Env(HTTPEnvClient[Connect4Action, Connect4Observation]):
|
| 27 |
+
"""
|
| 28 |
+
HTTP client for Connect4 Environment.
|
| 29 |
+
|
| 30 |
+
This client connects to a Connect4Environment HTTP server and provides
|
| 31 |
+
methods to interact with it: reset(), step(), and state access.
|
| 32 |
+
|
| 33 |
+
Example:
|
| 34 |
+
>>> client = Connect4Env(base_url="http://localhost:8000")
|
| 35 |
+
>>> result = client.reset()
|
| 36 |
+
>>> print(result.observation.board)
|
| 37 |
+
>>>
|
| 38 |
+
>>> # Take an action
|
| 39 |
+
>>> result = client.step(Connect4Action(column=3))
|
| 40 |
+
>>> print(result.reward, result.done)
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def _step_payload(self, action: Connect4Action) -> Dict[str, Any]:
|
| 44 |
+
"""
|
| 45 |
+
Convert Connect4Action to JSON payload for step request.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
action: Connect4Action instance.
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
Dictionary representation suitable for JSON encoding.
|
| 52 |
+
"""
|
| 53 |
+
return {
|
| 54 |
+
"column": action.column, # column index to drop piece
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
def _parse_result(self, payload: Dict[str, Any]) -> StepResult[Connect4Observation]:
|
| 58 |
+
"""
|
| 59 |
+
Parse server response into StepResult[Connect4Observation].
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
payload: JSON response from server.
|
| 63 |
+
|
| 64 |
+
Returns:
|
| 65 |
+
StepResult with Connect4Observation.
|
| 66 |
+
"""
|
| 67 |
+
obs_data = payload.get("observation", {})
|
| 68 |
+
|
| 69 |
+
observation = Connect4Observation(
|
| 70 |
+
board=obs_data.get("board", [[0] * 7 for _ in range(6)]),
|
| 71 |
+
legal_actions=obs_data.get("legal_actions", []),
|
| 72 |
+
done=payload.get("done", False),
|
| 73 |
+
reward=payload.get("reward", 0.0),
|
| 74 |
+
metadata=obs_data.get("metadata", {}),
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
return StepResult(
|
| 78 |
+
observation=observation,
|
| 79 |
+
reward=payload.get("reward", 0.0),
|
| 80 |
+
done=payload.get("done", False),
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def _parse_state(self, payload: Dict[str, Any]) -> Connect4State:
|
| 84 |
+
"""
|
| 85 |
+
Parse server response into Connect4State object.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
payload: JSON response from /state endpoint.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
Connect4State object with environment state information.
|
| 92 |
+
"""
|
| 93 |
+
return Connect4State(
|
| 94 |
+
episode_id=payload.get("episode_id", ""),
|
| 95 |
+
board=payload.get("board", [[0] * 7 for _ in range(6)]),
|
| 96 |
+
next_player=payload.get("next_player", 1),
|
| 97 |
+
step_count=payload.get("step_count", 0),
|
| 98 |
+
)
|
models.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Data models for Connect4 Environment.
|
| 9 |
+
|
| 10 |
+
This module defines the Action, Observation, and State types for Connect4 games
|
| 11 |
+
via the OpenEnv interface.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
import numpy as np
|
| 17 |
+
from typing import List
|
| 18 |
+
|
| 19 |
+
from openenv_core.env_server.types import Action, Observation, State
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class Connect4Action(Action):
|
| 24 |
+
"""
|
| 25 |
+
Action for Connect4 environment.
|
| 26 |
+
|
| 27 |
+
Attributes:
|
| 28 |
+
column: The column index (0 to 6) where the piece will be placed.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
column: int
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass(kw_only=True)
|
| 35 |
+
class Connect4Observation(Observation):
|
| 36 |
+
"""
|
| 37 |
+
Observation for Connect4 environment.
|
| 38 |
+
|
| 39 |
+
Attributes:
|
| 40 |
+
board: The current board as a 2D list (6 rows x 7 columns).
|
| 41 |
+
1 = current player, -1 = opponent, 0 = empty.
|
| 42 |
+
legal_actions: List of column indices that are valid moves.
|
| 43 |
+
done: Whether the game is over.
|
| 44 |
+
reward: Reward for the last action.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
board: List[List[int]]
|
| 48 |
+
legal_actions: List[int]
|
| 49 |
+
done: bool = False
|
| 50 |
+
reward: float = 0.0
|
| 51 |
+
metadata: dict = field(default_factory=dict)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass(kw_only=True)
|
| 55 |
+
class Connect4State(State):
|
| 56 |
+
"""
|
| 57 |
+
State for Connect4 environment.
|
| 58 |
+
|
| 59 |
+
Attributes:
|
| 60 |
+
episode_id: Unique ID for the current game.
|
| 61 |
+
board: Current board state (rows x columns), 0 = empty, 1 = player, -1 = opponent.
|
| 62 |
+
next_player: Whose turn it is (1 or -1).
|
| 63 |
+
step_count: Number of steps taken in the game.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
episode_id: str
|
| 67 |
+
board: List[List[int]] = field(default_factory=lambda: np.zeros((6, 7), dtype=int).tolist())
|
| 68 |
+
next_player: int = 1
|
| 69 |
+
step_count: int = 0
|
openenv.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: connect4_env
|
| 2 |
+
version: "0.1.0"
|
| 3 |
+
description: "Connect4 environment for OpenEnv"
|
| 4 |
+
action: Connect4Action
|
| 5 |
+
observation: Connect4Observation
|
openenv_connect4.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: openenv-connect4
|
| 3 |
+
Version: 0.1.0
|
| 4 |
+
Summary: Connect4 environment for OpenEnv
|
| 5 |
+
Requires-Python: >=3.10
|
| 6 |
+
Requires-Dist: openenv-core@ git+https://github.com/meta-pytorch/OpenEnv.git@main#subdirectory=src/core
|
| 7 |
+
Requires-Dist: fastapi>=0.115.0
|
| 8 |
+
Requires-Dist: pydantic>=2.0.0
|
| 9 |
+
Requires-Dist: uvicorn>=0.24.0
|
| 10 |
+
Requires-Dist: requests>=2.25.0
|
| 11 |
+
Requires-Dist: gymnasium>=0.29.0
|
| 12 |
+
Requires-Dist: ale-py>=0.8.0
|
| 13 |
+
Requires-Dist: numpy>=1.24.0
|
| 14 |
+
Provides-Extra: dev
|
| 15 |
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
| 16 |
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
| 17 |
+
Requires-Dist: ipykernel>=6.29.5; extra == "dev"
|
openenv_connect4.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
README.md
|
| 2 |
+
__init__.py
|
| 3 |
+
client.py
|
| 4 |
+
models.py
|
| 5 |
+
openenv.yaml
|
| 6 |
+
pyproject.toml
|
| 7 |
+
./__init__.py
|
| 8 |
+
./client.py
|
| 9 |
+
./models.py
|
| 10 |
+
./openenv.yaml
|
| 11 |
+
openenv_connect4.egg-info/PKG-INFO
|
| 12 |
+
openenv_connect4.egg-info/SOURCES.txt
|
| 13 |
+
openenv_connect4.egg-info/dependency_links.txt
|
| 14 |
+
openenv_connect4.egg-info/entry_points.txt
|
| 15 |
+
openenv_connect4.egg-info/requires.txt
|
| 16 |
+
openenv_connect4.egg-info/top_level.txt
|
| 17 |
+
server/__init__.py
|
| 18 |
+
server/app.py
|
| 19 |
+
server/connect4_environment.py
|
openenv_connect4.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
openenv_connect4.egg-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
server = connect4_env.server.app:main
|
openenv_connect4.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core@ git+https://github.com/meta-pytorch/OpenEnv.git@main#subdirectory=src/core
|
| 2 |
+
fastapi>=0.115.0
|
| 3 |
+
pydantic>=2.0.0
|
| 4 |
+
uvicorn>=0.24.0
|
| 5 |
+
requests>=2.25.0
|
| 6 |
+
gymnasium>=0.29.0
|
| 7 |
+
ale-py>=0.8.0
|
| 8 |
+
numpy>=1.24.0
|
| 9 |
+
|
| 10 |
+
[dev]
|
| 11 |
+
pytest>=8.0.0
|
| 12 |
+
pytest-cov>=4.0.0
|
| 13 |
+
ipykernel>=6.29.5
|
openenv_connect4.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
connect4_env
|
pyproject.toml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=45", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "openenv-connect4"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "Connect4 environment for OpenEnv"
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"openenv-core @ git+https://github.com/meta-pytorch/OpenEnv.git@main#subdirectory=src/core",
|
| 12 |
+
"fastapi>=0.115.0",
|
| 13 |
+
"pydantic>=2.0.0",
|
| 14 |
+
"uvicorn>=0.24.0",
|
| 15 |
+
"requests>=2.25.0",
|
| 16 |
+
# Add your environment-specific dependencies here
|
| 17 |
+
"gymnasium>=0.29.0",
|
| 18 |
+
"ale-py>=0.8.0",
|
| 19 |
+
"numpy>=1.24.0",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
[project.optional-dependencies]
|
| 23 |
+
dev = [
|
| 24 |
+
"pytest>=8.0.0",
|
| 25 |
+
"pytest-cov>=4.0.0",
|
| 26 |
+
"ipykernel>=6.29.5",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
[project.scripts]
|
| 30 |
+
server = "connect4_env.server.app:main"
|
| 31 |
+
|
| 32 |
+
[tool.setuptools]
|
| 33 |
+
packages = ["connect4_env", "connect4_env.server"]
|
| 34 |
+
package-dir = { "connect4_env" = ".", "connect4_env.server" = "server" }
|
| 35 |
+
|
| 36 |
+
[tool.setuptools.package-data]
|
| 37 |
+
connect4_env = ["**/*.yaml", "**/*.yml"]
|
server/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
connect4 Environment Server.
|
| 9 |
+
|
| 10 |
+
Server-side implementation of connect4 environment for OpenEnv.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from .connect4_environment import Connect4Environment
|
| 14 |
+
|
| 15 |
+
__all__ = ["Connect4Environment"]
|
server/app.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from openenv_core.env_server.http_server import create_app
|
| 2 |
+
|
| 3 |
+
from ..models import Connect4Action, Connect4Observation
|
| 4 |
+
from .connect4_environment import Connect4Environment
|
| 5 |
+
|
| 6 |
+
env = Connect4Environment()
|
| 7 |
+
app = create_app(
|
| 8 |
+
env,
|
| 9 |
+
Connect4Action,
|
| 10 |
+
Connect4Observation,
|
| 11 |
+
env_name="connect4_env",
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def main(port: int = 8000):
|
| 16 |
+
import uvicorn
|
| 17 |
+
|
| 18 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
import argparse
|
| 23 |
+
|
| 24 |
+
parser = argparse.ArgumentParser()
|
| 25 |
+
parser.add_argument("--port", type=int, default=8000)
|
| 26 |
+
args = parser.parse_args()
|
| 27 |
+
main(args.port)
|
server/connect4_environment.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uuid
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
from openenv_core.env_server.interfaces import Environment
|
| 5 |
+
|
| 6 |
+
from ..models import Connect4Action, Connect4Observation, Connect4State
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Connect4Environment(Environment):
|
| 10 |
+
ROWS = 6
|
| 11 |
+
COLUMNS = 7
|
| 12 |
+
|
| 13 |
+
def __init__(self, opponent=None):
|
| 14 |
+
super().__init__()
|
| 15 |
+
self._opponent = opponent
|
| 16 |
+
self.reset()
|
| 17 |
+
|
| 18 |
+
def reset(self):
|
| 19 |
+
self.board = np.zeros((self.ROWS, self.COLUMNS), dtype=np.int8)
|
| 20 |
+
self.next_player = 1
|
| 21 |
+
self.invalid_move_played = False
|
| 22 |
+
|
| 23 |
+
self._state = Connect4State(
|
| 24 |
+
board=self.board.copy().tolist(), next_player=self.next_player, episode_id=str(uuid.uuid4()), step_count=0
|
| 25 |
+
)
|
| 26 |
+
return self._make_observation()
|
| 27 |
+
|
| 28 |
+
def step(self, action: Connect4Action):
|
| 29 |
+
col = action.column
|
| 30 |
+
# reward = 0.0
|
| 31 |
+
done = False
|
| 32 |
+
|
| 33 |
+
# check action validity
|
| 34 |
+
if col < 0 or col >= self.COLUMNS or self.board[0, col] != 0:
|
| 35 |
+
self.invalid_move_played = True
|
| 36 |
+
reward = -1 # penalty for invalid move
|
| 37 |
+
done = True
|
| 38 |
+
else:
|
| 39 |
+
# drop piece
|
| 40 |
+
for row in range(self.ROWS - 1, -1, -1):
|
| 41 |
+
if self.board[row, col] == 0:
|
| 42 |
+
self.board[row, col] = self.next_player
|
| 43 |
+
break
|
| 44 |
+
|
| 45 |
+
# check win / full board
|
| 46 |
+
reward, done = self._check_win_or_draw(row, col)
|
| 47 |
+
|
| 48 |
+
self.next_player *= -1
|
| 49 |
+
|
| 50 |
+
self._state = Connect4State(
|
| 51 |
+
board=self.board.copy().tolist(),
|
| 52 |
+
next_player=self.next_player,
|
| 53 |
+
episode_id=self._state.episode_id,
|
| 54 |
+
step_count=self._state.step_count + 1,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
return self._make_observation(reward, done)
|
| 58 |
+
|
| 59 |
+
def _make_observation(self, reward=0.0, done=False):
|
| 60 |
+
legal_actions = [c for c in range(self.COLUMNS) if self.board[0, c] == 0]
|
| 61 |
+
return Connect4Observation(
|
| 62 |
+
board=self.board.copy().tolist(),
|
| 63 |
+
legal_actions=legal_actions,
|
| 64 |
+
reward=reward,
|
| 65 |
+
done=done,
|
| 66 |
+
metadata={"next_player": self.next_player},
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def _check_win_or_draw(self, row, col):
|
| 70 |
+
# Implement 4-in-a-row check (like your Gymnasium code)
|
| 71 |
+
player = self.board[row, col]
|
| 72 |
+
directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
|
| 73 |
+
for dr, dc in directions:
|
| 74 |
+
count = 0
|
| 75 |
+
for step in range(-3, 4):
|
| 76 |
+
r, c = row + step * dr, col + step * dc
|
| 77 |
+
if 0 <= r < self.ROWS and 0 <= c < self.COLUMNS and self.board[r, c] == player:
|
| 78 |
+
count += 1
|
| 79 |
+
if count >= 4:
|
| 80 |
+
return 1.0, True
|
| 81 |
+
else:
|
| 82 |
+
count = 0
|
| 83 |
+
if np.all(self.board != 0):
|
| 84 |
+
return 0.0, True
|
| 85 |
+
return 0.0, False
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def state(self):
|
| 89 |
+
return self._state
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|