From 0890bf30097d39cb69a561bbcb9f75b48536b663 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Thu, 2 Oct 2025 02:05:00 +0300 Subject: [PATCH] cmd: planet production --- pkg/error/generic.go | 3 ++ pkg/error/input.go | 4 ++ pkg/game/cmd_production.go | 19 +++++++ pkg/model/game/game.go | 9 ++++ pkg/model/game/production.go | 97 +++++++++++++++++++++++++++++++++++- pkg/model/game/ship.go | 27 +++++----- pkg/model/game/ship_test.go | 20 ++++++-- 7 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 pkg/game/cmd_production.go diff --git a/pkg/error/generic.go b/pkg/error/generic.go index 31cbb6e..6867ec4 100644 --- a/pkg/error/generic.go +++ b/pkg/error/generic.go @@ -31,6 +31,7 @@ const ( ErrInputShipTypeWeaponsAndArmamentValue ErrInputShipTypeZeroValues ErrInputScienceSumValues + ErrInputProductionInvalid ) func GenericErrorText(code int) string { @@ -77,6 +78,8 @@ func GenericErrorText(code int) string { return "Science in production on the Planet" case ErrInputScienceSumValues: return "Science proportions sum should be equal 1" + case ErrInputProductionInvalid: + return "Invalid Production type" default: return fmt.Sprintf("Undescribed error with code %d", code) } diff --git a/pkg/error/input.go b/pkg/error/input.go index 7402176..49eda19 100644 --- a/pkg/error/input.go +++ b/pkg/error/input.go @@ -59,3 +59,7 @@ func NewShipTypeShipTypeZeroValuesError(arg ...any) error { func NewScienceSumValuesError(arg ...any) error { return newGenericError(ErrInputScienceSumValues, arg...) } + +func NewProductionInvalidError(arg ...any) error { + return newGenericError(ErrInputProductionInvalid, arg...) +} diff --git a/pkg/game/cmd_production.go b/pkg/game/cmd_production.go new file mode 100644 index 0000000..8d9aa0c --- /dev/null +++ b/pkg/game/cmd_production.go @@ -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) +} diff --git a/pkg/model/game/game.go b/pkg/model/game/game.go index 336c3ca..821c802 100644 --- a/pkg/model/game/game.go +++ b/pkg/model/game/game.go @@ -2,6 +2,7 @@ package game import ( "encoding/json" + "slices" "strings" "github.com/google/uuid" @@ -33,6 +34,14 @@ func (g Game) hostRaceID(name string) (uuid.UUID, error) { 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) { if v, ok := g.raceID(name); ok { return v, nil diff --git a/pkg/model/game/production.go b/pkg/model/game/production.go index af4f6d3..6e4107c 100644 --- a/pkg/model/game/production.go +++ b/pkg/model/game/production.go @@ -1,6 +1,11 @@ package game -import "github.com/google/uuid" +import ( + "slices" + + "github.com/google/uuid" + e "github.com/iliadenisov/galaxy/pkg/error" +) type PlanetProduction string @@ -21,6 +26,7 @@ const ( type ProductionType struct { Production PlanetProduction `json:"type"` SubjectID *uuid.UUID `json:"subjectId"` + Progress *float64 `json:"progress"` } 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} } } + +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 +} diff --git a/pkg/model/game/ship.go b/pkg/model/game/ship.go index 7a213be..564f059 100644 --- a/pkg/model/game/ship.go +++ b/pkg/model/game/ship.go @@ -43,26 +43,23 @@ type Fleet struct { ShipGroups []ShipGroup `json:"group"` } -// TODO: test on real values 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 } -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 { - 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 } // Грузоподъёмность diff --git a/pkg/model/game/ship_test.go b/pkg/model/game/ship_test.go index e402ea9..b2ce769 100644 --- a/pkg/model/game/ship_test.go +++ b/pkg/model/game/ship_test.go @@ -8,8 +8,21 @@ import ( ) 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{ ShipTypeReport: game.ShipTypeReport{ + Name: "Gunship", Drive: 4, Armament: 2, Weapons: 2, @@ -17,10 +30,11 @@ func TestShipType(t *testing.T) { Cargo: 0, }, } - assert.Equal(t, Gunship.EmptyMass(), 11.) + assert.Equal(t, 11., Gunship.EmptyMass()) Cruiser := game.ShipType{ ShipTypeReport: game.ShipTypeReport{ + Name: "Cruiser", Drive: 15, Armament: 1, Weapons: 15, @@ -28,7 +42,7 @@ func TestShipType(t *testing.T) { Cargo: 0, }, } - assert.Equal(t, Cruiser.EmptyMass(), 45.) + assert.Equal(t, 45., Cruiser.EmptyMass()) sg := game.ShipGroup{ Type: Cruiser, @@ -43,7 +57,7 @@ func TestShipType(t *testing.T) { sg.UpgradeWeaponsCost(2.0) + sg.UpgradeShieldsCost(2.0) + sg.UpgradeCargoCost(2.0) - assert.Equal(t, upgradeCost, 225.) + assert.Equal(t, 225., upgradeCost) } func TestCargoCapacity(t *testing.T) {