# AI Code Battle — Implementation Plan ## 1. Overview AI Code Battle is a competitive bot programming platform where participants write HTTP servers that control units on a grid world. The game engine orchestrates matches asynchronously, stores replays, and serves a web platform where visitors watch rendered game replays and browse leaderboards. Matches are never live — they are evaluated offline by match workers and presented as completed replays. The platform ships with several built-in strategy bots, each deployed as its own container, serving as both opponents for new participants and reference implementations for the HTTP protocol. --- ## 2. System Architecture ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Web Platform │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │ │ │ Leaderboard │ │ Match History │ │ Replay Viewer (Canvas) │ │ │ └──────────────┘ └──────────────┘ └───────────────────────────┘ │ └──────────────────────────────┬──────────────────────────────────────┘ │ HTTPS ┌──────────▼──────────┐ │ API Server │ │ (user reg, bot reg, │ │ leaderboard, replay │ │ metadata, health) │ └──────────┬──────────┘ │ ┌────────────────┼────────────────┐ │ │ │ ┌────────▼───────┐ ┌─────▼──────┐ ┌───────▼────────┐ │ PostgreSQL │ │ Match │ │ Object Store │ │ (users, bots, │ │ Queue │ │ (S3-compat) │ │ matches, │ │ (Redis) │ │ replay JSON │ │ ratings) │ │ │ │ + map data │ └────────────────┘ └─────┬──────┘ └────────────────┘ │ ┌─────────▼─────────┐ │ Match Workers │ ← Rackspace Spot instances │ (stateless, │ │ interruptible) │ └─────────┬─────────┘ │ HTTP (per-turn requests) ┌───────────────┼───────────────┐ │ │ │ ┌────────▼──────┐ ┌─────▼─────┐ ┌───────▼──────┐ │ Participant │ │ Built-in │ │ Participant │ │ Bot A │ │ Strategy │ │ Bot B │ │ (external) │ │ Bots │ │ (external) │ └───────────────┘ │ (containers)│ └──────────────┘ └────────────┘ ``` ### Component Summary | Component | Role | Scaling Model | |-----------|------|---------------| | API Server | REST API for web platform, bot registration, match metadata | Horizontally scaled, always-on | | Match Worker | Pulls match jobs from queue, executes full game simulation, uploads replay | Stateless pods on Rackspace Spot | | Tournament Scheduler | Creates match jobs based on matchmaking algorithm | Single process, cron-like | | Web Frontend | Static SPA — replay viewer, leaderboard, registration | CDN / static hosting | | Strategy Bots | Built-in HTTP bots (one container each) | Always-on, lightweight | | PostgreSQL | Users, bots, matches, ratings | Single primary + read replica | | Redis | Match job queue, rate limiting, caching | Single instance | | Object Store | Replay JSON files, map definitions | S3-compatible (Minio or provider) | --- ## 3. Game Mechanics ### 3.1 Map & Grid The game plays on a **toroidal grid** — a rectangular grid that wraps both horizontally and vertically (no edges, no corners). This eliminates positional advantages from map boundaries. **Tile types:** | Tile | Symbol | Description | |------|--------|-------------| | Open | `.` | Passable empty tile | | Wall | `#` | Impassable barrier | | Energy | `*` | Collectible resource (respawns) | | Core | `C` | Player spawn point (owned by a player) | **Grid parameters (configurable per match):** | Parameter | Default | Range | Description | |-----------|---------|-------|-------------| | `rows` | 60 | 30–120 | Grid height | | `cols` | 60 | 30–120 | Grid width | | `wall_density` | 0.15 | 0.05–0.30 | Fraction of tiles that are walls | | `energy_nodes` | 20 | 8–50 | Number of energy spawn locations | | `cores_per_player` | 1 | 1–2 | Starting cores per player | ### 3.2 Units (Bots) Each player controls **bots** — mobile units on the grid. - Bots move one tile per turn in a cardinal direction: `N`, `E`, `S`, `W` - Bots that do not receive a move order hold position - Bots are binary — alive or dead, no hit points - A bot ordered into a wall tile stays in place (order ignored) - Two friendly bots ordered to the same tile: **both die** (self-collision) - A bot ordered onto a tile occupied by a stationary enemy: **both die** Each player starts with one bot spawned at each of their cores. ### 3.3 Energy & Economy Energy is the sole resource. It is used to spawn new bots. **Energy nodes:** - Fixed positions on the map that periodically produce collectible energy - Energy appears on a node every `energy_interval` turns (default: 10) - When energy is present on a node, it is visible to any player who can see the tile **Collection:** - A bot adjacent to (or on) an energy tile collects it if no enemy bot is also adjacent to that energy - If bots from multiple players are adjacent to the same energy, the energy is **destroyed** — nobody gets it (contested resources are denied) - Collection happens after combat resolution each turn **Spawning:** - Cost: **3 energy** per bot - Spawning happens automatically when a player has ≥3 energy and an unoccupied, unrazed core - One bot spawns per core per turn maximum - If a player has multiple cores and enough energy, one bot spawns at each eligible core simultaneously - Spawn priority: core that has been idle longest ### 3.4 Combat Combat uses a **focus fire** algorithm inspired by the aichallenge ants system. This rewards formations and positioning over raw unit count. **Attack radius:** squared Euclidean distance ≤ `attack_radius2` (default: **5**, meaning ~2.24 tiles — includes cardinal and diagonal neighbors plus one more ring). **Resolution (simultaneous):** ``` for each bot B on the grid: enemies_of_B = count of enemy bots within attack_radius2 of B for each enemy E within attack_radius2 of B: enemies_of_E = count of E's enemies within attack_radius2 of E if enemies_of_B >= enemies_of_E: mark B as dead break (B is already dead, no need to check further) ``` All deaths are resolved **simultaneously** — no cascading within a single turn. **Key properties:** - 2v1: the lone bot dies, the pair survives (superior numbers win cleanly) - 1v1: both die (mutual destruction) - Tight formations are defensive — a cluster facing scattered enemies takes fewer losses because each bot in the cluster has a lower enemy count - Multi-player battles create emergent alliances and third-party exploitation ### 3.5 Fog of War Each player has limited visibility. Only tiles within `vision_radius2` (default: **49**, ~7 tiles) of any owned bot are visible. **What players see within their vision:** - All tile types (open, wall, energy, core) - Enemy bots and their owner IDs - Dead bots (for one turn after death) **What players do NOT see:** - Anything outside their collective vision radius - How much energy opponents have - Total number of opponents (discovered through play) **Walls** are sent every turn they are visible (no incremental discovery state — keeps the protocol stateless-friendly for HTTP bots). ### 3.6 Scoring & Win Conditions **Scoring:** - Each player starts with **1 point per core** owned - **Capturing a core** (enemy bot moves onto an undefended enemy core): **+2 points** to capturer, **−1 point** to owner; core is razed - **Razed cores** stop spawning but the player continues with remaining bots - **Energy collected**: tracked as a tiebreaker statistic (not added to score) - **Bots eliminated**: tracked as a statistic **Win conditions (checked in order):** | Condition | Trigger | Resolution | |-----------|---------|------------| | **Sole Survivor** | Only one player has living bots | That player wins; bonus +2 per surviving enemy core | | **Annihilation** | All players eliminated simultaneously | Draw | | **Dominance** | One player controls ≥80% of all bots for 100 consecutive turns | That player wins | | **Turn Limit** | Turn count reaches `max_turns` (default: 500) | Highest score wins; ties broken by energy collected, then bots alive | ### 3.7 Turn Structure Each turn executes in a strict, deterministic sequence: ``` 1. Send game state to all players (HTTP POST, filtered by fog of war) 2. Await responses (up to 3-second timeout per player, in parallel) 3. Validate all responses against schema 4. Phase: MOVE — execute valid movement orders 5. Phase: COMBAT — resolve focus-fire algorithm, remove dead bots 6. Phase: CAPTURE — enemy bots on undefended cores raze them 7. Phase: COLLECT — uncontested energy adjacent to bots is collected 8. Phase: SPAWN — players with ≥3 energy spawn bots at eligible cores 9. Phase: ENERGY_TICK — energy nodes on their interval produce new energy 10. Phase: ENDGAME — check win conditions 11. Record turn state for replay ``` All player requests in step 1 are sent **concurrently**. Responses are collected with the 3-second deadline. The engine does not proceed to step 3 until all responses are in or timed out. ### 3.8 Map Generation Maps are generated offline and stored in the map library. They are not generated on-the-fly during matches. **Symmetry requirements:** - 2-player maps: 180° rotational symmetry (point symmetry through center) - 3-player maps: 120° rotational symmetry - 4-player maps: 90° rotational symmetry - 6-player maps: 60° rotational symmetry **Generation algorithm:** 1. Generate one **sector** (1/N of the map for N players) 2. Place walls using cellular automata (random seed → smooth with neighbor rules) 3. Place cores and energy nodes within the sector 4. Validate connectivity: BFS from core must reach all energy nodes and the sector boundary 5. Mirror/rotate the sector to fill the full map 6. Validate full-map connectivity: all cores must be reachable from each other 7. Store the map with metadata (player count, dimensions, wall density) **Map library:** - Pre-generated pool of 50+ maps per player count (2, 3, 4, 6) - Maps are curated — auto-generated then play-tested with strategy bots - Matchmaking selects the least-recently-used map for each match --- ## 4. Communication Protocol ### 4.1 HTTP Interface The game engine communicates with bots via HTTP POST requests. Each bot exposes a single endpoint. **Bot endpoint:** `POST {bot_base_url}/turn` The engine sends the game state as a JSON body. The bot responds with its moves as a JSON body. No other endpoints are required from the bot (though `/health` is recommended for registration validation). **Request flow per turn:** ``` Engine Bot │ │ │ POST /turn │ │ Headers: auth + metadata │ │ Body: game state JSON │ │─────────────────────────────►│ │ │ (bot computes moves) │ 200 OK │ │ Body: moves JSON │ │◄─────────────────────────────│ │ │ ``` ### 4.2 Game State Schema (Engine → Bot) ```json { "match_id": "m_7f3a9b2c", "turn": 42, "config": { "rows": 60, "cols": 60, "max_turns": 500, "vision_radius2": 49, "attack_radius2": 5, "spawn_cost": 3, "energy_interval": 10 }, "you": { "id": 0, "energy": 7, "score": 3 }, "bots": [ { "row": 10, "col": 15, "owner": 0 }, { "row": 12, "col": 15, "owner": 0 }, { "row": 30, "col": 40, "owner": 1 } ], "energy": [ { "row": 20, "col": 25 } ], "cores": [ { "row": 5, "col": 5, "owner": 0, "active": true }, { "row": 55, "col": 55, "owner": 1, "active": true } ], "walls": [ { "row": 10, "col": 10 }, { "row": 10, "col": 11 } ], "dead": [ { "row": 15, "col": 20, "owner": 1 } ] } ``` **Schema rules:** - `bots`, `energy`, `cores`, `walls`, `dead` — only includes tiles within the player's collective vision - `owner` IDs are consistent within a match but randomized per match (player 0 is always "you") - `config` is identical for all players and does not change between turns - `walls` are sent every turn they are visible (stateless — bot does not need to track previously seen walls, though smart bots will) - `dead` contains bots that died on the previous turn (visible for one turn) ### 4.3 Move Schema (Bot → Engine) ```json { "moves": [ { "row": 10, "col": 15, "direction": "N" }, { "row": 12, "col": 15, "direction": "E" } ] } ``` **Validation rules:** - `moves` must be an array (may be empty — all bots hold position) - Each move must reference a `(row, col)` where the player owns a bot - `direction` must be one of: `"N"`, `"E"`, `"S"`, `"W"` - Duplicate `(row, col)` entries: first valid entry wins, rest ignored - Moves referencing tiles with no owned bot: ignored - Moves into walls: ignored (bot stays) - Any response that fails top-level schema validation: entire response discarded, all bots hold - **The engine never parses, evaluates, or interprets any field beyond `moves[].row`, `moves[].col`, `moves[].direction`** ### 4.4 Authentication (HMAC Shared Secret) Each registered bot has a **shared secret** generated at registration time. The secret is known only to the bot owner and the game engine. It authenticates both directions — the bot can verify requests came from the real game engine, and the engine can verify responses came from the real bot. **Engine → Bot (request signing):** Headers sent with every request: ``` X-ACB-Match-Id: m_7f3a9b2c X-ACB-Turn: 42 X-ACB-Timestamp: 1711200000 X-ACB-Bot-Id: b_4e8c1d2f X-ACB-Signature: ``` Signature computation: ``` signing_string = "{match_id}.{turn}.{timestamp}.{sha256(request_body)}" signature = HMAC-SHA256(shared_secret, signing_string) ``` The bot verifies: 1. Compute the expected signature from the headers and request body 2. Compare with `X-ACB-Signature` (constant-time comparison) 3. Verify `X-ACB-Timestamp` is within ±30 seconds of current time (prevents replay attacks) 4. If verification fails: bot should return 401 and ignore the request **Bot → Engine (response signing):** Response headers: ``` X-ACB-Signature: ``` Signature computation: ``` signing_string = "{match_id}.{turn}.{sha256(response_body)}" signature = HMAC-SHA256(shared_secret, signing_string) ``` The engine verifies the response signature. If invalid, the response is discarded (bots hold position). This prevents man-in-the-middle from injecting moves. **Why HMAC over OAuth/JWT/mTLS:** - Minimal complexity — no token refresh, no certificate management - Bot developers add a single header computation, not an auth library - Symmetric: both sides can verify the other with the same secret - Sufficient for the threat model (prevent impersonation and tampering) **Secret management:** - Secrets are generated as 256-bit random values, hex-encoded (64 characters) - Displayed once at registration time; bot owner must save it - Can be rotated via the web platform (old secret invalidated immediately) - Stored hashed (bcrypt) in the database — the engine uses the hash to verify, so the raw secret is never stored. **Correction**: HMAC requires the raw secret, so it is stored encrypted (AES-256-GCM) with a master key, not hashed. The master key is held in an environment variable, never in the database. ### 4.5 Timeout & Error Handling | Scenario | Behavior | |----------|----------| | Bot responds within 3s | Moves validated and applied normally | | Bot responds after 3s | Response discarded; bots hold position for that turn | | Bot returns non-200 status | Treated as timeout; bots hold position | | Bot returns invalid JSON | Treated as timeout; bots hold position | | Bot returns valid JSON failing schema | Entire response discarded; bots hold position | | Bot connection refused | Bots hold position; engine retries next turn | | Bot connection timeout (TCP) | Engine uses 2s connect timeout within the 3s budget | | 10 consecutive failures | Bot marked as **crashed** for this match; bots become inert for remaining turns | The bot is **never killed or disconnected**. Even after being marked crashed, the match continues — the crashed bot's units simply hold position every turn until they are destroyed or the match ends. --- ## 5. Strategy Bots Six built-in strategy bots serve as reference implementations and permanent ladder opponents. Each is implemented in a **different programming language** to demonstrate that the HTTP protocol is truly language-agnostic and to provide starter code for participants across the most popular ecosystems. Each bot is deployed as its own container running a lightweight HTTP server. | Bot | Language | Complexity | Expected Rank | |-----|----------|------------|---------------| | RandomBot | Python | Trivial | 6th (floor) | | GathererBot | Go | Low | 4th–5th | | RusherBot | Rust | Low | 4th–5th | | GuardianBot | PHP | Medium | 3rd–4th | | SwarmBot | TypeScript | Medium | 1st–2nd | | HunterBot | Java | High | 1st–2nd | ### 5.1 RandomBot — Python **Language rationale:** Python is the most accessible language for newcomers. The random bot doubles as the simplest possible starter template — a participant can fork it and have a working bot in minutes. **Strategy:** Makes uniformly random valid moves each turn. **Behavior:** - For each owned bot, pick a random direction (N/E/S/W) or hold (20% chance) - No pathfinding, no memory, no awareness of enemies - Serves as the absolute baseline — any reasonable bot should beat this **Value:** Ensures new participants have an easy opponent to test against. Rating floor anchor. **Implementation:** Flask or bare `http.server`. ~50 lines of strategy code. HMAC verification via `hmac` stdlib module. ### 5.2 GathererBot — Go **Language rationale:** Go is the same language as the game engine and platform services, making this the canonical "how to build a bot" reference. Demonstrates idiomatic Go HTTP server patterns. **Strategy:** Maximize energy collection, avoid combat entirely. **Behavior:** - BFS from each owned bot to the nearest visible energy - Assign each bot to the closest uncontested energy (greedy matching) - If an enemy bot is within vision, move away from it - Never voluntarily enters attack range of an enemy - Spawns bots as fast as energy allows **Value:** Tests whether aggressive bots can actually close games or whether passive resource hoarding is dominant (it shouldn't be). **Implementation:** `net/http` stdlib server. Shared `game/` package with grid utilities, BFS, and distance calculations that participants can reuse. ### 5.3 RusherBot — Rust **Language rationale:** Rust participants get maximum compute within the 3-second timeout. This bot demonstrates that Rust's performance advantage matters less than strategy — a dumb fast bot still loses to a smart slow one. **Strategy:** Identify and rush the nearest enemy core as fast as possible. **Behavior:** - BFS from each owned bot toward the nearest known enemy core - If no enemy core is known, spread out to explore (random walk with bias toward unexplored territory) - Ignores energy except incidentally (walks over it) - Ignores enemy bots unless they block the path - Spawns bots immediately and sends all toward the target **Value:** Punishes bots that neglect defense. Tests whether the combat system allows pure aggression to dominate (it shouldn't — rusher bots will walk into defensive formations and die). **Implementation:** `axum` or `actix-web`. Serde for JSON. HMAC via `hmac` and `sha2` crates. Demonstrates Rust's zero-copy deserialization. ### 5.4 GuardianBot — PHP **Language rationale:** PHP is often overlooked in competitive programming but is widely known and trivially deployable. This demonstrates that even PHP — without async, without frameworks — can compete on equal footing when the interface is HTTP. Lowers the barrier for the large PHP developer community. **Strategy:** Defend own core, gather nearby energy, cautious expansion. **Behavior:** - Maintain a perimeter of bots within 5 tiles of each owned core - Assign excess bots (beyond perimeter needs) to gather energy within 10 tiles of a core - If enemy bots are spotted approaching, consolidate defenders between the enemy and the core - Only sends scouts (lone bots) to explore beyond the safe zone - Very conservative spawning — maintains energy reserve of 6 **Value:** Tests whether turtling is viable. Should beat rushers but lose to gatherers/swarms in the long game (inferior economy due to limited territory). **Implementation:** PHP built-in server (`php -S`) with a single router script. `hash_hmac()` for HMAC. JSON via `json_decode`/`json_encode`. BFS implemented with `SplQueue`. ### 5.5 SwarmBot — TypeScript **Language rationale:** TypeScript (Node.js) is the most popular language for web developers entering the platform. This bot demonstrates maintaining complex state across turns — the swarm's formation tracking, rally points, and center-of-mass calculation benefit from TypeScript's type system. **Strategy:** Keep units in tight formations, advance as a group toward enemies. **Behavior:** - All bots maintain cohesion — no bot moves if it would be >3 tiles from the nearest friendly bot - The swarm moves as a unit toward the nearest enemy presence - BFS-based center-of-mass steering: average position of all owned bots is the swarm center; steer toward enemy center of mass - Energy collection is incidental (pass over it during advance) - New spawns rally to the swarm before advancing **Value:** Exploits the focus combat system — a tight group defeats scattered enemies. But slow expansion means inferior economy. Should dominate combat but can be outscored by gatherers on large maps. **Implementation:** Express.js or Fastify. State persisted in-process across turns (the HTTP server stays alive between requests). HMAC via Node.js `crypto` module. Typed interfaces for game state and moves. ### 5.6 HunterBot — Java **Language rationale:** Java is dominant in competitive programming (Battlecode is Java-only). This is the most sophisticated strategy bot, demonstrating that Java's verbosity is offset by mature data structures (`PriorityQueue`, `HashMap`) and predictable GC behavior within the timeout window. **Strategy:** Target isolated enemy bots for efficient kills. **Behavior:** - Identify enemy bots that are ≥4 tiles from their nearest friendly bot (isolated targets) - Send pairs of bots to intercept isolated enemies (2v1 wins cleanly) - If no isolated targets, default to gatherer behavior - Maintain a map of known enemy positions across turns, predict movement based on last-seen direction and speed - Avoid engaging formations of 3+ enemy bots - Opportunistic energy collection when not actively hunting **Value:** Sophisticated target selection and prediction. Represents an intermediate-to-advanced-skill bot. Should beat random/gatherer/rusher but struggle against swarm formations. **Implementation:** Javalin or `com.sun.net.httpserver`. `javax.crypto.Mac` for HMAC. Maintains a `HashMap` across turns for movement prediction. Hungarian algorithm for optimal bot-to-target assignment. ### 5.7 Container Templates Each language has its own container structure. All share the same contract: listen on port 8080, serve `POST /turn` and `GET /health`. **Go (GathererBot):** ``` strategy-gatherer/ ├── Dockerfile ├── main.go # HTTP server, HMAC verification ├── strategy.go # Gatherer-specific logic ├── game/ │ ├── state.go # Game state types │ ├── grid.go # Grid utilities (BFS, distance, wrapping) │ └── moves.go # Move response types └── go.mod ``` **Python (RandomBot):** ``` strategy-random/ ├── Dockerfile ├── main.py # HTTP server, HMAC verification, strategy ├── game.py # Game state types and grid utilities └── requirements.txt # (minimal — stdlib only for random bot) ``` **Rust (RusherBot):** ``` strategy-rusher/ ├── Dockerfile ├── Cargo.toml └── src/ ├── main.rs # HTTP server, HMAC verification ├── strategy.rs # Rusher-specific logic └── game.rs # Game state types, grid utilities ``` **PHP (GuardianBot):** ``` strategy-guardian/ ├── Dockerfile ├── index.php # Router + HMAC verification ├── strategy.php # Guardian-specific logic ├── game.php # Game state types, BFS, grid utilities └── composer.json # (optional — no external deps needed) ``` **TypeScript (SwarmBot):** ``` strategy-swarm/ ├── Dockerfile ├── package.json ├── tsconfig.json └── src/ ├── index.ts # HTTP server, HMAC verification ├── strategy.ts # Swarm-specific logic └── game.ts # Game state types, grid utilities ``` **Java (HunterBot):** ``` strategy-hunter/ ├── Dockerfile ├── pom.xml └── src/main/java/com/acb/hunter/ ├── App.java # HTTP server, HMAC verification ├── Strategy.java # Hunter-specific logic ├── GameState.java # Game state deserialization └── Grid.java # Grid utilities, BFS, distance ``` **Shared contract (all languages):** - Listen on port 8080 - `POST /turn` — receives game state, runs strategy, returns moves - `GET /health` — returns 200 (used for registration health check) - HMAC signature verification on incoming requests - HMAC signature on outgoing responses - Request logging (turn number, compute time, move count) **Container specs:** | Bot | Build Image | Runtime Image | Memory Limit | CPU Limit | |-----|-------------|---------------|-------------|-----------| | RandomBot | `python:3.13-slim` | `python:3.13-slim` | 64MB | 0.1 cores | | GathererBot | `golang:1.24-alpine` | `alpine:3.21` | 128MB | 0.25 cores | | RusherBot | `rust:1.85-alpine` | `alpine:3.21` | 128MB | 0.25 cores | | GuardianBot | `php:8.4-cli-alpine` | `php:8.4-cli-alpine` | 128MB | 0.25 cores | | SwarmBot | `node:22-alpine` | `node:22-alpine` | 128MB | 0.25 cores | | HunterBot | `eclipse-temurin:21-alpine` | `eclipse-temurin:21-jre-alpine` | 256MB | 0.5 cores | Java gets a higher resource allocation due to JVM overhead. All others are intentionally constrained — strategy bots should be lightweight. ### 5.8 Starter Kit & SDK Libraries To lower the barrier for participants writing their own bots, the platform provides **starter kits** for each supported language. Each starter kit is a minimal, forkable repository containing: - A working HTTP server with HMAC verification already implemented - Type definitions for the game state and move schemas - Grid utility functions (toroidal distance, BFS, neighbor enumeration) - A stub strategy function that holds all bots in place (participant fills in) - A Dockerfile that builds and runs the bot - A README with quickstart instructions **Starter kit languages (matching strategy bots):** | Kit | Repository | Notes | |-----|-----------|-------| | `acb-starter-python` | Template repo | Flask-based, ~100 lines total | | `acb-starter-go` | Template repo | Shares `game/` package with GathererBot | | `acb-starter-rust` | Template repo | `axum` + `serde`, strongly typed | | `acb-starter-php` | Template repo | Zero dependencies, built-in server | | `acb-starter-typescript` | Template repo | Fastify, full type definitions | | `acb-starter-java` | Template repo | Javalin, Maven-based | Participants are not limited to these languages. Any language that can serve HTTP and compute HMAC-SHA256 can compete. The starter kits simply eliminate boilerplate for the most common choices. --- ## 6. Tournament System ### 6.1 Matchmaking Matches are created continuously by the **tournament scheduler**, a process that runs on a fixed interval (default: every 10 seconds). **Algorithm:** 1. **Select seed bot**: the registered bot with the most time since its last match (tiebreak: lowest bot ID) 2. **Determine match size**: based on the seed bot's least-played format (2-player, 3-player, 4-player, or 6-player) 3. **Select opponents**: from the eligible pool, preferring: a. Closest skill rating to seed (Pareto distribution: 80% within 16 ranks) b. Least recently paired with the seed c. Fewest games played in the last 24 hours (keeps game counts even) 4. **Select map**: least recently used map for the chosen player count 5. **Assign player slots**: random 6. **Create match job**: push to Redis queue with match config + bot endpoints **Eligibility:** - Bot must be registered and active (passed health check within last hour) - Bot must not be in a match currently (one match at a time per bot) - Bot must not have been marked crashed in its last 3 consecutive matches (cooldown: 30 minutes) ### 6.2 Rating System **Algorithm: Glicko-2** Glicko-2 is preferred over TrueSkill for this platform because: - No licensing concerns (TrueSkill is patented by Microsoft) - Includes a volatility parameter (σ) that adapts to inconsistent performance - Well-suited to multi-player games via pairwise decomposition - Established in competitive gaming (chess, Go, online games) **Parameters per bot:** - `mu` (μ): rating estimate (default: 1500) - `phi` (φ): rating deviation / uncertainty (default: 350) - `sigma` (σ): rating volatility (default: 0.06) **Display rating:** `mu - 2*phi` (conservative estimate shown on leaderboard) **Update frequency:** after every match. Ratings converge quickly — a new bot reaches a stable rating within ~30 matches. **Multi-player adaptation:** - A 4-player match produces 6 pairwise results (every pair of players) - Each pairwise result is: win/loss based on relative score, or draw if equal - Glicko-2 update is applied once per match using all pairwise outcomes ### 6.3 Continuous Tournament The tournament runs indefinitely with no seasons or resets (initially). **Match throughput target:** enough matches that every active bot plays at least 10 matches per day. With N active bots and M match workers: - 2-player matches: each match involves 2 bots, takes ~3 minutes (500 turns × 3s max + overhead) - One worker produces ~20 matches/hour - 3 workers: ~60 matches/hour, ~1440/day — supports ~288 active bots at 10 games/day **Scaling:** add more spot workers to increase throughput. --- ## 7. Replay System ### 7.1 Replay Data Format Replays are JSON files optimized for compact storage while supporting full client-side reconstruction of every game turn. ```json { "version": 1, "match_id": "m_7f3a9b2c", "date": "2026-03-23T14:30:00Z", "players": [ { "bot_id": "b_4e8c1d2f", "name": "SwarmBot", "owner": "alice" }, { "bot_id": "b_9a1b3c4d", "name": "HunterBot", "owner": "bob" } ], "result": { "winner": 0, "condition": "turn_limit", "final_scores": [7, 3], "final_energy": [12, 4], "final_bots": [18, 6] }, "config": { "rows": 60, "cols": 60, "max_turns": 500, "vision_radius2": 49, "attack_radius2": 5, "spawn_cost": 3, "energy_interval": 10 }, "map": { "walls": [[10,10], [10,11], [10,12]], "energy_nodes": [[20,25], [40,35]], "cores": [ { "pos": [5,5], "owner": 0 }, { "pos": [55,55], "owner": 1 } ] }, "turns": [ { "moves": { "0": [{"from":[10,15],"dir":"N"},{"from":[12,15],"dir":"E"}], "1": [{"from":[50,45],"dir":"S"}] }, "spawns": [[5,5,0]], "deaths": [[30,40,1]], "captures": [], "energy_collected": {"0": [[20,25]]}, "energy_spawned": [[35,15]], "scores": [3, 1] } ] } ``` **Size estimate:** a 500-turn, 4-player match with ~50 bots total produces a replay of ~200–500 KB uncompressed, ~30–80 KB gzipped. **Optimization:** for very long matches, the `turns` array can use delta encoding — only recording events that changed from the previous turn. ### 7.2 Storage - Replays are stored in S3-compatible object storage (Minio self-hosted or provider-managed) - Path: `replays/{year}/{month}/{match_id}.json.gz` - Retention: indefinite for top-100 matches per month; older matches pruned after 90 days - Map definitions stored separately: `maps/{map_id}.json` - The API server returns signed URLs for replay access (no public bucket) ### 7.3 Browser Replay Viewer The replay viewer is a client-side TypeScript application rendered on HTML5 Canvas. **Rendering pipeline:** 1. Fetch replay JSON from object storage (via signed URL from API) 2. Parse and index: build per-turn game state by replaying events from turn 0 3. Render the current turn to canvas 4. User controls advance/rewind the turn index **Visual design:** | Element | Rendering | |---------|-----------| | Grid | Subtle grid lines on dark background | | Walls | Dark gray filled squares | | Open tiles | Transparent (background shows through) | | Energy nodes | Small yellow diamond; pulse animation when energy is present | | Cores | Large player-colored circle with ring; X overlay when razed | | Bots | Player-colored filled circles; brief trail showing last move direction | | Dead bots | Fading red X for one turn | | Fog of war | Dark semi-transparent overlay on tiles outside selected player's vision | | Combat | Flash effect on tiles where kills occurred | **Controls:** | Control | Function | |---------|----------| | Play / Pause | Toggle automatic playback | | Speed slider | 1x, 2x, 4x, 8x, 16x (turns per second: 2, 4, 8, 16, 32) | | Turn scrubber | Drag to any turn; displays turn number | | Perspective dropdown | "All" (omniscient) or per-player fog of war view | | Zoom | Scroll to zoom; drag to pan | | Score overlay | Per-player score, energy, bot count — updates each turn | | Minimap | Small overview of full grid in corner (for large maps) | **Shareable URLs:** `https://aicodebattle.com/replay/{match_id}` — the replay viewer is the landing page for any match. No login required to watch. --- ## 8. Web Platform ### 8.1 User Registration - Email + password or OAuth (GitHub recommended — target audience is developers) - Email verification required before bot registration - Profile: username (unique), display name, avatar (from OAuth provider) ### 8.2 Bot Registration **Registration flow:** 1. User navigates to "Register Bot" in their dashboard 2. Provides: - **Bot name** (unique, alphanumeric + hyphens, 3–32 chars) - **Endpoint URL** (HTTPS required for competitive play; HTTP allowed for development with a flag) - **Description** (optional, shown on leaderboard) 3. Platform generates: - `bot_id`: unique identifier (`b_` prefix + 8 hex chars) - `shared_secret`: 256-bit random, hex-encoded (64 chars) 4. Platform displays the shared secret **once** — user must copy it 5. Platform performs a **health check**: `GET {endpoint_url}/health` - Must return 200 within 5 seconds - If health check fails, registration is saved but bot is marked **inactive** 6. Platform performs a **protocol test**: sends a mock turn-0 game state to `POST {endpoint_url}/turn` with valid HMAC - Bot must return a valid (possibly empty) moves response within 3 seconds - If protocol test fails, bot is marked **inactive** with an error message **Bot status lifecycle:** ``` PENDING → ACTIVE → INACTIVE (health check failed) → SUSPENDED (manual by admin) → RETIRED (by owner) ``` Only `ACTIVE` bots participate in matchmaking. **Ongoing health checks:** the platform pings each active bot's `/health` endpoint every 15 minutes. Three consecutive failures → marked `INACTIVE`. Bots automatically return to `ACTIVE` when health checks resume passing. ### 8.3 Leaderboard - Default sort: Glicko-2 display rating (mu - 2*phi) descending - Columns: rank, bot name, owner, rating, games played, win rate, last active - Filterable by: player count tier (2p, 3p, 4p, 6p), time range - Updates in near-real-time (WebSocket push or 30-second polling) - Public — no login required to view ### 8.4 Match History & Profiles **Bot profile page** (`/bot/{bot_name}`): - Current rating + rating history chart - Recent matches (last 50) with links to replay viewer - Win/loss/draw breakdown - Performance vs. each opponent - Bot description, owner, registration date **User profile page** (`/user/{username}`): - List of owned bots - Aggregate statistics across all bots **Match page** (`/match/{match_id}`): - Participants, map, final scores - Embedded replay viewer (auto-plays) - Turn-by-turn event log (collapsible) --- ## 9. Deployment & Infrastructure ### 9.1 Container Architecture | Image | Base | Purpose | Replicas | |-------|------|---------|----------| | `acb-api` | Go binary on Alpine | REST API server | 2 (always-on) | | `acb-worker` | Go binary on Alpine | Match execution worker | 3–10 (spot) | | `acb-scheduler` | Go binary on Alpine | Tournament matchmaking | 1 (always-on) | | `acb-web` | Nginx + static files | Frontend SPA | 1 (or CDN) | | `acb-strategy-random` | Python 3.13 slim | RandomBot | 1 | | `acb-strategy-gatherer` | Go on Alpine | GathererBot | 1 | | `acb-strategy-rusher` | Rust on Alpine | RusherBot | 1 | | `acb-strategy-guardian` | PHP 8.4 CLI Alpine | GuardianBot | 1 | | `acb-strategy-swarm` | Node 22 Alpine | SwarmBot (TypeScript) | 1 | | `acb-strategy-hunter` | Temurin 21 JRE Alpine | HunterBot (Java) | 1 | ### 9.2 Rackspace Spot Deployment Match workers are the primary consumers of compute and are **perfectly suited for spot instances**: - **Stateless**: workers pull jobs from a queue, execute, and push results. No persistent local state. - **Interruptible**: if a spot instance is reclaimed mid-match, the match job is re-queued after a staleness timeout (10 minutes with no progress update). The match is replayed from scratch on another worker. - **Bursty**: match throughput can flex with spot availability. More instances = faster ladder convergence, but no hard deadline. **Instance sizing:** - Match workers: 2 vCPU, 4 GB RAM per instance (each runs one match at a time) - Strategy bots: can share a single small instance (all 6 use <1GB total; Java's JVM is the biggest consumer at ~256MB) - API server + scheduler: 2 vCPU, 4 GB RAM, always-on (not spot) **Deployment layout:** ``` Always-on tier (standard instances): ├── acb-api (×2, behind load balancer) ├── acb-scheduler (×1) ├── acb-web (×1 or CDN) ├── acb-strategy-* (×1 each, shared instance) ├── PostgreSQL (managed or self-hosted) ├── Redis (managed or self-hosted) └── Minio / S3-compatible store Spot tier (preemptible instances): ├── acb-worker (×3 minimum, scale up as available) └── (each worker is a standalone container, no coordination needed) ``` **Spot reclaim handling:** 1. Worker registers a shutdown hook that catches SIGTERM 2. On SIGTERM, worker sets the current match status to `interrupted` in Redis 3. Worker exits gracefully (within the 30-second SIGTERM grace period) 4. Scheduler's stale-match reaper detects `interrupted` or stale `in_progress` matches and re-queues them 5. Another worker picks up the job ### 9.3 Data Stores **PostgreSQL:** - Tables: `users`, `bots`, `matches`, `match_participants`, `maps`, `ratings` - Single primary instance; read replica for leaderboard queries - Connection pooling via PgBouncer - Backup: daily automated dumps to object storage **Redis:** - Match job queue (Redis Streams or List-based queue) - Rate limiting (per-bot, per-endpoint) - Session cache - Leaderboard cache (sorted sets) - No persistence required — queue jobs are recoverable from PostgreSQL match records with `queued` status **Object Storage (S3-compatible):** - Replay files (gzipped JSON) - Map definition files - Bot submission metadata / logs - Signed URL generation for replay access (1-hour expiry) ### 9.4 Networking & Security **External traffic:** - Web platform: HTTPS only, behind reverse proxy (Caddy or nginx) - Bot endpoints: engine connects outbound to registered URLs **Internal traffic:** - API ↔ PostgreSQL: private network - API ↔ Redis: private network - Workers ↔ Redis: private network (workers may be in different regions — use Redis over TLS if cross-region) - Workers → bot endpoints: public internet (HTTPS required for competitive bots) - Workers → strategy bots: private network (same infrastructure) **Security boundaries:** - The game engine (workers) never executes bot code — HTTP only - All bot responses are schema-validated before processing - HMAC authentication prevents request/response forgery - Rate limiting on API endpoints (registration, health checks) - Bot endpoint URLs validated at registration (no internal IPs, no localhost) - Workers run with no inbound ports — they only make outbound HTTP calls ### 9.5 Monitoring | Signal | Tool | Alert Threshold | |--------|------|-----------------| | Match throughput | Prometheus counter | <10 matches/hour for >30 minutes | | Worker count | Prometheus gauge | <2 live workers for >15 minutes | | Bot health check failures | Prometheus counter | >50% of active bots failing | | API latency (p99) | Prometheus histogram | >500ms | | Match queue depth | Redis metric | >100 pending matches | | Replay storage usage | S3 metric | >80% of quota | | Error rate (5xx) | Access logs | >1% of requests | --- ## 10. Implementation Phases ### Phase 1: Core Engine (foundation) Build the game simulation as a standalone Go library with a CLI runner. **Deliverables:** - `engine/` package: grid, bots, energy, combat, fog of war, turn execution - `cmd/acb-local/` CLI: run a match between two local bot processes (stdin/stdout for dev convenience) and output a replay JSON file - Replay JSON writer - Comprehensive unit tests for combat resolution, fog of war, wrapping, collision, scoring, endgame conditions - Map generation tool: `cmd/acb-mapgen/` **Exit criteria:** can run a complete 500-turn match between two bots locally and produce a valid replay file. ### Phase 2: HTTP Protocol & Strategy Bots **Deliverables:** - HTTP bot interface in the engine (replaces stdin/stdout for production) - HMAC signing and verification library (Go, reusable by GathererBot) - GathererBot (Go) and RandomBot (Python) — validate the protocol works across languages before building the remaining four - RusherBot (Rust), GuardianBot (PHP), SwarmBot (TypeScript), HunterBot (Java) - All 6 bots containerized with language-appropriate Dockerfiles - Starter kit template repos for each language (fork-ready) - Integration test: engine runs a full match between bots in different languages over HTTP **Exit criteria:** can run a complete match between any two strategy bot containers (in different languages) over HTTP, with HMAC authentication, producing a valid replay. ### Phase 3: Replay Viewer **Deliverables:** - TypeScript Canvas-based replay viewer - Play/pause, scrub, speed control - Fog of war perspective toggle - Score overlay - Loads replay JSON from local file or URL **Exit criteria:** can open a replay file in a browser and watch a complete match with all visual elements rendering correctly. ### Phase 4: Match Orchestration **Deliverables:** - Match worker service (`acb-worker`): pulls from Redis queue, runs matches, uploads replays, records results - Tournament scheduler (`acb-scheduler`): matchmaking algorithm, creates jobs - PostgreSQL schema and migrations - Stale match reaper (handles interrupted spot instances) - Match result → Glicko-2 rating update pipeline **Exit criteria:** scheduler creates matches, workers execute them autonomously, ratings update, replays are stored. System recovers from worker interruption. ### Phase 5: Web Platform **Deliverables:** - API server (`acb-api`): user registration, bot registration, leaderboard, match history, replay URLs - Web frontend (`acb-web`): registration, bot management dashboard, leaderboard, match history, embedded replay viewer - Bot health check system (periodic + on-registration) - Shared secret generation, display, rotation **Exit criteria:** a user can register, add a bot, see it appear on the leaderboard after matches are played, and watch replays of its games. ### Phase 6: Deployment & Production **Deliverables:** - Container images pushed to registry - Rackspace Spot deployment for workers - Always-on deployment for API, scheduler, strategy bots, datastores - TLS termination, DNS, CDN for static assets - Monitoring dashboards and alerts - Backup automation for PostgreSQL and replay storage **Exit criteria:** platform is publicly accessible, matches run continuously, strategy bots compete on the ladder, external participants can register and play.