ai-code-battle/engine/bot_strategies_phase13.go
jedarden c81bf1de7d docs: clarify Phase 13 strategies vs §10 evolver pipeline
Add comprehensive file header to bot_strategies_phase13.go explaining:
- These are hand-coded strategy implementations, NOT LLM-evolved
- How they relate to the autonomous evolution system in plan §10
- Key distinction: Phase 13 = human-designed fixed archetypes,
  Evolver = LLM-generated continuously evolving candidates

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:57:50 -04:00

1148 lines
30 KiB
Go

package engine
import (
"math"
"math/rand"
"sort"
)
// ────────────────────────────────────────────────────────────────────────────
// Phase 13 expansion bots — 10 new strategy archetypes
//
// These are HAND-CODED strategy implementations, NOT LLM-evolved bots.
// They serve as:
// - Reference implementations for polyglot bot development
// - Strategic diversity fixtures for the ladder (similar to RandomBot, GathererBot, etc.)
// - Inspiration/parents for the LLM evolver pipeline (see plan §10)
//
// For the autonomous LLM evolution system, see docs/plan/plan.md §10, which describes:
// - Island model with FunSearch/AlphaEvolve approach
// - Programs database storing LLM-generated candidates
// - Validation pipeline, evaluation arena, promotion gates
// - Auto-deployment of evolved bots to ladder
//
// Phase 13 archetypes vs §10 evolver:
// - Phase 13: Human-designed strategies, fixed behavior, deterministic
// - Evolver: LLM-generated strategies, continuously evolving, experimental
// ────────────────────────────────────────────────────────────────────────────
// DefenderBot hugs own cores and intercepts enemies within a perimeter.
type DefenderBot struct{ rng *rand.Rand }
func NewDefenderBot(seed int64) *DefenderBot {
return &DefenderBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *DefenderBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
myCores := myActiveCores(state.Cores, myID)
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
energySet := posSetFromPositions(state.Energy)
coreSet := posSetFromCorePositions(myCores)
const perimeterRadius2 = 25
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
nearestEnemy, enemyDist := findNearestPos(bot.Position, enemySet, config)
_, coreDist := findNearestPos(bot.Position, coreSet, config)
var dir Direction
// Priority 1: Intercept nearby enemies
if nearestEnemy != nil && enemyDist <= 50 {
dir = moveToward(bot.Position, *nearestEnemy, wallSet, claimed, config)
}
// Priority 2: Return to core perimeter if too far
if dir == DirNone && coreDist > perimeterRadius2 {
nearestCore, _ := findNearestPos(bot.Position, coreSet, config)
if nearestCore != nil {
dir = moveToward(bot.Position, *nearestCore, wallSet, claimed, config)
}
}
// Priority 3: Gather energy within perimeter
if dir == DirNone && len(energySet) > 0 {
nearestEnergy, _ := findNearestPos(bot.Position, energySet, config)
if nearestEnergy != nil {
dir = moveToward(bot.Position, *nearestEnergy, wallSet, claimed, config)
}
}
// Priority 4: Patrol near core
if dir == DirNone && len(coreSet) > 0 {
nearestCore, _ := findNearestPos(bot.Position, coreSet, config)
if nearestCore != nil {
dir = moveToward(bot.Position, *nearestCore, wallSet, claimed, config)
}
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// ScoutBot maximizes map coverage, avoids combat.
type ScoutBot struct {
rng *rand.Rand
seen map[Position]int
}
func NewScoutBot(seed int64) *ScoutBot {
return &ScoutBot{
rng: rand.New(rand.NewSource(seed)),
seen: make(map[Position]int),
}
}
func (b *ScoutBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
vr := intSqrt(config.VisionRadius2) + 1
for _, bot := range myBots {
for dr := -vr; dr <= vr; dr++ {
for dc := -vr; dc <= vr; dc++ {
if dr*dr+dc*dc > config.VisionRadius2 {
continue
}
r := (bot.Position.Row + dr + config.Rows) % config.Rows
c := (bot.Position.Col + dc + config.Cols) % config.Cols
b.seen[Position{Row: r, Col: c}] = state.Turn
}
}
}
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
if shouldFleeFromEnemies(bot.Position, enemySet, config) {
dir := fleeDirection(bot.Position, enemySet, wallSet, config)
if dir != DirNone {
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
continue
}
}
}
dir := b.bestExploreDir(bot.Position, config, state.Turn, claimed, wallSet)
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
func (b *ScoutBot) bestExploreDir(pos Position, config Config, turn int, claimed, wallSet map[Position]bool) Direction {
bestDir := DirNone
bestScore := -1
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
score := 0
for step := 1; step <= 8; step++ {
dr, dc := directionDelta(dir)
r := (pos.Row + dr*step + config.Rows) % config.Rows
c := (pos.Col + dc*step + config.Cols) % config.Cols
p := Position{Row: r, Col: c}
if wallSet[p] {
continue
}
lastSeen, ok := b.seen[p]
if !ok {
score += turn + 1
} else {
staleness := turn - lastSeen
if staleness > 0 {
score += staleness
}
}
}
dest := simulateMove(pos, dir, config.Rows, config.Cols)
if claimed[dest] || wallSet[dest] {
score = -1
}
if score > bestScore {
bestScore = score
bestDir = dir
}
}
return bestDir
}
// FarmerBot maximizes energy collection, avoids combat entirely.
type FarmerBot struct{ rng *rand.Rand }
func NewFarmerBot(seed int64) *FarmerBot {
return &FarmerBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *FarmerBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
energySet := posSetFromPositions(state.Energy)
coreSet := posSetFromCorePositions(myActiveCores(state.Cores, myID))
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
usedEnergy := make(map[Position]bool)
for _, bot := range myBots {
var dir Direction
if shouldFleeFromEnemies(bot.Position, enemySet, config) {
dir = fleeDirection(bot.Position, enemySet, wallSet, config)
}
if dir == DirNone && len(energySet) > 0 {
var bestE *Position
bestDist := int(1e9)
for e := range energySet {
if usedEnergy[e] {
continue
}
d := distance2(bot.Position, e, config.Rows, config.Cols)
if d < bestDist {
bestDist = d
eCopy := e
bestE = &eCopy
}
}
if bestE != nil {
usedEnergy[*bestE] = true
dir = moveToward(bot.Position, *bestE, wallSet, claimed, config)
}
}
if dir == DirNone && len(coreSet) > 0 {
nearestCore, coreDist := findNearestPos(bot.Position, coreSet, config)
if nearestCore != nil && coreDist > 9 {
dir = moveToward(bot.Position, *nearestCore, wallSet, claimed, config)
}
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// PacifistBot maximizes distance from enemies, never initiates combat.
type PacifistBot struct{ rng *rand.Rand }
func NewPacifistBot(seed int64) *PacifistBot {
return &PacifistBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *PacifistBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
coreSet := posSetFromCorePositions(myActiveCores(state.Cores, myID))
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
sortBotsByEnemyDist(myBots, enemySet, config)
for _, bot := range myBots {
bestDir := DirNone
bestScore := float64(math.MinInt64)
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if wallSet[dest] || claimed[dest] || enemySet[dest] {
continue
}
score := 0.0
if len(enemySet) > 0 {
minDist := float64(math.MaxInt32)
for ep := range enemySet {
d := float64(distance2(dest, ep, config.Rows, config.Cols))
if d < minDist {
minDist = d
}
}
score += minDist * 10
}
if isInDanger(bot.Position, enemySet, config) && len(coreSet) > 0 {
coreDist := float64(distToSet(dest, coreSet, config))
currentCoreDist := float64(distToSet(bot.Position, coreSet, config))
score += (currentCoreDist - coreDist) * 15
}
if score > bestScore {
bestScore = score
bestDir = dir
}
}
if bestDir != DirNone {
dest := simulateMove(bot.Position, bestDir, config.Rows, config.Cols)
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: bestDir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// PhalanxBot moves in tight formation toward enemies.
type PhalanxBot struct{ rng *rand.Rand }
func NewPhalanxBot(seed int64) *PhalanxBot {
return &PhalanxBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *PhalanxBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
center := circularMeanOf(botsToPositions(myBots), config.Rows, config.Cols)
target := Position{Row: config.Rows / 2, Col: config.Cols / 2}
if len(enemyBots) > 0 {
target = circularMeanOf(botsToPositions(enemyBots), config.Rows, config.Cols)
}
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
bestDir := DirNone
bestScore := float64(math.MinInt64)
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if wallSet[dest] || enemySet[dest] || claimed[dest] {
continue
}
score := 0.0
distToTarget := float64(distance2(dest, target, config.Rows, config.Cols))
currentDist := float64(distance2(bot.Position, target, config.Rows, config.Cols))
score += (currentDist - distToTarget) * 10
distToCenter := float64(distance2(dest, center, config.Rows, config.Cols))
score -= distToCenter * 0.5
for ep := range enemySet {
if distance2(dest, ep, config.Rows, config.Cols) <= config.AttackRadius2 {
score += 50
break
}
}
if score > bestScore {
bestScore = score
bestDir = dir
}
}
if bestDir != DirNone {
dest := simulateMove(bot.Position, bestDir, config.Rows, config.Cols)
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: bestDir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// RaiderBot performs hit-and-run attacks on isolated enemies.
type RaiderBot struct {
rng *rand.Rand
engagementTurns map[Position]int
}
func NewRaiderBot(seed int64) *RaiderBot {
return &RaiderBot{
rng: rand.New(rand.NewSource(seed)),
engagementTurns: make(map[Position]int),
}
}
func (b *RaiderBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
wallSet := posSetFromPositions(state.Walls)
energySet := posSetFromPositions(state.Energy)
isolated := findIsolatedEnemies(enemyBots, config)
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
assigned := make(map[Position]bool)
for _, target := range isolated {
for _, bot := range myBots {
if assigned[bot.Position] {
continue
}
d := distance2(bot.Position, target.Position, config.Rows, config.Cols)
if d < 400 {
turns := b.engagementTurns[bot.Position]
var dir Direction
if turns >= 2 {
dir = fleeDirection(bot.Position, map[Position]bool{target.Position: true}, wallSet, config)
if turns >= 4 {
b.engagementTurns[bot.Position] = 0
}
} else {
dir = moveToward(bot.Position, target.Position, wallSet, claimed, config)
b.engagementTurns[bot.Position] = turns + 1
}
if dir != DirNone {
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
assigned[bot.Position] = true
goto nextRaider
}
}
}
}
nextRaider:
}
for _, bot := range myBots {
if assigned[bot.Position] {
continue
}
dir := DirNone
if len(energySet) > 0 {
nearest, _ := findNearestPos(bot.Position, energySet, config)
if nearest != nil {
dir = moveToward(bot.Position, *nearest, wallSet, claimed, config)
}
}
if dir == DirNone {
center := Position{Row: config.Rows / 2, Col: config.Cols / 2}
dir = moveToward(bot.Position, center, wallSet, claimed, config)
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// NomadBot constantly relocates to new regions of the map.
type NomadBot struct {
rng *rand.Rand
target *Position
targetTurn int
arrived bool
arriveTurn int
}
func NewNomadBot(seed int64) *NomadBot {
return &NomadBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *NomadBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
centroid := circularMeanOf(botsToPositions(myBots), config.Rows, config.Cols)
arriveRadius := int(float64(min(config.Rows, config.Cols)) * 0.15)
needNew := b.target == nil
if b.arrived && state.Turn-b.arriveTurn >= 10 {
needNew = true
}
if !b.arrived && state.Turn-b.targetTurn >= 40 {
needNew = true
}
if needNew {
b.target = pickNomadTarget(centroid, config, b.rng)
b.targetTurn = state.Turn
b.arrived = false
}
if !b.arrived && b.target != nil {
d := distance2(centroid, *b.target, config.Rows, config.Cols)
if d <= arriveRadius*arriveRadius {
b.arrived = true
b.arriveTurn = state.Turn
}
}
target := b.target
if target == nil {
t := Position{Row: config.Rows / 2, Col: config.Cols / 2}
target = &t
}
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
var dir Direction
if shouldFleeFromEnemies(bot.Position, enemySet, config) {
dir = fleeDirection(bot.Position, enemySet, wallSet, config)
}
if dir == DirNone && b.arrived && len(enemySet) > 0 {
nearest, dist := findNearestPos(bot.Position, enemySet, config)
if nearest != nil && dist <= config.AttackRadius2*4 {
dir = moveToward(bot.Position, *nearest, wallSet, claimed, config)
}
}
if dir == DirNone {
dir = moveToward(bot.Position, *target, wallSet, claimed, config)
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// OpportunistBot targets weakest visible enemy, fights only with numerical advantage.
type OpportunistBot struct{ rng *rand.Rand }
func NewOpportunistBot(seed int64) *OpportunistBot {
return &OpportunistBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *OpportunistBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
energySet := posSetFromPositions(state.Energy)
coreSet := posSetFromCorePositions(myActiveCores(state.Cores, myID))
// Find best target: most isolated enemy where we have numerical advantage
var bestTarget *Position
bestScore := -1.0
for _, enemy := range enemyBots {
isolation := 0.0
minFriendDist := float64(1e9)
for _, other := range enemyBots {
if other.Position == enemy.Position {
continue
}
d := float64(distance2(enemy.Position, other.Position, config.Rows, config.Cols))
if d < minFriendDist {
minFriendDist = d
}
}
if minFriendDist == 1e9 {
isolation = 10.0
} else {
isolation = math.Sqrt(minFriendDist)
}
localAlly := 0
localEnemy := 0
for _, mb := range myBots {
if distance2(mb.Position, enemy.Position, config.Rows, config.Cols) <= 25 {
localAlly++
}
}
for _, oe := range enemyBots {
if distance2(oe.Position, enemy.Position, config.Rows, config.Cols) <= 25 {
localEnemy++
}
}
vulnerability := 1.0
if localEnemy > 0 {
vulnerability = 1.0 / float64(localEnemy)
}
score := isolation * vulnerability
if localAlly >= localEnemy && score > bestScore {
bestScore = score
p := enemy.Position
bestTarget = &p
}
}
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
var dir Direction
if bestTarget != nil {
dir = moveToward(bot.Position, *bestTarget, wallSet, claimed, config)
}
if dir == DirNone && shouldFleeFromEnemies(bot.Position, enemySet, config) {
mySet := posSetFromBots(myBots)
nearestAlly, _ := findNearestPos(bot.Position, mySet, config)
if nearestAlly != nil {
dir = moveToward(bot.Position, *nearestAlly, wallSet, claimed, config)
}
}
if dir == DirNone && len(energySet) > 0 {
nearest, _ := findNearestPos(bot.Position, energySet, config)
if nearest != nil {
dir = moveToward(bot.Position, *nearest, wallSet, claimed, config)
}
}
if dir == DirNone && len(coreSet) > 0 {
nearestCore, _ := findNearestPos(bot.Position, coreSet, config)
if nearestCore != nil {
dir = moveToward(bot.Position, *nearestCore, wallSet, claimed, config)
}
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// AssassinBot rushes enemy cores exclusively, ignoring enemy bots.
type AssassinBot struct {
rng *rand.Rand
knownCores map[Position]bool
}
func NewAssassinBot(seed int64) *AssassinBot {
return &AssassinBot{
rng: rand.New(rand.NewSource(seed)),
knownCores: make(map[Position]bool),
}
}
func (b *AssassinBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots := part.friendly
if len(myBots) == 0 {
return nil, nil
}
wallSet := posSetFromPositions(state.Walls)
for _, core := range state.Cores {
if core.Owner != myID && core.Active {
b.knownCores[core.Position] = true
}
}
var target *Position
if len(b.knownCores) > 0 {
center := circularMeanOf(botsToPositions(myBots), config.Rows, config.Cols)
bestDist := int(1e9)
for core := range b.knownCores {
d := distance2(center, core, config.Rows, config.Cols)
if d < bestDist {
bestDist = d
p := core
target = &p
}
}
}
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
for _, bot := range myBots {
var dir Direction
if target != nil {
dir = moveToward(bot.Position, *target, wallSet, claimed, config)
}
if dir == DirNone {
opposite := Position{Row: config.Rows - bot.Position.Row, Col: config.Cols - bot.Position.Col}
dir = moveToward(bot.Position, opposite, wallSet, claimed, config)
}
if dir == DirNone {
dir = randDirection(b.rng)
}
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: dir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// KamikazeBot aggressively attacks nearest enemies at all costs.
type KamikazeBot struct{ rng *rand.Rand }
func NewKamikazeBot(seed int64) *KamikazeBot {
return &KamikazeBot{rng: rand.New(rand.NewSource(seed))}
}
func (b *KamikazeBot) GetMoves(state *VisibleState) ([]Move, error) {
myID := state.You.ID
config := state.Config
part := partitionBots(state.Bots, myID)
myBots, enemyBots := part.friendly, part.enemy
if len(myBots) == 0 {
return nil, nil
}
enemySet := posSetFromBots(enemyBots)
wallSet := posSetFromPositions(state.Walls)
energySet := posSetFromPositions(state.Energy)
moves := make([]Move, 0, len(myBots))
claimed := make(map[Position]bool)
sortBotsByEnemyDist(myBots, enemySet, config)
for _, bot := range myBots {
nearestEnemy, _ := findNearestPos(bot.Position, enemySet, config)
bestDir := DirNone
bestScore := float64(math.MinInt64)
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
dest := simulateMove(bot.Position, dir, config.Rows, config.Cols)
if wallSet[dest] || claimed[dest] {
continue
}
score := 0.0
if nearestEnemy != nil {
distToEnemy := float64(distance2(dest, *nearestEnemy, config.Rows, config.Cols))
currentDist := float64(distance2(bot.Position, *nearestEnemy, config.Rows, config.Cols))
score -= distToEnemy * 100
if distToEnemy <= float64(config.AttackRadius2) {
score += 200
}
score += (currentDist - distToEnemy) * 50
} else {
enemyCores := enemyActiveCorePositions(state.Cores, myID)
if len(enemyCores) > 0 {
nearestCore, _ := findNearestPos(dest, enemyCores, config)
if nearestCore != nil {
coreDist := float64(distance2(dest, *nearestCore, config.Rows, config.Cols))
score -= coreDist * 100
}
}
}
if energySet[dest] {
score += 5
}
if score > bestScore {
bestScore = score
bestDir = dir
}
}
if bestDir != DirNone {
dest := simulateMove(bot.Position, bestDir, config.Rows, config.Cols)
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: bestDir})
} else {
claimed[bot.Position] = true
}
}
return moves, nil
}
// ────────────────────────────────────────────────────────────────────────────
// Phase 13 shared helpers
// ────────────────────────────────────────────────────────────────────────────
type botPartition struct {
friendly []VisibleBot
enemy []VisibleBot
}
func partitionBots(bots []VisibleBot, myID int) botPartition {
var friendly, enemy []VisibleBot
for _, b := range bots {
if b.Owner == myID {
friendly = append(friendly, b)
} else {
enemy = append(enemy, b)
}
}
return botPartition{friendly: friendly, enemy: enemy}
}
func myActiveCores(cores []VisibleCore, myID int) []VisibleCore {
var result []VisibleCore
for _, c := range cores {
if c.Owner == myID && c.Active {
result = append(result, c)
}
}
return result
}
func posSetFromBots(bots []VisibleBot) map[Position]bool {
m := make(map[Position]bool, len(bots))
for _, b := range bots {
m[b.Position] = true
}
return m
}
func posSetFromPositions(positions []Position) map[Position]bool {
m := make(map[Position]bool, len(positions))
for _, p := range positions {
m[p] = true
}
return m
}
func posSetFromCorePositions(cores []VisibleCore) map[Position]bool {
m := make(map[Position]bool, len(cores))
for _, c := range cores {
m[c.Position] = true
}
return m
}
func enemyActiveCorePositions(cores []VisibleCore, myID int) map[Position]bool {
m := make(map[Position]bool)
for _, c := range cores {
if c.Owner != myID && c.Active {
m[c.Position] = true
}
}
return m
}
func findNearestPos(pos Position, targets map[Position]bool, config Config) (*Position, int) {
var best *Position
bestDist := int(1e9)
for t := range targets {
d := distance2(pos, t, config.Rows, config.Cols)
if d < bestDist {
bestDist = d
p := t
best = &p
}
}
return best, bestDist
}
func moveToward(from, to Position, wallSet, claimed map[Position]bool, config Config) Direction {
bestDir := DirNone
bestDist := int(1e9)
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
dest := simulateMove(from, dir, config.Rows, config.Cols)
if wallSet[dest] || claimed[dest] {
continue
}
d := distance2(dest, to, config.Rows, config.Cols)
if d < bestDist {
bestDist = d
bestDir = dir
}
}
return bestDir
}
func shouldFleeFromEnemies(pos Position, enemySet map[Position]bool, config Config) bool {
for ep := range enemySet {
if distance2(pos, ep, config.Rows, config.Cols) <= config.AttackRadius2+9 {
return true
}
}
return false
}
func isInDanger(pos Position, enemySet map[Position]bool, config Config) bool {
for ep := range enemySet {
if distance2(pos, ep, config.Rows, config.Cols) <= config.AttackRadius2 {
return true
}
}
return false
}
func fleeDirection(pos Position, enemySet, wallSet map[Position]bool, config Config) Direction {
bestDir := DirNone
bestMinDist := -1
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
dest := simulateMove(pos, dir, config.Rows, config.Cols)
if wallSet[dest] {
continue
}
minDist := int(1e9)
for ep := range enemySet {
d := distance2(dest, ep, config.Rows, config.Cols)
if d < minDist {
minDist = d
}
}
if minDist > bestMinDist {
bestMinDist = minDist
bestDir = dir
}
}
return bestDir
}
func randDirection(rng *rand.Rand) Direction {
dirs := []Direction{DirN, DirE, DirS, DirW}
return dirs[rng.Intn(4)]
}
func circularMeanOf(positions []Position, rows, cols int) Position {
if len(positions) == 0 {
return Position{Row: rows / 2, Col: cols / 2}
}
rowScale := 2.0 * math.Pi / float64(rows)
colScale := 2.0 * math.Pi / float64(cols)
var sumSinR, sumCosR, sumSinC, sumCosC float64
for _, p := range positions {
sumSinR += math.Sin(float64(p.Row) * rowScale)
sumCosR += math.Cos(float64(p.Row) * rowScale)
sumSinC += math.Sin(float64(p.Col) * colScale)
sumCosC += math.Cos(float64(p.Col) * colScale)
}
n := float64(len(positions))
r := math.Atan2(sumSinR/n, sumCosR/n) / rowScale
c := math.Atan2(sumSinC/n, sumCosC/n) / colScale
return Position{
Row: int(math.Mod(math.Mod(r, float64(rows))+float64(rows), float64(rows))),
Col: int(math.Mod(math.Mod(c, float64(cols))+float64(cols), float64(cols))),
}
}
func botsToPositions(bots []VisibleBot) []Position {
positions := make([]Position, len(bots))
for i, b := range bots {
positions[i] = b.Position
}
return positions
}
func findIsolatedEnemies(enemies []VisibleBot, config Config) []VisibleBot {
var isolated []VisibleBot
for _, bot := range enemies {
minDist := int(1e9)
for _, other := range enemies {
if bot.Position == other.Position {
continue
}
d := distance2(bot.Position, other.Position, config.Rows, config.Cols)
if d < minDist {
minDist = d
}
}
if minDist >= 16 || len(enemies) == 1 {
isolated = append(isolated, bot)
}
}
return isolated
}
func pickNomadTarget(centroid Position, config Config, rng *rand.Rand) *Position {
candidates := []Position{
{Row: config.Rows / 5, Col: config.Cols / 5},
{Row: config.Rows / 5, Col: 4 * config.Cols / 5},
{Row: 4 * config.Rows / 5, Col: config.Cols / 5},
{Row: 4 * config.Rows / 5, Col: 4 * config.Cols / 5},
{Row: (centroid.Row + config.Rows/2) % config.Rows, Col: (centroid.Col + config.Cols/2) % config.Cols},
{Row: 0, Col: config.Cols / 2},
{Row: config.Rows - 1, Col: config.Cols / 2},
{Row: config.Rows / 2, Col: 0},
{Row: config.Rows / 2, Col: config.Cols - 1},
}
p := candidates[rng.Intn(len(candidates))]
return &p
}
func sortBotsByEnemyDist(bots []VisibleBot, enemySet map[Position]bool, config Config) {
sort.Slice(bots, func(i, j int) bool {
di := distToSet(bots[i].Position, enemySet, config)
dj := distToSet(bots[j].Position, enemySet, config)
return di < dj
})
}
func distToSet(pos Position, targets map[Position]bool, config Config) int {
minDist := int(1e9)
for t := range targets {
d := distance2(pos, t, config.Rows, config.Cols)
if d < minDist {
minDist = d
}
}
return minDist
}
func intSqrt(x int) int {
if x <= 0 {
return 0
}
r := 0
for r*r <= x {
r++
}
return r - 1
}
func directionDelta(d Direction) (int, int) {
switch d {
case DirN:
return -1, 0
case DirS:
return 1, 0
case DirE:
return 0, 1
case DirW:
return 0, -1
default:
return 0, 0
}
}