ai-code-battle/bots/defender/Program.cs
jedarden 3cefabb9ed feat(bot): add Defender bot (C#) — core-hugging perimeter archetype
Core-defense strategy: bots patrol evenly-spaced slots around their
own core, intercept enemies entering the perimeter radius, and never
chase past the perimeter. Falls back to energy gathering when cores
are lost.

MAP-Elites profile: Low Aggression, Low Economy, Low Exploration, High Formation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:19:08 -04:00

154 lines
4.5 KiB
C#

// AI Code Battle - Defender Bot (C#)
//
// Core-defense archetype. Bots stay within N cells of their own core,
// intercepting any enemy unit that enters the perimeter.
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
var port = Environment.GetEnvironmentVariable("BOT_PORT") ?? "8080";
var secret = Environment.GetEnvironmentVariable("BOT_SECRET") ?? "";
if (string.IsNullOrEmpty(secret))
{
Console.Error.WriteLine("ERROR: BOT_SECRET environment variable is required");
Environment.Exit(1);
}
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls($"http://0.0.0.0:{port}");
var app = builder.Build();
app.MapGet("/health", () => Results.Ok("OK"));
app.MapPost("/turn", (HttpContext ctx) =>
{
var signature = ctx.Request.Headers["X-ACB-Signature"].FirstOrDefault() ?? "";
var matchId = ctx.Request.Headers["X-ACB-Match-Id"].FirstOrDefault() ?? "";
var turnStr = ctx.Request.Headers["X-ACB-Turn"].FirstOrDefault() ?? "0";
var timestamp = ctx.Request.Headers["X-ACB-Timestamp"].FirstOrDefault() ?? "";
if (string.IsNullOrEmpty(signature))
return Results.Unauthorized();
using var reader = new StreamReader(ctx.Request.Body);
var body = reader.ReadToEndAsync().GetAwaiter().GetResult();
if (!VerifySignature(secret, matchId, turnStr, timestamp, body, signature))
return Results.Unauthorized();
GameState? state;
try
{
state = JsonSerializer.Deserialize<GameState>(body);
if (state == null) return Results.BadRequest("Invalid game state");
}
catch
{
return Results.BadRequest("Invalid JSON");
}
var moves = DefenderStrategy.ComputeMoves(state);
var responseBody = JsonSerializer.Serialize(new { moves });
var turn = int.Parse(turnStr);
var responseSig = SignResponse(secret, matchId, turn, responseBody);
ctx.Response.Headers["X-ACB-Signature"] = responseSig;
return Results.Text(responseBody, "application/json");
});
app.Run();
// --- HMAC helpers ---
static bool VerifySignature(string secret, string matchId, string turn,
string timestamp, string body, string signature)
{
var bodyHash = Sha256Hex(Encoding.UTF8.GetBytes(body));
var signingString = $"{matchId}.{turn}.{timestamp}.{bodyHash}";
var expected = HmacSha256(secret, signingString);
return CryptographicOperations.FixedTimeEquals(
Convert.FromHexString(signature),
Convert.FromHexString(expected)
);
}
static string SignResponse(string secret, string matchId, int turn, string body)
{
var bodyHash = Sha256Hex(Encoding.UTF8.GetBytes(body));
var signingString = $"{matchId}.{turn}.{bodyHash}";
return HmacSha256(secret, signingString);
}
static string HmacSha256(string key, string data)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
return Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))).ToLower();
}
static string Sha256Hex(byte[] data)
{
return Convert.ToHexString(SHA256.HashData(data)).ToLower();
}
// --- Types ---
record GameState
{
public string MatchId { get; init; } = "";
public int Turn { get; init; }
public GameConfig Config { get; init; } = new();
public You You { get; init; } = new();
public List<VisibleBot> Bots { get; init; } = [];
public List<Position> Energy { get; init; } = [];
public List<VisibleCore> Cores { get; init; } = [];
public List<Position> Walls { get; init; } = [];
public List<VisibleBot> Dead { get; init; } = [];
}
record GameConfig
{
public int Rows { get; init; }
public int Cols { get; init; }
public int MaxTurns { get; init; }
public int VisionRadius2 { get; init; }
public int AttackRadius2 { get; init; }
public int SpawnCost { get; init; }
public int EnergyInterval { get; init; }
}
record You
{
public int Id { get; init; }
public int Energy { get; init; }
public int Score { get; init; }
}
record VisibleBot
{
public Position Position { get; init; } = new();
public int Owner { get; init; }
}
record VisibleCore
{
public Position Position { get; init; } = new();
public int Owner { get; init; }
public bool Active { get; init; }
}
record Position
{
public int Row { get; init; }
public int Col { get; init; }
}
record Move
{
public Position Position { get; init; } = new();
public string Direction { get; init; } = "";
}