Skip to content

Latest commit

 

History

1,284 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI Code Battle

A competitive bot programming platform where participants write HTTP servers that control units on a grid world. Bots compete in real-time matches, and the winner is the last player with surviving cores.

Overview

  • Write a bot in any language — it just needs to run an HTTP server
  • The game engine calls your bot every turn with the current game state
  • Your bot responds with move orders for each of its units
  • Matches run offline; results are published as animated replays with a leaderboard
  • Ratings use Glicko-2 (same system as chess.com)

Table of Contents

  1. Game Rules
  2. Writing a Bot
  3. Starter Templates
  4. Strategy Bots
  5. Running Locally
  6. Project Structure
  7. Architecture
  8. Testing

Game Rules

The Grid World

Matches are played on a toroidal (wrapping) grid — moving off one edge brings you out the other side. Tiles:

Tile Symbol Effect
Open . Units can occupy and traverse
Wall # Impassable
Energy * Collected by units standing on it
Core C Win condition — protect yours, destroy theirs

Win Condition

The last player with at least one surviving Core tile wins. Cores are destroyed when an enemy unit occupies an undefended core tile during the Capture phase.

Turn Phases

Each turn resolves in this order:

  1. Move — Each unit executes its ordered direction (N, E, S, W) or stays
  2. Combat (focus-fire) — Units in the same tile deal damage; outnumbered units take extra damage
  3. Zone — A shrinking storm closes in from the edges, dealing damage to units caught in it
  4. Capture — Enemy units on undefended Core tiles raze them
  5. Collect — Units on Energy tiles collect energy for their player
  6. Spawn — The engine spawns units automatically: each of your active, unoccupied Cores produces one unit per turn while your energy covers the spawn_cost (default 3, deducted per unit). When energy allows only some cores to spawn, the core that spawned longest ago goes first (ties broken by lowest core ID). Bots cannot request spawns — there is no spawn order in the /turn response
  7. Energy Tick — Passive energy regeneration
  8. Endgame Check — Victory condition evaluated

Key Rules

  • Self-collision: If two or more of your own units move to the same tile, they all die
  • Energy economy: Spawning units costs energy and happens automatically at your Cores (see the Spawn phase above); energy is collected from * tiles and regenerated passively. You influence spawning indirectly — by keeping cores unoccupied and by collecting energy
  • Visibility: Bots only see tiles and units within their units' sight radius (fog of war)

Writing a Bot

A bot is an HTTP server with two endpoints. The game engine calls your server every turn.

Required Endpoints

GET /health

Returns 200 OK when your bot is ready. The engine polls this before starting a match.

GET /health HTTP/1.1

HTTP/1.1 200 OK

POST /turn

The engine sends the current game state; your bot responds with move orders.

Request headers:

Header Value
Content-Type application/json
X-ACB-Match-Id Match identifier string
X-ACB-Turn Current turn number (integer as string)
X-ACB-Timestamp Unix timestamp in seconds; requests older/newer than 30 seconds are rejected
X-ACB-Bot-Id Bot identifier string
X-ACB-Signature HMAC-SHA256 signature of this request

Request body (JSON game state):

{
  "match_id": "m_7f3a9b2c",
  "turn": 42,
  "config": {
    "rows": 40,
    "cols": 40,
    "max_turns": 500,
    "vision_radius2": 49,
    "attack_radius2": 25,
    "spawn_cost": 3,
    "energy_interval": 10,
    "cores_per_player": 2,
    "zone_enabled": true,
    "zone_start_turn": 10,
    "zone_shrink_interval": 1,
    "zone_shrink_step": 1,
    "zone_min_radius": 2,
    "kill_score": 1
  },
  "you": { "id": 0, "energy": 7, "score": 3 },
  "bots": [
    { "position": { "row": 10, "col": 15 }, "owner": 0 }
  ],
  "energy": [
    { "row": 20, "col": 25 }
  ],
  "cores": [
    { "position": { "row": 5, "col": 5 }, "owner": 0, "active": true }
  ],
  "walls": [
    { "row": 10, "col": 10 }
  ],
  "dead": []
}

Only tiles and units visible to your bots are included (fog of war).

Response Timeout and Inactivity

Each /turn request has a 3-second response budget. A timeout or transport/protocol error is treated as a no-op for that turn, so the bot's living units hold position. A single failure does not kill or disconnect the bot; it continues receiving future turns. Any successfully processed response received before the deadline, including a schema-valid response with no usable moves, resets the consecutive-failure count.

After 10 consecutive failed turn attempts, the engine marks the bot inactive for the rest of that match. It sends no further requests to that bot, leaves its living units in the game holding position, and continues the match for the other bots under the normal win conditions. The replay contains a bot_inactive event on the threshold turn, and result.crashed is true for that player. The inactive bot is not treated as eliminated merely because it stopped responding.

The budget itself is not a fixed constant: it arrives in every /turn request as config.turn_timeout (integer nanoseconds; the full contract is in docs/bot-protocol.md). Read it each turn instead of hardcoding a deadline, and treat an absent field as the 3-second default above. The clock starts when the engine begins waiting for your response. A response that misses the deadline is discarded even if it arrives a moment later — it is never applied to the turn it missed, nor credited to the next one.

Response body (JSON move orders):

{
  "moves": [
    { "position": { "row": 5, "col": 5 }, "direction": "N" },
    { "position": { "row": 7, "col": 8 }, "direction": "E" },
    { "position": { "row": 9, "col": 2 }, "direction": "stay" }
  ]
}

Valid directions: N, E, S, W, stay

The response schema is moves-only (plus the optional debug object). There is no spawn order field — spawning is resolved by the engine's automatic Spawn phase (see Turn Phases above), so spend your effort on movement and energy collection, not on spawn requests. moves is required and must be an array; every move must include integer position.row, integer position.col, and a direction from the list above. Malformed known fields invalidate the whole response, so the bot's units hold position. Unknown additive fields are ignored; only moves and debug have any effect.

Response header:

Header Value
X-ACB-Signature HMAC-SHA256 signature of your response body

HMAC Authentication

Every request and response is signed with HMAC-SHA256. The key is the UTF-8 bytes of your SHARED_SECRET environment variable. Hash the exact bytes received or sent; do not parse and re-serialize JSON before hashing.

Canonical request payload:

{match_id}.{turn}.{timestamp}.{sha256_hex(raw_body)}

Canonical response payload:

{match_id}.{turn}.{sha256_hex(raw_response_body)}

Each signature is HMAC-SHA256(key, canonical_payload), encoded as 64 lowercase hex characters.

To verify an incoming request:

  1. Require X-ACB-Match-Id, X-ACB-Turn, X-ACB-Timestamp, X-ACB-Bot-Id, and X-ACB-Signature
  2. Reject a timestamp more than 30 seconds before or after the current time
  3. Compute sha256(raw_body) as lowercase hex
  4. Build "{match_id}.{turn}.{timestamp}.{body_sha256}"
  5. Compute HMAC-SHA256(SHARED_SECRET, payload) as 32 bytes
  6. Decode the supplied 64-hex signature and compare the bytes in constant time
  7. Parse the authenticated body and require its match_id and turn to equal the headers

To sign your response:

  1. Compute sha256(raw_response_body) as lowercase hex
  2. Build "{match_id}.{turn}.{body_sha256}" using the authenticated request values
  3. Compute HMAC-SHA256(SHARED_SECRET, payload) and encode it as lowercase hex
  4. Set X-ACB-Signature to that value before writing the exact response bytes

Environment Variables

Variable Description
SHARED_SECRET Shared HMAC key provided by the match coordinator
PORT Port your HTTP server listens on (default: 8080)

Starter Templates

Ready-to-use templates are in the starters/ directory. Each implements /health and /turn with HMAC auth and a random-move strategy — replace the move logic with your own.

Language Directory
Python starters/python/
Go starters/go/
Rust starters/rust/
TypeScript starters/typescript/
JavaScript starters/javascript/
Java starters/java/
PHP starters/php/
C# starters/csharp/

Quick-start with Python

cd starters/python
pip install -r requirements.txt
SHARED_SECRET=test PORT=8080 python main.py

Then in another terminal, test your bot health:

curl http://localhost:8080/health

Strategy Bots

The bots/ directory contains 21 reference bots demonstrating different strategies. These serve as benchmarks and opponents in the automated ladder.

Bot Strategy
random Random valid moves — baseline reference
gatherer Energy collection priority, avoids combat
rusher Rushes enemy cores aggressively
guardian Defends cores, cautious expansion
swarm Formation cohesion, advances as a group
hunter Targets isolated enemy units
assassin Prioritizes high-value targets
coordinator Coordinates unit actions across the field
defender Pure defensive posture
economist Maximizes energy efficiency
farmer Long-term energy farming strategy
kamikaze Sacrificial rush tactics
leader-targeter Focuses fire on the strongest opponent
nomad Mobile, avoids prolonged engagements
opportunist Exploits momentary advantages
pacifist Avoids combat entirely
phalanx Tight defensive formation
raider Hit-and-run energy denial
scout Prioritizes map exploration
siege Methodical core destruction
zone-driver Exploits the shrinking zone mechanic

Bot implementations span Go, Rust, Python, TypeScript, PHP, and Java.

Bot Evolver

The acb-evolver/ component mutates and evolves bot strategies automatically. It runs genetic-algorithm-style evolution over strategy parameters, producing improved variants over time.


Running Locally

Prerequisites

  • Go 1.21+ (game engine and CLI tools)
  • Node.js 18+ (web app and worker API)
  • Docker (for containerized bots and deployment)

Build and Run a Match

# Build CLI tools
go build ./cmd/acb-local
go build ./cmd/acb-mapgen

# Run a match between two built-in bots (outputs replay JSON)
./acb-local -seed 42 -max-turns 100 -output replay.json -verbose

Test Your Bot

# Start the Python starter bot
cd starters/python
pip install -r requirements.txt
SHARED_SECRET=test PORT=8080 python main.py

# In another terminal — run a match against a built-in bot
./acb-local -bot1 http://localhost:8080 -bot2 builtin:rusher \
  -secret1 test -seed 42 -output replay.json -verbose

View Replays

# Start the web dev server
cd web && npm install && npm run dev
# Open http://localhost:3000/ → Replay Viewer → load replay.json

The replay viewer shows turn-by-turn animation on a canvas, win probability tracking, an event timeline, and auto-generated commentary.


Project Structure

ai-code-battle/
├── engine/              # Go game simulation library
│   ├── types.go         # Core data types
│   ├── grid.go          # Toroidal grid implementation
│   ├── game.go          # Game state management
│   ├── turn.go          # Turn execution phases
│   ├── replay.go        # Replay recording
│   └── *_test.go        # Test files
├── cmd/
│   ├── acb-local/       # CLI match runner (local testing)
│   ├── acb-mapgen/      # Map generator
│   ├── acb-worker/      # Match execution worker
│   ├── acb-api/         # Go match-tier API (undeployed; see docs/notes/public-api-descope.md)
│   ├── acb-matchmaker/  # Internal match scheduler
│   ├── acb-indexer/     # Index builder for static files
│   └── acb-evolver/     # LLM evolution pipeline
├── web/                 # Cloudflare Pages SPA
│   ├── src/
│   │   ├── replay-viewer.ts  # Canvas replay renderer
│   │   └── app.ts            # SPA entry point
│   └── functions/
│       ├── api/              # Same-origin /api/* transport (Pages Function)
│       └── r2/               # R2 replay serving (Pages Functions)
├── bots/                # Strategy bot implementations (21 bots)
├── starters/            # Starter templates (8 languages)
│   ├── python/
│   ├── go/
│   ├── rust/
│   ├── typescript/
│   ├── javascript/
│   ├── java/
│   ├── php/
│   └── csharp/
└── wasm/                # WebAssembly bot interface

Architecture

The platform uses a static-first architecture. The public-facing product is a Cloudflare Pages static site — nearly all data visitors see (leaderboards, match history, bot profiles, replays) is pre-computed JSON served from the CDN; the dynamic remainder is community interaction (replay/site feedback, map voting), served by a same-origin Pages Function under /api/* backed by R2 (see docs/notes/api-transport.md). All compute runs in a Kubernetes cluster (Rackspace Spot), which acts as a match factory: it runs battles, generates replays, and periodically publishes the updated site to Pages.

  • Cloudflare Pages — Static SPA (replay viewer, leaderboard, match history) with all data pre-computed as JSON
  • Cloudflare Pages Functions + R2 — Replay storage and serving through R2 bucket binding, plus the same-origin /api/* transport for community flows (match-tier routes answer 503 match_tier_offline until acb-api is deployed)
  • Kubernetes cluster — Match workers, matchmaker, API service, bot containers, and index builder

Match flow:

  1. Matchmaker queries active bots from PostgreSQL and enqueues match jobs to Valkey
  2. Match worker pulls a job from Valkey and starts both bot containers
  3. Engine runs the match turn-by-turn, calling each bot's /turn endpoint
  4. Replay JSON is uploaded to R2 via ARMOR credentials (B2-compatible API)
  5. Worker writes results to PostgreSQL
  6. Index builder reads PostgreSQL, generates JSON indexes, and deploys to Cloudflare Pages
  7. Web frontend fetches the new replay through Pages Functions (R2 proxy)

Rating system: Glicko-2 — accounts for rating reliability (RD) and rating volatility, converges faster than Elo, same algorithm used by chess.com.

Rating parameters (Glicko-2 paper defaults, implemented in cmd/acb-worker/glicko2.go, unit-tested in glicko2_test.go against the paper's worked example):

Parameter Value Meaning
Initial rating (μ) 1500 Every new bot starts at the center of the scale
Initial rating deviation (RD) 350 Maximal uncertainty — early matches move ratings quickly
System constant (τ) 0.5 Caps how fast volatility may change between rating periods (paper suggests 0.3–1.2)
Initial volatility (σ) 0.06 Paper's worked-example value

The leaderboard and bot profiles display μ with the RD shown as ±RD; the matchmaker's top-N selection and the evolver's culling order by the conservative estimate μ − 2φ. Ratings are updated by the match worker after every match (computeRatingUpdates in cmd/acb-worker/main.go), persisted to PostgreSQL, and can be rebuilt from scratch by replaying all matches (recalcRatings).

Publication pipeline (per match, in executeMatch): the worker computes updates for every participant, persists them (SubmitMatchResult writes the new μ/RD/σ to bots, the after-values to match_participants, and the display rating μ − 2φ to rating_history), then publishes the movement to leaderboard/live-delta.json in R2 via updateLiveDelta/mergeLiveDeltas — rating deltas measured on the display scale accumulate there between full index rebuilds, while the absolute rating/RD/match-record fields always reflect the latest match. The acb-index-builder rebuild turns the persisted ratings into data/leaderboard.json (its counts come from match_participants itself, so they are authoritative where live-delta's per-match record is only the current match). The math and the publication contract are pinned in glicko2_test.go and live_delta_test.go; rating_pipeline_test.go covers the DB persistence round trip (requires ACB_TEST_DATABASE_URL).


Testing

# Game engine unit tests
go test ./engine/... -v

# Web app tests
cd web && npm test

License

MIT


Part of jedarden.com

This GitHub repo is a read-only mirror of git.ardenone.com/jedarden/ai-code-battle — issues and PRs are welcome here either way.


Last CI test: 2026-08-28

Test: Fri Aug 28 08:11:34 AM EDT 2026

About

AI bot programming challenge platform — write bots that compete in a shared grid world

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages