feat(map-evolver): bias energy toward center, carve corridors to force contact
Energy node placement now uses a tiered radius distribution: 30% in the contested central zone (0.05-0.20 from center), 40% in the mid-zone (0.20-0.40), and 30% in the home zone (0.40-0.60). Previously nodes were placed uniformly at 0.20-0.70, letting bots farm their home quadrant indefinitely without crossing the midline. After cellular automata wall generation, a 3-wide corridor is carved from each core straight to the map center, plus a 5x5 open arena at the center tile. This creates lanes that funnel bots into contact — replicating the key mechanic that drove frequent fights in the original AI Challenge Ants game, where symmetric food spawning near the midfield forced both colonies to expand outward and collide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4937f94afd
commit
42e9561e46
1 changed files with 72 additions and 1 deletions
|
|
@ -1026,7 +1026,17 @@ func generateMapOnce(numPlayers, rows, cols int, wallDensity float64, numEnergyN
|
|||
for i := 0; i < nodesPerSector; i++ {
|
||||
for attempt := 0; attempt < 100; attempt++ {
|
||||
angle := rng.Float64() * 2.0 * math.Pi / float64(numPlayers)
|
||||
radius := 0.2 + rng.Float64()*0.5
|
||||
// Tiered radius: bias toward center to force contested energy collection.
|
||||
// 30% central (forces both players to midfield), 40% mid, 30% home.
|
||||
var radius float64
|
||||
switch {
|
||||
case i < nodesPerSector*3/10:
|
||||
radius = 0.05 + rng.Float64()*0.15 // 0.05–0.20: contested central zone
|
||||
case i < nodesPerSector*7/10:
|
||||
radius = 0.20 + rng.Float64()*0.20 // 0.20–0.40: mid-zone
|
||||
default:
|
||||
radius = 0.40 + rng.Float64()*0.20 // 0.40–0.60: home zone
|
||||
}
|
||||
r := centerRow + int(float64(centerRow)*radius*math.Cos(angle))
|
||||
c := centerCol + int(float64(centerCol)*radius*math.Sin(angle))
|
||||
pos := wrap(r, c)
|
||||
|
|
@ -1133,6 +1143,20 @@ func generateMapOnce(numPlayers, rows, cols int, wallDensity float64, numEnergyN
|
|||
}
|
||||
}
|
||||
|
||||
// Carve corridors from each core to the map center.
|
||||
// Creates 3-wide lanes that funnel bots into contact at midfield.
|
||||
for _, core := range m.Cores {
|
||||
carveCorridor(grid, core.Position.Row, core.Position.Col, centerRow, centerCol, rows, cols)
|
||||
}
|
||||
// Open a 5×5 arena at the center so all corridors connect.
|
||||
for dr := -2; dr <= 2; dr++ {
|
||||
for dc := -2; dc <= 2; dc++ {
|
||||
rr := ((centerRow+dr)%rows + rows) % rows
|
||||
cc := ((centerCol+dc)%cols + cols) % cols
|
||||
grid[rr][cc] = false
|
||||
}
|
||||
}
|
||||
|
||||
// Collect wall positions and thin to target density.
|
||||
totalTiles := rows * cols
|
||||
targetWalls := int(float64(totalTiles) * wallDensity)
|
||||
|
|
@ -1153,6 +1177,53 @@ func generateMapOnce(numPlayers, rows, cols int, wallDensity float64, numEnergyN
|
|||
return m
|
||||
}
|
||||
|
||||
// carveCorridor opens a 3-wide path from (r0,c0) to (r1,c1) using integer stepping.
|
||||
// Perpendicular width is 1 tile on each side of the center line.
|
||||
func carveCorridor(grid [][]bool, r0, c0, r1, c1, rows, cols int) {
|
||||
dr := r1 - r0
|
||||
dc := c1 - c0
|
||||
steps := dr
|
||||
if steps < 0 {
|
||||
steps = -steps
|
||||
}
|
||||
if dc < 0 && -dc > steps {
|
||||
steps = -dc
|
||||
} else if dc > steps {
|
||||
steps = dc
|
||||
}
|
||||
if steps == 0 {
|
||||
return
|
||||
}
|
||||
horizontal := dc < 0 && -dc > (dr+1) || dc > 0 && dc > (dr+1) // wider in col direction
|
||||
if dr < 0 {
|
||||
dr = -dr
|
||||
}
|
||||
// recompute: use originals
|
||||
origDR := r1 - r0
|
||||
origDC := c1 - c0
|
||||
for step := 0; step <= steps; step++ {
|
||||
r := r0 + origDR*step/steps
|
||||
c := c0 + origDC*step/steps
|
||||
// Widen perpendicular to primary movement direction
|
||||
if !horizontal {
|
||||
// Mostly vertical: widen horizontally
|
||||
for wc := -1; wc <= 1; wc++ {
|
||||
rr := ((r)%rows + rows) % rows
|
||||
cc := ((c+wc)%cols + cols) % cols
|
||||
grid[rr][cc] = false
|
||||
}
|
||||
} else {
|
||||
// Mostly horizontal: widen vertically
|
||||
for wr := -1; wr <= 1; wr++ {
|
||||
rr := ((r+wr)%rows + rows) % rows
|
||||
cc := ((c)%cols + cols) % cols
|
||||
grid[rr][cc] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = dr // suppress unused warning
|
||||
}
|
||||
|
||||
// generateMapID creates a random map ID.
|
||||
func generateMapID(rng *rand.Rand) string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue