Metadata-Version: 2.4
Name: game-ai-arena-sdk
Version: 0.5.3
Summary: Python SDK for building and running Game Arena competition bots
Requires-Python: >=3.11
Requires-Dist: pydantic>=2.0
Requires-Dist: websockets>=12.0
Description-Content-Type: text/markdown

# Game Arena Python SDK

Python SDK for building and running Game Arena competition bots.

## Installation

```bash
pip install --upgrade game-ai-arena-sdk==0.5.3
```

Use `0.5.3` or newer with the current Game Arena server.

## Quick Start

```python
import random
from game_ai_arena_sdk import Bot, start, GameType, Move, GameStateLoop

class MyBot(Bot):
    async def on_move(self, state: GameStateLoop) -> Move:
        piece = random.choice(state.legal_moves)
        dest = random.choice(piece.valid_moves)
        return Move(from_pos=piece.pos, to_pos=dest)

bot = MyBot(bot_id="YOUR_BOT_ID", api_key="YOUR_API_KEY")

start(bot, GameType.FLIPFLOP_3X3)
```

## Getting Your Credentials

Create a bot from the Game Arena web UI or the platform API docs page. You need
two values to run the SDK:

- `bot_id`
- `bot_api_key`

Save the API key when it is shown. It may only be displayed once.

## Running In Matchmaking

Set the matchmaker URL via environment variable or pass directly:

```bash
# Environment variable (recommended)
export MATCHMAKER_URL=ws://<SERVER_ADDRESS>:9000/matchmaking/ws

# Or for tunnel mode
export MATCHMAKER_URL=wss://<YOUR_TUNNEL>.trycloudflare.com/matchmaking/ws
```

Get the correct URL from your admin.

```python
start(bot, GameType.FLIPFLOP_3X3)
# Or:
start(bot, GameType.FLIPFLOP_3X3, matchmaker_url="ws://...")
```

## Practice Mode

Test your bot locally using the Practice Arena in the web UI. Create a session, then run your bot with the provided connection info:

```python
from game_ai_arena_sdk import Bot, start_practice, Move, GameStateLoop

class MyBot(Bot):
    async def on_move(self, state: GameStateLoop) -> Move:
        ...

bot = MyBot(bot_id="YOUR_BOT_ID", api_key="YOUR_API_KEY")

start_practice(
    bot,
    room_id="<from Practice Arena>",
    game_server_ws="<game server WebSocket URL from Practice Arena>",
)
```

Or run with env vars:

```bash
ROOM_ID=... GAME_SERVER_WS=... python my_bot.py
```

No matchmaker needed — your bot connects directly to the game room. Practice games don't affect ELO.

## Game State

Received in `on_move`:

| Field | Description |
|-------|-------------|
| `board` | Board string representation |
| `my_side` | Your side (`"white"` / `"black"`) |
| `current_turn` | Whose turn it is |
| `legal_moves` | List of `PieceMovesInfo` you can move |

Each `PieceMovesInfo` has:

| Field | Description |
|-------|-------------|
| `name` | Piece name or move variant |
| `pos` | Source position, or `"HAND"` for FlipFour placement |
| `valid_moves` | Destination positions this action can choose |
| `splitting` | Amoeba split-stack flag; pass it through when present |

## Hooks

Only `on_move` is required. Override these hooks if your bot needs lifecycle
events:

```python
async def on_move(self, state: GameStateLoop) -> Move  # Required
async def on_match_found(self, game_id: str) -> None   # Matched with opponent
async def on_room_joined(self, room_id: str) -> None   # Joined game room
async def on_game_start(self, state: GameStateLoop) -> None
async def on_game_end(self, winner: str | None, state: GameStateEnd) -> None
```

## Errors

Game-server errors during room join, game start, or gameplay raise
`GameArenaProtocolError`. Catch it around `start(...)` or `start_practice(...)`
if your bot needs custom logging or recovery.

## Multiple Bots

```python
import asyncio
from game_ai_arena_sdk import run

async def main():
    await asyncio.gather(
        run(MyBot("id1", "key1"), GameType.FLIPFLOP_3X3),
        run(MyBot("id2", "key2"), GameType.FLIPFLOP_3X3),
    )

asyncio.run(main())
```

> **Note:** Bots owned by the same user cannot be matched against each other. To test two bots locally, they must belong to different user accounts.

## Game Types

| Type | Enum | Description |
|------|------|-------------|
| FlipFlop 3x3 | `FLIPFLOP_3X3` | 3x3 board, rook/bishop pieces |
| FlipFlop 5x5 | `FLIPFLOP_5X5` | 5x5 board, rook/bishop pieces |
| FlipFour | `FLIPFOUR` | 5x5 board, 4 pieces per player, line-of-four wins |
| Amoeba | `AMOEBA` | Hexagonal grid, kernel capture wins |

## Game-Specific Notes

### FlipFour

When placing a piece from hand, pass the piece side through from `legal_moves`:

```python
side = piece.name if piece.pos == "HAND" else None
return Move(from_pos=piece.pos, to_pos=target, side=side)
```

### Amoeba

Use axial coordinates like `"0,0"` and pass the `splitting` value through from
`legal_moves`:

```python
return Move(from_pos=piece.pos, to_pos=target, splitting=piece.splitting)
```

For full game rules, API details, and examples, use the Game Arena `/docs` page.
