ai-code-battle/cmd/acb-wasm/bot-template/main.go
jedarden ea04f4debb style: apply gofmt alignment fixes across codebase
Tab/space alignment consistency from running gofmt on all packages.

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

197 lines
4.5 KiB
Go

//go:build js && wasm
// Package main implements a WASM bot for the AI Code Battle sandbox.
// Compile with: GOOS=js GOARCH=wasm go build -o mybot.wasm .
//
// The bot exports an 'acbBot' global object with:
//
// init(configJSON: string) - called once at match start
// compute_moves(stateJSON: string) - called each turn, returns moves JSON
package main
import (
"encoding/json"
"syscall/js"
"github.com/aicodebattle/acb/engine"
)
// botState holds persistent state across turns (e.g., pathfinding cache).
type botState struct {
config engine.Config
myID int
knownPos map[string]bool // positions we've seen
}
var state = &botState{
knownPos: make(map[string]bool),
}
// jsInit is called once at match start with the game config.
func jsInit(_ js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return map[string]interface{}{"ok": false, "error": "configJSON required"}
}
var cfg engine.Config
if err := json.Unmarshal([]byte(args[0].String()), &cfg); err != nil {
return map[string]interface{}{"ok": false, "error": err.Error()}
}
state.config = cfg
return map[string]interface{}{"ok": true}
}
// jsComputeMoves is called each turn with the visible game state.
func jsComputeMoves(_ js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return "[]"
}
var visible engine.VisibleState
if err := json.Unmarshal([]byte(args[0].String()), &visible); err != nil {
return "[]"
}
state.myID = visible.You.ID
moves := computeMoves(&visible)
jsonBytes, _ := json.Marshal(moves)
return string(jsonBytes)
}
// computeMoves contains your bot logic. This is a simple example:
// move each bot toward the nearest energy, avoiding enemies if close.
func computeMoves(visible *engine.VisibleState) []engine.Move {
var moves []engine.Move
energySet := make(map[engine.Position]bool)
for _, e := range visible.Energy {
energySet[e] = true
}
enemySet := make(map[engine.Position]bool)
for _, b := range visible.Bots {
if b.Owner != state.myID {
enemySet[b.Position] = true
}
}
for _, bot := range visible.Bots {
if bot.Owner != state.myID {
continue
}
dir := fleeFromEnemies(bot.Position, enemySet)
if dir == engine.DirNone {
dir = towardNearest(bot.Position, energySet)
}
if dir == engine.DirNone {
dir = randomDir()
}
moves = append(moves, engine.Move{
Position: bot.Position,
Direction: dir,
})
}
return moves
}
func fleeFromEnemies(from engine.Position, enemies map[engine.Position]bool) engine.Direction {
thr := state.config.AttackRadius2 + 4
for e := range enemies {
if dist2(from, e) <= thr {
return bestFleeDir(from, enemies)
}
}
return engine.DirNone
}
func bestFleeDir(from engine.Position, enemies map[engine.Position]bool) engine.Direction {
bestDir := engine.DirNone
bestDist := -1
for _, d := range []engine.Direction{engine.DirN, engine.DirE, engine.DirS, engine.DirW} {
dr, dc := d.Delta()
np := engine.Position{
Row: ((from.Row+dr)%state.config.Rows + state.config.Rows) % state.config.Rows,
Col: ((from.Col+dc)%state.config.Cols + state.config.Cols) % state.config.Cols,
}
minDist := 1 << 30
for e := range enemies {
if d2 := dist2(np, e); d2 < minDist {
minDist = d2
}
}
if minDist > bestDist {
bestDist = minDist
bestDir = d
}
}
return bestDir
}
func towardNearest(from engine.Position, targets map[engine.Position]bool) engine.Direction {
if len(targets) == 0 {
return engine.DirNone
}
bestDir := engine.DirNone
bestDist := 1 << 30
for _, d := range []engine.Direction{engine.DirN, engine.DirE, engine.DirS, engine.DirW} {
dr, dc := d.Delta()
np := engine.Position{
Row: ((from.Row+dr)%state.config.Rows + state.config.Rows) % state.config.Rows,
Col: ((from.Col+dc)%state.config.Cols + state.config.Cols) % state.config.Cols,
}
for t := range targets {
if d2 := dist2(np, t); d2 < bestDist {
bestDist = d2
bestDir = d
}
}
}
return bestDir
}
func dist2(a, b engine.Position) int {
dr := a.Row - b.Row
if dr < 0 {
dr = -dr
}
if dr > state.config.Rows/2 {
dr = state.config.Rows - dr
}
dc := a.Col - b.Col
if dc < 0 {
dc = -dc
}
if dc > state.config.Cols/2 {
dc = state.config.Cols - dc
}
return dr*dr + dc*dc
}
func randomDir() engine.Direction {
dirs := []engine.Direction{engine.DirN, engine.DirE, engine.DirS, engine.DirW}
return dirs[(state.config.Rows+state.config.Cols)%4]
}
func main() {
done := make(chan struct{})
js.Global().Set("acbBot", js.ValueOf(map[string]interface{}{
"init": js.FuncOf(jsInit),
"compute_moves": js.FuncOf(jsComputeMoves),
}))
<-done
}