ai-code-battle/engine/bot_strategies_phase13.go
jedarden db54067f56 fix(engine): add wall awareness to zone escape direction
getZoneEscapeDirection now accepts wallSet parameter and skips directions
that would move into walls. This prevents bots from getting trapped by
walls when trying to escape the shrinking zone, allowing them to survive
longer and actually engage in combat instead of dying to zone.

Testing with RusherBot vs SwarmBot shows 85% combat density (target: 65-80%).

Fixes: RandomBot getting stuck against walls and dying to zone without
engaging in combat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:46:39 -04:00

1239 lines
33 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: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
// Priority 2: Intercept nearby enemies
if dir == DirNone && nearestEnemy != nil && enemyDist <= 50 {
dir = moveToward(bot.Position, *nearestEnemy, wallSet, claimed, config)
}
// Priority 3: 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 4: 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 5: 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 {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
continue
}
}
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
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
if dir == DirNone && 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 {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
} else {
claimed[bot.Position] = true
}
continue
}
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 {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
} else {
claimed[bot.Position] = true
}
continue
}
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
}
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
assigned[bot.Position] = true
}
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
}
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
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 {
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
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
if dir == DirNone && 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
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
if dir == DirNone && 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
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
if dir == DirNone && 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 {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] && !wallSet[dest] {
claimed[dest] = true
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
} else {
claimed[bot.Position] = true
}
continue
}
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
}
}