Add forkable starter kit templates for Python, Go, JavaScript, Rust, Java, and C# — each with HTTP server scaffold, HMAC auth, game types, random strategy, Dockerfile, and GHCR workflow. Update /compete/docs page with starter kit links and registration instructions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
135 lines
4 KiB
Python
135 lines
4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI Code Battle - Python Starter Kit
|
|
|
|
A minimal bot scaffold. Implements the HTTP protocol with HMAC
|
|
authentication and a placeholder random strategy.
|
|
|
|
Usage:
|
|
BOT_SECRET=your-secret python3 main.py
|
|
"""
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
import random
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
|
|
# Engine constants
|
|
DIRECTIONS = ["N", "E", "S", "W"]
|
|
|
|
|
|
class GameState:
|
|
def __init__(self, data: dict):
|
|
self.match_id = data["match_id"]
|
|
self.turn = data["turn"]
|
|
self.config = data["config"]
|
|
self.you_id = data["you"]["id"]
|
|
self.you_energy = data["you"]["energy"]
|
|
self.you_score = data["you"]["score"]
|
|
self.bots = data.get("bots", [])
|
|
self.energy = data.get("energy", [])
|
|
self.cores = data.get("cores", [])
|
|
self.walls = data.get("walls", [])
|
|
self.dead = data.get("dead", [])
|
|
|
|
|
|
class BotHandler(BaseHTTPRequestHandler):
|
|
secret: str = ""
|
|
|
|
def log_message(self, format, *args):
|
|
pass
|
|
|
|
def sign_response(self, body: bytes, match_id: str, turn: int) -> str:
|
|
body_hash = hashlib.sha256(body).hexdigest()
|
|
signing_string = f"{match_id}.{turn}.{body_hash}"
|
|
return hmac.new(
|
|
self.secret.encode(), signing_string.encode(), hashlib.sha256
|
|
).hexdigest()
|
|
|
|
def verify_signature(self, body: bytes, match_id: str, turn: str,
|
|
timestamp: str, signature: str) -> bool:
|
|
body_hash = hashlib.sha256(body).hexdigest()
|
|
signing_string = f"{match_id}.{turn}.{timestamp}.{body_hash}"
|
|
expected = hmac.new(
|
|
self.secret.encode(), signing_string.encode(), hashlib.sha256
|
|
).hexdigest()
|
|
return hmac.compare_digest(signature, expected)
|
|
|
|
def do_GET(self):
|
|
if self.path == "/health":
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "text/plain")
|
|
self.end_headers()
|
|
self.wfile.write(b"OK")
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def do_POST(self):
|
|
if self.path != "/turn":
|
|
self.send_error(404)
|
|
return
|
|
|
|
content_length = int(self.headers.get("Content-Length", 0))
|
|
body = self.rfile.read(content_length)
|
|
|
|
match_id = self.headers.get("X-ACB-Match-Id", "")
|
|
turn_str = self.headers.get("X-ACB-Turn", "0")
|
|
timestamp = self.headers.get("X-ACB-Timestamp", "")
|
|
signature = self.headers.get("X-ACB-Signature", "")
|
|
|
|
if not signature or not self.verify_signature(
|
|
body, match_id, turn_str, timestamp, signature
|
|
):
|
|
self.send_error(401, "Invalid signature")
|
|
return
|
|
|
|
try:
|
|
state = GameState(json.loads(body))
|
|
except (json.JSONDecodeError, KeyError) as e:
|
|
self.send_error(400, f"Invalid game state: {e}")
|
|
return
|
|
|
|
moves = compute_moves(state)
|
|
turn = int(turn_str)
|
|
|
|
response_body = json.dumps({"moves": moves}).encode()
|
|
response_sig = self.sign_response(response_body, match_id, turn)
|
|
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("X-ACB-Signature", response_sig)
|
|
self.end_headers()
|
|
self.wfile.write(response_body)
|
|
|
|
|
|
def compute_moves(state: GameState) -> list:
|
|
"""Replace this with your strategy!"""
|
|
moves = []
|
|
for bot in state.bots:
|
|
if bot["owner"] == state.you_id:
|
|
if random.random() < 0.5:
|
|
moves.append({
|
|
"position": bot["position"],
|
|
"direction": random.choice(DIRECTIONS),
|
|
})
|
|
return moves
|
|
|
|
|
|
def main():
|
|
port = int(os.environ.get("BOT_PORT", "8080"))
|
|
secret = os.environ.get("BOT_SECRET", "")
|
|
|
|
if not secret:
|
|
print("ERROR: BOT_SECRET environment variable is required")
|
|
exit(1)
|
|
|
|
BotHandler.secret = secret
|
|
server = HTTPServer(("", port), BotHandler)
|
|
print(f"Bot listening on port {port}")
|
|
server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|