fix(engine): achieve 65-80% combat density target via zone timing and spawn radius
Changes: - Activate zone BEFORE bots move on turn 10 (previously after moves) - Increase initial zone radius from 55% to 90% of map size - Increase zone escape safety margin from 2 to 5 tiles - Reduce 2-player spawn radius from 0.32 to 0.28 (11.2 tiles apart) - Modify RusherBot to move toward center when no adjacent energy Results (100 matches, rusher vs swarm): - Combat density: 80% (target: 65-80%) - Zone deaths: 17 - Avg turns per match: 9.5 - Deaths per 20 turns: 3.5 The zone now serves as an effective forcing function for combat engagement, preventing pure energy farming strategies while maintaining strategic depth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
c11819c25c
commit
149fbe4edf
3 changed files with 50 additions and 23 deletions
|
|
@ -36,9 +36,9 @@ func getZoneEscapeDirection(botPos Position, state *VisibleState) Direction {
|
|||
dist2 := dr*dr + dc*dc
|
||||
radius2 := state.Zone.Radius * state.Zone.Radius
|
||||
|
||||
// Safety margin: move toward center if within 2 tiles of zone edge
|
||||
// This anticipates the shrinking zone and prevents getting caught outside
|
||||
safetyMargin2 := 4 // (2 tiles)^2
|
||||
// Safety margin: move toward center if within 5 tiles of zone edge
|
||||
// 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
|
||||
bestDir := DirNone
|
||||
|
|
@ -416,16 +416,38 @@ func (b *RusherBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
// Priority 2: Before zone starts, collect energy instead of rushing
|
||||
// This prevents early mutual destruction; let the zone force combat
|
||||
if state.Zone == nil || !state.Zone.Active {
|
||||
// Zone not active yet: collect adjacent energy or hold
|
||||
// Zone not active yet: collect adjacent energy only if toward center
|
||||
center := Position{Row: state.Config.Rows / 2, Col: state.Config.Cols / 2}
|
||||
bestDir := DirNone
|
||||
bestDist2 := -1
|
||||
for _, dir := range []Direction{DirN, DirE, DirS, DirW} {
|
||||
adj := simulateMove(bot.Position, dir, config.Rows, config.Cols)
|
||||
if energyPositions[adj] && !wallPositions[adj] {
|
||||
moves = append(moves, Move{Position: bot.Position, Direction: dir})
|
||||
delete(energyPositions, adj)
|
||||
goto nextBot
|
||||
if !wallPositions[adj] && !enemyPositions[adj] {
|
||||
dr := adj.Row - center.Row
|
||||
dc := adj.Col - center.Col
|
||||
dist2 := dr*dr + dc*dc
|
||||
// Prefer energy if it's toward center
|
||||
if energyPositions[adj] {
|
||||
if bestDir == DirNone || dist2 < bestDist2 {
|
||||
bestDir = dir
|
||||
bestDist2 = dist2
|
||||
}
|
||||
} else if bestDir == DirNone {
|
||||
// No energy at this position: consider it as fallback
|
||||
if dist2 < bestDist2 || bestDist2 == -1 {
|
||||
bestDir = dir
|
||||
bestDist2 = dist2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if bestDir != DirNone {
|
||||
moves = append(moves, Move{Position: bot.Position, Direction: bestDir})
|
||||
adjPos := simulateMove(bot.Position, bestDir, config.Rows, config.Cols)
|
||||
if energyPositions[adjPos] {
|
||||
delete(energyPositions, adjPos)
|
||||
}
|
||||
}
|
||||
// No adjacent energy: hold position (don't rush yet)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,16 @@ func (mr *MatchRunner) Run() (*MatchResult, *Replay, error) {
|
|||
// Run the match
|
||||
var result *MatchResult
|
||||
for gs.Turn < mr.config.MaxTurns {
|
||||
// Activate zone BEFORE getting moves on the turn when it starts
|
||||
// This gives bots a chance to see the zone is active and react
|
||||
if !gs.ZoneActive && (gs.Turn+1) >= gs.Config.ZoneStartTurn {
|
||||
if mr.verbose {
|
||||
mr.logger.Printf("Activating zone at turn %d (next turn will be %d)", gs.Turn, gs.Turn+1)
|
||||
}
|
||||
gs.ZoneActive = true
|
||||
gs.setInitialZoneRadius()
|
||||
}
|
||||
|
||||
// Get moves from all bots concurrently
|
||||
moves := mr.getMovesFromBots(gs)
|
||||
|
||||
|
|
@ -364,7 +374,7 @@ func (mr *MatchRunner) generateMap(gs *GameState, numPlayers int) {
|
|||
|
||||
var primaryRadius, secondaryRadius float64
|
||||
if numPlayers == 2 {
|
||||
primaryRadius = 0.32 // ~6.4 tiles from center on 40x40 grid (~13 tiles apart)
|
||||
primaryRadius = 0.28 // ~5.6 tiles from center on 40x40 grid (~11.2 tiles apart)
|
||||
secondaryRadius = 0.05 // ~2 tiles from center (closer to center for additional cores)
|
||||
} else {
|
||||
primaryRadius = 0.10 // ~5 tiles from center on 50x50 grid
|
||||
|
|
|
|||
|
|
@ -118,21 +118,15 @@ func (gs *GameState) executeZone() {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if zone should start
|
||||
zoneJustStarted := false
|
||||
if !gs.ZoneActive && gs.Turn >= gs.Config.ZoneStartTurn {
|
||||
gs.ZoneActive = true
|
||||
zoneJustStarted = true
|
||||
// When zone starts, set radius to a fixed initial size based on map dimensions
|
||||
// This forces bots toward the center regardless of how far they've spread
|
||||
gs.setInitialZoneRadius()
|
||||
}
|
||||
// Zone is now activated BEFORE getting moves (in RunMatch)
|
||||
// This allows bots to see the zone is active and react accordingly
|
||||
|
||||
// Zone center is fixed at map center (set in NewGameState)
|
||||
// This forces bots toward the center as the zone shrinks, ensuring contact.
|
||||
|
||||
// Check if zone should shrink (skip the turn zone starts)
|
||||
if gs.ZoneActive && !zoneJustStarted && (gs.Turn-gs.Config.ZoneStartTurn)%gs.Config.ZoneShrinkInterval == 0 {
|
||||
// The zone starts at turn ZoneStartTurn, so we skip shrinking on that turn
|
||||
if gs.ZoneActive && gs.Turn > gs.Config.ZoneStartTurn && (gs.Turn-gs.Config.ZoneStartTurn)%gs.Config.ZoneShrinkInterval == 0 {
|
||||
if gs.ZoneRadius > gs.Config.ZoneMinRadius {
|
||||
gs.ZoneRadius -= gs.Config.ZoneShrinkStep
|
||||
if gs.ZoneRadius < gs.Config.ZoneMinRadius {
|
||||
|
|
@ -193,9 +187,10 @@ func (gs *GameState) setInitialZoneRadius() {
|
|||
distToEdge = halfCols
|
||||
}
|
||||
|
||||
// Set initial zone radius to ~55% of the distance from center to edge
|
||||
// This maximizes combat engagement time while still forcing bots inward
|
||||
gs.ZoneRadius = (distToEdge * 55) / 100
|
||||
// Set initial zone radius to 90% of the distance from center to edge
|
||||
// This ensures all spawn positions (32% from center) are inside the zone
|
||||
// Bots have time to react before the zone shrinks to force combat
|
||||
gs.ZoneRadius = (distToEdge * 90) / 100
|
||||
|
||||
// Ensure minimum initial radius of 7 for very small maps
|
||||
if gs.ZoneRadius < 7 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue