Tab/space alignment consistency from running gofmt on all packages. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
100 lines
2.5 KiB
Go
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
|
|
}
|