feat: produce ships
This commit is contained in:
@@ -10,18 +10,19 @@ type BombingReport struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BombingPlanetReport struct {
|
type BombingPlanetReport struct {
|
||||||
Planet string `json:"name"`
|
ID uuid.UUID `json:"id"`
|
||||||
Number uint `json:"number"`
|
Planet string `json:"name"`
|
||||||
Owner string `json:"owner"`
|
Number uint `json:"number"`
|
||||||
Attacker string `json:"attacker"`
|
Owner string `json:"owner"`
|
||||||
Production string `json:"production"`
|
Attacker string `json:"attacker"`
|
||||||
Industry float64 `json:"industry"` // I - Промышленность
|
Production string `json:"production"`
|
||||||
Population float64 `json:"population"` // P - Население
|
Industry float64 `json:"industry"` // I - Промышленность
|
||||||
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
Population float64 `json:"population"` // P - Население
|
||||||
Capital float64 `json:"capital"` // CAP $ - Запасы промышленности
|
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
||||||
Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья
|
Capital float64 `json:"capital"` // CAP $ - Запасы промышленности
|
||||||
AttackPower float64 `json:"attack"`
|
Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья
|
||||||
Wiped bool `json:"wiped"`
|
AttackPower float64 `json:"attack"`
|
||||||
|
Wiped bool `json:"wiped"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) BombingPlanetReport {
|
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) BombingPlanetReport {
|
||||||
@@ -32,6 +33,7 @@ func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) BombingPlane
|
|||||||
attackPower += sg.BombingPower(st)
|
attackPower += sg.BombingPower(st)
|
||||||
}
|
}
|
||||||
r := &BombingPlanetReport{
|
r := &BombingPlanetReport{
|
||||||
|
ID: uuid.New(),
|
||||||
Planet: p.Name,
|
Planet: p.Name,
|
||||||
Number: p.Number,
|
Number: p.Number,
|
||||||
Owner: c.g.Race[c.RaceIndex(p.Owner)].Name,
|
Owner: c.g.Race[c.RaceIndex(p.Owner)].Name,
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ func TestProduceBombings(t *testing.T) {
|
|||||||
reports := c.ProduceBombings()
|
reports := c.ProduceBombings()
|
||||||
assert.Len(t, reports, 2)
|
assert.Len(t, reports, 2)
|
||||||
for _, r := range reports {
|
for _, r := range reports {
|
||||||
|
assert.NotEqual(t, uuid.Nil, r.ID)
|
||||||
switch pn := r.Number; pn {
|
switch pn := r.Number; pn {
|
||||||
case R1_Planet_1_num:
|
case R1_Planet_1_num:
|
||||||
assert.Equal(t, Race_1.Name, r.Owner)
|
assert.Equal(t, Race_1.Name, r.Owner)
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
|
|||||||
battles = append(battles, ProduceBattles(c.Cache)...)
|
battles = append(battles, ProduceBattles(c.Cache)...)
|
||||||
|
|
||||||
// 07. Корабли бомбят вражеские планеты.
|
// 07. Корабли бомбят вражеские планеты.
|
||||||
_ = c.Cache.ProduceBombings() // TODO: store bombings for reports
|
_ = c.Cache.ProduceBombings()
|
||||||
|
|
||||||
|
// 08. На планетах строятся корабли.
|
||||||
|
c.Cache.ProduceShips()
|
||||||
|
|
||||||
/*** Last steps ***/
|
/*** Last steps ***/
|
||||||
|
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s
|
|||||||
if p.Owner != c.g.Race[ri].ID {
|
if p.Owner != c.g.Race[ri].ID {
|
||||||
return e.NewEntityNotOwnedError("planet #%d", number)
|
return e.NewEntityNotOwnedError("planet #%d", number)
|
||||||
}
|
}
|
||||||
i := c.MustPlanetIndex(p.Number)
|
|
||||||
var subjectID *uuid.UUID
|
var subjectID *uuid.UUID
|
||||||
if (prod == game.ResearchScience || prod == game.ProductionShip) && subj == "" {
|
if (prod == game.ResearchScience || prod == game.ProductionShip) && subj == "" {
|
||||||
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
|
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
|
||||||
}
|
}
|
||||||
|
|
||||||
if prod == game.ResearchScience {
|
if prod == game.ResearchScience {
|
||||||
i := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == subj })
|
i := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == subj })
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
@@ -90,6 +90,7 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s
|
|||||||
}
|
}
|
||||||
subjectID = &c.g.Race[ri].Sciences[i].ID
|
subjectID = &c.g.Race[ri].Sciences[i].ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if prod == game.ProductionShip {
|
if prod == game.ProductionShip {
|
||||||
st, _, ok := c.ShipClass(ri, subj)
|
st, _, ok := c.ShipClass(ri, subj)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -102,30 +103,24 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
subjectID = &st.ID
|
subjectID = &st.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Production.Type == game.ProductionShip && (prod != game.ProductionShip || *subjectID != *p.Production.SubjectID) {
|
||||||
|
mat, _ := c.MustShipType(ri, *p.Production.SubjectID).ProductionCost()
|
||||||
|
p.Material += mat * (*p.Production.Progress)
|
||||||
|
*p.Production.Progress = 0.
|
||||||
|
} else if prod == game.ProductionShip {
|
||||||
|
// new ship class to produce; otherwise we must have been returned from the func earlier
|
||||||
var progress float64 = 0.
|
var progress float64 = 0.
|
||||||
c.g.Map.Planet[i].Production.Progress = &progress
|
p.Production.Progress = &progress
|
||||||
} else {
|
|
||||||
c.g.Map.Planet[i].Production.Progress = nil
|
|
||||||
}
|
}
|
||||||
if p.Production.Type == game.ProductionShip && prod != game.ProductionShip {
|
|
||||||
if p.Production.SubjectID == nil {
|
if prod != game.ProductionShip {
|
||||||
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", p.Number)
|
p.Production.Progress = nil
|
||||||
}
|
|
||||||
s := *p.Production.SubjectID
|
|
||||||
if p.Production.Progress == nil {
|
|
||||||
return e.NewGameStateError("planet #%d produces ship but Progress is empty", p.Number)
|
|
||||||
}
|
|
||||||
progress := *p.Production.Progress
|
|
||||||
st, ok := c.ShipType(ri, s)
|
|
||||||
if !ok {
|
|
||||||
return e.NewGameStateError("planet #%d produces ship but ShipType was not found for race %s", p.Number, c.g.Race[ri].Name)
|
|
||||||
}
|
|
||||||
mat, _ := st.ProductionCost()
|
|
||||||
extra := mat * progress
|
|
||||||
c.g.Map.Planet[i].Material += extra
|
|
||||||
}
|
}
|
||||||
c.g.Map.Planet[i].Production.Type = prod
|
|
||||||
c.g.Map.Planet[i].Production.SubjectID = subjectID
|
p.Production.Type = prod
|
||||||
|
p.Production.SubjectID = subjectID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +196,23 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 {
|
|||||||
return p.ProductionCapacity() - busyResources
|
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 {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internal funcs
|
// Internal funcs
|
||||||
|
|
||||||
func (c *Cache) putPopulation(pn uint, v float64) {
|
func (c *Cache) putPopulation(pn uint, v float64) {
|
||||||
@@ -230,16 +242,14 @@ func ProduceShip(p *game.Planet, shipMass float64) int {
|
|||||||
if CAP_deficit := p.Capital - CAP_perShip; CAP_deficit < 0 {
|
if CAP_deficit := p.Capital - CAP_perShip; CAP_deficit < 0 {
|
||||||
productionExtraCAP = -CAP_deficit
|
productionExtraCAP = -CAP_deficit
|
||||||
}
|
}
|
||||||
|
productionCost := productionExtraCAP + productionForMass
|
||||||
ship_prod := productionExtraCAP + productionForMass
|
if productionAvailable >= productionCost {
|
||||||
|
productionAvailable -= productionCost
|
||||||
if productionAvailable >= ship_prod {
|
|
||||||
productionAvailable -= ship_prod
|
|
||||||
p.Capital = p.Capital - (CAP_perShip - productionExtraCAP)
|
p.Capital = p.Capital - (CAP_perShip - productionExtraCAP)
|
||||||
ships++
|
ships++
|
||||||
} else {
|
} else {
|
||||||
progress := productionAvailable / ship_prod
|
progress := productionAvailable / productionCost
|
||||||
productionAvailable -= ship_prod * progress
|
productionAvailable -= productionCost * progress
|
||||||
p.Production.Progress = &progress
|
p.Production.Progress = &progress
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller_test
|
package controller_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -122,6 +123,41 @@ func TestPlanetProductionCapacity(t *testing.T) {
|
|||||||
assert.Equal(t, 53.125, c.PlanetProductionCapacity(R0_Planet_0_num))
|
assert.Equal(t, 53.125, c.PlanetProductionCapacity(R0_Planet_0_num))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProduceShips(t *testing.T) {
|
||||||
|
c, g := newCache()
|
||||||
|
|
||||||
|
pn := int(R0_Planet_0_num)
|
||||||
|
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIP", Race_0_Gunship))
|
||||||
|
assert.Equal(t, game.ProductionShip, c.MustPlanet(R0_Planet_0_num).Production.Type)
|
||||||
|
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
|
||||||
|
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
|
||||||
|
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).Population = 1000.
|
||||||
|
c.MustPlanet(R0_Planet_0_num).Industry = 1000.
|
||||||
|
c.MustPlanet(R0_Planet_0_num).Resources = 10.
|
||||||
|
shipMass := st.EmptyMass()
|
||||||
|
|
||||||
|
c.ProduceShips()
|
||||||
|
|
||||||
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 0)
|
||||||
|
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
|
||||||
|
progress := *c.MustPlanet(R0_Planet_0_num).Production.Progress
|
||||||
|
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, 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()
|
||||||
|
|
||||||
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1)
|
||||||
|
assert.Equal(t, uint(99), 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
|
||||||
|
}
|
||||||
|
|
||||||
func TestProduceShip(t *testing.T) {
|
func TestProduceShip(t *testing.T) {
|
||||||
Drone := game.ShipType{
|
Drone := game.ShipType{
|
||||||
ShipTypeReport: game.ShipTypeReport{
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const (
|
|||||||
|
|
||||||
type Production struct {
|
type Production struct {
|
||||||
Type ProductionType `json:"type"`
|
Type ProductionType `json:"type"`
|
||||||
SubjectID *uuid.UUID `json:"subjectId"`
|
SubjectID *uuid.UUID `json:"subjectId"` // TODO: get rid of Nils?
|
||||||
Progress *float64 `json:"progress"`
|
Progress *float64 `json:"progress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func (st ShipType) EmptyMass() float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType]
|
// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType]
|
||||||
|
// TODO: do we need this?
|
||||||
func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
||||||
mat = st.EmptyMass()
|
mat = st.EmptyMass()
|
||||||
pop = mat * 10
|
pop = mat * 10
|
||||||
|
|||||||
Reference in New Issue
Block a user