Complete Go starter kit for AI Code Battle with: - main.go: HTTP server with HMAC authentication, placeholder computeMoves() - game/ package: Shared utilities (types, auth, grid) for reuse - types.go: Game state types, Direction constants, Position, etc. - auth.go: HMAC-SHA256 signing/verification with timestamp validation - grid.go: Toroidal distance, BFS pathfinding, neighbor functions - Tests: Comprehensive test coverage for grid and auth utilities - Dockerfile: Multi-stage build with Go 1.24-alpine - README: Complete documentation with examples and protocol reference The starter kit provides a minimal working bot that holds position by default. Participants implement their strategy in computeMoves() using the provided grid utilities. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
// AI Code Battle - Go Starter Kit
|
|
//
|
|
// A minimal bot scaffold with HMAC authentication and a placeholder
|
|
// strategy. Implement computeMoves() to build your bot.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
|
|
"acb-starter-go/game"
|
|
)
|
|
|
|
func main() {
|
|
port := getEnv("BOT_PORT", "8080")
|
|
secret := getEnv("BOT_SECRET", "")
|
|
|
|
if secret == "" {
|
|
log.Fatal("BOT_SECRET environment variable is required")
|
|
}
|
|
|
|
server := &Server{
|
|
secret: secret,
|
|
}
|
|
|
|
http.HandleFunc("/turn", server.handleTurn)
|
|
http.HandleFunc("/health", server.handleHealth)
|
|
|
|
addr := fmt.Sprintf(":%s", port)
|
|
log.Printf("Bot listening on %s", addr)
|
|
if err := http.ListenAndServe(addr, nil); err != nil {
|
|
log.Fatalf("Server failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Server handles HTTP requests for the bot.
|
|
type Server struct {
|
|
secret string
|
|
}
|
|
|
|
func (s *Server) handleTurn(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "failed to read body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
headers := game.AuthHeaders{
|
|
MatchID: r.Header.Get("X-ACB-Match-Id"),
|
|
Turn: r.Header.Get("X-ACB-Turn"),
|
|
Timestamp: r.Header.Get("X-ACB-Timestamp"),
|
|
Signature: r.Header.Get("X-ACB-Signature"),
|
|
}
|
|
|
|
if !game.VerifyRequest(s.secret, headers, body) {
|
|
http.Error(w, "invalid signature", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
var state game.GameState
|
|
if err := json.Unmarshal(body, &state); err != nil {
|
|
http.Error(w, "invalid game state", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
moves := computeMoves(&state)
|
|
response := game.MoveResponse{Moves: moves}
|
|
responseBody, err := json.Marshal(response)
|
|
if err != nil {
|
|
http.Error(w, "failed to marshal response", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
responseSig := game.SignResponse(s.secret, headers.MatchID, headers.Turn, responseBody)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("X-ACB-Signature", responseSig)
|
|
w.Write(responseBody)
|
|
}
|
|
|
|
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("OK"))
|
|
}
|
|
|
|
// computeMoves is where you implement your bot's strategy.
|
|
//
|
|
// The game engine calls this function every turn with the current game state.
|
|
// Return a list of moves for your bots. Any bot not included in the response
|
|
// will hold position.
|
|
//
|
|
// Use the utilities in the game package:
|
|
// - game.ToroidalManhattan() for distance calculations
|
|
// - game.BFSDirection() for pathfinding
|
|
// - game.Neighbors() for getting adjacent positions
|
|
//
|
|
// Example:
|
|
// moves := []game.Move{}
|
|
// for _, bot := range state.Bots {
|
|
// if bot.Owner == state.You.ID {
|
|
// moves = append(moves, game.Move{
|
|
// Position: bot.Position,
|
|
// Direction: game.DirN, // Move north
|
|
// })
|
|
// }
|
|
// }
|
|
// return moves
|
|
func computeMoves(state *game.GameState) []game.Move {
|
|
// TODO: Implement your strategy here!
|
|
//
|
|
// This stub returns no moves, which means all your bots will hold
|
|
// position every turn. Replace this with your own logic.
|
|
|
|
return nil
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|