Arena Hero Python
The official typed Python SDK for Arena Hero.
You own the game loop. The SDK connects to the HTTP and WebSocket APIs, parses
authoritative state, exposes control methods for every Unit type and Core, and
submits one complete plan when you call submit().
Documentation
- Quickstart: installation, synchronous and asynchronous loops, state access, Unit control, and local development.
- API reference: every client option, Turn field, control method, model, event, enum, and exception.
- Game rules and wire API: authoritative gameplay, HTTP, and WebSocket behavior.
Install
Python 3.11 or newer is required.
Install the published release:
pip install arena-hero
Synchronous game loop
from getpass import getpass
from arena_hero import ArenaHeroClient, Direction
api_key = getpass("Arena Hero API key: ")
with ArenaHeroClient(api_key=api_key) as game:
for turn in game.turns():
for worker in turn.workers:
if worker.position in turn.resource_cells:
worker.harvest()
else:
worker.move(Direction.RIGHT)
turn.submit()
worker.harvest() and worker.move() only queue actions on the current
Turn. They do not make network requests. turn.submit() sends the complete
queued plan in one HTTP request.
Asynchronous game loop
import asyncio
from getpass import getpass
from arena_hero import AsyncArenaHeroClient, Direction
async def play(api_key: str) -> None:
async with AsyncArenaHeroClient(api_key=api_key) as game:
async for turn in game.turns():
for vanguard in turn.vanguards:
vanguard.sweep(Direction.LEFT)
await turn.submit()
asyncio.run(play(getpass("Arena Hero API key: ")))
The synchronous and asynchronous clients use the same models and control methods:
ArenaHeroClientwithforandturn.submit()AsyncArenaHeroClientwithasync forandawait turn.submit()
Control interfaces
Every controlled object exposes its authoritative state through .view.
Calling another method for the same object replaces its earlier queued action
on that Turn.
| Object | Available methods |
|---|---|
Worker |
move, harvest, deposit, pickup_beacon, drop_beacon, self_destruct, wait, clear_action |
Vanguard |
move, sweep, pickup_beacon, drop_beacon, self_destruct, wait, clear_action |
Ranger |
move, shoot, pickup_beacon, drop_beacon, self_destruct, wait, clear_action |
Core |
spawn, repair_shield, start_move, cancel_move, pickup_beacon, drop_beacon, wait, clear_action |
Useful Turn data:
turn.tick
turn.state
turn.resources
turn.core
turn.units
turn.workers
turn.vanguards
turn.rangers
turn.visible_enemies
turn.resource_cells
turn.obstacle_cells
turn.beacon
turn.events
turn.plan
turn.resource_cells includes visible natural points and Worker cargo piles.
Pile amounts are not exposed; a partially recovered pile remains in the set.
Use event.resource_amount and event.harvest_source on turn.events to read
private cargo-drop and recovery results without unpacking values yourself.
Worker
worker = turn.workers[0]
worker.move(Direction.UP)
worker.harvest()
worker.deposit()
worker.pickup_beacon()
worker.drop_beacon()
worker.self_destruct()
worker.wait()
worker.clear_action()
If a Worker dies, including through self_destruct(), its complete cargo amount
becomes a recoverable resource pile on its final cell.
Vanguard
vanguard = turn.vanguards[0]
vanguard.move(Direction.DOWN)
vanguard.sweep(Direction.RIGHT)
vanguard.pickup_beacon()
vanguard.drop_beacon()
vanguard.self_destruct()
vanguard.wait()
Ranger
Pass a visible Unit or Core to derive both the target UUID and expected cell:
ranger = turn.rangers[0]
enemy = turn.visible_enemies[0]
ranger.shoot(enemy)
When you only have a UUID, provide the expected cell:
from uuid import UUID
target_id = UUID("8d60b600-78d4-4aba-83fd-4e5e27b88c9d")
ranger.shoot(target_id, expected_cell=(120, 85))
Core
The Core may be absent while the player is respawning.
if turn.core is not None:
turn.core.spawn(UnitType.WORKER)
turn.core.repair_shield()
turn.core.start_move(Direction.RIGHT)
turn.core.cancel_move()
turn.core.pickup_beacon()
turn.core.drop_beacon()
turn.core.wait()
Complete event stream
game.turns() yields each actionable Tick once. Use game.events() when the
program also needs tick notices and canonical plans submitted by this or
another client:
from arena_hero import Received, Tick, Turn
with ArenaHeroClient(api_key=api_key) as game:
for event in game.events():
if isinstance(event, Tick):
current_tick = event.tick
elif isinstance(event, Turn):
# Queue actions, then submit one complete plan.
event.submit()
elif isinstance(event, Received):
latest_received = event
The latest current-Tick AGENT and MANUAL plans are also available through
game.latest_receipts. A new Received value replaces the earlier value for
that source.
Use either events() or turns() on a client, not both at the same time.
Direct plan submission
Advanced callers can build and submit the exact public protocol model:
from uuid import UUID
from arena_hero import CommandPlan, Direction, MoveAction
plan = CommandPlan(
tick=turn.tick,
unit_actions={
UUID("9d3e4941-2816-4a39-a220-df8cd95e877d"): MoveAction(direction=Direction.UP)
},
)
receipt = game.submit(plan)
For asynchronous code, use await game.submit(plan).
Connection behavior
The SDK:
- sends the API key only in the
Authorizationheader; - never reads credentials or endpoints from environment variables;
- disables WebSocket message compression to match the server contract;
- uses protocol Ping/Pong automatically;
- reconnects with jittered exponential backoff from 250 ms to 5 seconds;
- retries an uncertain HTTP submission with the same idempotency key and exact request bytes;
- stops on WebSocket close code
1008; - treats each
stateas a complete replacement; - preserves unknown resolution event names and reason codes as strings.
The default backend is https://api.arenahero.io. Pass test endpoints
explicitly:
client = ArenaHeroClient(
api_key=api_key,
base_url="http://localhost:8080",
websocket_url="ws://localhost:8080/api/v1/game/ws",
)
Errors
All SDK exceptions inherit from ArenaHeroError.
| Exception | Meaning |
|---|---|
ConfigurationError |
Invalid constructor option or idempotency key |
AuthenticationError |
WebSocket authentication was rejected |
PolicyViolationError |
WebSocket closed with code 1008 |
ProtocolError |
The server returned an invalid public-protocol message |
APIError |
The command API returned a structured rejection |
TransportError |
A network operation failed after safe retries |
TurnClosedError |
Code tried to change a Turn after a newer Tick arrived |
InvalidActionError |
A local action target or owned Unit was invalid |
Dynamic gameplay failures are not exceptions. They arrive in the next
Turn.events as normal resolution results.
Development
This project uses uv, a src/ layout, and a locked development environment.
uv sync --locked --all-groups
uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run bandit -r src -c pyproject.toml
uv run pytest
uv build
The public game protocol is documented at doc.arenahero.io.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file arena_hero-0.2.0.tar.gz.
File metadata
- Download URL: arena_hero-0.2.0.tar.gz
- Upload date:
- Size: 77.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2d10a4f70bab075931b8a2b2d6d33f04bf8a7bd341585e887085e7fc28af48f
|
|
| MD5 |
2bfd8c38939c90ea43fe249bcdf0b96d
|
|
| BLAKE2b-256 |
440ccd97875ca1cd33d07ec20be65680157a4bc8b8d51c4f8da25d4075a9ccf2
|
Provenance
The following attestation bundles were made for arena_hero-0.2.0.tar.gz:
Publisher:
publish.yml on arena-hero/arena-hero-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arena_hero-0.2.0.tar.gz -
Subject digest:
e2d10a4f70bab075931b8a2b2d6d33f04bf8a7bd341585e887085e7fc28af48f - Sigstore transparency entry: 2280324908
- Sigstore integration time:
-
Permalink:
arena-hero/arena-hero-python@9cc5a2054d2375d80a83c67d6da12df3bb2bb149 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/arena-hero
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9cc5a2054d2375d80a83c67d6da12df3bb2bb149 -
Trigger Event:
release
-
Statement type:
File details
Details for the file arena_hero-0.2.0-py3-none-any.whl.
File metadata
- Download URL: arena_hero-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
327e0abe357f685f1fbe5fcca38644f6a2c1be82d4f30ca73963ff3294d608cf
|
|
| MD5 |
0df71500c99a7ecc50a8dbae9c341222
|
|
| BLAKE2b-256 |
f72a1d6a2233b1cf4c7b0df043664efeffaa1cb255b508b731291f0fa01d7fc6
|
Provenance
The following attestation bundles were made for arena_hero-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on arena-hero/arena-hero-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arena_hero-0.2.0-py3-none-any.whl -
Subject digest:
327e0abe357f685f1fbe5fcca38644f6a2c1be82d4f30ca73963ff3294d608cf - Sigstore transparency entry: 2280324918
- Sigstore integration time:
-
Permalink:
arena-hero/arena-hero-python@9cc5a2054d2375d80a83c67d6da12df3bb2bb149 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/arena-hero
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9cc5a2054d2375d80a83c67d6da12df3bb2bb149 -
Trigger Event:
release
-
Statement type: