game generation process
This commit is contained in:
+74
-31
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -3,6 +3,5 @@ package game
|
||||
type Map struct {
|
||||
Width uint32
|
||||
Height uint32
|
||||
|
||||
Planet []Planet
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user