feat(engine): reduce default map size to 40x40 and add skirmish map class

Reduce default 2-player map size from 60x60 to 40x40 (from 3600 to 1600
tiles) to increase encounter frequency and combat density. Add -skirmish
flag to acb-mapgen for generating even smaller dense maps (32x32, 0.20
wall density, 15 energy nodes) with "skirmish_" ID prefix.

Changes:
- engine/types.go: DefaultConfig() returns 40x40, ConfigForPlayers()
  uses 800 tiles/player for 2-player (40x40) and 2000 tiles/player for
  3+ players
- cmd/acb-matchmaker/tickers.go: gridForPlayers() returns 40x40 for 2
  players
- cmd/acb-map-evolver/main.go: gridForPlayers() returns 40x40 for 2
  players
- cmd/acb-mapgen/main.go: defaults to 40x40, adds -skirmish flag for
  32x32 high-density maps
- cmd/acb-matchmaker/tickers_test.go: update test expectations for new
  40x40 default

Closes: bf-39wt
This commit is contained in:
jedarden 2026-05-24 10:10:57 -04:00
parent 16d474aceb
commit 18ac1ff2b4
5 changed files with 45 additions and 17 deletions

View file

@ -1047,9 +1047,11 @@ func (e *MapEvolver) saveMap(ctx context.Context, m *Map) error {
}
// gridForPlayers returns default grid dimensions for a given player count.
// For 2-player matches, uses 40x40 (down from 60x60) to increase encounter frequency.
// For 3+ players, targets ~2000 tiles per player.
func gridForPlayers(n int) (rows, cols int) {
if n <= 2 {
return 60, 60
return 40, 40
}
side := int(math.Sqrt(float64(2000 * n)))
if side < 40 {

View file

@ -39,13 +39,14 @@ type Core struct {
func main() {
// Command-line flags
players := flag.Int("players", 2, "Number of players (2, 3, 4, or 6)")
rows := flag.Int("rows", 60, "Grid rows")
cols := flag.Int("cols", 60, "Grid columns")
rows := flag.Int("rows", 40, "Grid rows")
cols := flag.Int("cols", 40, "Grid columns")
wallDensity := flag.Float64("wall-density", 0.15, "Wall density (0.0-0.3)")
energyNodes := flag.Int("energy-nodes", 20, "Energy nodes")
seed := flag.Int64("seed", time.Now().UnixNano(), "Random seed")
output := flag.String("output", "", "Output file (default: stdout)")
maxAttempts := flag.Int("max-attempts", 100, "Max attempts to generate a connected map")
skirmish := flag.Bool("skirmish", false, "Generate a skirmish map (32x32, higher density)")
help := flag.Bool("help", false, "Show help")
flag.Usage = func() {
@ -58,6 +59,8 @@ func main() {
fmt.Fprintf(flag.CommandLine.Output(), " 3 players: 120° rotational\n")
fmt.Fprintf(flag.CommandLine.Output(), " 4 players: 90° rotational\n")
fmt.Fprintf(flag.CommandLine.Output(), " 6 players: 60° rotational\n\n")
fmt.Fprintf(flag.CommandLine.Output(), "Map presets:\n")
fmt.Fprintf(flag.CommandLine.Output(), " -skirmish Small dense map (32x32, 0.20 wall density, 15 energy)\n\n")
fmt.Fprintf(flag.CommandLine.Output(), "Options:\n")
flag.PrintDefaults()
}
@ -69,6 +72,14 @@ func main() {
os.Exit(0)
}
// Apply skirmish preset if requested
if *skirmish {
*rows = 32
*cols = 32
*wallDensity = 0.20
*energyNodes = 15
}
// Validate player count
validPlayers := map[int]bool{2: true, 3: true, 4: true, 6: true}
if !validPlayers[*players] {
@ -91,8 +102,12 @@ func main() {
os.Exit(1)
}
// Generate map ID
m.ID = generateMapID(rng)
// Generate map ID with skirmish prefix if applicable
if *skirmish {
m.ID = generateMapIDWithPrefix(rng, "skirmish")
} else {
m.ID = generateMapID(rng)
}
m.Generated = time.Now().UTC()
// Output
@ -114,12 +129,16 @@ func main() {
}
func generateMapID(rng *rand.Rand) string {
return generateMapIDWithPrefix(rng, "map")
}
func generateMapIDWithPrefix(rng *rand.Rand, prefix string) string {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, 8)
for i := range b {
b[i] = chars[rng.Intn(len(chars))]
}
return "map_" + string(b)
return prefix + "_" + string(b)
}
func generateMap(numPlayers, rows, cols int, wallDensity float64, numEnergyNodes int, rng *rand.Rand) *Map {

View file

@ -348,11 +348,12 @@ func (m *Matchmaker) selectMapLRU(ctx context.Context, playerCount int, rng *ran
return fmt.Sprintf("map_%d", seed%100000), rows, cols, seed
}
// gridForPlayers returns default grid dimensions for a given player count,
// mirroring the formula in engine.ConfigForPlayers (~2000 tiles per player).
// gridForPlayers returns default grid dimensions for a given player count.
// For 2-player matches, uses 40x40 (down from 60x60) to increase encounter frequency.
// For 3+ players, targets ~2000 tiles per player.
func gridForPlayers(n int) (rows, cols int) {
if n <= 2 {
return 60, 60
return 40, 40
}
side := int(math.Sqrt(float64(2000 * n)))
if side < 40 {

View file

@ -187,7 +187,7 @@ func TestGridForPlayers(t *testing.T) {
minArea int
maxArea int
}{
{2, 3000, 4200}, // 60x60 = 3600
{2, 1200, 2000}, // 40x40 = 1600 (reduced from 60x60 for combat density)
{3, 4000, 6000},
{4, 5000, 8500},
{6, 7000, 12000},

View file

@ -182,8 +182,8 @@ type Config struct {
// DefaultConfig returns the default game configuration.
func DefaultConfig() Config {
return Config{
Rows: 60,
Cols: 60,
Rows: 40,
Cols: 40,
MaxTurns: 500,
VisionRadius2: 49, // ~7 tiles
AttackRadius2: 5, // ~2.24 tiles
@ -199,7 +199,8 @@ func DefaultConfig() Config {
}
// ConfigForPlayers returns a config scaled for the given player count and cores per player.
// Uses ~1800-2000 tiles per player (following aichallenge Ants sizing).
// For 2 players, uses 40x40 (800 tiles per player) to increase encounter frequency.
// For 3+ players, uses ~2000 tiles per player (following aichallenge Ants sizing).
func ConfigForPlayers(numPlayers, coresPerPlayer int) Config {
cfg := DefaultConfig()
cfg.CoresPerPlayer = coresPerPlayer
@ -207,14 +208,19 @@ func ConfigForPlayers(numPlayers, coresPerPlayer int) Config {
cfg.CoresPerPlayer = 1
}
// Scale grid: ~2000 tiles per player, square grid
areaPerPlayer := 2000
// Scale grid: smaller maps for 2-player, ~2000 tiles/player for 3+ players
var areaPerPlayer int
if numPlayers == 2 {
areaPerPlayer = 800 // 40x40 for 2 players
} else {
areaPerPlayer = 2000
}
totalArea := areaPerPlayer * numPlayers
side := int(math.Sqrt(float64(totalArea)))
// Clamp to valid range
if side < 40 {
side = 40
if side < 30 {
side = 30
}
if side > 200 {
side = 200