ai-code-battle/starters/go/main.go
jedarden 7694723758 feat(starter-go): implement Go starter kit with shared game package
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>
2026-05-08 10:10:51 -04:00

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
}