ai-code-battle/wasm/bots/hunter/main.go
jedarden 1839f5e7d1 style: format Go files with gofmt
- cmd/acb-maps-loader/main.go
- wasm/bots/guardian/main.go
- wasm/bots/hunter/main.go
- wasm/bots/random/main.go
- web/package-lock.json (npm ci update)
2026-05-25 15:33:38 -04:00

151 lines
3.4 KiB
Go

//go:build js && wasm
// Package main compiles to hunter.wasm for the browser sandbox.
// HunterBot hunts nearest enemy bot.
package main
import (
"encoding/json"
"math/rand"
"syscall/js"
"github.com/aicodebattle/acb/engine"
)
var (
cfg engine.Config
rng *rand.Rand
visible *engine.VisibleState
)
func jsInit(_ js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return jsErr("configJSON argument required")
}
if err := json.Unmarshal([]byte(args[0].String()), &cfg); err != nil {
return jsErr("parse config: " + err.Error())
}
rng = rand.New(rand.NewSource(42))
visible = &engine.VisibleState{}
return map[string]interface{}{"ok": true}
}
func jsComputeMoves(_ js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return "[]"
}
if err := json.Unmarshal([]byte(args[0].String()), visible); err != nil {
return "[]"
}
moves := getMoves(visible)
json, _ := json.Marshal(moves)
return string(json)
}
func jsFreeResult(_ js.Value, _ []js.Value) interface{} {
return nil
}
// getMoves implements HunterBot: hunt nearest enemy.
func getMoves(state *engine.VisibleState) []engine.Move {
myID := state.You.ID
enemySet := enemyPositions(state.Bots, myID)
energySet := posSet(state.Energy)
var moves []engine.Move
for _, bot := range state.Bots {
if bot.Owner != myID {
continue
}
var dir engine.Direction
if len(enemySet) > 0 {
dir = towardNearest(bot.Position, enemySet)
} else {
dir = towardNearest(bot.Position, energySet)
}
if dir == engine.DirNone {
dir = randDir()
}
moves = append(moves, engine.Move{Position: bot.Position, Direction: dir})
}
return moves
}
func main() {
js.Global().Set("hunterBot", js.ValueOf(map[string]interface{}{
"init": js.FuncOf(jsInit),
"compute_moves": js.FuncOf(jsComputeMoves),
"free_result": js.FuncOf(jsFreeResult),
"version": "1.0.0",
}))
select {}
}
func jsErr(msg string) map[string]interface{} {
return map[string]interface{}{"ok": false, "error": msg}
}
func posSet(positions []engine.Position) map[engine.Position]bool {
m := make(map[engine.Position]bool, len(positions))
for _, p := range positions {
m[p] = true
}
return m
}
func enemyPositions(bots []engine.VisibleBot, myID int) map[engine.Position]bool {
m := make(map[engine.Position]bool)
for _, b := range bots {
if b.Owner != myID {
m[b.Position] = true
}
}
return m
}
func applyDir(p engine.Position, d engine.Direction) engine.Position {
dr, dc := d.Delta()
row := ((p.Row+dr)%cfg.Rows + cfg.Rows) % cfg.Rows
col := ((p.Col+dc)%cfg.Cols + cfg.Cols) % cfg.Cols
return engine.Position{Row: row, Col: col}
}
func dist2(a, b engine.Position) int {
dr := a.Row - b.Row
if dr < 0 {
dr = -dr
}
if dr > cfg.Rows/2 {
dr = cfg.Rows - dr
}
dc := a.Col - b.Col
if dc < 0 {
dc = -dc
}
if dc > cfg.Cols/2 {
dc = cfg.Cols - dc
}
return dr*dr + dc*dc
}
func towardNearest(from engine.Position, targets map[engine.Position]bool) engine.Direction {
if len(targets) == 0 {
return engine.DirNone
}
allDirs := []engine.Direction{engine.DirN, engine.DirE, engine.DirS, engine.DirW}
best, bestD := engine.DirNone, 1<<31-1
for _, d := range allDirs {
np := applyDir(from, d)
for t := range targets {
if d2 := dist2(np, t); d2 < bestD {
bestD = d2
best = d
}
}
}
return best
}
func randDir() engine.Direction {
allDirs := []engine.Direction{engine.DirN, engine.DirE, engine.DirS, engine.DirW}
return allDirs[rng.Intn(4)]
}