From 84578dc61cb41caaaea0ab2c2fa191f752cc9773 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Sat, 13 Sep 2025 02:13:44 +0300 Subject: [PATCH] generator: verified --- pkg/generator/generator.go | 72 +++++++++++++-------------- pkg/generator/generator_test.go | 24 ++++++++- pkg/generator/map.go | 14 +++++- pkg/generator/plotter/plotter.go | 7 ++- pkg/generator/plotter/plotter_test.go | 4 +- pkg/generator/settings.go | 47 +++++++++++++---- 6 files changed, 114 insertions(+), 54 deletions(-) diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index cd0956b..2b80512 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -2,72 +2,72 @@ package generator import ( "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) { ms := DefaultMapSetting() for i := range cfg { 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 { - return Map{}, fmt.Errorf("NewMap: %s", err) + return Map{}, fmt.Errorf("%s: NewMap: %s", ms, err) } - totalPlanets := ms.Players * 10 // TODO: why 10? - freePlanets := totalPlanets - ms.Players*(ms.DWCount+1) + freePlanets := ms.NobodysPlanets() + + 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 - giantsNum := int(math.Ceil(float64(freePlanets) * float64(ms.GiantPlanets.Probability))) - m.CreatePlanets(giantsNum, float32(ms.GiantPlanets.MinDistanceHW), - RandIFn(ms.GiantPlanets.MinSize, ms.GiantPlanets.MaxSize), - RandIFn(ms.GiantPlanets.MinResource, ms.GiantPlanets.MaxResource), - ) + if err := createPlanets(ms.GiantPlanets); err != nil { + return Map{}, fmt.Errorf("%s: create giant planets: %s", ms, err) + } // 2. Place Big planets - bigsNum := int(math.Ceil(float64(freePlanets) * float64(ms.BigPlanets.Probability))) - m.CreatePlanets(bigsNum, float32(ms.BigPlanets.MinDistanceHW), - RandIFn(ms.BigPlanets.MinSize, ms.BigPlanets.MaxSize), - RandIFn(ms.BigPlanets.MinResource, ms.BigPlanets.MaxResource), - ) + if err := createPlanets(ms.BigPlanets); err != nil { + return Map{}, fmt.Errorf("%s: create big planets: %s", ms, err) + } - // X. Place players' Home Worlds + // 3. Place players' Home Worlds for player := 0; player < int(ms.Players); player++ { coord, err := m.NewCoordinate(float32(ms.HWMinDistance)) 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)) m.HomePlanets[player] = PlanetarySystem{HW: planet} } + // 4. Clear plotter and set dead zones around existing planets m.plotter.Clear() - 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 { - 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, 5) + m.plotter.MarkDeadZone(m.FreePlanets[i].Position.X, m.FreePlanets[i].Position.Y, ms.OthersMinDistance) + } + + // 5. Place Normal planets + 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 diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go index 9276ca8..475b0ba 100644 --- a/pkg/generator/generator_test.go +++ b/pkg/generator/generator_test.go @@ -1,11 +1,31 @@ package generator_test import ( + "fmt" "testing" "github.com/iliadenisov/galaxy/pkg/generator" ) -func Test_Generator(t *testing.T) { - generator.Generate() +func TestGenerator(t *testing.T) { + 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) + } + }) + } } diff --git a/pkg/generator/map.go b/pkg/generator/map.go index 28f60bb..294a685 100644 --- a/pkg/generator/map.go +++ b/pkg/generator/map.go @@ -31,7 +31,7 @@ type PlanetarySystem struct { } 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 { return nil, fmt.Errorf("NewPlotter: %s", err) } @@ -43,6 +43,18 @@ func NewMap(width, height, players uint32) (*Map, error) { }, 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) { m.FreePlanets = append(m.FreePlanets, planet) } diff --git a/pkg/generator/plotter/plotter.go b/pkg/generator/plotter/plotter.go index c86e75a..a97ec1b 100644 --- a/pkg/generator/plotter/plotter.go +++ b/pkg/generator/plotter/plotter.go @@ -39,9 +39,6 @@ func NewBitmapPlotter(bm bitmap.Bitmap, factor float32) (Plotter, 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() if fsCount == 0 { 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 { return 0, 0, fmt.Errorf("RandomFreePoint: freeNumberToCoordFn: %s", err) } - p.plotDeadZone(x, y, deadZoneRaduis) + if deadZoneRaduis > 0 { + p.plotDeadZone(x, y, deadZoneRaduis) + } planetX := float32(x)*p.factor + rand.Float32()*p.factor planetY := float32(y)*p.factor + rand.Float32()*p.factor return planetX, planetY, nil diff --git a/pkg/generator/plotter/plotter_test.go b/pkg/generator/plotter/plotter_test.go index 15ed866..3bf8dbc 100644 --- a/pkg/generator/plotter/plotter_test.go +++ b/pkg/generator/plotter/plotter_test.go @@ -63,8 +63,8 @@ func TestRandomFreePoint(t *testing.T) { } _, _, err = p.RandomFreePoint(0) - if err == nil { - t.Error("expect: error when radius not positive, got: none") + if err != nil { + 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 diff --git a/pkg/generator/settings.go b/pkg/generator/settings.go index c0c46fb..1e55fbf 100644 --- a/pkg/generator/settings.go +++ b/pkg/generator/settings.go @@ -1,5 +1,12 @@ package generator +import ( + "fmt" + "math" +) + +const defaultFactor float32 = 0.1 + type MapSetting struct { Players uint32 HWSize uint32 @@ -11,11 +18,28 @@ type MapSetting struct { DWMinDistance uint32 DWMaxDistance uint32 - GiantPlanets PlanetSetting - BigPlanets PlanetSetting - NormalPlanets PlanetSetting - RichPlanets PlanetSetting - Asterioids PlanetSetting + GiantPlanets PlanetSetting + BigPlanets PlanetSetting + OthersMinDistance float32 + NormalPlanets PlanetSetting + RichPlanets 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 { @@ -27,6 +51,10 @@ type PlanetSetting struct { Probability float32 } +func (ps PlanetSetting) Count(freePlanets uint32) int { + return int(math.Ceil(float64(freePlanets) * float64(ps.Probability))) +} + func DefaultMapSetting() MapSetting { return MapSetting{ Players: 25, @@ -54,9 +82,10 @@ func DefaultMapSetting() MapSetting { MaxResource: 10, Probability: 0.18, }, + OthersMinDistance: defaultFactor, // minimal of 1 pixel on the plotter NormalPlanets: PlanetSetting{ MinDistanceHW: 0, - MinSize: 0.001, + MinSize: 0, MaxSize: 1000, MinResource: 0, MaxResource: 10, @@ -64,7 +93,7 @@ func DefaultMapSetting() MapSetting { }, RichPlanets: PlanetSetting{ MinDistanceHW: 0, - MinSize: 0.001, + MinSize: 0, MaxSize: 500, MinResource: 5, MaxResource: 25, @@ -72,8 +101,8 @@ func DefaultMapSetting() MapSetting { }, Asterioids: PlanetSetting{ MinDistanceHW: 0, - MinSize: 0.001, - MaxSize: 10, + MinSize: 0, + MaxSize: 0, MinResource: 0, MaxResource: 0, Probability: 0.08,