diff --git a/pkg/game/game.go b/pkg/game/game.go index c14ba3e..fed7ace 100644 --- a/pkg/game/game.go +++ b/pkg/game/game.go @@ -3,6 +3,7 @@ package game import ( "fmt" "math" + "math/rand/v2" "github.com/google/uuid" "github.com/iliadenisov/galaxy/pkg/generator" @@ -15,9 +16,9 @@ type Repo interface { } func NewGame(r Repo, races []string) (uuid.UUID, error) { - id, err := uuid.NewRandom() + gameID, err := uuid.NewRandom() if err != nil { - return uuid.Nil, fmt.Errorf("generate uuid: %s", err) + return uuid.Nil, fmt.Errorf("generate game uuid: %s", err) } m, err := generator.Generate(func(ms *generator.MapSetting) { ms.Players = uint32(len(races)) @@ -29,7 +30,7 @@ func NewGame(r Repo, races []string) (uuid.UUID, error) { return uuid.Nil, fmt.Errorf("generate map: wrong number of home planets: %d, expected: %d ", len(m.HomePlanets), len(races)) } g := &game.Game{ - ID: id, + ID: gameID, Race: make([]game.Race, len(races)), } @@ -38,44 +39,71 @@ func NewGame(r Repo, races []string) (uuid.UUID, error) { Height: m.Height, Planet: make([]game.Planet, 0), } - for hw := range races { - g.Race[hw] = game.Race{ - Name: races[hw], - Votes: 1, // TODO: check with rules - VoteFor: races[hw], + var planetCount uint = 0 + for i := range races { + raceID, err := uuid.NewRandom() + if err != nil { + return uuid.Nil, fmt.Errorf("generate race uuid: %s", err) + } + g.Race[i] = game.Race{ + ID: raceID, + Name: races[i], + Ally: raceID, Drive: 1, Weapons: 1, Shields: 1, Cargo: 1, } - gameMap.Planet = append(gameMap.Planet, game.Planet{ - Owner: races[hw], - X: m.HomePlanets[hw].HW.Position.X, - Y: m.HomePlanets[hw].HW.Position.Y, - Size: m.HomePlanets[hw].HW.Size, - Resources: m.HomePlanets[hw].HW.Resources, - Production: game.ProductionCapital.AsType(""), // TODO: check default production - }) - for dw := range m.HomePlanets[hw].DW { - gameMap.Planet = append(gameMap.Planet, game.Planet{ - X: m.HomePlanets[hw].DW[dw].Position.X, - Y: m.HomePlanets[hw].DW[dw].Position.Y, - Size: m.HomePlanets[hw].DW[dw].Size, - Resources: m.HomePlanets[hw].DW[dw].Resources, - Production: game.ProductionNone.AsType(""), - }) + gameMap.Planet = append(gameMap.Planet, newPlanet( + planetCount, + m.HomePlanets[i].HW.RandomName(), + raceID, + m.HomePlanets[i].HW.Position.X, + m.HomePlanets[i].HW.Position.Y, + m.HomePlanets[i].HW.Size, + m.HomePlanets[i].HW.Size, // HW's pop & ind = size + m.HomePlanets[i].HW.Size, + m.HomePlanets[i].HW.Resources, + game.ResearchDrive.AsType(""), + )) + planetCount++ + for dw := range m.HomePlanets[i].DW { + gameMap.Planet = append(gameMap.Planet, newPlanet( + planetCount, + m.HomePlanets[i].DW[dw].RandomName(), + raceID, + m.HomePlanets[i].DW[dw].Position.X, + m.HomePlanets[i].DW[dw].Position.Y, + m.HomePlanets[i].DW[dw].Size, + m.HomePlanets[i].DW[dw].Size, // DW's pop & ind = size + m.HomePlanets[i].DW[dw].Size, + m.HomePlanets[i].DW[dw].Resources, + game.ResearchDrive.AsType(""), + )) + planetCount++ } } for i := range m.FreePlanets { - gameMap.Planet = append(gameMap.Planet, game.Planet{ - X: m.FreePlanets[i].Position.X, - Y: m.FreePlanets[i].Position.Y, - Size: m.FreePlanets[i].Size, - Resources: m.FreePlanets[i].Resources, - Production: game.ProductionNone.AsType(""), - }) + gameMap.Planet = append(gameMap.Planet, newPlanet( + planetCount, + m.FreePlanets[i].RandomName(), + uuid.Nil, + m.FreePlanets[i].Position.X, + m.FreePlanets[i].Position.Y, + m.FreePlanets[i].Size, + 0, + 0, + m.FreePlanets[i].Resources, + game.ProductionNone.AsType(""), + )) + planetCount++ } + // TODO: check code below actually works + rand.Shuffle(len(gameMap.Planet), func(i, j int) { + gameMap.Planet[i].Number, gameMap.Planet[j].Number = gameMap.Planet[j].Number, gameMap.Planet[i].Number + }) + g.Map = *gameMap if err := r.Persist(*g); err != nil { @@ -84,6 +112,21 @@ func NewGame(r Repo, races []string) (uuid.UUID, error) { return g.ID, nil } +func newPlanet(num uint, name string, owner uuid.UUID, x, y, size, pop, ind, res float64, prod game.ProductionType) game.Planet { + return game.Planet{ + Name: name, + Number: num, + Owner: owner, + X: x, + Y: y, + Size: size, + Population: pop, + Industry: ind, + Resources: res, + Production: prod, + } +} + func (r Race) FlightDistance() float64 { return r.Drive * 40 } diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 9cacb3a..4cdabc9 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -22,7 +22,7 @@ func Generate(cfg ...func(*MapSetting)) (Map, error) { freePlanets := ms.NobodysPlanets() createPlanets := func(pc PlanetClass, ps PlanetSetting) error { - return m.CreatePlanets(pc, rand.Intn(ps.MaxCount(freePlanets))+1, float64(ps.MinDistanceHW), RandIFn(ps.MinSize, ps.MaxSize), RandIFn(ps.MinResource, ps.MaxResource)) + return m.CreatePlanets(pc, ps.Number(freePlanets), float64(ps.MinDistanceHW), RandIFn(ps.MinSize, ps.MaxSize), RandIFn(ps.MinResource, ps.MaxResource)) } // 1. Place Giant planets diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go index 3073b64..ccd0251 100644 --- a/pkg/generator/generator_test.go +++ b/pkg/generator/generator_test.go @@ -21,11 +21,11 @@ func TestGenerator(t *testing.T) { t.Fatalf("generate: %s", err) return } - assert.Equal(t, players, len(m.HomePlanets), "hw count") + assert.Equal(t, players, len(m.HomePlanets), "hw-s count") for hw := range m.HomePlanets { assert.Equal(t, s.HWSize, m.HomePlanets[hw].HW.Size, "hw #%d: size", hw) assert.Equal(t, s.HWResources, m.HomePlanets[hw].HW.Resources, "hw #%d: resources", hw) - assert.Equal(t, int(s.DWCount), len(m.HomePlanets[hw].DW), "hw #%d: dw count", hw) + assert.Equal(t, int(s.DWCount), len(m.HomePlanets[hw].DW), "hw #%d: dw-s count", hw) for dw := range m.HomePlanets[hw].DW { assert.Equal(t, s.DWSize, m.HomePlanets[hw].DW[dw].Size, "hw #%d dw #%d: size", hw, dw) assert.Equal(t, s.DWResources, m.HomePlanets[hw].DW[dw].Resources, "hw #%d dw #%d: resources", hw, dw) @@ -38,6 +38,7 @@ func TestGenerator(t *testing.T) { m.HomePlanets[hw].DW[dw].Position.X, m.HomePlanets[hw].DW[dw].Position.Y) } } + assert.LessOrEqualf(t, int(s.NobodysPlanets()), len(m.FreePlanets), "free planets clount") freePlanetCount := make(map[generator.PlanetClass]int) for fp := range m.FreePlanets { ps := planetSettings(t, m.FreePlanets[fp].PlanetClass, s) @@ -67,8 +68,8 @@ func TestGenerator(t *testing.T) { } for pc, num := range freePlanetCount { ps := planetSettings(t, pc, s) - maxNum := ps.MaxCount(s.NobodysPlanets()) - assert.LessOrEqualf(t, num, maxNum, "planet_class=%v probability=%f of total %d", pc, ps.Probability, s.NobodysPlanets()) + maxNum := ps.Number(s.NobodysPlanets()) + assert.Equalf(t, num, maxNum, "planet_class=%v ratio=%f of total %d", pc, ps.Ratio, s.NobodysPlanets()) } }) } diff --git a/pkg/generator/map.go b/pkg/generator/map.go index 36c92e6..5bc042e 100644 --- a/pkg/generator/map.go +++ b/pkg/generator/map.go @@ -20,30 +20,6 @@ type Coordinate struct { X, Y float64 } -type PlanetClass int - -const ( - PlanetClassHW PlanetClass = iota - PlanetClassDW - PlanetClassGiant - PlanetClassBig - PlanetClassNormal - PlanetClassRich - PlanetClassAsterioid -) - -type Planet struct { - PlanetClass PlanetClass - Position Coordinate - Size float64 - Resources float64 // Сырьё -} - -type PlanetarySystem struct { - HW Planet - DW []Planet -} - func NewMap(width, height, players uint32) (*Map, error) { p, err := plotter.NewPlotter(width, height, defaultFactor) if err != nil { @@ -93,15 +69,6 @@ func (m Map) ShortDistance(from, to Coordinate) float64 { return math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2)) } -func NewPlanet(pc PlanetClass, c Coordinate, size, resources float64) Planet { - return Planet{ - PlanetClass: pc, - Position: c, - Size: size, - Resources: resources, - } -} - // RandI returns a random float64 value between min and max func RandI(min, max float64) float64 { return min + rand.Float64()*(max-min) diff --git a/pkg/generator/planet.go b/pkg/generator/planet.go new file mode 100644 index 0000000..b22d3fc --- /dev/null +++ b/pkg/generator/planet.go @@ -0,0 +1,43 @@ +package generator + +import ( + "fmt" + "math/rand" +) + +type PlanetClass string + +const ( + PlanetClassHW PlanetClass = "HW" + PlanetClassDW PlanetClass = "DW" + PlanetClassGiant PlanetClass = "Giant" + PlanetClassBig PlanetClass = "Big" + PlanetClassNormal PlanetClass = "Normal" + PlanetClassRich PlanetClass = "Rich" + PlanetClassAsterioid PlanetClass = "Asteroid" +) + +type Planet struct { + PlanetClass PlanetClass + Position Coordinate + Size float64 + Resources float64 // Сырьё +} + +type PlanetarySystem struct { + HW Planet + DW []Planet +} + +func (p Planet) RandomName() string { + return fmt.Sprintf("%s-%04d-%04d", p.PlanetClass, rand.Intn(1000), rand.Intn(1000)) +} + +func NewPlanet(pc PlanetClass, c Coordinate, size, resources float64) Planet { + return Planet{ + PlanetClass: pc, + Position: c, + Size: size, + Resources: resources, + } +} diff --git a/pkg/generator/planet_test.go b/pkg/generator/planet_test.go new file mode 100644 index 0000000..316cbf8 --- /dev/null +++ b/pkg/generator/planet_test.go @@ -0,0 +1,30 @@ +package generator_test + +import ( + "regexp" + "testing" + + g "github.com/iliadenisov/galaxy/pkg/generator" + "github.com/stretchr/testify/assert" +) + +func TestPlanetRandomName(t *testing.T) { + re, err := regexp.Compile(`^([a-zA-Z]+)-(\d{4})-(\d{4})$`) + assert.NoError(t, err) + if err != nil { + return + } + for _, pc := range []g.PlanetClass{g.PlanetClassHW, g.PlanetClassDW, g.PlanetClassGiant, g.PlanetClassBig, g.PlanetClassNormal, g.PlanetClassRich, g.PlanetClassAsterioid} { + t.Run(string(pc), func(t *testing.T) { + name := g.NewPlanet(pc, g.Coordinate{0, 0}, 0, 0).RandomName() + g := re.FindStringSubmatch(name) + assert.NotNilf(t, g, "cannot parse: %q", name) + if g == nil { + return + } + assert.Equalf(t, 4, len(g), "regexp groups") + assert.Equal(t, string(pc), g[1]) + assert.NotEqual(t, g[2], g[3]) + }) + } +} diff --git a/pkg/generator/settings.go b/pkg/generator/settings.go index fa7709b..3b32878 100644 --- a/pkg/generator/settings.go +++ b/pkg/generator/settings.go @@ -31,7 +31,6 @@ func (ms MapSetting) String() string { } func (ms MapSetting) ExpectedSize() uint32 { - // 1.5 coefficient is enough if all free planet probability will be 1.0 return uint32(math.Sqrt(float64(ms.Players)) * float64(ms.HWMinDistance) * 1.5) } @@ -49,11 +48,12 @@ type PlanetSetting struct { MaxSize float64 MinResource float64 MaxResource float64 - Probability float64 + Ratio float64 // The proportion of the total number of free planets in the galaxy } -func (ps PlanetSetting) MaxCount(freePlanets uint32) int { - return int(math.Ceil(float64(freePlanets) * ps.Probability)) +// Number of planets need to be placed within freePlanets amount +func (ps PlanetSetting) Number(freePlanets uint32) int { + return int(math.Ceil(float64(freePlanets) * ps.Ratio)) } func DefaultMapSetting() MapSetting { @@ -73,7 +73,7 @@ func DefaultMapSetting() MapSetting { MaxSize: 2500, MinResource: 0, MaxResource: 3, - Probability: 0.06, + Ratio: 0.06, }, BigPlanets: PlanetSetting{ MinDistanceHW: 10, @@ -81,7 +81,7 @@ func DefaultMapSetting() MapSetting { MaxSize: 2000, MinResource: 1, MaxResource: 10, - Probability: 0.18, + Ratio: 0.18, }, OthersMinDistance: defaultFactor, // min. is 1 pixel on the plotter NormalPlanets: PlanetSetting{ @@ -90,7 +90,7 @@ func DefaultMapSetting() MapSetting { MaxSize: 1000, MinResource: 0, MaxResource: 10, - Probability: 0.5, + Ratio: 0.5, }, RichPlanets: PlanetSetting{ MinDistanceHW: 0, @@ -98,7 +98,7 @@ func DefaultMapSetting() MapSetting { MaxSize: 500, MinResource: 5, MaxResource: 25, - Probability: 0.18, + Ratio: 0.18, }, Asterioids: PlanetSetting{ MinDistanceHW: 0, @@ -106,7 +106,7 @@ func DefaultMapSetting() MapSetting { MaxSize: 0, MinResource: 0, MaxResource: 0, - Probability: 0.08, + Ratio: 0.08, }, } } diff --git a/pkg/model/game/game.go b/pkg/model/game/game.go index a7edf27..2126e6d 100644 --- a/pkg/model/game/game.go +++ b/pkg/model/game/game.go @@ -4,6 +4,18 @@ import "github.com/google/uuid" type Game struct { ID uuid.UUID + Age uint // Game's turn number Map Map Race []Race } + +func (g Game) Votes(raceID uuid.UUID) float64 { + // XXX: calculate [Race]Population once when loading Game from Storage? + var pop float64 + for i := range g.Map.Planet { + if g.Map.Planet[i].Owner == raceID { + pop += g.Map.Planet[i].Population + } + } + return pop / 1000. +} diff --git a/pkg/model/game/map.go b/pkg/model/game/map.go index 7eb2b76..4a60d0f 100644 --- a/pkg/model/game/map.go +++ b/pkg/model/game/map.go @@ -3,6 +3,5 @@ package game type Map struct { Width uint32 Height uint32 - Planet []Planet } diff --git a/pkg/model/game/planet.go b/pkg/model/game/planet.go index f4d82f8..2ac05c6 100644 --- a/pkg/model/game/planet.go +++ b/pkg/model/game/planet.go @@ -1,27 +1,33 @@ package game -import "math" +import ( + "math" + + "github.com/google/uuid" +) type Planet struct { X, Y float64 Size float64 - Name string - Owner string + Name string + Number uint + Owner uuid.UUID Production ProductionType - Resources float64 // Сырьё - Industry float64 // Промышленность - Population float64 // Население + Population float64 // P - Население + Industry float64 // I - Промышленность + Resources float64 // R - Ресурсы / сырьё Capital float64 // CAP $ - Запасы промышленности - Material float64 // MAT M - Запасы сырья + Material float64 // MAT M - Запасы ресурсов / сырья Colonists float64 // COL C - Количество колонистов - // Параметр "L" означает количество свободных производственных единиц. + // Параметр "L" - Свободный производственный потенциал } -// Производственный потенциал (I) +// Свободный производственный потенциал (L) // промышленность * 0.75 + население * 0.25 +// TODO: за вычетом затрат, расходуемых в течение хода на модернизацию кораблей func (p Planet) ProductionCapacity() float64 { return p.Industry*0.75 + p.Population*0.25 } diff --git a/pkg/model/game/production.go b/pkg/model/game/production.go index 541883f..7fbbbed 100644 --- a/pkg/model/game/production.go +++ b/pkg/model/game/production.go @@ -3,26 +3,27 @@ package game type PlanetProduction string const ( - ProductionNone PlanetProduction = "NONE" - ProductionMaterial PlanetProduction = "MAT" - ProductionCapital PlanetProduction = "CAP" - ProductionDrive PlanetProduction = "DRIVE" - ProductionWeapons PlanetProduction = "WEAPONS" - ProductionShields PlanetProduction = "SHIELDS" - ProductionCargo PlanetProduction = "CARGO" + ProductionNone PlanetProduction = "-" + ProductionMaterial PlanetProduction = "MAT" // Сырьё + ProductionCapital PlanetProduction = "CAP" // Промышленность - ProductionScience PlanetProduction = "SCIENCE" - ProductionShip PlanetProduction = "SHIP" + ResearchDrive PlanetProduction = "DRIVE" + ResearchWeapons PlanetProduction = "WEAPONS" + ResearchShields PlanetProduction = "SHIELDS" + ResearchCargo PlanetProduction = "CARGO" + + ResearchScience PlanetProduction = "SCIENCE" + ProductionShip PlanetProduction = "SHIP" ) type ProductionType struct { Production PlanetProduction - SubjectName string + SubjectName string // TODO: change to UUID } func (p PlanetProduction) AsType(subject string) ProductionType { switch p { - case ProductionScience, ProductionShip: + case ResearchScience, ProductionShip: return ProductionType{Production: p, SubjectName: subject} default: return ProductionType{Production: p} diff --git a/pkg/model/game/race.go b/pkg/model/game/race.go index 323eaf0..a4cc0ef 100644 --- a/pkg/model/game/race.go +++ b/pkg/model/game/race.go @@ -1,11 +1,14 @@ package game +import "github.com/google/uuid" + type Race struct { + ID uuid.UUID Name string Killed bool - Votes float64 - VoteFor string + // Votes float64 + Ally uuid.UUID // Race's Votes receiver Drive float64 Weapons float64