burtenshaw HF Staff commited on
Commit
be665b3
·
verified ·
1 Parent(s): 2346d4f

Upload folder using huggingface_hub

Browse files
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 Exp
3
- emoji: 🌍
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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