ai-code-battle/starters/python/main.py
jedarden c8721a9015 feat(starter-python): implement Flask-based Python starter kit
Flask HTTP server (~130 lines) with HMAC-SHA256 signing, game state
type definitions, stub strategy, and Dockerfile.

- Flask-based /turn and /health endpoints
- HMAC-SHA256 request verification and response signing
- Type-annotated compute_moves() stub (holds all bots in place)
- Grid utilities: toroidal distance, BFS, neighbor enumeration
- README with quickstart, protocol spec, and customization guide

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:10:51 -04:00

132 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""AI Code Battle - Python Starter Kit.
Flask-based HTTP bot with HMAC authentication. Implement your strategy
in compute_moves() below.
Usage:
BOT_SECRET=your-secret python3 main.py
"""
import hashlib
import hmac
import json
import os
import time
from typing import List, Dict, Any
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET = os.environ.get("BOT_SECRET", "")
DIRECTIONS = ["N", "E", "S", "W"]
def verify_signature(body: bytes, match_id: str, turn: str,
timestamp: str, signature: str) -> bool:
"""Verify HMAC-SHA256 signature from engine."""
try:
ts = int(timestamp)
now = int(time.time())
if abs(now - ts) > 30:
return False
except (ValueError, TypeError):
return False
body_hash = hashlib.sha256(body).hexdigest()
signing_string = f"{match_id}.{turn}.{timestamp}.{body_hash}"
expected = hmac.new(
SECRET.encode(), signing_string.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
def sign_response(body: bytes, match_id: str, turn: int) -> str:
"""Generate HMAC-SHA256 signature for response."""
body_hash = hashlib.sha256(body).hexdigest()
signing_string = f"{match_id}.{turn}.{body_hash}"
return hmac.new(
SECRET.encode(), signing_string.encode(), hashlib.sha256
).hexdigest()
@app.route("/health", methods=["GET"])
def health():
"""Health check endpoint."""
return "OK", 200
@app.route("/turn", methods=["POST"])
def turn():
"""Main game turn endpoint."""
match_id = request.headers.get("X-ACB-Match-Id", "")
turn_str = request.headers.get("X-ACB-Turn", "0")
timestamp = request.headers.get("X-ACB-Timestamp", "")
signature = request.headers.get("X-ACB-Signature", "")
body = request.get_data()
if not signature or not verify_signature(body, match_id, turn_str, timestamp, signature):
return "Invalid signature", 401
try:
state = json.loads(body)
except json.JSONDecodeError:
return "Invalid JSON", 400
moves = compute_moves(state)
turn_num = int(turn_str)
response_data = {"moves": moves}
response_body = json.dumps(response_data).encode()
response_sig = sign_response(response_body, match_id, turn_num)
response = jsonify(response_data)
response.headers["X-ACB-Signature"] = response_sig
return response
def compute_moves(state: Dict[str, Any]) -> List[Dict[str, Any]]:
"""YOUR STRATEGY GOES HERE.
Args:
state: Game state dict with keys:
- match_id: str
- turn: int
- config: dict (rows, cols, max_turns, vision_radius2, attack_radius2, etc.)
- you: dict (id, energy, score)
- bots: list of dict (each has row, col, owner)
- energy: list of dict (each has row, col)
- cores: list of dict (each has row, col, owner, active)
- walls: list of dict (each has row, col)
- dead: list of dict (each has row, col, owner)
Returns:
List of move dicts, each with:
- row: int (your bot's current row)
- col: int (your bot's current col)
- direction: "N" | "E" | "S" | "W"
"""
import random
my_id = state["you"]["id"]
rows, cols = state["config"]["rows"], state["config"]["cols"]
moves = []
for bot in state.get("bots", []):
if bot["owner"] != my_id:
continue
# STUB: hold all bots in place (return empty list)
# Replace this with your strategy!
pass
return moves
if __name__ == "__main__":
if not SECRET:
print("ERROR: BOT_SECRET environment variable is required")
exit(1)
port = int(os.environ.get("BOT_PORT", "8080"))
app.run(host="0.0.0.0", port=port, debug=False)