diff --git a/internal/controller/bombing.go b/internal/controller/bombing.go index f55be31..1d2bd09 100644 --- a/internal/controller/bombing.go +++ b/internal/controller/bombing.go @@ -66,6 +66,7 @@ func (c *Cache) ProduceBombings() []BombingPlanetReport { if p.Population == 0 { // TODO: what aboul colonists left on planet? p.Owner = uuid.Nil + p.Production = game.ProductionNone.AsType(uuid.Nil) clear(p.Route) } else { // Если на планете остались также и колонисты, то они превращаются в население, diff --git a/internal/controller/controller_export_test.go b/internal/controller/controller_export_test.go index a790e1c..7f9e3c9 100644 --- a/internal/controller/controller_export_test.go +++ b/internal/controller/controller_export_test.go @@ -82,3 +82,7 @@ func (c *Cache) CollectBombingGroups() map[uint]map[int][]int { func BombPlanet(p *game.Planet, power float64) { bombPlanet(p, power) } + +func (c *Cache) ListProducingPlanets() iter.Seq[uint] { + return c.listProducingPlanets() +} diff --git a/internal/controller/generate_turn.go b/internal/controller/generate_turn.go index ecde741..6be61c0 100644 --- a/internal/controller/generate_turn.go +++ b/internal/controller/generate_turn.go @@ -10,7 +10,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error { g.Age += 1 // 01. Корабли, где это возможно, объединяются в группы. - c.Cache.CmdJoinEqualGroups() + c.Cache.TurnMergeEqualShipGroups() // 02. Враждующие корабли вступают в схватку. battles := ProduceBattles(c.Cache) @@ -22,7 +22,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error { c.Cache.MoveShipGroups() // 05. Корабли, где это возможно, объединяются в группы. - c.Cache.CmdJoinEqualGroups() + c.Cache.TurnMergeEqualShipGroups() // 06. Враждующие корабли снова вступают в схватку (это происходит после выхода из гиперпространства). battles = append(battles, ProduceBattles(c.Cache)...) @@ -31,7 +31,15 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error { _ = c.Cache.ProduceBombings() // 08. На планетах строятся корабли. - c.Cache.ProduceShips() + // 09. Корабли, где это возможно, объединяются в группы. + // 10. На планетах производится промышленность, добывается сырье, разрабатываются новые технологии. + // 11. Увеличивается население планет. + // TODO: tests + c.Cache.TurnPlanetProductions() + + // 12. Товары выгружаются в конце грузовых маршрутов. + // TODO: tests + c.Cache.TurnUnloadEnroutedGroups() /*** Last steps ***/ diff --git a/internal/controller/planet.go b/internal/controller/planet.go index fe3f643..33cbc89 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "iter" "slices" "strings" @@ -196,23 +197,78 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 { return p.ProductionCapacity() - busyResources } -func (c *Cache) ProduceShips() { - for pl := range c.g.Map.Planet { - p := c.MustPlanet(c.g.Map.Planet[pl].Number) - if p.Owner == uuid.Nil || p.Production.Type != game.ProductionShip { +func (c *Cache) TurnPlanetProductions() { + for pn := range c.listProducingPlanets() { + p := c.MustPlanet(pn) + 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 } - ri := c.RaceIndex(p.Owner) - st := c.MustShipType(ri, *p.Production.SubjectID) - ships := ProduceShip(p, st.EmptyMass()) - if ships > 0 { - if err := c.CreateShips(ri, st.Name, p.Number, ships); err != nil { - panic(fmt.Sprintf("ProduceShips: CreateShip: %s", err)) + ordered = append(ordered, i) + } + slices.SortFunc(ordered, func(l, r int) int { + if c.g.Map.Planet[l].Production.Type == game.ProductionShip && c.g.Map.Planet[r].Production.Type != game.ProductionShip { + return -1 + } + 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 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 } -func ProduceShip(p *game.Planet, shipMass float64) int { +func ProduceShip(p *game.Planet, shipMass float64) uint { productionAvailable := p.ProductionCapacity() if productionAvailable <= 0 { return 0 } CAP_perShip := shipMass / p.Resources productionForMass := shipMass * 10. - ships := 0 + ships := uint(0) flZero := 0. p.Production.Progress = &flZero for productionAvailable > 0 { diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go index 34bd611..42ec128 100644 --- a/internal/controller/planet_test.go +++ b/internal/controller/planet_test.go @@ -134,12 +134,13 @@ func TestProduceShips(t *testing.T) { st := c.MustShipClass(Race_0_idx, Race_0_Gunship) 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).Industry = 1000. c.MustPlanet(R0_Planet_0_num).Resources = 10. shipMass := st.EmptyMass() - c.ProduceShips() + c.TurnPlanetProductions() assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 0) 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.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.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.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 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)) 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) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 10. 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., 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).Capital = 0. 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) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 20. 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) } + +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) +} diff --git a/internal/controller/route.go b/internal/controller/route.go index 86771b6..725dce8 100644 --- a/internal/controller/route.go +++ b/internal/controller/route.go @@ -3,9 +3,12 @@ package controller import ( "cmp" "iter" + "maps" "math" + "math/rand/v2" "slices" + "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" "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() { for pi := range c.g.Map.Planet { 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 + } + } + } + } + } +} diff --git a/internal/controller/science.go b/internal/controller/science.go index 028aad6..de41516 100644 --- a/internal/controller/science.go +++ b/internal/controller/science.go @@ -1,6 +1,7 @@ package controller import ( + "fmt" "slices" "github.com/google/uuid" @@ -86,3 +87,14 @@ func (c *Cache) raceScience(ri int) []game.Science { c.validateRaceIndex(ri) 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] + +} diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index 5411c38..e6ed391 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -25,10 +25,16 @@ func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quan 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{ OwnerID: c.g.Race[ri].ID, - TypeID: class.ID, - Destination: p.Number, + TypeID: classID, + Destination: planet, Number: uint(quantity), Tech: map[game.Tech]float64{ 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), }, }) - return nil + } // ShipGroup is a proxy func, nothing to cache @@ -123,7 +129,7 @@ func (c *Controller) JoinEqualGroups(raceName string) error { return nil } -func (c *Cache) CmdJoinEqualGroups() { +func (c *Cache) TurnMergeEqualShipGroups() { for i := range c.g.Race { 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 { return e.NewCargoUnloadEmptyError() } + ct := *c.ShipGroup(sgi).CargoType - p, ok := c.Planet(c.ShipGroup(sgi).Destination) - if !ok { - return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination) - } - 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) + p := c.MustPlanet(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 ships > 0 && ships < c.ShipGroup(sgi).Number { 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 } + toBeUnloaded := quantity if quantity == 0 { 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 { 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 { c.ShipGroup(sgi).CargoType = nil } - return nil } + func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error { ri, err := c.Cache.raceIndex(raceName) if err != nil { diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index d293cbc..79052bd 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -18,7 +18,7 @@ type UninhabitedPlanet struct { Name string `json:"name"` Resources float64 `json:"resources"` // R - Ресурсы Capital float64 `json:"capital"` // CAP $ - Запасы промышленности - Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья + Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырьё } type PlanetReport struct { @@ -52,13 +52,23 @@ func PlanetProduction(industry, population float64) float64 { // Производство промышленности // 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() { - prod := p.ProductionCapacity() / 5. - industryIncrement := math.Min(prod, p.Material) - p.Industry += industryIncrement + production := p.ProductionCapacity() + var ind float64 + 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 { + p.Capital += 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 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() { p.Population *= 1.08 if p.Population > p.Size { diff --git a/internal/model/game/planet_test.go b/internal/model/game/planet_test.go index eb884e4..a5c816d 100644 --- a/internal/model/game/planet_test.go +++ b/internal/model/game/planet_test.go @@ -13,3 +13,45 @@ func TestPlanetProduction(t *testing.T) { assert.Equal(t, 750., game.PlanetProduction(1000., 0.)) 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) +}