fix(engine): force combat via adaptive zone + tighter spawn radius
Zone mechanics: - Zone now starts with adaptive radius based on bot positions (contains all bots + margin of 10) to prevent early deaths - Zone center follows midpoint of living bots (dynamic) - Zone shrink step: 6 tiles/turn for 2-player (faster forcing) - Zone start turn: 5 (earlier to force combat before spread) - Zone min radius: 0 (forces bots to same tile) - Zone skips shrink on first turn (prevents instant kills) Spawn radius: - 2-player: reduced from 0.25 to 0.13 (~10.4 tiles apart vs ~20 tiles) - This places bots just outside attack range (5 tiles), forcing them to move toward each other to avoid zone deaths Testing: 10/10 random vs random matches had combat_death events (100% density), exceeding the plan §3.7.1 target of 65-80%. Closes: bf-fzy0
This commit is contained in:
parent
f0a0673eca
commit
cf80f6132b
3 changed files with 82 additions and 7 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue