Metadata-Version: 2.4
Name: game-ai-arena-sdk
Version: 0.6.0
Summary: Python SDK for building and running Game Arena competition bots
Author: Game Arena Team
Keywords: ai,board-game,bot,competition,game-arena,sdk,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Games/Entertainment
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: prompt-toolkit<4,>=3.0
Requires-Dist: pydantic>=2.0
Requires-Dist: websockets>=12.0
Description-Content-Type: text/markdown

# Game Arena Python SDK

Build Python bots for Game Arena. Implement `on_move()`; the SDK handles
authentication, matchmaking, game connections, and the turn protocol.

Requires Python 3.11+. Game rules and board formats live in the **Bot Docs**
page of the Game Arena web app.

## Install and prepare

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

Use SDK `0.6.0` or newer for continuous ranked play.

Create a bot in the web app and save its Bot ID and API key. The key may only be
shown once. Then configure the matchmaker URL supplied by your administrator:

```bash
export MATCHMAKER_URL="wss://<arena-host>/matchmaking/ws"
```

Use `ws://` only for an arena served without HTTPS.

## Your first bot

This complete FlipFlop bot chooses a random legal move:

```python
import random

from game_ai_arena_sdk import Bot, GameStateLoop, Move, start_continuous


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


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

if __name__ == "__main__":
    start_continuous(bot)
```

```bash
python my_bot.py
```

`start_continuous()` verifies and displays the bot's registered game and arena
before joining ranked matchmaking. Its compact live display shows the runner
state, opponent, Bot Run record, and last committed result without flooding the
terminal. Its commands are `pause`, `resume`, `status`, `stop`, `help`,
`display compact`, and `display verbose`. Commands remain editable while games
update the display. Stop and Ctrl-C finish any active game before disconnecting.

## Adapt it for your game

Always choose from `state.legal_moves`, then preserve the fields required by
that game:

| Game | `GameType` | Returned `Move` |
|---|---|---|
| FlipFlop 3x3 | `GameType.FLIPFLOP_3X3` | `Move(from_pos=piece.pos, to_pos=destination)` |
| FlipFlop 5x5 | `GameType.FLIPFLOP_5X5` | `Move(from_pos=piece.pos, to_pos=destination)` |
| FlipFour | `GameType.FLIPFOUR` | Also pass `side` for a piece in `"HAND"` |
| Amoeba | `GameType.AMOEBA` | Also pass the legal move's `splitting` value |

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

# Amoeba
return Move(
    from_pos=piece.pos,
    to_pos=destination,
    splitting=piece.splitting,
)
```

Missing these fields is a common cause of `INVALID_MOVE`.

## Build a better bot

The random bot only proves the connection works. A stronger bot should:

1. Parse `state.board` using its game's documented format.
2. Evaluate every legal action from `state.my_side` using heuristics, search,
   or a model.
3. Return the best action with its game-specific fields intact.
4. Run in Practice Mode before entering ranked play.

Keeping this logic in `choose_move(state) -> Move` separates bot strategy from
SDK lifecycle code.

## Execution modes

`start*()` functions block and suit ordinary scripts. `run*()` functions are
coroutines for code that already uses `asyncio`.

| Need | API |
|---|---|
| Interactive continuous ranked play (recommended) | `start_continuous(bot, display="compact")` |
| Headless continuous ranked play | `await run_continuous(bot, game_type)` |
| One ranked game | `start(bot, game_type)` / `await run(bot, game_type)` |
| One practice game | `start_practice(...)` / `await run_practice(...)` |
| Custom continuous controls | `ContinuousRunner` |

`start_continuous()` detects the registered game and adds terminal controls.
Compact display is the default; pass `display="verbose"` or switch modes while
running. Redirected input or output automatically uses plain verbose output.
Headless `run_continuous()` requires an explicit `GameType` and returns the
final immutable `RunnerStatus`, including safe failure details when the runner
fails.

Practice Mode connects directly to a room from the web app and does not affect
rating:

```python
from game_ai_arena_sdk import start_practice

start_practice(
    bot,
    room_id="ROOM_ID_FROM_PRACTICE_ARENA",
    game_server_ws="wss://GAME_SERVER_URL_FROM_PRACTICE_ARENA",
)
```

For custom operation, `ContinuousRunner` provides `pause()`, `resume()`,
`status()`, `stop()`, and `wait()`.

If phase visibility changes while the bot is queued, the continuous runner
rejoins automatically on the same Bot Run and preserves whether it was paused.
A switch to Blind removes an earlier rating from public status and the compact
display even when the bot is already playing.

## Core data

| Type | Important fields |
|---|---|
| `GameStateLoop` | `board`, `my_side`, `current_turn`, `legal_moves` |
| `PieceMovesInfo` | `name`, `pos`, `valid_moves`, optional `splitting` |
| `Move` | `from_pos`, `to_pos`, optional `side`, optional `splitting` |
| `MatchFinishedResult` | `result`, `end_reason`, `opponent`, `run`, `finished_at`, optional `rating` |
| `RunnerStatus` | `state`, `run`, `opponent`, `last_result`, safe failure fields |

`board` is game-specific. `legal_moves` is the server-provided set of allowed
actions. `MatchFinishedResult` is immutable and arrives after a continuous
ranked result is committed; its rating is absent during phases that hide it.

## Lifecycle hooks

Override only what you need. `on_move()` is the only required hook.

| Hook | Called when |
|---|---|
| `on_move(state) -> Move` | Your turn requires a move |
| `on_ready()` | The SDK connects |
| `on_queue_entry()` | The bot joins matchmaking |
| `on_queue_exit()` | The bot leaves the queue or finds a match |
| `on_match_found(game_id)` | A game is assigned |
| `on_room_joined(room_id)` | The game room is joined |
| `on_game_start(state)` | Gameplay starts |
| `on_game_end(winner, state)` | Gameplay ends |
| `on_match_finished(result)` | The ranked result is committed in continuous play |
| `on_disconnect(reason)` | Reserved for disconnect notifications |

There is no `on_error` hook. Handle decision errors inside `on_move()` and SDK
failures around the chosen entry point.

## Errors and troubleshooting

Game-server protocol failures raise `GameArenaProtocolError` with `code`,
`message`, and optional `details`. Continuous play fails closed after an
uncertain failure instead of silently joining another game.

| Problem | Check |
|---|---|
| Authentication rejected | Bot ID and API key |
| `INVALID_MOVE` | Use `legal_moves`; preserve `side` or `splitting` |
| Cannot connect | Arena hostname and `ws://` versus `wss://` |
| Two test bots never match | Bots owned by the same user cannot match |
| Continuous play stops | Read the display or `RunnerStatus.failure_code` and `failure_message`; unsafe requeueing is prevented |

For game rules, coordinates, strategy, arena URLs, and deeper troubleshooting,
open **Bot Docs** in the Game Arena web app.
