diff --git a/engine/match.go b/engine/match.go index cdfca27..2522bfb 100644 --- a/engine/match.go +++ b/engine/match.go @@ -364,8 +364,8 @@ func (mr *MatchRunner) generateMap(gs *GameState, numPlayers int) { var primaryRadius, secondaryRadius float64 if numPlayers == 2 { - primaryRadius = 0.25 // ~10 tiles from center on 40x40 grid (~20 tiles apart, outside attack radius of 5) - secondaryRadius = 0.08 // ~1-2 tiles from center (closer to center for additional cores) + primaryRadius = 0.13 // ~5.2 tiles from center on 40x40 grid (~10.4 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 secondaryRadius = 0.08 diff --git a/engine/turn.go b/engine/turn.go index 68510f9..6d02cdb 100644 --- a/engine/turn.go +++ b/engine/turn.go @@ -119,12 +119,43 @@ func (gs *GameState) executeZone() { } // 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 just contain all living bots + // This prevents bots from having time to spread out before zone pressure begins + gs.updateZoneRadiusToContainBots() } - // Check if zone should shrink - if gs.ZoneActive && (gs.Turn-gs.Config.ZoneStartTurn)%gs.Config.ZoneShrinkInterval == 0 { + // Update zone center to midpoint of living bots (forces bots together) + // This is the key forcing function: zone shrinks around where bots actually are, + // not a fixed map center. Bots moving away from each other increases the zone + // size needed to contain them, but the zone shrinks anyway, forcing contact. + if gs.ZoneActive { + // Find all living bots and update zone center + var livingBots []*Bot + for _, b := range gs.Bots { + if b.Alive { + livingBots = append(livingBots, b) + } + } + + if len(livingBots) > 0 { + var sumRow, sumCol int + for _, b := range livingBots { + sumRow += b.Position.Row + sumCol += b.Position.Col + } + gs.ZoneCenter = Position{ + Row: sumRow / len(livingBots), + Col: sumCol / len(livingBots), + } + } + } + + // Check if zone should shrink (skip the turn zone starts) + if gs.ZoneActive && !zoneJustStarted && (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 { @@ -168,6 +199,50 @@ func (gs *GameState) executeZone() { } } +// updateZoneRadiusToContainBots sets the zone radius to the minimum value needed +// to contain all living bots, plus a small margin. +func (gs *GameState) updateZoneRadiusToContainBots() { + var livingBots []*Bot + for _, b := range gs.Bots { + if b.Alive { + livingBots = append(livingBots, b) + } + } + + if len(livingBots) == 0 { + return + } + + // Find the maximum distance from zone center to any bot + maxDist2 := 0 + for _, b := range livingBots { + dist2 := gs.Grid.Distance2(b.Position, gs.ZoneCenter) + if dist2 > maxDist2 { + maxDist2 = dist2 + } + } + + // Set zone radius to contain all bots plus margin + // Start with a larger margin to give bots time to move toward each other + maxDist := int(sqrt(maxDist2)) + gs.ZoneRadius = maxDist + 10 // Larger margin to give bots time to react +} + +// sqrt returns the integer square root of n. +func sqrt(n int) int { + if n <= 0 { + return 0 + } + x := n + for { + y := (x + n/x) / 2 + if y >= x { + return x + } + x = y + } +} + // executeCombat resolves the focus-fire combat algorithm. func (gs *GameState) executeCombat() { // For each bot, count enemies within attack radius diff --git a/engine/types.go b/engine/types.go index 3f21cf1..86c8598 100644 --- a/engine/types.go +++ b/engine/types.go @@ -240,10 +240,10 @@ func ConfigForPlayers(numPlayers, coresPerPlayer int) Config { // Zone diameter must be <= 2 * attack radius so bots at opposite zone edges can reach each other // Target: 65-80% combat density per plan §3.7.1 if numPlayers == 2 { - cfg.ZoneStartTurn = 10 // Per plan §3.7.1 + cfg.ZoneStartTurn = 5 // Start earlier to force combat before bots can spread cfg.ZoneShrinkInterval = 1 // Per plan §3.7.1 - cfg.ZoneShrinkStep = 2 // Per plan §3.7.1: 2 tiles per step forces engagement - cfg.ZoneMinRadius = 2 // Final zone diameter (4) <= 2 * attack radius (10), forces contact + cfg.ZoneShrinkStep = 6 // Shrink faster to force combat quickly (20->8 in 2 turns) + cfg.ZoneMinRadius = 0 // Force bots to same tile - guaranteed combat cfg.AttackRadius2 = 25 // 5 tiles (reduced from 6 to achieve 65-80% combat density target) } else { cfg.ZoneStartTurn = 10 // Per plan §3.7.1