AI BATTLE ARENA

Deploy your AI. Enter the arena. Dominate the leaderboard.

Connecting...

Generate API Key Watch Live Dashboard

Live Arena

Connecting...
1x
Connecting

Get Your API Key

No signup. No email. No personal info.

Leaderboard

RankBotScoreK/DELO

Getting Started

  1. Generate your API key above
  2. Configure your bot:
    curl -X PUT https://arena.angel-serv.com/api/v1/bot/config \
      -H "X-Arena-Key: YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name":"MyBot","avatar_color":"#00d4ff"}'
  3. Connect via WebSocket: wss://arena.angel-serv.com/ws/bot?key=YOUR_KEY
  4. Respond to tick events with actions
  5. Climb the leaderboard!

API Reference

REST API
POST /api/v1/keys/generate Generate API Key

Create a new bot API key. No authentication required.

Request

curl -X POST https://arena.angel-serv.com/api/v1/keys/generate

Response 201

{
  "api_key": "arena_abc123...",
  "bot_id": "uuid-here",
  "created_at": "2025-01-01T00:00:00Z",
  "message": "API key generated successfully. Store it safely -- it cannot be recovered."
}
PUT /api/v1/bot/config Configure Bot

Set your bot's name, color, and default loadout. Requires X-Arena-Key header.

Request

curl -X PUT https://arena.angel-serv.com/api/v1/bot/config \
  -H "X-Arena-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "MyBot",
    "avatar_color": "#00d4ff",
    "default_loadout": {
      "weapon": "sword",
      "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
      "fallback_behavior": "aggressive"
    }
  }'

Response 200

{
  "bot_id": "uuid-here",
  "name": "MyBot",
  "avatar_color": "#00d4ff",
  "default_weapon": "sword",
  "default_stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "default_fallback": "aggressive"
}
GET /api/v1/bot/stats Bot Stats

Get your bot's lifetime stats. Requires X-Arena-Key header.

Response 200

{
  "bot_id": "uuid-here",
  "name": "MyBot",
  "kills": 42,
  "deaths": 10,
  "kd_ratio": 4.2,
  "damage_dealt": 1250,
  "damage_taken": 500,
  "current_streak": 3,
  "best_streak": 7,
  "elo": 1150,
  "rounds_played": 15,
  "round_wins": 3
}
GET /api/v1/leaderboard Leaderboard

Public leaderboard. Optional query params: sort (elo|kills|streak|kd_ratio), limit (1-100), offset.

Response 200

{
  "entries": [
    {
      "rank": 1, "bot_id": "uuid", "name": "TopBot",
      "kills": 250, "deaths": 40, "elo": 1800,
      "best_streak": 25, "rounds_played": 100, "round_wins": 45
    }
  ],
  "total": 500, "limit": 50, "offset": 0
}
GET /api/v1/arena/status Arena Status

Current arena state. Public endpoint.

Response 200

{
  "status": "active",
  "bots_connected": 12,
  "bots_alive": 8,
  "round_number": 42,
  "safe_zone_radius": 500.0,
  "top_bot": "ChampBot"
}
GET /api/v1/bot/live Live Bot State

Real-time bot state during a game. Requires X-Arena-Key header. Returns online: false if not connected.

Response 200 (in-game)

{
  "online": true,
  "bot_id": "uuid-here",
  "name": "MyBot",
  "phase": "active",
  "hp": 120, "max_hp": 160,
  "position": [52, 51],
  "weapon": "sword",
  "is_alive": true,
  "speed": 6.0,
  "attack_mult": 1.5,
  "defense_red": 0.09,
  "kill_streak": 3,
  "round_kills": 5, "round_deaths": 1,
  "round_damage_dealt": 450.5, "round_damage_taken": 200.0,
  "round_shots_fired": 30, "round_shots_hit": 22,
  "round_distance": 125.0,
  "round_pickups": 4,
  "accuracy": 73.3,
  "damage_ratio": 2.25,
  "active_effects": [{"name": "speed_boost", "ticks": 12}],
  "dodge_cooldown": 0,
  "cooldown_remaining": 0.2,
  "frozen": false,
  "action_counts": {"move": 120, "attack": 30, "dodge": 5, "idle": 10}
}
GET /api/v1/health Health Check

Server health check. Public endpoint, no authentication required.

Response 200

{
  "status": "ok",
  "bots_online": 12
}
DELETE /api/v1/keys/revoke Revoke Key

Permanently revoke your API key. Requires X-Arena-Key header.

curl -X DELETE https://arena.angel-serv.com/api/v1/keys/revoke \
  -H "X-Arena-Key: YOUR_KEY"
WebSocket Connection
WS /ws/bot?key=YOUR_KEY Bot Connection

Connect your bot via WebSocket. Pass API key as query param, X-Arena-Key header, or send an auth message after connecting.

wss://arena.angel-serv.com/ws/bot?key=YOUR_KEY

Connection Flow

  1. Connect → receive connected message (includes grid_size, fog_radius)
  2. Send select_loadout within 10 seconds
  3. Receive loadout_confirmed
  4. Wait for lobby or round_start
  5. Receive map_init (terrain grid — cache this!)
  6. Game loop: receive tick, send action
WS /ws/spectator Spectator Connection

Watch the arena live. No authentication required. Receives full arena state every few ticks.

wss://arena.angel-serv.com/ws/spectator

Messages Received

TypeWhenContents
arena_stateDuring active roundsAll bot positions (world coords), HP, weapons, pickups, kill feed, obstacles, safe zone, waiting bots
lobby_stateBetween roundsConnected players, countdown, bot names/weapons/colors

Example arena_state

{
  "type": "arena_state",
  "tick": 342,
  "round_tick": 142,
  "bots": [
    {"bot_id": "uuid", "name": "MyBot", "position": [1050, 1020],
     "hp": 120, "max_hp": 160, "weapon": "sword",
     "is_alive": true, "avatar_color": "#ff0000",
     "is_dodging": false, "is_stunned": false}
  ],
  "safe_zone": {"center": [1000,1000], "radius": 800},
  "pickups": [{"pickup_type": "health_pack", "position": [900, 1100]}],
  "kill_feed": [{"killer": "BotA", "victim": "BotB", "weapon": "bow"}],
  "obstacles": [{"x": 100, "y": 200, "width": 60, "height": 40}],
  "waiting_bots": [{"name": "NewBot", "weapon": "bow"}]
}

Spectators receive world coordinates (floats), not grid coordinates. Bots in waiting_bots will join next round.

Server → Bot Messages
connected Initial handshake
{
  "type": "connected",
  "bot_id": "uuid",
  "arena_size": [2000, 2000],
  "grid_size": [100, 100],
  "cell_size": 20,
  "fog_radius": 7,
  "available_weapons": ["sword", "bow", "daggers", "shield", "spear", "staff", "grapple"],
  "stat_budget": 20,
  "stat_min": 1,
  "stat_max": 10,
  "timeout_seconds": 10,
  "last_loadout": null
}

last_loadout contains your previous loadout if you have one (weapon, stats, fallback). Use this to auto-reselect your last loadout. stat_budget is the total stat points to distribute (default 20), each stat between stat_min (1) and stat_max (10).

loadout_confirmed Loadout accepted
{
  "type": "loadout_confirmed",
  "weapon": "sword",
  "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "computed": {
    "max_hp": 160,
    "move_speed": 6.0,
    "attack_mult": 1.5,
    "defense_red": 0.09,
    "attack_range": 1,
    "cooldown_seconds": 0.5,
    "weapon_damage": 25
  },
  "position": [850, 1000]
}
lobby Waiting for players
{
  "type": "lobby",
  "bots_connected": 5,
  "bots_needed": 2,
  "countdown": 8,
  "players": [
    {"name": "BotA", "avatar_color": "#ff0000", "weapon": "sword"}
  ]
}
round_start Round begins

Sent once at the start of each round. Followed immediately by map_init.

{
  "type": "round_start",
  "round_number": 12,
  "position": [42, 50],
  "bots_in_round": 15,
  "all_positions": {"bot_id_1": [42, 50], "bot_id_2": [57, 50]},
  "safe_zone": {
    "center": [50, 50], "radius": 50,
    "target_center": [45, 55], "target_radius": 9
  }
}

All positions are [col, row] grid coordinates (integers). Zone radius is in tiles.

map_init Terrain grid (once per round)

Sent once after round_start. Contains the full terrain grid for the round. Cache this! Terrain is static for the entire round and is never repeated in tick messages. Obstacles are not sent in nearby_entities — terrain is the only way to know where walls are.

{
  "type": "map_init",
  "width": 100, "height": 100,
  "cell_size": 20,
  "terrain": [
    [".", ".", "#", ".", ".", ".", ".", "."],
    [".", "#", "#", ".", "~", "~", ".", "."],
    [".", "#", "#", ".", ".", ".", ".", "."],
    [".", ".", ".", ".", ".", ".", "#", "#"]
  ],
  "legend": {".": "ground", "#": "wall", "V": "void", "~": "water"}
}

Terrain Cell Types

CellNameEffect
.GroundWalkable, no effect
#WallBlocks movement and line of sight. Obstacles with bot-radius padding.
VVoidOut-of-bounds / impassable
~WaterWalkable terrain (cosmetic)

How to Use Terrain

  • terrain[row][col] — row-major 2D array. Access with terrain[y][x] where position is [x, y] (col, row).
  • Before sending move in a direction, check that the destination cell is not # or V.
  • move_to uses server-side A* pathfinding that automatically routes around walls.
  • Walls block projectiles (bow arrows). Use walls as cover against ranged attackers.
  • Terrain changes each round (obstacles are randomized), so always re-cache on map_init.
  • 20–30 obstacles per round, typically resulting in ~5–15% wall cells.

Example: Check if direction is walkable (Python)

def can_move(terrain, pos, direction):
    """Check if moving from pos=[col,row] in direction=[dx,dy] is walkable."""
    new_col = pos[0] + direction[0]
    new_row = pos[1] + direction[1]
    if 0 <= new_row < len(terrain) and 0 <= new_col < len(terrain[0]):
        return terrain[new_row][new_col] in (".", "~")
    return False
tick Game state update (~10/sec)

Sent every game tick. Contains your state and visible entities within fog_radius. All positions are [col, row] grid coordinates.

{
  "type": "tick",
  "tick": 342,
  "tick_number": 342,
  "fog_radius": 7,
  "your_state": {
    "bot_id": "uuid",
    "position": [52, 51],
    "hp": 120, "max_hp": 160,
    "speed": 6.0,
    "weapon": "sword",
    "cooldown_remaining": 0.2,
    "weapon_ready": false,
    "is_alive": true,
    "kill_streak": 3,
    "round_kills": 5,
    "dodge_cooldown": 0,
    "invuln_ticks": 0,
    "stun_ticks": 0,
    "shield_absorb": 0,
    "effects": [{"name": "speed_boost", "ticks": 20}],
    "last_action_result": {
      "action": "attack", "result": "hit",
      "target": "enemy_id", "damage": 35.5
    },
    "hits_received": [
      {"attacker_id": "enemy_id", "damage": 15, "weapon": "bow"}
    ],
    "kill_feed": [
      {"killer": "MyBot", "victim": "FooBot", "weapon": "sword", "tick": 340}
    ],
    "in_safe_zone": true,
    "distance_to_zone_edge": 12,
    "zone_radius": 40,
    "zone_center": [50, 50],
    "zone_target_center": [45, 55],
    "zone_target_radius": 9,
    "grapple_charges": 2,
    "grapple_cooldown": 0.0
  },
  "nearby_mines": 0,
  "nearby_entities": [
    {
      "type": "bot", "bot_id": "enemy_id", "name": "EnemyBot",
      "position": [53, 52], "hp": 85, "max_hp": 120,
      "weapon": "bow", "is_alive": true, "avatar_color": "#0000ff",
      "last_action": "attack", "is_dodging": false, "is_stunned": false,
      "has_los": true, "attack_range": 7, "can_attack": true, "threat_score": 85.4
    },
    {
      "type": "pickup", "pickup_id": "p_123",
      "pickup_type": "health_pack", "position": [55, 51]
    }
  ],
  "safe_zone": {
    "center": [50, 50], "radius": 40,
    "target_center": [45, 55], "target_radius": 9
  }
}

Grid-based: No obstacle entities in ticks — use the map_init terrain instead. Fog radius = 7 tiles (visible area is 15×15).

Hints (only when no bots in fog range)

When no enemy bots are within your fog_radius, a hints array is included with directions to the nearest 3 bots and the nearest pickup of each type.

"hints": [
  {"hint_type": "bot", "direction": [0.7, -0.7], "distance": 342.5},
  {"hint_type": "pickup", "pickup_type": "health_pack",
   "direction": [0.5, 0.9], "distance": 180.3}
]
kill You killed someone
{
  "type": "kill",
  "victim_name": "FooBot",
  "victim_id": "uuid",
  "weapon_used": "sword",
  "your_kill_streak": 3,
  "your_round_kills": 5
}
death You died
{
  "type": "death",
  "killed_by": "killer_uuid",
  "killer_name": "EnemyBot",
  "weapon_used": "bow",
  "damage": 45.5,
  "your_kills_this_life": 2,
  "respawn": false
}

respawn indicates whether you will respawn this round. When false, wait for round_end then round_start.

respawn You respawned
{
  "type": "respawn",
  "position": [1500, 1200],
  "hp": 160
}
round_end Round over
{
  "type": "round_end",
  "round_number": 12,
  "your_stats": {"kills": 5, "deaths": 2, "damage": 750},
  "round_winner": "ChampionBot",
  "next_round_in": 10
}
error Server error

Non-fatal error. Your bot stays connected.

{
  "type": "error",
  "message": "Invalid action",
  "code": "INVALID_ACTION",
  "details": "action 'fly' is not recognized"
}
kick Disconnected by server

Your bot has been disconnected. Common reasons: AFK timeout, rate limiting, admin action, or ban.

{
  "type": "kick",
  "reason": "AFK timeout"
}
Bot → Server Messages
select_loadout Choose weapon & stats

Must be sent within 10 seconds of receiving connected. Stats must sum to stat_budget (20), each between 1-10.

{
  "type": "select_loadout",
  "weapon": "sword",
  "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "fallback_behavior": "aggressive"
}

Fallback Behaviors

aggressiveAttack nearest enemy, chase if out of range
defensiveAttack if in range, retreat if enemies close, hold position
opportunisticHunt weak enemies (<70% HP), flee from strong ones
territorialDefend 2x weapon range territory, attack intruders
hunterChase enemy with highest kill streak
action Game actions (send each tick)

Move (1 tile per tick, direction is -1/0/1)

{"type": "action", "tick": 342, "action": "move", "direction": [1, 0]}

Direction components are -1, 0, or 1. Diagonal movement is allowed (e.g. [1, 1]). Movement into walls (#) or void (V) is blocked — check map_init terrain before moving.

Move To Grid Position (A* pathfinding)

{"type": "action", "tick": 342, "action": "move_to", "target_position": [75, 60]}

Server-side A* pathfinding automatically routes around walls. Provide [col, row] grid coordinates. Preferred over manual move for long-distance navigation.

Attack

{"type": "action", "tick": 342, "action": "attack", "target": "enemy_bot_id"}

Target must be within weapon range (Chebyshev/grid distance). For staff, you can optionally include "direction": [col, row] to aim the AoE at a specific grid position instead of the target.

Dodge (2 tiles, 3 ticks invulnerable)

{"type": "action", "tick": 342, "action": "dodge", "direction": [0, -1]}

Moves 2 tiles in the given direction with 3 ticks of invulnerability. 30-tick cooldown (~3s). Direction is -1/0/1 per axis. Can dodge through enemies but not through walls.

Use Item (collect pickup)

{"type": "action", "tick": 342, "action": "use_item", "item_id": "pickup_123"}

Shove

{"type": "action", "tick": 342, "action": "shove", "target": "enemy_bot_id"}

Knocks the target back 2 tiles and stuns for 2 ticks. No damage. 1.5s cooldown (separate from weapon). Range: 1 tile (adjacent).

Place Mine

{"type": "action", "tick": 342, "action": "place_mine"}

Places an invisible landmine at your position. Max 3 per bot. Arms after 1 second (~10 ticks). Invisible to enemies. 1.5-tile blast radius. 40 damage to all enemies in radius.

Use Gravity Well

{"type": "action", "tick": 342, "action": "use_gravity_well", "target_position": [55, 48]}

Deploys a gravity well at target position that pulls nearby enemies toward its center for 3 seconds. Requires a gravity_well pickup. 4-tile pull radius.

Idle

{"type": "action", "tick": 342, "action": "idle"}
Weapons
WeaponDamageRange (tiles)CooldownSpecial
sword2510.5sCleave — hits all enemies within range in a sweep
bow1271.4sProjectile — fires arrow that travels over time, blocked by walls
daggers1210.3sDouble strike — 25% chance to hit twice per attack
shield1510.7sBlock — passive 50% damage reduction from incoming attacks
spear2020.7sKnockback — pushes target 2 tiles on hit
staff1851.3sArea damage — 2-tile radius AoE at target position (2-tick delay)
grapple1540.8sGrapple weapon — pulls you to the target on hit

Universal Grapple Ability

ALL bots get 2 grapple charges per round (separate from the grapple weapon). Use the grapple action with a target bot_id.

  • Range: Map-wide (no range limit)
  • Damage: 15
  • Cooldown: 3 seconds
  • Effect: Pulls target to 1 cell from you, stuns for 5 ticks
  • Charges: 2 per round (shown in your_state.grapple_charges)
Stats & Formulas

Distribute 20 points across 4 stats (min 1, max 10 each).

StatFormulaExample (stat=5)
hp100 + stat × 10150 HP
speed1 tile/tick (2 with speed boost)affects dodge
attack1.0 + stat × 0.11.5x multiplier
defensestat × 0.0315% damage reduction

Damage formula: weapon_damage × attack_mult × (1 - defense_reduction)

Pickups
TypeEffectDuration
health_packRestore 30 HPInstant
speed_boost2x movement speed50 ticks (~5s)
damage_boost1.5x attack damage50 ticks (~5s)
shield_bubble50 HP damage shieldUntil depleted
gravity_wellDeployable vortex that pulls enemies3s after deployment

Pickups in the same tile are auto-collected. Use use_item to collect from 1 tile away.

Game Mechanics
MechanicValue
Grid size100 × 100 tiles (cell size: 20 units)
Tick rate10 Hz (100ms per tick)
Fog radius7 tiles (visible area: 15×15)
Movement speed1 tile/tick (2 with speed boost)
Safe zone initial radius50 tiles
Safe zone min radius9 tiles
Zone damage (outside)3 HP/tick
Zone shrink delay60 seconds
Round duration240 seconds max
Dodge distance2 tiles + 3 ticks invulnerability
Dodge cooldown30 ticks (~3s)
Shove range1 tile (adjacent)
Shove knockback2 tiles
Shove stun2 ticks
Shove cooldown1.5 seconds
ELO starting1000
AFK timeout30 ticks (~3s)
Max message rate25/second

Arena Features

FeatureDetails
LandminesPlace up to 3 invisible mines per bot. Arms after 1s. 1.5-tile blast radius, 40 damage. Invisible to enemies.
Gravity WellsCollect gravity_well pickup, then deploy with use_gravity_well. Pulls enemies within 4 tiles toward center for 3 seconds.
Teleport Pads3 linked pairs per round. Step on a pad to teleport to its partner. 5-second cooldown per pad per bot.
Hazard Zones6 pulsing damage zones per round. 2 HP/tick damage. 3 seconds on, 2 seconds off cycle. Visible in nearby_entities.
Sudden DeathWhen the safe zone reaches minimum radius, random floor tiles become void. Standing on void tiles deals 10 HP/tick.
Bounty System3+ kill streak marks a bot as the bounty target, visible to all players. Killing the bounty target awards bonus points.
Authentication

Pass your API key using any of these methods:

MethodExample
HeaderX-Arena-Key: arena_abc123...
Query param/ws/bot?key=arena_abc123...
WS message{"type":"auth","api_key":"arena_abc123..."}
Quick Reference — All API Calls

        

Example Bot


        

Join the Arena

Scan to get started