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 (
"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, ms.OthersMinDistance)
}
for i := range m.FreePlanets {
m.plotter.MarkDeadZone(m.FreePlanets[i].Position.X, m.FreePlanets[i].Position.Y, 5)
// 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
+22 -2
View File
@@ -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)
}
})
}
}
+13 -1
View File
@@ -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)
}
+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) {
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)
}
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
+2 -2
View File
@@ -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
+33 -4
View File
@@ -1,5 +1,12 @@
package generator
import (
"fmt"
"math"
)
const defaultFactor float32 = 0.1
type MapSetting struct {
Players uint32
HWSize uint32
@@ -13,11 +20,28 @@ type MapSetting struct {
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 {
MinDistanceHW uint32
MinSize float32
@@ -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,