cmd: planet production

This commit is contained in:
Ilia Denisov
2025-10-02 02:05:00 +03:00
parent 8a7e2f57c7
commit 0890bf3009
7 changed files with 160 additions and 19 deletions
+3
View File
@@ -31,6 +31,7 @@ const (
ErrInputShipTypeWeaponsAndArmamentValue ErrInputShipTypeWeaponsAndArmamentValue
ErrInputShipTypeZeroValues ErrInputShipTypeZeroValues
ErrInputScienceSumValues ErrInputScienceSumValues
ErrInputProductionInvalid
) )
func GenericErrorText(code int) string { func GenericErrorText(code int) string {
@@ -77,6 +78,8 @@ func GenericErrorText(code int) string {
return "Science in production on the Planet" return "Science in production on the Planet"
case ErrInputScienceSumValues: case ErrInputScienceSumValues:
return "Science proportions sum should be equal 1" return "Science proportions sum should be equal 1"
case ErrInputProductionInvalid:
return "Invalid Production type"
default: default:
return fmt.Sprintf("Undescribed error with code %d", code) return fmt.Sprintf("Undescribed error with code %d", code)
} }
+4
View File
@@ -59,3 +59,7 @@ func NewShipTypeShipTypeZeroValuesError(arg ...any) error {
func NewScienceSumValuesError(arg ...any) error { func NewScienceSumValuesError(arg ...any) error {
return newGenericError(ErrInputScienceSumValues, arg...) return newGenericError(ErrInputScienceSumValues, arg...)
} }
func NewProductionInvalidError(arg ...any) error {
return newGenericError(ErrInputProductionInvalid, arg...)
}
+19
View File
@@ -0,0 +1,19 @@
package game
import "github.com/iliadenisov/galaxy/pkg/model/game"
func PlanetProduction(configure func(*Param), race string, planetNumber int, prodType, subject string) (err error) {
control(configure, func(c *ctrl) {
c.execute(func(r Repo, g game.Game) {
err = planetProduction(r, g, race, planetNumber, prodType, subject)
})
})
return
}
func planetProduction(r Repo, g game.Game, race string, planetNumber int, prodType, subject string) error {
if err := g.PlanetProduction(race, planetNumber, prodType, subject); err != nil {
return err
}
return r.SaveState(g)
}
+9
View File
@@ -2,6 +2,7 @@ package game
import ( import (
"encoding/json" "encoding/json"
"slices"
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
@@ -33,6 +34,14 @@ func (g Game) hostRaceID(name string) (uuid.UUID, error) {
return uuid.Nil, e.NewHostRaceUnknownError(name) return uuid.Nil, e.NewHostRaceUnknownError(name)
} }
func (g Game) raceIndex(name string) (int, error) {
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name })
if i < 0 {
return i, e.NewHostRaceUnknownError(name)
}
return i, nil
}
func (g Game) opponentRaceID(name string) (uuid.UUID, error) { func (g Game) opponentRaceID(name string) (uuid.UUID, error) {
if v, ok := g.raceID(name); ok { if v, ok := g.raceID(name); ok {
return v, nil return v, nil
+96 -1
View File
@@ -1,6 +1,11 @@
package game package game
import "github.com/google/uuid" import (
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/pkg/error"
)
type PlanetProduction string type PlanetProduction string
@@ -21,6 +26,7 @@ const (
type ProductionType struct { type ProductionType struct {
Production PlanetProduction `json:"type"` Production PlanetProduction `json:"type"`
SubjectID *uuid.UUID `json:"subjectId"` SubjectID *uuid.UUID `json:"subjectId"`
Progress *float64 `json:"progress"`
} }
func (p PlanetProduction) AsType(subject uuid.UUID) ProductionType { func (p PlanetProduction) AsType(subject uuid.UUID) ProductionType {
@@ -31,3 +37,92 @@ func (p PlanetProduction) AsType(subject uuid.UUID) ProductionType {
return ProductionType{Production: p, SubjectID: nil} return ProductionType{Production: p, SubjectID: nil}
} }
} }
func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subject string) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
var prod PlanetProduction
switch PlanetProduction(prodType) {
case ProductionMaterial:
prod = ProductionMaterial
case ProductionCapital:
prod = ProductionCapital
case ResearchDrive:
prod = ResearchDrive
case ResearchWeapons:
prod = ResearchWeapons
case ResearchShields:
prod = ResearchShields
case ResearchCargo:
prod = ResearchCargo
case ResearchScience:
prod = ResearchScience
case ProductionShip:
prod = ProductionShip
default:
return e.NewProductionInvalidError(prodType)
}
return g.planetProductionInternal(ri, planetNumber, prod, subject)
}
func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction, subj string) error {
if number < 0 {
return e.NewPlanetNumberError(number)
}
i := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(number) })
if i < 0 {
return e.NewEntityNotExistsError("planet #%d", number)
}
if g.Map.Planet[i].Owner != g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet %#d", number)
}
g.Map.Planet[i].Production.Progress = nil
var subjectID *uuid.UUID
if (prod == ResearchScience || prod == ProductionShip) && subj == "" {
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
}
if prod == ResearchScience {
i := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == subj })
if i < 0 {
return e.NewEntityNotExistsError("science %w", subj)
}
subjectID = &g.Race[ri].Sciences[i].ID
}
if prod == ProductionShip {
i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == subj })
if i < 0 {
return e.NewEntityNotExistsError("ship type %w", subj)
}
if g.Map.Planet[i].Production.Production == ProductionShip &&
g.Map.Planet[i].Production.SubjectID != nil &&
*g.Map.Planet[i].Production.SubjectID == g.Race[ri].ShipTypes[i].ID {
// Planet already produces this ship type, keeping progress intact
return nil
}
subjectID = &g.Race[ri].ShipTypes[i].ID
var progress float64 = 0.
g.Map.Planet[i].Production.Progress = &progress
}
if g.Map.Planet[i].Production.Production == ProductionShip {
if g.Map.Planet[i].Production.SubjectID == nil {
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", g.Map.Planet[i].Number)
}
s := *g.Map.Planet[i].Production.SubjectID
if g.Map.Planet[i].Production.Progress == nil {
return e.NewGameStateError("planet #%d produces ship but Progress is empty", g.Map.Planet[i].Number)
}
progress := *g.Map.Planet[i].Production.Progress
i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == s })
if i < 0 {
return e.NewGameStateError("planet #%d produces ship but ShipType was not found for race %s", g.Map.Planet[i].Number, g.Race[ri].Name)
}
mat, _ := g.Race[ri].ShipTypes[i].ProductionCost()
extra := mat * progress
g.Map.Planet[i].Material += extra
}
g.Map.Planet[i].Production.Production = prod
g.Map.Planet[i].Production.SubjectID = subjectID
return nil
}
+12 -15
View File
@@ -43,26 +43,23 @@ type Fleet struct {
ShipGroups []ShipGroup `json:"group"` ShipGroups []ShipGroup `json:"group"`
} }
// TODO: test on real values
func (st ShipType) EmptyMass() float64 { func (st ShipType) EmptyMass() float64 {
shipMass := st.DriveMass() + st.ShieldsMass() + st.CargoMass() + st.WeaponsMass() shipMass := st.Drive + st.Shields + st.Cargo + st.WeaponsMass()
return shipMass return shipMass
} }
func (st ShipType) DriveMass() float64 {
return st.Drive
}
func (st ShipType) ShieldsMass() float64 {
return st.Shields
}
func (st ShipType) CargoMass() float64 {
return st.Cargo
}
func (st ShipType) WeaponsMass() float64 { func (st ShipType) WeaponsMass() float64 {
return float64(st.Armament)*(st.Weapons/2) + st.Weapons/2 if st.Armament == 0 || st.Weapons == 0 {
return 0
}
return float64(st.Armament+1) * (st.Weapons / 2)
}
// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType]
func (st ShipType) ProductionCost() (mat float64, pop float64) {
mat = st.EmptyMass()
pop = mat * 10
return
} }
// Грузоподъёмность // Грузоподъёмность
+17 -3
View File
@@ -8,8 +8,21 @@ import (
) )
func TestShipType(t *testing.T) { func TestShipType(t *testing.T) {
Freighter := game.ShipType{
ShipTypeReport: game.ShipTypeReport{
Name: "Freighter",
Drive: 8,
Armament: 0,
Weapons: 0,
Shields: 2,
Cargo: 10,
},
}
assert.Equal(t, 20., Freighter.EmptyMass())
Gunship := game.ShipType{ Gunship := game.ShipType{
ShipTypeReport: game.ShipTypeReport{ ShipTypeReport: game.ShipTypeReport{
Name: "Gunship",
Drive: 4, Drive: 4,
Armament: 2, Armament: 2,
Weapons: 2, Weapons: 2,
@@ -17,10 +30,11 @@ func TestShipType(t *testing.T) {
Cargo: 0, Cargo: 0,
}, },
} }
assert.Equal(t, Gunship.EmptyMass(), 11.) assert.Equal(t, 11., Gunship.EmptyMass())
Cruiser := game.ShipType{ Cruiser := game.ShipType{
ShipTypeReport: game.ShipTypeReport{ ShipTypeReport: game.ShipTypeReport{
Name: "Cruiser",
Drive: 15, Drive: 15,
Armament: 1, Armament: 1,
Weapons: 15, Weapons: 15,
@@ -28,7 +42,7 @@ func TestShipType(t *testing.T) {
Cargo: 0, Cargo: 0,
}, },
} }
assert.Equal(t, Cruiser.EmptyMass(), 45.) assert.Equal(t, 45., Cruiser.EmptyMass())
sg := game.ShipGroup{ sg := game.ShipGroup{
Type: Cruiser, Type: Cruiser,
@@ -43,7 +57,7 @@ func TestShipType(t *testing.T) {
sg.UpgradeWeaponsCost(2.0) + sg.UpgradeWeaponsCost(2.0) +
sg.UpgradeShieldsCost(2.0) + sg.UpgradeShieldsCost(2.0) +
sg.UpgradeCargoCost(2.0) sg.UpgradeCargoCost(2.0)
assert.Equal(t, upgradeCost, 225.) assert.Equal(t, 225., upgradeCost)
} }
func TestCargoCapacity(t *testing.T) { func TestCargoCapacity(t *testing.T) {