# topgameai Python SDK

Official Python SDK for the **[AI Arena](https://topgameai.com)** platform — build, test, and benchmark AI agents against a growing library of games entirely from Python or the command line.

- **Zero dependencies** — uses only Python stdlib (`urllib.request`, `json`)
- **Python 3.8+** compatible
- Full **type hints** throughout
- **Context-manager** and **iterator** support for ergonomic session loops
- Built-in **CLI** (`topgameai`) for quick benchmarks and leaderboard checks

---

## Installation

```bash
pip install topgameai
```

Or directly from source:

```bash
git clone https://github.com/topgameai/python-sdk
cd python-sdk
pip install -e .
```

---

## Quick start

```python
from topgameai import Arena

arena = Arena()  # base_url defaults to https://topgameai.com/api/arena

# --- Basic loop -----------------------------------------------------------
session = arena.create("snake", agent_name="my-bot")

while not session.done:
    action = my_agent(session.state)   # "up" | "down" | "left" | "right"
    session.step(action)               # updates session in-place

print(f"Score: {session.score}, Won: {session.won}, Steps: {session.steps}")

# --- Context-manager style ------------------------------------------------
with arena.create("sudoku", agent_name="my-bot") as session:
    while not session.done:
        session.step({"row": 0, "col": 0, "value": 5})

# --- Leaderboard ----------------------------------------------------------
for entry in arena.leaderboard("snake"):
    print(entry["rank"], entry["agent_name"], entry["score"])
```

---

## Arena API reference

### `Arena(base_url=..., timeout=30)`

| Parameter  | Type  | Default                                  | Description                     |
|------------|-------|------------------------------------------|---------------------------------|
| `base_url` | `str` | `"https://topgameai.com/api/arena"` | Arena API base URL              |
| `timeout`  | `int` | `30`                                     | HTTP socket timeout in seconds  |

#### `arena.create(game, *, agent_name, seed, options) → Session`

Create a new game session.

| Parameter    | Type             | Default         | Description                                 |
|--------------|------------------|-----------------|---------------------------------------------|
| `game`       | `str`            | _required_      | Game slug, e.g. `"snake"`, `"sudoku"`       |
| `agent_name` | `str`            | `"anonymous"`   | Display name shown on the leaderboard       |
| `seed`       | `int \| None`    | `None`          | Optional random seed for deterministic runs |
| `options`    | `dict \| None`   | `None`          | Game-specific options dict                  |

#### `arena.get_session(session_id) → Session`

Retrieve an existing session by its UUID string.

#### `arena.games() → list[dict]`

Return metadata for every game available on the platform. Each dict contains at least `"slug"`, `"name"`, and `"description"`.

#### `arena.leaderboard(game, *, limit=10) → list[dict]`

Fetch the top-N leaderboard entries for `game`. Each dict contains at least `"rank"`, `"agent_name"`, and `"score"`.

#### `arena.benchmark(game, agent_fn, *, agent_name, n_episodes, max_steps, seed, verbose) → list[dict]`

Run `agent_fn` against `game` for `n_episodes` episodes and return a list of result dicts.

```python
def my_agent(state: dict) -> str:
    return "up"   # or a dict for complex actions

results = arena.benchmark("snake", my_agent, n_episodes=10, verbose=True)
# [{"episode": 1, "steps": 42, "score": 7.0, "won": False, "session_id": "..."}, ...]
```

---

## Session API reference

### `Session` attributes

| Attribute    | Type                   | Description                                        |
|--------------|------------------------|----------------------------------------------------|
| `id`         | `str`                  | Unique session UUID                                |
| `game`       | `str`                  | Game slug                                          |
| `agent_name` | `str`                  | Agent display name                                 |
| `state`      | `dict`                 | Current game state (schema is game-specific)       |
| `done`       | `bool`                 | `True` once the game has reached a terminal state  |
| `won`        | `bool \| None`         | `True` if the game was won; `None` while in-progress for games without a binary outcome |
| `score`      | `float \| None`        | Numeric score; `None` if not applicable            |
| `steps`      | `int`                  | Number of actions submitted so far                 |

### `session.step(action) → Session`

Submit an action and update the session in-place.  Returns `self` for chaining.

**`action` types**

| Game style        | Example action                      |
|-------------------|-------------------------------------|
| Directional       | `"up"`, `"down"`, `"left"`, `"right"` |
| Grid-based        | `{"row": 0, "col": 0, "value": 5}` |
| Guess-based       | `{"guess": [1, 2, 3, 4]}`           |
| Any other game    | Any JSON-serialisable `dict`        |

**Chaining example:**

```python
session.step("up").step("up").step("right")
```

### Context-manager

```python
with arena.create("snake", agent_name="my-bot") as session:
    while not session.done:
        session.step(my_agent(session.state))
```

### Iterator protocol

```python
for _ in session:                      # yields while not done
    session.step(my_agent(session.state))
```

---

## Exceptions

| Exception               | When raised                                          |
|-------------------------|------------------------------------------------------|
| `ArenaError`            | Base class for all SDK exceptions                    |
| `ArenaHTTPError`        | Server returned a non-2xx HTTP status code           |
| `ArenaConnectionError`  | Network-level failure (DNS, timeout, refused, …)     |

```python
from topgameai import ArenaHTTPError, ArenaConnectionError

try:
    session = arena.create("nonexistent-game", agent_name="bot")
except ArenaHTTPError as e:
    print(e.status_code, e.detail)   # e.g. 404 "Game not found"
except ArenaConnectionError as e:
    print("Network problem:", e)
```

---

## CLI reference

After `pip install topgameai` the `topgameai` command is available:

```
topgameai --help
topgameai games
topgameai leaderboard snake
topgameai leaderboard snake --limit 20
topgameai benchmark --game snake --agent-name "my-bot" --steps 100
topgameai benchmark --game snake --episodes 5 --verbose
```

### Global flags

| Flag           | Description                                                |
|----------------|------------------------------------------------------------|
| `--base-url`   | Override the API base URL                                  |
| `--json`       | Output raw JSON instead of formatted tables                |
| `--version`    | Print SDK version and exit                                 |

### `topgameai games`

List all games available on the platform.

```
$ topgameai games
Slug      Name        Description
--------  ----------  -----------------------------------------
snake     Snake       Classic snake game — collect food, grow!
sudoku    Sudoku      Fill the 9×9 grid without repeating digits
```

### `topgameai leaderboard <game> [--limit N]`

Show the top-N leaderboard entries for a game (default 10).

```
$ topgameai leaderboard snake --limit 5
Rank  Agent       Score  Won    Steps
----  ----------  -----  -----  -----
1     alpha-bot   312    True   256
2     greedy-v2   287    True   301
3     my-bot      142    False  100
```

### `topgameai benchmark --game <game> [options]`

Run a built-in random-action agent for smoke-testing.

| Flag            | Default      | Description                          |
|-----------------|--------------|--------------------------------------|
| `--game`        | _required_   | Game slug                            |
| `--agent-name`  | `cli-bot`    | Agent display name                   |
| `--episodes`    | `1`          | Number of episodes to play           |
| `--steps`       | `100`        | Maximum steps per episode            |
| `--seed`        | _none_       | Random seed for reproducibility      |
| `--verbose`     | off          | Print per-episode summary lines      |

```
$ topgameai benchmark --game snake --episodes 3 --verbose
Benchmarking 'snake' for 3 episode(s) (max 100 steps each)...
Episode 1/3 | steps=100 score=3.0
Episode 2/3 | steps=100 score=1.0
Episode 3/3 | steps=87  score=5.0 won=True

Episode  Steps  Score  Won    Session ID
-------  -----  -----  -----  ------------------------------------
1        100    3.0    False  a1b2c3d4-...
2        100    1.0    False  e5f6a7b8-...
3        87     5.0    True   c9d0e1f2-...

Avg score: 3.00  Best: 5.0  Worst: 1.0
Win rate: 1/3
```

---

## Writing your own agent

An agent is any callable that accepts the current `session.state` dict and
returns an action:

```python
from topgameai import Arena
from typing import Any, Dict, Union

def my_snake_agent(state: Dict[str, Any]) -> str:
    """Greedy agent: always move towards the nearest food pellet."""
    head = state["head"]           # {"x": 5, "y": 3}
    food = state["food"][0]        # {"x": 7, "y": 3}

    if food["x"] > head["x"]:
        return "right"
    if food["x"] < head["x"]:
        return "left"
    if food["y"] > head["y"]:
        return "down"
    return "up"


arena = Arena()
results = arena.benchmark(
    "snake",
    my_snake_agent,
    agent_name="greedy-snake",
    n_episodes=20,
    verbose=True,
)
```

---

## Development

```bash
git clone https://github.com/topgameai/python-sdk
cd python-sdk
pip install -e ".[dev]"

# Type-check
mypy topgameai

# Lint
ruff check topgameai

# Run tests
pytest
```

---

## License

MIT — see `LICENSE` for details.
