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>
This commit is contained in:
jedarden 2026-05-26 19:46:39 -04:00
parent 2495aedd8d
commit db54067f56
3 changed files with 29 additions and 20 deletions

View file

@ -77,7 +77,8 @@ func (b *RandomBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range state.Bots {
if bot.Owner == state.You.ID {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
// Note: RandomBot doesn't have wall info, so pass nil for wallSet
if zoneDir := getZoneEscapeDirection(bot.Position, state, nil); zoneDir != DirNone {
moves = append(moves, Move{
Position: bot.Position,
Direction: zoneDir,

View file

@ -8,7 +8,8 @@ import (
// getZoneEscapeDirection returns the direction toward the zone center if the bot is outside
// or near the edge of the safe zone radius. Returns DirNone if the bot is safe or zone is disabled.
func getZoneEscapeDirection(botPos Position, state *VisibleState) Direction {
// Avoids walls when choosing the escape direction.
func getZoneEscapeDirection(botPos Position, state *VisibleState, wallSet map[Position]bool) Direction {
if state.Zone == nil || !state.Zone.Active {
return DirNone
}
@ -40,7 +41,7 @@ func getZoneEscapeDirection(botPos Position, state *VisibleState) Direction {
// This accounts for zone shrinking (1 tile/turn) and gives time to reach safety
safetyMargin2 := 25 // (5 tiles)^2 - anticipates ~5 turns of zone shrink
if dist2 >= radius2-safetyMargin2 {
// Move toward center: choose direction that reduces distance
// Move toward center: choose direction that reduces distance and avoids walls
bestDir := DirNone
bestReduction := 0
@ -51,6 +52,13 @@ func getZoneEscapeDirection(botPos Position, state *VisibleState) Direction {
Col: ((botPos.Col+ddc)%cols + cols) % cols,
}
// Skip if blocked by wall (only check if wallSet is provided)
if wallSet != nil && wallSet[newPos] {
continue
}
// If wallSet is nil, assume all tiles are passable (RandomBot fallback)
// This is safe because the engine will ignore moves into walls
newDr := newPos.Row - center.Row
newDc := newPos.Col - center.Col
@ -150,7 +158,7 @@ func (b *GathererBot) computeBotMove(
state *VisibleState,
) *Move {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
return &Move{
Position: bot.Position,
Direction: zoneDir,
@ -408,7 +416,7 @@ func (b *RusherBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range myBots {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
continue
}
@ -641,7 +649,7 @@ func (b *GuardianBot) computeBotMove(
state *VisibleState,
) *Move {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
return &Move{Position: bot.Position, Direction: zoneDir}
}
@ -868,7 +876,7 @@ func (b *SwarmBot) computeBotMove(
state *VisibleState,
) *Move {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
return &Move{Position: bot.Position, Direction: zoneDir}
}
@ -1113,7 +1121,7 @@ func (b *HunterBot) GetMoves(state *VisibleState) ([]Move, error) {
}
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
assignedHunters[bot.Position] = true
continue
@ -1141,7 +1149,7 @@ func (b *HunterBot) GetMoves(state *VisibleState) ([]Move, error) {
}
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallPositions); zoneDir != DirNone {
moves = append(moves, Move{Position: bot.Position, Direction: zoneDir})
continue
}

View file

@ -61,7 +61,7 @@ func (b *DefenderBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -155,7 +155,7 @@ func (b *ScoutBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range myBots {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
@ -260,7 +260,7 @@ func (b *FarmerBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -339,7 +339,7 @@ func (b *PacifistBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range myBots {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
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
@ -427,7 +427,7 @@ func (b *PhalanxBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range myBots {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
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
@ -518,7 +518,7 @@ func (b *RaiderBot) GetMoves(state *VisibleState) ([]Move, error) {
}
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dest := simulateMove(bot.Position, zoneDir, config.Rows, config.Cols)
if !claimed[dest] {
claimed[dest] = true
@ -563,7 +563,7 @@ func (b *RaiderBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -656,7 +656,7 @@ func (b *NomadBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -767,7 +767,7 @@ func (b *OpportunistBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -865,7 +865,7 @@ func (b *AssassinBot) GetMoves(state *VisibleState) ([]Move, error) {
var dir Direction
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
if zoneDir := getZoneEscapeDirection(bot.Position, state, wallSet); zoneDir != DirNone {
dir = zoneDir
}
@ -920,7 +920,7 @@ func (b *KamikazeBot) GetMoves(state *VisibleState) ([]Move, error) {
for _, bot := range myBots {
// Priority 1: Escape zone if threatened
if zoneDir := getZoneEscapeDirection(bot.Position, state); zoneDir != DirNone {
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