cmd: planet production
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Грузоподъёмность
|
// Грузоподъёмность
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user