feat: produce on planets, unload on routes
This commit is contained in:
@@ -66,6 +66,7 @@ func (c *Cache) ProduceBombings() []BombingPlanetReport {
|
|||||||
if p.Population == 0 {
|
if p.Population == 0 {
|
||||||
// TODO: what aboul colonists left on planet?
|
// TODO: what aboul colonists left on planet?
|
||||||
p.Owner = uuid.Nil
|
p.Owner = uuid.Nil
|
||||||
|
p.Production = game.ProductionNone.AsType(uuid.Nil)
|
||||||
clear(p.Route)
|
clear(p.Route)
|
||||||
} else {
|
} else {
|
||||||
// Если на планете остались также и колонисты, то они превращаются в население,
|
// Если на планете остались также и колонисты, то они превращаются в население,
|
||||||
|
|||||||
@@ -82,3 +82,7 @@ func (c *Cache) CollectBombingGroups() map[uint]map[int][]int {
|
|||||||
func BombPlanet(p *game.Planet, power float64) {
|
func BombPlanet(p *game.Planet, power float64) {
|
||||||
bombPlanet(p, power)
|
bombPlanet(p, power)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) ListProducingPlanets() iter.Seq[uint] {
|
||||||
|
return c.listProducingPlanets()
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
|
|||||||
g.Age += 1
|
g.Age += 1
|
||||||
|
|
||||||
// 01. Корабли, где это возможно, объединяются в группы.
|
// 01. Корабли, где это возможно, объединяются в группы.
|
||||||
c.Cache.CmdJoinEqualGroups()
|
c.Cache.TurnMergeEqualShipGroups()
|
||||||
|
|
||||||
// 02. Враждующие корабли вступают в схватку.
|
// 02. Враждующие корабли вступают в схватку.
|
||||||
battles := ProduceBattles(c.Cache)
|
battles := ProduceBattles(c.Cache)
|
||||||
@@ -22,7 +22,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
|
|||||||
c.Cache.MoveShipGroups()
|
c.Cache.MoveShipGroups()
|
||||||
|
|
||||||
// 05. Корабли, где это возможно, объединяются в группы.
|
// 05. Корабли, где это возможно, объединяются в группы.
|
||||||
c.Cache.CmdJoinEqualGroups()
|
c.Cache.TurnMergeEqualShipGroups()
|
||||||
|
|
||||||
// 06. Враждующие корабли снова вступают в схватку (это происходит после выхода из гиперпространства).
|
// 06. Враждующие корабли снова вступают в схватку (это происходит после выхода из гиперпространства).
|
||||||
battles = append(battles, ProduceBattles(c.Cache)...)
|
battles = append(battles, ProduceBattles(c.Cache)...)
|
||||||
@@ -31,7 +31,15 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
|
|||||||
_ = c.Cache.ProduceBombings()
|
_ = c.Cache.ProduceBombings()
|
||||||
|
|
||||||
// 08. На планетах строятся корабли.
|
// 08. На планетах строятся корабли.
|
||||||
c.Cache.ProduceShips()
|
// 09. Корабли, где это возможно, объединяются в группы.
|
||||||
|
// 10. На планетах производится промышленность, добывается сырье, разрабатываются новые технологии.
|
||||||
|
// 11. Увеличивается население планет.
|
||||||
|
// TODO: tests
|
||||||
|
c.Cache.TurnPlanetProductions()
|
||||||
|
|
||||||
|
// 12. Товары выгружаются в конце грузовых маршрутов.
|
||||||
|
// TODO: tests
|
||||||
|
c.Cache.TurnUnloadEnroutedGroups()
|
||||||
|
|
||||||
/*** Last steps ***/
|
/*** Last steps ***/
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -196,23 +197,78 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 {
|
|||||||
return p.ProductionCapacity() - busyResources
|
return p.ProductionCapacity() - busyResources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) ProduceShips() {
|
func (c *Cache) TurnPlanetProductions() {
|
||||||
for pl := range c.g.Map.Planet {
|
for pn := range c.listProducingPlanets() {
|
||||||
p := c.MustPlanet(c.g.Map.Planet[pl].Number)
|
p := c.MustPlanet(pn)
|
||||||
if p.Owner == uuid.Nil || p.Production.Type != game.ProductionShip {
|
ri := c.RaceIndex(p.Owner)
|
||||||
|
r := &c.g.Race[ri]
|
||||||
|
|
||||||
|
switch pt := p.Production.Type; pt {
|
||||||
|
case game.ProductionShip:
|
||||||
|
st := c.MustShipType(ri, *p.Production.SubjectID)
|
||||||
|
if ships := ProduceShip(p, st.EmptyMass()); ships > 0 {
|
||||||
|
c.createShipsUnsafe(ri, st.ID, p.Number, ships)
|
||||||
|
}
|
||||||
|
case game.ResearchScience:
|
||||||
|
sc := c.mustScience(ri, *p.Production.SubjectID)
|
||||||
|
IncreaseTech(r, p, sc.Drive, sc.Weapons, sc.Shields, sc.Cargo)
|
||||||
|
case game.ResearchDrive:
|
||||||
|
IncreaseTech(r, p, 1., 0, 0, 0)
|
||||||
|
case game.ResearchWeapons:
|
||||||
|
IncreaseTech(r, p, 0, 1., 0, 0)
|
||||||
|
case game.ResearchShields:
|
||||||
|
IncreaseTech(r, p, 0, 0, 1., 0)
|
||||||
|
case game.ResearchCargo:
|
||||||
|
IncreaseTech(r, p, 0, 0, 0, 1.)
|
||||||
|
case game.ProductionMaterial:
|
||||||
|
p.IncreaseMaterial()
|
||||||
|
case game.ProductionCapital:
|
||||||
|
p.IncreaseIndustry()
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unprocessed production type: %v", pt))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.IncreasePopulation()
|
||||||
|
}
|
||||||
|
c.TurnMergeEqualShipGroups()
|
||||||
|
}
|
||||||
|
|
||||||
|
// listProducingPlanets iterates over all inhabited planet numbers with production not set to None.
|
||||||
|
// Planets producing ships guaranteed to be iterated first for correct turn actions order.
|
||||||
|
func (c *Cache) listProducingPlanets() iter.Seq[uint] {
|
||||||
|
ordered := make([]int, 0)
|
||||||
|
for i := range c.g.Map.Planet {
|
||||||
|
if c.g.Map.Planet[i].Owner == uuid.Nil || c.g.Map.Planet[i].Production.Type == game.ProductionNone {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ri := c.RaceIndex(p.Owner)
|
ordered = append(ordered, i)
|
||||||
st := c.MustShipType(ri, *p.Production.SubjectID)
|
}
|
||||||
ships := ProduceShip(p, st.EmptyMass())
|
slices.SortFunc(ordered, func(l, r int) int {
|
||||||
if ships > 0 {
|
if c.g.Map.Planet[l].Production.Type == game.ProductionShip && c.g.Map.Planet[r].Production.Type != game.ProductionShip {
|
||||||
if err := c.CreateShips(ri, st.Name, p.Number, ships); err != nil {
|
return -1
|
||||||
panic(fmt.Sprintf("ProduceShips: CreateShip: %s", err))
|
}
|
||||||
|
if c.g.Map.Planet[l].Production.Type != game.ProductionShip && c.g.Map.Planet[r].Production.Type == game.ProductionShip {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
return func(yield func(uint) bool) {
|
||||||
|
for _, i := range ordered {
|
||||||
|
if !yield(c.g.Map.Planet[i].Number) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IncreaseTech(r *game.Race, p *game.Planet, d, w, s, c float64) {
|
||||||
|
increment := 5000. / p.ProductionCapacity()
|
||||||
|
r.Tech.Set(game.TechDrive, r.Tech.Value(game.TechDrive)+increment*d)
|
||||||
|
r.Tech.Set(game.TechWeapons, r.Tech.Value(game.TechWeapons)+increment*w)
|
||||||
|
r.Tech.Set(game.TechShields, r.Tech.Value(game.TechShields)+increment*s)
|
||||||
|
r.Tech.Set(game.TechCargo, r.Tech.Value(game.TechCargo)+increment*c)
|
||||||
|
}
|
||||||
|
|
||||||
// Internal funcs
|
// Internal funcs
|
||||||
|
|
||||||
func (c *Cache) putPopulation(pn uint, v float64) {
|
func (c *Cache) putPopulation(pn uint, v float64) {
|
||||||
@@ -227,14 +283,14 @@ func (c *Cache) putMaterial(pn uint, v float64) {
|
|||||||
c.MustPlanet(pn).Material = v
|
c.MustPlanet(pn).Material = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProduceShip(p *game.Planet, shipMass float64) int {
|
func ProduceShip(p *game.Planet, shipMass float64) uint {
|
||||||
productionAvailable := p.ProductionCapacity()
|
productionAvailable := p.ProductionCapacity()
|
||||||
if productionAvailable <= 0 {
|
if productionAvailable <= 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
CAP_perShip := shipMass / p.Resources
|
CAP_perShip := shipMass / p.Resources
|
||||||
productionForMass := shipMass * 10.
|
productionForMass := shipMass * 10.
|
||||||
ships := 0
|
ships := uint(0)
|
||||||
flZero := 0.
|
flZero := 0.
|
||||||
p.Production.Progress = &flZero
|
p.Production.Progress = &flZero
|
||||||
for productionAvailable > 0 {
|
for productionAvailable > 0 {
|
||||||
|
|||||||
@@ -134,12 +134,13 @@ func TestProduceShips(t *testing.T) {
|
|||||||
st := c.MustShipClass(Race_0_idx, Race_0_Gunship)
|
st := c.MustShipClass(Race_0_idx, Race_0_Gunship)
|
||||||
assert.Equal(t, st.ID, *c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
|
assert.Equal(t, st.ID, *c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
|
||||||
|
|
||||||
|
c.MustPlanet(R0_Planet_0_num).Size = 1000.
|
||||||
c.MustPlanet(R0_Planet_0_num).Population = 1000.
|
c.MustPlanet(R0_Planet_0_num).Population = 1000.
|
||||||
c.MustPlanet(R0_Planet_0_num).Industry = 1000.
|
c.MustPlanet(R0_Planet_0_num).Industry = 1000.
|
||||||
c.MustPlanet(R0_Planet_0_num).Resources = 10.
|
c.MustPlanet(R0_Planet_0_num).Resources = 10.
|
||||||
shipMass := st.EmptyMass()
|
shipMass := st.EmptyMass()
|
||||||
|
|
||||||
c.ProduceShips()
|
c.TurnPlanetProductions()
|
||||||
|
|
||||||
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 0)
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 0)
|
||||||
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
|
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
|
||||||
@@ -147,13 +148,19 @@ func TestProduceShips(t *testing.T) {
|
|||||||
assert.InDelta(t, 0.45, progress, 0.001)
|
assert.InDelta(t, 0.45, progress, 0.001)
|
||||||
|
|
||||||
assert.NoError(t, c.CreateShipType(Race_0_idx, "Drone", 1, 0, 0, 0, 0))
|
assert.NoError(t, c.CreateShipType(Race_0_idx, "Drone", 1, 0, 0, 0, 0))
|
||||||
|
assert.NoError(t, c.CreateShips(Race_0_idx, "Drone", uint(pn), 7))
|
||||||
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1)
|
||||||
|
assert.Equal(t, uint(7), c.ShipGroup(0).Number)
|
||||||
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIP", "Drone"))
|
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIP", "Drone"))
|
||||||
assert.InDelta(t, shipMass*progress, c.MustPlanet(R0_Planet_0_num).Material, 0.00001) // 99.(0099) material build
|
assert.InDelta(t, shipMass*progress, c.MustPlanet(R0_Planet_0_num).Material, 0.00001) // 99.(0099) material build
|
||||||
|
|
||||||
c.ProduceShips()
|
c.MustPlanet(R0_Planet_0_num).Material = 0
|
||||||
|
c.MustPlanet(R0_Planet_0_num).Colonists = 0
|
||||||
|
|
||||||
|
c.TurnPlanetProductions()
|
||||||
|
|
||||||
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1)
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1)
|
||||||
assert.Equal(t, uint(99), c.ShipGroup(0).Number)
|
assert.Equal(t, uint(106), c.ShipGroup(0).Number)
|
||||||
progress = *c.MustPlanet(R0_Planet_0_num).Production.Progress
|
progress = *c.MustPlanet(R0_Planet_0_num).Production.Progress
|
||||||
assert.InDelta(t, 0.0099, progress, 0.00001) // 1.(0099) drones with no CAP on planet
|
assert.InDelta(t, 0.0099, progress, 0.00001) // 1.(0099) drones with no CAP on planet
|
||||||
}
|
}
|
||||||
@@ -182,25 +189,43 @@ func TestProduceShip(t *testing.T) {
|
|||||||
p := controller.NewPlanet(0, "Planet_0", uuid.New(), 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil))
|
p := controller.NewPlanet(0, "Planet_0", uuid.New(), 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil))
|
||||||
|
|
||||||
r := controller.ProduceShip(&p, Drone.EmptyMass())
|
r := controller.ProduceShip(&p, Drone.EmptyMass())
|
||||||
assert.Equal(t, 99, r)
|
assert.Equal(t, uint(99), r)
|
||||||
assert.InDelta(t, 0.0099, *p.Production.Progress, 0.000001)
|
assert.InDelta(t, 0.0099, *p.Production.Progress, 0.000001)
|
||||||
|
|
||||||
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
||||||
(&p).Capital = 10.
|
(&p).Capital = 10.
|
||||||
r = controller.ProduceShip(&p, Drone.EmptyMass())
|
r = controller.ProduceShip(&p, Drone.EmptyMass())
|
||||||
assert.Equal(t, 100, r)
|
assert.Equal(t, uint(100), r)
|
||||||
assert.Equal(t, 0., *p.Production.Progress)
|
assert.Equal(t, 0., *p.Production.Progress)
|
||||||
assert.Equal(t, 0., number.Fixed3(p.Capital)) // TODO: zero CAP is not actual zero after series of decrements
|
assert.Equal(t, 0., number.Fixed3(p.Capital)) // TODO: zero CAP is not actual '0.0' after series of decrements
|
||||||
|
|
||||||
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
||||||
(&p).Capital = 0.
|
(&p).Capital = 0.
|
||||||
r = controller.ProduceShip(&p, BattleShip.EmptyMass())
|
r = controller.ProduceShip(&p, BattleShip.EmptyMass())
|
||||||
assert.Equal(t, 1, r)
|
assert.Equal(t, uint(1), r)
|
||||||
assert.InDelta(t, 0.1, *p.Production.Progress, 0.001)
|
assert.InDelta(t, 0.1, *p.Production.Progress, 0.001)
|
||||||
|
|
||||||
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
||||||
(&p).Capital = 20.
|
(&p).Capital = 20.
|
||||||
r = controller.ProduceShip(&p, BattleShip.EmptyMass())
|
r = controller.ProduceShip(&p, BattleShip.EmptyMass())
|
||||||
assert.Equal(t, 1, r)
|
assert.Equal(t, uint(1), r)
|
||||||
assert.Equal(t, 1./9., *p.Production.Progress)
|
assert.Equal(t, 1./9., *p.Production.Progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListProducingPlanets(t *testing.T) {
|
||||||
|
c, g := newCache()
|
||||||
|
|
||||||
|
planets := slices.Collect(c.ListProducingPlanets())
|
||||||
|
assert.Len(t, planets, 0)
|
||||||
|
|
||||||
|
assert.NoError(t, g.PlanetProduction(Race_0.Name, int(R0_Planet_0_num), "CAP", ""))
|
||||||
|
planets = slices.Collect(c.ListProducingPlanets())
|
||||||
|
assert.Len(t, planets, 1)
|
||||||
|
assert.Equal(t, R0_Planet_0_num, c.MustPlanet(planets[0]).Number)
|
||||||
|
|
||||||
|
assert.NoError(t, g.PlanetProduction(Race_0.Name, int(R0_Planet_2_num), "SHIP", Race_0_Gunship))
|
||||||
|
planets = slices.Collect(c.ListProducingPlanets())
|
||||||
|
assert.Len(t, planets, 2)
|
||||||
|
assert.Equal(t, R0_Planet_2_num, c.MustPlanet(planets[0]).Number)
|
||||||
|
assert.Equal(t, R0_Planet_0_num, c.MustPlanet(planets[1]).Number)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"iter"
|
"iter"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand/v2"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
"github.com/iliadenisov/galaxy/internal/util"
|
"github.com/iliadenisov/galaxy/internal/util"
|
||||||
@@ -88,7 +91,7 @@ func (c *Cache) RemovePlanetRoute(rt game.RouteType, origin uint) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: NOT IN THIS FUNC: remove routes if planet became uninhabited
|
// TODO: NOT IN THIS FUNC: remove routes if planet became uninhabited (bombing, quit game, etc)
|
||||||
func (c *Cache) EnrouteGroups() {
|
func (c *Cache) EnrouteGroups() {
|
||||||
for pi := range c.g.Map.Planet {
|
for pi := range c.g.Map.Planet {
|
||||||
if len(c.g.Map.Planet[pi].Route) == 0 {
|
if len(c.g.Map.Planet[pi].Route) == 0 {
|
||||||
@@ -188,3 +191,81 @@ func (c *Cache) listRouteEligibleGroupIds(pn uint) iter.Seq[int] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Невозможно лишь выгрузить колонистов на чужой планете.
|
||||||
|
func (c *Cache) TurnUnloadEnroutedGroups() {
|
||||||
|
for pi := range c.g.Map.Planet {
|
||||||
|
p := &c.g.Map.Planet[pi]
|
||||||
|
colGroups := c.listUnloadEligibleShipGroupIds(p.Number, game.RouteColonist)
|
||||||
|
if p.Owner == uuid.Nil {
|
||||||
|
c.selectColUnloadGroup(colGroups)
|
||||||
|
} else {
|
||||||
|
for sgi := range colGroups {
|
||||||
|
sg := c.ShipGroup(sgi)
|
||||||
|
if sg.OwnerID != p.Owner {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.unloadCargoUnsafe(sgi, sg.Load)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rt := range []game.RouteType{game.RouteMaterial, game.RouteCapital} {
|
||||||
|
for sgi := range c.listUnloadEligibleShipGroupIds(p.Number, rt) {
|
||||||
|
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) selectColUnloadGroup(seq iter.Seq[int]) {
|
||||||
|
groupByRace := make(map[int][]int)
|
||||||
|
loadByRace := make(map[int]float64)
|
||||||
|
for i := range seq {
|
||||||
|
sg := c.ShipGroup(i)
|
||||||
|
ri := c.RaceIndex(sg.OwnerID)
|
||||||
|
groupByRace[ri] = append(groupByRace[ri], i)
|
||||||
|
loadByRace[ri] += sg.Load
|
||||||
|
}
|
||||||
|
if len(loadByRace) < 2 {
|
||||||
|
for _, gr := range groupByRace {
|
||||||
|
for _, sgi := range gr {
|
||||||
|
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// select winner to unload
|
||||||
|
|
||||||
|
raceIdx := slices.Collect(maps.Keys(loadByRace))
|
||||||
|
slices.SortFunc(raceIdx, func(ri1, ri2 int) int { return cmp.Compare(loadByRace[ri2], loadByRace[ri1]) })
|
||||||
|
if loadByRace[raceIdx[0]] == loadByRace[raceIdx[1]] {
|
||||||
|
// no single winner with highest load
|
||||||
|
raceIdx = slices.DeleteFunc(raceIdx, func(v int) bool { return loadByRace[v] < loadByRace[raceIdx[0]] })
|
||||||
|
rand.Shuffle(len(raceIdx), func(i, j int) { raceIdx[i], raceIdx[j] = raceIdx[j], raceIdx[i] })
|
||||||
|
// now raceIdx[0] has a random race index
|
||||||
|
}
|
||||||
|
for _, sgi := range groupByRace[raceIdx[0]] {
|
||||||
|
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) listUnloadEligibleShipGroupIds(pn uint, routeType game.RouteType) iter.Seq[int] {
|
||||||
|
return func(yield func(int) bool) {
|
||||||
|
for i := range c.g.Map.Planet {
|
||||||
|
for rt, dest := range c.g.Map.Planet[i].Route {
|
||||||
|
if dest != pn || rt != routeType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range c.ShipGroupsIndex() {
|
||||||
|
sg := c.ShipGroup(i)
|
||||||
|
if sg.FleetID != nil || sg.State() != game.StateInOrbit || sg.CargoType == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !yield(i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -86,3 +87,14 @@ func (c *Cache) raceScience(ri int) []game.Science {
|
|||||||
c.validateRaceIndex(ri)
|
c.validateRaceIndex(ri)
|
||||||
return c.g.Race[ri].Sciences
|
return c.g.Race[ri].Sciences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) mustScience(ri int, id uuid.UUID) *game.Science {
|
||||||
|
c.validateRaceIndex(ri)
|
||||||
|
r := &c.g.Race[ri]
|
||||||
|
i := slices.IndexFunc(r.Sciences, func(s game.Science) bool { return s.ID == id })
|
||||||
|
if i < 0 {
|
||||||
|
panic(fmt.Sprintf("science not found for race=%q id=%v", r.Name, id))
|
||||||
|
}
|
||||||
|
return &c.g.Race[ri].Sciences[i]
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,10 +25,16 @@ func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quan
|
|||||||
return e.NewEntityNotOwnedError("planet #%d", planetNumber)
|
return e.NewEntityNotOwnedError("planet #%d", planetNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.createShipsUnsafe(ri, class.ID, p.Number, uint(quantity))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) createShipsUnsafe(ri int, classID uuid.UUID, planet uint, quantity uint) {
|
||||||
c.appendShipGroup(ri, &game.ShipGroup{
|
c.appendShipGroup(ri, &game.ShipGroup{
|
||||||
OwnerID: c.g.Race[ri].ID,
|
OwnerID: c.g.Race[ri].ID,
|
||||||
TypeID: class.ID,
|
TypeID: classID,
|
||||||
Destination: p.Number,
|
Destination: planet,
|
||||||
Number: uint(quantity),
|
Number: uint(quantity),
|
||||||
Tech: map[game.Tech]float64{
|
Tech: map[game.Tech]float64{
|
||||||
game.TechDrive: c.g.Race[ri].TechLevel(game.TechDrive),
|
game.TechDrive: c.g.Race[ri].TechLevel(game.TechDrive),
|
||||||
@@ -37,7 +43,7 @@ func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quan
|
|||||||
game.TechCargo: c.g.Race[ri].TechLevel(game.TechCargo),
|
game.TechCargo: c.g.Race[ri].TechLevel(game.TechCargo),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShipGroup is a proxy func, nothing to cache
|
// ShipGroup is a proxy func, nothing to cache
|
||||||
@@ -123,7 +129,7 @@ func (c *Controller) JoinEqualGroups(raceName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) CmdJoinEqualGroups() {
|
func (c *Cache) TurnMergeEqualShipGroups() {
|
||||||
for i := range c.g.Race {
|
for i := range c.g.Race {
|
||||||
c.JoinEqualGroups(i)
|
c.JoinEqualGroups(i)
|
||||||
}
|
}
|
||||||
@@ -354,29 +360,12 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6
|
|||||||
if c.ShipGroup(sgi).CargoType == nil || c.ShipGroup(sgi).Load == 0 {
|
if c.ShipGroup(sgi).CargoType == nil || c.ShipGroup(sgi).Load == 0 {
|
||||||
return e.NewCargoUnloadEmptyError()
|
return e.NewCargoUnloadEmptyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := *c.ShipGroup(sgi).CargoType
|
ct := *c.ShipGroup(sgi).CargoType
|
||||||
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
p := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
||||||
if !ok {
|
|
||||||
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
if ct == game.CargoColonist && p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
||||||
}
|
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
||||||
if ct == game.CargoColonist {
|
|
||||||
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
|
||||||
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
|
||||||
}
|
|
||||||
if p.Owner == uuid.Nil {
|
|
||||||
p.Owner = c.g.Race[ri].ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var availableOnPlanet *float64
|
|
||||||
switch ct {
|
|
||||||
case game.CargoMaterial:
|
|
||||||
availableOnPlanet = &p.Material
|
|
||||||
case game.CargoCapital:
|
|
||||||
availableOnPlanet = &p.Capital
|
|
||||||
case game.CargoColonist:
|
|
||||||
availableOnPlanet = &p.Colonists
|
|
||||||
default:
|
|
||||||
return e.NewGameStateError("CargoType not accepted: %v", ct)
|
|
||||||
}
|
}
|
||||||
if ships > 0 && ships < c.ShipGroup(sgi).Number {
|
if ships > 0 && ships < c.ShipGroup(sgi).Number {
|
||||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
||||||
@@ -385,6 +374,7 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6
|
|||||||
}
|
}
|
||||||
sgi = nsgi
|
sgi = nsgi
|
||||||
}
|
}
|
||||||
|
|
||||||
toBeUnloaded := quantity
|
toBeUnloaded := quantity
|
||||||
if quantity == 0 {
|
if quantity == 0 {
|
||||||
toBeUnloaded = c.ShipGroup(sgi).Load
|
toBeUnloaded = c.ShipGroup(sgi).Load
|
||||||
@@ -392,13 +382,43 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6
|
|||||||
if toBeUnloaded > c.ShipGroup(sgi).Load {
|
if toBeUnloaded > c.ShipGroup(sgi).Load {
|
||||||
return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load)
|
return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load)
|
||||||
}
|
}
|
||||||
*availableOnPlanet += toBeUnloaded
|
|
||||||
c.ShipGroup(sgi).Load -= toBeUnloaded
|
c.unloadCargoUnsafe(sgi, toBeUnloaded)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) unloadCargoUnsafe(sgi int, q float64) {
|
||||||
|
if q <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.validateShipGroupIndex(sgi)
|
||||||
|
p := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
||||||
|
ct := *c.ShipGroup(sgi).CargoType
|
||||||
|
|
||||||
|
var availableOnPlanet *float64
|
||||||
|
switch ct {
|
||||||
|
case game.CargoColonist:
|
||||||
|
availableOnPlanet = &p.Colonists
|
||||||
|
if p.Owner == uuid.Nil {
|
||||||
|
p.Owner = c.ShipGroup(sgi).OwnerID
|
||||||
|
p.Production = game.ProductionCapital.AsType(uuid.Nil)
|
||||||
|
}
|
||||||
|
case game.CargoMaterial:
|
||||||
|
availableOnPlanet = &p.Material
|
||||||
|
case game.CargoCapital:
|
||||||
|
availableOnPlanet = &p.Capital
|
||||||
|
}
|
||||||
|
*availableOnPlanet += q
|
||||||
|
|
||||||
|
// FIXME: unpack COL / CAP
|
||||||
|
|
||||||
|
c.ShipGroup(sgi).Load -= q
|
||||||
if c.ShipGroup(sgi).Load == 0 {
|
if c.ShipGroup(sgi).Load == 0 {
|
||||||
c.ShipGroup(sgi).CargoType = nil
|
c.ShipGroup(sgi).CargoType = nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
|
func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
|
||||||
ri, err := c.Cache.raceIndex(raceName)
|
ri, err := c.Cache.raceIndex(raceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type UninhabitedPlanet struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Resources float64 `json:"resources"` // R - Ресурсы
|
Resources float64 `json:"resources"` // R - Ресурсы
|
||||||
Capital float64 `json:"capital"` // CAP $ - Запасы промышленности
|
Capital float64 `json:"capital"` // CAP $ - Запасы промышленности
|
||||||
Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья
|
Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырьё
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlanetReport struct {
|
type PlanetReport struct {
|
||||||
@@ -52,13 +52,23 @@ func PlanetProduction(industry, population float64) float64 {
|
|||||||
|
|
||||||
// Производство промышленности
|
// Производство промышленности
|
||||||
// TODO: test on real values
|
// TODO: test on real values
|
||||||
|
// [x] ind * 5 + ind / res = prod
|
||||||
|
// [x] ind = (res * prod) / (5 * res + 1)
|
||||||
|
// ind = 10 * 1000 / (5 * 10 + 1) = 10000 / 55 = 181.(81)
|
||||||
|
// int = 10 * 500 / 55 = 5000 / 55
|
||||||
func (p *Planet) IncreaseIndustry() {
|
func (p *Planet) IncreaseIndustry() {
|
||||||
prod := p.ProductionCapacity() / 5.
|
production := p.ProductionCapacity()
|
||||||
industryIncrement := math.Min(prod, p.Material)
|
var ind float64
|
||||||
p.Industry += industryIncrement
|
if p.Material > 0 {
|
||||||
|
ind = math.Min(production/5, p.Material)
|
||||||
|
p.Material -= ind
|
||||||
|
production -= ind * 5.
|
||||||
|
}
|
||||||
|
ind += (production * p.Resources) / (5.*p.Resources + 1.)
|
||||||
|
p.Industry += ind
|
||||||
if p.Industry > p.Population {
|
if p.Industry > p.Population {
|
||||||
|
p.Capital += p.Industry - p.Population
|
||||||
p.Industry = p.Population
|
p.Industry = p.Population
|
||||||
p.Capital += p.Population - p.Industry
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +87,11 @@ func (p *Planet) UnpackCAPtoIND() {
|
|||||||
// Производство материалов
|
// Производство материалов
|
||||||
// TODO: test on real values
|
// TODO: test on real values
|
||||||
func (p *Planet) IncreaseMaterial() {
|
func (p *Planet) IncreaseMaterial() {
|
||||||
p.Material += p.ProductionCapacity() * p.Industry
|
p.Material += p.ProductionCapacity() * p.Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
// Автоматическое увеличение населения на каждом ходу
|
// Автоматическое увеличение населения на каждом ходу
|
||||||
// TODO: test
|
// TODO: test - whether POP is busy on production or not?
|
||||||
func (p *Planet) IncreasePopulation() {
|
func (p *Planet) IncreasePopulation() {
|
||||||
p.Population *= 1.08
|
p.Population *= 1.08
|
||||||
if p.Population > p.Size {
|
if p.Population > p.Size {
|
||||||
|
|||||||
@@ -13,3 +13,45 @@ func TestPlanetProduction(t *testing.T) {
|
|||||||
assert.Equal(t, 750., game.PlanetProduction(1000., 0.))
|
assert.Equal(t, 750., game.PlanetProduction(1000., 0.))
|
||||||
assert.Equal(t, 250., game.PlanetProduction(0., 1000.))
|
assert.Equal(t, 250., game.PlanetProduction(0., 1000.))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncreaseIndustry(t *testing.T) {
|
||||||
|
HW := &game.Planet{
|
||||||
|
PlanetReport: game.PlanetReport{
|
||||||
|
UninhabitedPlanet: game.UninhabitedPlanet{
|
||||||
|
Size: 1000,
|
||||||
|
Resources: 10,
|
||||||
|
},
|
||||||
|
Population: 1000,
|
||||||
|
Industry: 1000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
DW := &game.Planet{
|
||||||
|
PlanetReport: game.PlanetReport{
|
||||||
|
UninhabitedPlanet: game.UninhabitedPlanet{
|
||||||
|
Size: 500,
|
||||||
|
Resources: 10,
|
||||||
|
},
|
||||||
|
Population: 500,
|
||||||
|
Industry: 500,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
HW.IncreaseIndustry()
|
||||||
|
assert.InDelta(t, 196.078, HW.Capital, 0.0005)
|
||||||
|
|
||||||
|
HW.Capital = 0
|
||||||
|
HW.Material = 200
|
||||||
|
|
||||||
|
HW.IncreaseIndustry()
|
||||||
|
assert.Equal(t, 200., HW.Capital)
|
||||||
|
assert.Equal(t, 0., HW.Material)
|
||||||
|
|
||||||
|
DW.IncreaseIndustry()
|
||||||
|
assert.InDelta(t, 98.039, DW.Capital, 0.0003)
|
||||||
|
|
||||||
|
DW.Capital = 0
|
||||||
|
DW.Material = 100
|
||||||
|
|
||||||
|
DW.IncreaseIndustry()
|
||||||
|
assert.Equal(t, 100., DW.Capital)
|
||||||
|
assert.Equal(t, 0., DW.Material)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user