ai-code-battle/cmd/acb-mapgen/connectivity.go
jedarden ea04f4debb style: apply gofmt alignment fixes across codebase
Tab/space alignment consistency from running gofmt on all packages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:40:33 -04:00

100 lines
2.5 KiB
Go

// Command acb-mapgen generates symmetric maps for AI Code Battle.
package main
import (
"container/list"
"math/rand"
)
// PositionSet is a set of positions for fast lookup.
type PositionSet map[Position]bool
// CheckConnectivity verifies that all passable tiles in the map are reachable
// from each player's core. This is critical for ensuring fair gameplay.
//
// For toroidal grids, we use BFS from a starting point and verify all
// passable tiles are visited.
func CheckConnectivity(m *Map) bool {
if len(m.Cores) == 0 {
return true // No cores, nothing to validate
}
// Build a set of passable positions
passable, totalPassable := m.passablePositions()
if totalPassable == 0 {
return false // No passable tiles at all
}
// BFS from first core position
start := m.Cores[0].Position
if !passable[start] {
return false // Core itself is not passable
}
visited := make(PositionSet)
queue := list.New()
queue.PushBack(start)
visited[start] = true
count := 1
// Direction deltas for 4-connected neighbors (cardinal directions)
dirs := []Position{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
for queue.Len() > 0 {
front := queue.Front()
queue.Remove(front)
curr := front.Value.(Position)
for _, d := range dirs {
// Toroidal wrapping
nr := ((curr.Row+d.Row)%m.Rows + m.Rows) % m.Rows
nc := ((curr.Col+d.Col)%m.Cols + m.Cols) % m.Cols
np := Position{Row: nr, Col: nc}
if passable[np] && !visited[np] {
visited[np] = true
queue.PushBack(np)
count++
}
}
}
// All passable tiles must be visited
return count == totalPassable
}
// passablePositions returns all positions that are not walls and the count.
func (m *Map) passablePositions() (PositionSet, int) {
result := make(PositionSet)
// Build wall set for fast lookup
wallSet := make(map[Position]bool)
for _, w := range m.Walls {
wallSet[w] = true
}
for r := 0; r < m.Rows; r++ {
for c := 0; c < m.Cols; c++ {
p := Position{Row: r, Col: c}
if !wallSet[p] {
result[p] = true
}
}
}
return result, len(result)
}
// EnsureConnectivity generates walls while maintaining full connectivity.
// It uses a retry mechanism: if walls disconnect the map, regenerate and try again.
func EnsureConnectivity(numPlayers, rows, cols int, wallDensity float64, numEnergyNodes int, rng *rand.Rand, maxAttempts int) *Map {
for attempt := 0; attempt < maxAttempts; attempt++ {
m := generateMap(numPlayers, rows, cols, wallDensity, numEnergyNodes, rng)
if CheckConnectivity(m) {
return m
}
}
// If all attempts failed, return nil
return nil
}