generator: verified

This commit is contained in:
Ilia Denisov
2025-09-13 02:13:44 +03:00
parent 05999687aa
commit 84578dc61c
6 changed files with 114 additions and 54 deletions
+36 -36
View File
@@ -2,72 +2,72 @@ package generator
import ( import (
"fmt" "fmt"
"math"
) )
func (m *Map) CreatePlanets(num int, deadZoneRadius float32, size, resources func() float32) error {
for range num {
coord, err := m.NewCoordinate(deadZoneRadius)
if err != nil {
return err
}
planet := NewPlanet(coord, size(), resources())
m.AddPlanet(planet)
}
return nil
}
func Generate(cfg ...func(*MapSetting)) (Map, error) { func Generate(cfg ...func(*MapSetting)) (Map, error) {
ms := DefaultMapSetting() ms := DefaultMapSetting()
for i := range cfg { for i := range cfg {
cfg[i](&ms) cfg[i](&ms)
} }
// TODO: pre-calculate sufficient map size
var mapSize uint32 = 200
m, err := NewMap(mapSize, mapSize, ms.Players) size := ms.ExpectedSize()
m, err := NewMap(size, size, ms.Players)
if err != nil { if err != nil {
return Map{}, fmt.Errorf("NewMap: %s", err) return Map{}, fmt.Errorf("%s: NewMap: %s", ms, err)
} }
totalPlanets := ms.Players * 10 // TODO: why 10? freePlanets := ms.NobodysPlanets()
freePlanets := totalPlanets - ms.Players*(ms.DWCount+1)
createPlanets := func(ps PlanetSetting) error {
return m.CreatePlanets(ps.Count(freePlanets), float32(ps.MinDistanceHW), RandIFn(ps.MinSize, ps.MaxSize), RandIFn(ps.MinResource, ps.MaxResource))
}
// 1. Place Giant planets // 1. Place Giant planets
giantsNum := int(math.Ceil(float64(freePlanets) * float64(ms.GiantPlanets.Probability))) if err := createPlanets(ms.GiantPlanets); err != nil {
m.CreatePlanets(giantsNum, float32(ms.GiantPlanets.MinDistanceHW), return Map{}, fmt.Errorf("%s: create giant planets: %s", ms, err)
RandIFn(ms.GiantPlanets.MinSize, ms.GiantPlanets.MaxSize), }
RandIFn(ms.GiantPlanets.MinResource, ms.GiantPlanets.MaxResource),
)
// 2. Place Big planets // 2. Place Big planets
bigsNum := int(math.Ceil(float64(freePlanets) * float64(ms.BigPlanets.Probability))) if err := createPlanets(ms.BigPlanets); err != nil {
m.CreatePlanets(bigsNum, float32(ms.BigPlanets.MinDistanceHW), return Map{}, fmt.Errorf("%s: create big planets: %s", ms, err)
RandIFn(ms.BigPlanets.MinSize, ms.BigPlanets.MaxSize), }
RandIFn(ms.BigPlanets.MinResource, ms.BigPlanets.MaxResource),
)
// X. Place players' Home Worlds // 3. Place players' Home Worlds
for player := 0; player < int(ms.Players); player++ { for player := 0; player < int(ms.Players); player++ {
coord, err := m.NewCoordinate(float32(ms.HWMinDistance)) coord, err := m.NewCoordinate(float32(ms.HWMinDistance))
if err != nil { if err != nil {
return Map{}, err return Map{}, fmt.Errorf("%s: hw new_coordinate: %s", ms, err)
} }
planet := NewPlanet(coord, float32(ms.HWSize), float32(ms.HWResources)) planet := NewPlanet(coord, float32(ms.HWSize), float32(ms.HWResources))
m.HomePlanets[player] = PlanetarySystem{HW: planet} m.HomePlanets[player] = PlanetarySystem{HW: planet}
} }
// 4. Clear plotter and set dead zones around existing planets
m.plotter.Clear() m.plotter.Clear()
for i := range m.HomePlanets { for i := range m.HomePlanets {
m.plotter.MarkDeadZone(m.HomePlanets[i].HW.Position.X, m.HomePlanets[i].HW.Position.Y, 5) m.plotter.MarkDeadZone(m.HomePlanets[i].HW.Position.X, m.HomePlanets[i].HW.Position.Y, ms.OthersMinDistance)
for j := range m.HomePlanets[i].DW { for j := range m.HomePlanets[i].DW {
m.plotter.MarkDeadZone(m.HomePlanets[i].DW[j].Position.X, m.HomePlanets[i].DW[j].Position.Y, 5) m.plotter.MarkDeadZone(m.HomePlanets[i].DW[j].Position.X, m.HomePlanets[i].DW[j].Position.Y, ms.OthersMinDistance)
} }
} }
for i := range m.FreePlanets {
m.plotter.MarkDeadZone(m.FreePlanets[i].Position.X, m.FreePlanets[i].Position.Y, ms.OthersMinDistance)
}
for i := range m.FreePlanets { // 5. Place Normal planets
m.plotter.MarkDeadZone(m.FreePlanets[i].Position.X, m.FreePlanets[i].Position.Y, 5) if err := createPlanets(ms.NormalPlanets); err != nil {
return Map{}, fmt.Errorf("%s: create normal planets: %s", ms, err)
}
// 6. Place Rich planets
if err := createPlanets(ms.RichPlanets); err != nil {
return Map{}, fmt.Errorf("%s: create rich planets: %s", ms, err)
}
// 7. Place Asteroids
if err := createPlanets(ms.Asterioids); err != nil {
return Map{}, fmt.Errorf("%s: create asteroids: %s", ms, err)
} }
return *m, nil return *m, nil
+22 -2
View File
@@ -1,11 +1,31 @@
package generator_test package generator_test
import ( import (
"fmt"
"testing" "testing"
"github.com/iliadenisov/galaxy/pkg/generator" "github.com/iliadenisov/galaxy/pkg/generator"
) )
func Test_Generator(t *testing.T) { func TestGenerator(t *testing.T) {
generator.Generate() for players := 10; players <= 50; players++ {
_, err := generator.Generate(func(ms *generator.MapSetting) { ms.Players = uint32(players) })
if err != nil {
t.Errorf("generate: %s", err)
break
}
}
}
func BenchmarkGenerator(b *testing.B) {
i := 0
for b.Loop() {
i++
b.Run(fmt.Sprintf("instance #%02d", i), func(b *testing.B) {
_, err := generator.Generate()
if err != nil {
b.Error(err)
}
})
}
} }
+13 -1
View File
@@ -31,7 +31,7 @@ type PlanetarySystem struct {
} }
func NewMap(width, height, players uint32) (*Map, error) { func NewMap(width, height, players uint32) (*Map, error) {
p, err := plotter.NewPlotter(width, height, 1.0) p, err := plotter.NewPlotter(width, height, defaultFactor)
if err != nil { if err != nil {
return nil, fmt.Errorf("NewPlotter: %s", err) return nil, fmt.Errorf("NewPlotter: %s", err)
} }
@@ -43,6 +43,18 @@ func NewMap(width, height, players uint32) (*Map, error) {
}, nil }, nil
} }
func (m *Map) CreatePlanets(num int, deadZoneRadius float32, size, resources func() float32) error {
for range num {
coord, err := m.NewCoordinate(deadZoneRadius)
if err != nil {
return err
}
planet := NewPlanet(coord, size(), resources())
m.AddPlanet(planet)
}
return nil
}
func (m *Map) AddPlanet(planet Planet) { func (m *Map) AddPlanet(planet Planet) {
m.FreePlanets = append(m.FreePlanets, planet) m.FreePlanets = append(m.FreePlanets, planet)
} }
+2 -3
View File
@@ -39,9 +39,6 @@ func NewBitmapPlotter(bm bitmap.Bitmap, factor float32) (Plotter, error) {
} }
func (p Plotter) RandomFreePoint(deadZoneRaduis float32) (float32, float32, error) { func (p Plotter) RandomFreePoint(deadZoneRaduis float32) (float32, float32, error) {
if deadZoneRaduis <= 0. {
return 0, 0, fmt.Errorf("radius must be positive value: %f", deadZoneRaduis)
}
fsCount := p.freeCountFn() fsCount := p.freeCountFn()
if fsCount == 0 { if fsCount == 0 {
return 0, 0, errors.New("RandomFreePoint: no free space left") return 0, 0, errors.New("RandomFreePoint: no free space left")
@@ -51,7 +48,9 @@ func (p Plotter) RandomFreePoint(deadZoneRaduis float32) (float32, float32, erro
if err != nil { if err != nil {
return 0, 0, fmt.Errorf("RandomFreePoint: freeNumberToCoordFn: %s", err) return 0, 0, fmt.Errorf("RandomFreePoint: freeNumberToCoordFn: %s", err)
} }
if deadZoneRaduis > 0 {
p.plotDeadZone(x, y, deadZoneRaduis) p.plotDeadZone(x, y, deadZoneRaduis)
}
planetX := float32(x)*p.factor + rand.Float32()*p.factor planetX := float32(x)*p.factor + rand.Float32()*p.factor
planetY := float32(y)*p.factor + rand.Float32()*p.factor planetY := float32(y)*p.factor + rand.Float32()*p.factor
return planetX, planetY, nil return planetX, planetY, nil
+2 -2
View File
@@ -63,8 +63,8 @@ func TestRandomFreePoint(t *testing.T) {
} }
_, _, err = p.RandomFreePoint(0) _, _, err = p.RandomFreePoint(0)
if err == nil { if err != nil {
t.Error("expect: error when radius not positive, got: none") t.Errorf("expect: no error when radius is zero, got: %s", err)
} }
_, _, err = p.RandomFreePoint(float32(w + h)) // guaranteed to mark whole area dead zone _, _, err = p.RandomFreePoint(float32(w + h)) // guaranteed to mark whole area dead zone
+33 -4
View File
@@ -1,5 +1,12 @@
package generator package generator
import (
"fmt"
"math"
)
const defaultFactor float32 = 0.1
type MapSetting struct { type MapSetting struct {
Players uint32 Players uint32
HWSize uint32 HWSize uint32
@@ -13,11 +20,28 @@ type MapSetting struct {
GiantPlanets PlanetSetting GiantPlanets PlanetSetting
BigPlanets PlanetSetting BigPlanets PlanetSetting
OthersMinDistance float32
NormalPlanets PlanetSetting NormalPlanets PlanetSetting
RichPlanets PlanetSetting RichPlanets PlanetSetting
Asterioids PlanetSetting Asterioids PlanetSetting
} }
func (ms MapSetting) String() string {
return fmt.Sprintf("MapSetting[players=%d HWMinDistance=%d Size=%d]", ms.Players, ms.HWMinDistance, ms.ExpectedSize())
}
func (ms MapSetting) ExpectedSize() uint32 {
return uint32(math.Sqrt(float64(ms.Players)) * float64(ms.HWMinDistance) * 1.4)
}
func (ms MapSetting) TotalPlanets() uint32 {
return ms.Players * 10
}
func (ms MapSetting) NobodysPlanets() uint32 {
return ms.TotalPlanets() - ms.Players*(ms.DWCount+1)
}
type PlanetSetting struct { type PlanetSetting struct {
MinDistanceHW uint32 MinDistanceHW uint32
MinSize float32 MinSize float32
@@ -27,6 +51,10 @@ type PlanetSetting struct {
Probability float32 Probability float32
} }
func (ps PlanetSetting) Count(freePlanets uint32) int {
return int(math.Ceil(float64(freePlanets) * float64(ps.Probability)))
}
func DefaultMapSetting() MapSetting { func DefaultMapSetting() MapSetting {
return MapSetting{ return MapSetting{
Players: 25, Players: 25,
@@ -54,9 +82,10 @@ func DefaultMapSetting() MapSetting {
MaxResource: 10, MaxResource: 10,
Probability: 0.18, Probability: 0.18,
}, },
OthersMinDistance: defaultFactor, // minimal of 1 pixel on the plotter
NormalPlanets: PlanetSetting{ NormalPlanets: PlanetSetting{
MinDistanceHW: 0, MinDistanceHW: 0,
MinSize: 0.001, MinSize: 0,
MaxSize: 1000, MaxSize: 1000,
MinResource: 0, MinResource: 0,
MaxResource: 10, MaxResource: 10,
@@ -64,7 +93,7 @@ func DefaultMapSetting() MapSetting {
}, },
RichPlanets: PlanetSetting{ RichPlanets: PlanetSetting{
MinDistanceHW: 0, MinDistanceHW: 0,
MinSize: 0.001, MinSize: 0,
MaxSize: 500, MaxSize: 500,
MinResource: 5, MinResource: 5,
MaxResource: 25, MaxResource: 25,
@@ -72,8 +101,8 @@ func DefaultMapSetting() MapSetting {
}, },
Asterioids: PlanetSetting{ Asterioids: PlanetSetting{
MinDistanceHW: 0, MinDistanceHW: 0,
MinSize: 0.001, MinSize: 0,
MaxSize: 10, MaxSize: 0,
MinResource: 0, MinResource: 0,
MaxResource: 0, MaxResource: 0,
Probability: 0.08, Probability: 0.08,