cmd: upgrade group
This commit is contained in:
@@ -57,13 +57,15 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
|||||||
}
|
}
|
||||||
relations[i] = game.RaceRelation{RaceID: raceID, Relation: game.RelationWar}
|
relations[i] = game.RaceRelation{RaceID: raceID, Relation: game.RelationWar}
|
||||||
g.Race[i] = game.Race{
|
g.Race[i] = game.Race{
|
||||||
ID: raceID,
|
ID: raceID,
|
||||||
Name: races[i],
|
Name: races[i],
|
||||||
Vote: raceID,
|
Vote: raceID,
|
||||||
Drive: 1,
|
Tech: map[game.Tech]float64{
|
||||||
Weapons: 1,
|
game.TechDrive: 1,
|
||||||
Shields: 1,
|
game.TechWeapons: 1,
|
||||||
Cargo: 1,
|
game.TechShields: 1,
|
||||||
|
game.TechCargo: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
||||||
planetCount,
|
planetCount,
|
||||||
@@ -125,7 +127,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlanet(num uint, name string, owner uuid.UUID, x, y, size, pop, ind, res float64, prod game.ProductionType) game.Planet {
|
func NewPlanet(num uint, name string, owner uuid.UUID, x, y, size, pop, ind, res float64, prod game.Production) game.Planet {
|
||||||
return game.Planet{
|
return game.Planet{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
PlanetReport: game.PlanetReport{
|
PlanetReport: game.PlanetReport{
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const (
|
|||||||
ErrShipsBusy = 5007
|
ErrShipsBusy = 5007
|
||||||
ErrShipsNotOnSamePlanet = 5008
|
ErrShipsNotOnSamePlanet = 5008
|
||||||
ErrGiveawayGroupShipsTypeNotEqual = 5009
|
ErrGiveawayGroupShipsTypeNotEqual = 5009
|
||||||
|
ErrUpgradeGroupNumberNotEnough = 5010
|
||||||
|
ErrUpgradeInsufficientResources = 5011
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -49,6 +51,13 @@ const (
|
|||||||
ErrInputCargoUnloadEmpty
|
ErrInputCargoUnloadEmpty
|
||||||
ErrInputCargoUnoadNotEnough
|
ErrInputCargoUnoadNotEnough
|
||||||
ErrInputBreakGroupIllegalNumber
|
ErrInputBreakGroupIllegalNumber
|
||||||
|
ErrInputTechUnknown
|
||||||
|
ErrInputTechInvalidMixing
|
||||||
|
ErrInputUpgradeShipTechNotUsed
|
||||||
|
ErrInputUpgradeParameterNotAllowed
|
||||||
|
ErrInputUpgradeShipsAlreadyUpToDate
|
||||||
|
ErrInputUpgradeGroupBreakNotAllowed
|
||||||
|
ErrInputUpgradeTechLevelInsufficient
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenericErrorText(code int) string {
|
func GenericErrorText(code int) string {
|
||||||
@@ -131,6 +140,24 @@ func GenericErrorText(code int) string {
|
|||||||
return "Ships not on the same planet"
|
return "Ships not on the same planet"
|
||||||
case ErrGiveawayGroupShipsTypeNotEqual:
|
case ErrGiveawayGroupShipsTypeNotEqual:
|
||||||
return "Ship type already defined with different specifications"
|
return "Ship type already defined with different specifications"
|
||||||
|
case ErrInputTechUnknown:
|
||||||
|
return "Technology name unknown"
|
||||||
|
case ErrInputTechInvalidMixing:
|
||||||
|
return "Technologies list must containt only specific values"
|
||||||
|
case ErrInputUpgradeShipTechNotUsed:
|
||||||
|
return "Technology is not used with ship class"
|
||||||
|
case ErrInputUpgradeParameterNotAllowed:
|
||||||
|
return "Parameter not allowed for upgrade"
|
||||||
|
case ErrInputUpgradeShipsAlreadyUpToDate:
|
||||||
|
return "Ships already up to date, nothing to upgrade"
|
||||||
|
case ErrUpgradeGroupNumberNotEnough:
|
||||||
|
return "Not enough ships in the group to make an upgrade"
|
||||||
|
case ErrUpgradeInsufficientResources:
|
||||||
|
return "Insufficient planet production capacity"
|
||||||
|
case ErrInputUpgradeGroupBreakNotAllowed:
|
||||||
|
return "The Group is already in upgrade state and can't be divided to a smaller group"
|
||||||
|
case ErrInputUpgradeTechLevelInsufficient:
|
||||||
|
return "Insifficient Tech level for requested upgrade"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Undescribed error with code %d", code)
|
return fmt.Sprintf("Undescribed error with code %d", code)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,3 +131,39 @@ func NewShipsNotOnSamePlanetError(arg ...any) error {
|
|||||||
func NewGiveawayGroupShipsTypeNotEqualError(arg ...any) error {
|
func NewGiveawayGroupShipsTypeNotEqualError(arg ...any) error {
|
||||||
return newGenericError(ErrGiveawayGroupShipsTypeNotEqual, arg...)
|
return newGenericError(ErrGiveawayGroupShipsTypeNotEqual, arg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewTechUnknownError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputTechUnknown, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTechInvalidMixingError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputTechInvalidMixing, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeShipTechNotUsedError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUpgradeShipTechNotUsed, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeParameterNotAllowedError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUpgradeParameterNotAllowed, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeShipsAlreadyUpToDateError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUpgradeShipsAlreadyUpToDate, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeGroupNumberNotEnoughError(arg ...any) error {
|
||||||
|
return newGenericError(ErrUpgradeGroupNumberNotEnough, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeInsufficientResourcesError(arg ...any) error {
|
||||||
|
return newGenericError(ErrUpgradeInsufficientResources, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeGroupBreakNotAllowedError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUpgradeGroupBreakNotAllowed, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeTechLevelInsufficientError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUpgradeTechLevelInsufficient, arg...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (g *Game) joinShipGroupToFleetInternal(ri int, fleetName string, group, cou
|
|||||||
return e.NewEntityNotExistsError("group #%d", group)
|
return e.NewEntityNotExistsError("group #%d", group)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil {
|
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||||
return e.NewShipsBusyError()
|
return e.NewShipsBusyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJoinShipGroupToFleet(t *testing.T) {
|
func TestJoinShipGroupToFleet(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
var groupIndex uint = 1
|
var groupIndex uint = 1
|
||||||
|
|
||||||
assert.ErrorContains(t,
|
assert.ErrorContains(t,
|
||||||
@@ -85,11 +86,14 @@ func TestJoinShipGroupToFleet(t *testing.T) {
|
|||||||
// group not In_Orbit
|
// group not In_Orbit
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7))
|
||||||
gi = 3
|
gi = 3
|
||||||
g.ShipGroups[gi].State = "In_Space"
|
g.ShipGroups[gi].StateInSpace = &game.InSpace{
|
||||||
|
Origin: 2,
|
||||||
|
Range: 1,
|
||||||
|
}
|
||||||
assert.ErrorContains(t,
|
assert.ErrorContains(t,
|
||||||
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0),
|
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0),
|
||||||
e.GenericErrorText(e.ErrShipsBusy))
|
e.GenericErrorText(e.ErrShipsBusy))
|
||||||
g.ShipGroups[gi].State = "In_Orbit"
|
g.ShipGroups[gi].StateInSpace = nil
|
||||||
|
|
||||||
// existing fleet not on the same planet or in_orbit
|
// existing fleet not on the same planet or in_orbit
|
||||||
g.Fleets[0].Destination = R0_Planet_2_num
|
g.Fleets[0].Destination = R0_Planet_2_num
|
||||||
@@ -100,7 +104,7 @@ func TestJoinShipGroupToFleet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJoinFleets(t *testing.T) {
|
func TestJoinFleets(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
// creating ShipGroup at Planet_0
|
// creating ShipGroup at Planet_0
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
||||||
// creating ShipGroup at Planet_2
|
// creating ShipGroup at Planet_2
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package game
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"iter"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -9,6 +12,22 @@ import (
|
|||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TechSet map[Tech]float64
|
||||||
|
|
||||||
|
func (ts TechSet) Value(t Tech) float64 {
|
||||||
|
if v, ok := ts[t]; ok {
|
||||||
|
return v
|
||||||
|
} else {
|
||||||
|
panic(fmt.Sprintf("TechSet: Value: %s's value not set", t.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TechSet) Set(t Tech, v float64) TechSet {
|
||||||
|
m := maps.Clone(ts)
|
||||||
|
m[t] = v
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Age uint `json:"turn"` // Game's turn number
|
Age uint `json:"turn"` // Game's turn number
|
||||||
@@ -29,6 +48,26 @@ func (g Game) Votes(raceID uuid.UUID) float64 {
|
|||||||
return pop / 1000.
|
return pop / 1000.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g Game) PlanetByNumber(number uint) (Planet, error) {
|
||||||
|
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number })
|
||||||
|
if pi < 0 {
|
||||||
|
return Planet{}, e.NewGameStateError("PlanetByNumber: planet with number=%d not found", number)
|
||||||
|
}
|
||||||
|
return g.Map.Planet[pi], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Game) ShipsInUpgrade(planetNumber uint) iter.Seq[ShipGroup] {
|
||||||
|
return func(yield func(ShipGroup) bool) {
|
||||||
|
for sg := range g.ShipGroups {
|
||||||
|
if g.ShipGroups[sg].Destination == planetNumber && g.ShipGroups[sg].State() == StateUpgrade {
|
||||||
|
if !yield(g.ShipGroups[sg]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (g Game) raceIndex(name string) (int, error) {
|
func (g Game) raceIndex(name string) (int, error) {
|
||||||
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name })
|
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name })
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
|
|||||||
@@ -10,37 +10,26 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Race_0 = game.Race{
|
Race_0 = game.Race{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
Name: "Race_0",
|
Name: "Race_0",
|
||||||
Drive: 1.1,
|
Tech: map[game.Tech]float64{
|
||||||
Weapons: 1.2,
|
game.TechDrive: 1.1,
|
||||||
Shields: 1.3,
|
game.TechWeapons: 1.2,
|
||||||
Cargo: 1.4,
|
game.TechShields: 1.3,
|
||||||
|
game.TechCargo: 1.4,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Race_1 = game.Race{
|
Race_1 = game.Race{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
Name: "Race_1",
|
Name: "Race_1",
|
||||||
Drive: 2.1,
|
Tech: map[game.Tech]float64{
|
||||||
Weapons: 2.2,
|
game.TechDrive: 2.1,
|
||||||
Shields: 2.3,
|
game.TechWeapons: 2.2,
|
||||||
Cargo: 2.4,
|
game.TechShields: 2.3,
|
||||||
}
|
game.TechCargo: 2.4,
|
||||||
Map = game.Map{
|
|
||||||
Width: 10,
|
|
||||||
Height: 10,
|
|
||||||
Planet: []game.Planet{
|
|
||||||
controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 0, 0, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
|
||||||
controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 1, 1, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
|
||||||
controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Game = &game.Game{
|
|
||||||
Race: []game.Race{
|
|
||||||
Race_0,
|
|
||||||
Race_1,
|
|
||||||
},
|
|
||||||
Map: Map,
|
|
||||||
}
|
|
||||||
Race_0_idx = 0
|
Race_0_idx = 0
|
||||||
Race_0_Gunship = "R0_Gunship"
|
Race_0_Gunship = "R0_Gunship"
|
||||||
Race_0_Freighter = "R0_Freighter"
|
Race_0_Freighter = "R0_Freighter"
|
||||||
@@ -59,25 +48,47 @@ var (
|
|||||||
Race_1_Cruiser_idx = 2
|
Race_1_Cruiser_idx = 2
|
||||||
|
|
||||||
ShipType_Cruiser = "Cruiser"
|
ShipType_Cruiser = "Cruiser"
|
||||||
|
|
||||||
|
Cruiser = game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Name: "Cruiser",
|
||||||
|
Drive: 15,
|
||||||
|
Armament: 1,
|
||||||
|
Weapons: 15,
|
||||||
|
Shields: 15,
|
||||||
|
Cargo: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
assertNoError(Game.CreateShipType(Race_0.Name, Race_0_Gunship, 60, 30, 100, 0, 3))
|
|
||||||
assertNoError(Game.CreateShipType(Race_0.Name, Race_0_Freighter, 8, 0, 2, 10, 0))
|
|
||||||
assertNoError(Game.CreateShipType(Race_0.Name, ShipType_Cruiser, 15, 15, 15, 0, 1))
|
|
||||||
|
|
||||||
assertNoError(Game.CreateShipType(Race_1.Name, Race_1_Gunship, 60, 30, 100, 0, 3))
|
|
||||||
assertNoError(Game.CreateShipType(Race_1.Name, Race_1_Freighter, 8, 0, 2, 10, 0))
|
|
||||||
assertNoError(Game.CreateShipType(Race_1.Name, ShipType_Cruiser, 15, 15, 15, 0, 2)) // same name - different type
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertNoError(err error) {
|
func assertNoError(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("init assertion failed: %v", err))
|
panic(fmt.Sprintf("init assertion failed: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyGame() *game.Game {
|
func newGame() *game.Game {
|
||||||
g := *Game
|
g := &game.Game{
|
||||||
return &g
|
Race: []game.Race{
|
||||||
|
Race_0,
|
||||||
|
Race_1,
|
||||||
|
},
|
||||||
|
Map: game.Map{
|
||||||
|
Width: 10,
|
||||||
|
Height: 10,
|
||||||
|
Planet: []game.Planet{
|
||||||
|
controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 0, 0, 100, 100, 100, 0, game.ProductionNone.AsType(uuid.Nil)),
|
||||||
|
controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 1, 1, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
||||||
|
controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assertNoError(g.CreateShipType(Race_0.Name, Race_0_Gunship, 60, 30, 100, 0, 3))
|
||||||
|
assertNoError(g.CreateShipType(Race_0.Name, Race_0_Freighter, 8, 0, 2, 10, 0))
|
||||||
|
assertNoError(g.CreateShipType(Race_0.Name, ShipType_Cruiser, Cruiser.Drive, Cruiser.Weapons, Cruiser.Shields, Cruiser.Cargo, int(Cruiser.Armament)))
|
||||||
|
|
||||||
|
assertNoError(g.CreateShipType(Race_1.Name, Race_1_Gunship, 60, 30, 100, 0, 3))
|
||||||
|
assertNoError(g.CreateShipType(Race_1.Name, Race_1_Freighter, 8, 0, 2, 10, 0))
|
||||||
|
assertNoError(g.CreateShipType(Race_1.Name, ShipType_Cruiser, 15, 15, 15, 0, 2)) // same name - different type (why.)
|
||||||
|
return g
|
||||||
}
|
}
|
||||||
|
|||||||
+115
-39
@@ -1,6 +1,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"maps"
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
@@ -35,50 +36,128 @@ func (ct CargoType) String() string {
|
|||||||
return string(ct)
|
return string(ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShipGroupState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateInOrbit ShipGroupState = "In_Orbit"
|
||||||
|
StateLaunched ShipGroupState = "Launched"
|
||||||
|
StateInSpace ShipGroupState = "In_Space"
|
||||||
|
StateUpgrade ShipGroupState = "Upgrade"
|
||||||
|
StateTransfer ShipGroupState = "Transfer_Status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InSpace struct {
|
||||||
|
Origin uint `json:"origin"`
|
||||||
|
// zero is for Launched status
|
||||||
|
Range float64 `json:"range"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InUpgrade struct {
|
||||||
|
UpgradeTech []UpgradePreference `json:"preference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iu InUpgrade) Cost() float64 {
|
||||||
|
var sum float64
|
||||||
|
for i := range iu.UpgradeTech {
|
||||||
|
sum += iu.UpgradeTech[i].Cost
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iu InUpgrade) TechCost(t Tech) float64 {
|
||||||
|
for i := range iu.UpgradeTech {
|
||||||
|
if iu.UpgradeTech[i].Tech == t {
|
||||||
|
return iu.UpgradeTech[i].Cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpgradePreference struct {
|
||||||
|
Tech Tech `json:"tech"`
|
||||||
|
Level float64 `json:"level"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tech string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TechAll Tech = "ALL"
|
||||||
|
TechDrive Tech = "DRIVE"
|
||||||
|
TechWeapons Tech = "WEAPONS"
|
||||||
|
TechShields Tech = "SHIELDS"
|
||||||
|
TechCargo Tech = "CARGO"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Tech) String() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
type ShipGroup struct {
|
type ShipGroup struct {
|
||||||
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
||||||
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
||||||
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
||||||
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
||||||
Number uint `json:"number"` // Number (quantity) ships of specific ShipType
|
Number uint `json:"number"` // Number (quantity) ships of specific ShipType
|
||||||
State string `json:"state"` // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade
|
|
||||||
|
|
||||||
CargoType *CargoType `json:"loadType,omitempty"`
|
CargoType *CargoType `json:"loadType,omitempty"`
|
||||||
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
|
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
|
||||||
|
|
||||||
Drive float64 `json:"drive"`
|
Tech TechSet `json:"tech"`
|
||||||
Weapons float64 `json:"weapons"`
|
|
||||||
Shields float64 `json:"shields"`
|
|
||||||
Cargo float64 `json:"cargo"`
|
|
||||||
|
|
||||||
// TODO: TEST: Destination, Origin, Range
|
// TODO: TEST: Destination, Origin, Range
|
||||||
Destination uint `json:"destination"`
|
Destination uint `json:"destination"`
|
||||||
Origin *uint `json:"origin,omitempty"`
|
StateInSpace *InSpace `json:"stateInSpace,omitempty"`
|
||||||
Range *float64 `json:"range,omitempty"`
|
StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sg ShipGroup) TechLevel(t Tech) float64 {
|
||||||
|
return sg.Tech.Value(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refactor to separate method with *ShipGroup as parameter
|
||||||
|
func (sg *ShipGroup) SetTechLevel(t Tech, v float64) {
|
||||||
|
sg.Tech = sg.Tech.Set(t, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sg ShipGroup) State() ShipGroupState {
|
||||||
|
switch {
|
||||||
|
case sg.StateInSpace == nil && sg.StateUpgrade == nil:
|
||||||
|
return StateInOrbit
|
||||||
|
case sg.StateInSpace != nil && sg.StateUpgrade == nil:
|
||||||
|
if sg.StateInSpace.Range > 0 {
|
||||||
|
return StateInSpace
|
||||||
|
}
|
||||||
|
return StateLaunched
|
||||||
|
case sg.StateUpgrade != nil && sg.StateInSpace == nil:
|
||||||
|
return StateUpgrade
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("ambigous group state: in_space=%#v upgrage=%#v", sg.StateInSpace, sg.StateUpgrade))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg ShipGroup) Equal(other ShipGroup) bool {
|
func (sg ShipGroup) Equal(other ShipGroup) bool {
|
||||||
return sg.OwnerID == other.OwnerID &&
|
return sg.OwnerID == other.OwnerID &&
|
||||||
sg.TypeID == other.TypeID &&
|
sg.TypeID == other.TypeID &&
|
||||||
sg.FleetID == other.FleetID &&
|
sg.FleetID == other.FleetID &&
|
||||||
sg.Drive == other.Drive &&
|
sg.TechLevel(TechDrive) == other.TechLevel(TechDrive) &&
|
||||||
sg.Weapons == other.Weapons &&
|
sg.TechLevel(TechWeapons) == other.TechLevel(TechWeapons) &&
|
||||||
sg.Shields == other.Shields &&
|
sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
|
||||||
sg.Cargo == other.Cargo &&
|
sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
|
||||||
sg.CargoType == other.CargoType &&
|
sg.CargoType == other.CargoType &&
|
||||||
sg.Load == other.Load &&
|
sg.Load == other.Load &&
|
||||||
sg.State == other.State
|
sg.State() == other.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Грузоподъёмность
|
// Грузоподъёмность
|
||||||
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
|
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
|
||||||
return sg.Cargo * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number)
|
return sg.TechLevel(TechCargo) * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Масса перевозимого груза -
|
// Масса перевозимого груза -
|
||||||
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
|
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
|
||||||
func (sg ShipGroup) CarryingMass() float64 {
|
func (sg ShipGroup) CarryingMass() float64 {
|
||||||
return sg.Load / sg.Cargo
|
return sg.Load / sg.TechLevel(TechCargo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Масса группы без учёта груза
|
// Масса группы без учёта груза
|
||||||
@@ -95,7 +174,7 @@ func (sg ShipGroup) FullMass(st *ShipType) float64 {
|
|||||||
// Эффективность двигателя -
|
// Эффективность двигателя -
|
||||||
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
|
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
|
||||||
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
|
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
|
||||||
return st.Drive * sg.Drive
|
return st.Drive * sg.TechLevel(TechDrive)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Корабли перемещаются за один ход на количество световых лет, равное
|
// Корабли перемещаются за один ход на количество световых лет, равное
|
||||||
@@ -105,29 +184,29 @@ func (sg ShipGroup) Speed(st *ShipType) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 {
|
func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 {
|
||||||
return (1 - sg.Drive/drive) * 10 * st.Drive
|
return (1 - sg.TechLevel(TechDrive)/drive) * 10 * st.Drive
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test on other values
|
// TODO: test on other values
|
||||||
func (sg ShipGroup) UpgradeWeaponsCost(st *ShipType, weapons float64) float64 {
|
func (sg ShipGroup) UpgradeWeaponsCost(st *ShipType, weapons float64) float64 {
|
||||||
return (1 - sg.Weapons/weapons) * 10 * st.WeaponsMass()
|
return (1 - sg.TechLevel(TechWeapons)/weapons) * 10 * st.WeaponsBlockMass()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 {
|
func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 {
|
||||||
return (1 - sg.Shields/shields) * 10 * st.Shields
|
return (1 - sg.TechLevel(TechShields)/shields) * 10 * st.Shields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 {
|
func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 {
|
||||||
return (1 - sg.Cargo/cargo) * 10 * st.Cargo
|
return (1 - sg.TechLevel(TechCargo)/cargo) * 10 * st.Cargo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Мощность бомбардировки
|
// Мощность бомбардировки
|
||||||
// TODO: maybe rounding must be done only for display?
|
// TODO: maybe rounding must be done only for display?
|
||||||
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
|
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
|
||||||
// return math.Sqrt(sg.Type.Weapons * sg.Weapons)
|
// return math.Sqrt(sg.Type.Weapons * sg.Weapons)
|
||||||
result := (math.Sqrt(st.Weapons*sg.Weapons)/10. + 1.) *
|
result := (math.Sqrt(st.Weapons*sg.TechLevel(TechWeapons))/10. + 1.) *
|
||||||
st.Weapons *
|
st.Weapons *
|
||||||
sg.Weapons *
|
sg.TechLevel(TechWeapons) *
|
||||||
float64(st.Armament) *
|
float64(st.Armament) *
|
||||||
float64(sg.Number)
|
float64(sg.Number)
|
||||||
return number.Fixed3(result)
|
return number.Fixed3(result)
|
||||||
@@ -173,7 +252,7 @@ func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error
|
|||||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil {
|
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||||
return e.NewShipsBusyError()
|
return e.NewShipsBusyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +336,7 @@ func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity
|
|||||||
if sgi < 0 {
|
if sgi < 0 {
|
||||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||||
}
|
}
|
||||||
if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil {
|
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||||
return e.NewShipsBusyError()
|
return e.NewShipsBusyError()
|
||||||
}
|
}
|
||||||
var sti int
|
var sti int
|
||||||
@@ -336,7 +415,7 @@ func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships ui
|
|||||||
if sgi < 0 {
|
if sgi < 0 {
|
||||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||||
}
|
}
|
||||||
if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil {
|
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||||
return e.NewShipsBusyError()
|
return e.NewShipsBusyError()
|
||||||
}
|
}
|
||||||
pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
||||||
@@ -464,19 +543,15 @@ func (g *Game) giveawayGroupInternal(ri, riAccept int, groupIndex, quantity uint
|
|||||||
OwnerID: g.Race[riAccept].ID,
|
OwnerID: g.Race[riAccept].ID,
|
||||||
TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
||||||
Number: uint(quantity),
|
Number: uint(quantity),
|
||||||
State: g.ShipGroups[sgi].State,
|
|
||||||
|
|
||||||
CargoType: g.ShipGroups[sgi].CargoType,
|
CargoType: g.ShipGroups[sgi].CargoType,
|
||||||
Load: g.ShipGroups[sgi].Load,
|
Load: g.ShipGroups[sgi].Load,
|
||||||
|
|
||||||
Drive: g.ShipGroups[sgi].Drive,
|
Tech: maps.Clone(g.ShipGroups[sgi].Tech),
|
||||||
Weapons: g.ShipGroups[sgi].Weapons,
|
|
||||||
Shields: g.ShipGroups[sgi].Shields,
|
|
||||||
Cargo: g.ShipGroups[sgi].Cargo,
|
|
||||||
|
|
||||||
Destination: g.ShipGroups[sgi].Destination,
|
Destination: g.ShipGroups[sgi].Destination,
|
||||||
Origin: g.ShipGroups[sgi].Origin,
|
StateInSpace: g.ShipGroups[sgi].StateInSpace,
|
||||||
Range: g.ShipGroups[sgi].Range,
|
StateUpgrade: g.ShipGroups[sgi].StateUpgrade,
|
||||||
})
|
})
|
||||||
|
|
||||||
if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
|
if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
|
||||||
@@ -503,7 +578,7 @@ func (g *Game) breakGroupInternal(ri int, groupIndex, quantity uint) error {
|
|||||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil {
|
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||||
return e.NewShipsBusyError()
|
return e.NewShipsBusyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,11 +674,12 @@ func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quanti
|
|||||||
TypeID: g.Race[ri].ShipTypes[st].ID,
|
TypeID: g.Race[ri].ShipTypes[st].ID,
|
||||||
Destination: g.Map.Planet[pl].Number,
|
Destination: g.Map.Planet[pl].Number,
|
||||||
Number: uint(quantity),
|
Number: uint(quantity),
|
||||||
State: "In_Orbit",
|
Tech: map[Tech]float64{
|
||||||
Drive: g.Race[ri].Drive,
|
TechDrive: g.Race[ri].TechLevel(TechDrive),
|
||||||
Weapons: g.Race[ri].Weapons,
|
TechWeapons: g.Race[ri].TechLevel(TechWeapons),
|
||||||
Shields: g.Race[ri].Shields,
|
TechShields: g.Race[ri].TechLevel(TechShields),
|
||||||
Cargo: g.Race[ri].Cargo,
|
TechCargo: g.Race[ri].TechLevel(TechCargo),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ func TestCargoCapacity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sg := game.ShipGroup{
|
sg := game.ShipGroup{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
State: "In_Orbit",
|
Tech: map[game.Tech]float64{
|
||||||
Drive: 1.5,
|
game.TechDrive: 1.5,
|
||||||
Weapons: 1.1,
|
game.TechWeapons: 1.1,
|
||||||
Shields: 2.0,
|
game.TechShields: 2.0,
|
||||||
Cargo: 1.0,
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
|
assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
|
||||||
}
|
}
|
||||||
@@ -52,13 +53,14 @@ func TestCarryingAndFullMass(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sg := &game.ShipGroup{
|
sg := &game.ShipGroup{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
State: "In_Orbit",
|
Tech: map[game.Tech]float64{
|
||||||
Drive: 1.0,
|
game.TechDrive: 1.0,
|
||||||
Weapons: 1.0,
|
game.TechWeapons: 1.0,
|
||||||
Shields: 1.0,
|
game.TechShields: 1.0,
|
||||||
Cargo: 1.0,
|
game.TechCargo: 1.0,
|
||||||
Load: 0.0,
|
},
|
||||||
|
Load: 0.0,
|
||||||
}
|
}
|
||||||
em := Freighter.EmptyMass()
|
em := Freighter.EmptyMass()
|
||||||
assert.Equal(t, 0.0, sg.CarryingMass())
|
assert.Equal(t, 0.0, sg.CarryingMass())
|
||||||
@@ -68,7 +70,7 @@ func TestCarryingAndFullMass(t *testing.T) {
|
|||||||
assert.Equal(t, 10.0, sg.CarryingMass())
|
assert.Equal(t, 10.0, sg.CarryingMass())
|
||||||
assert.Equal(t, em+10.0, sg.FullMass(Freighter))
|
assert.Equal(t, em+10.0, sg.FullMass(Freighter))
|
||||||
|
|
||||||
sg.Cargo = 2.5
|
sg.SetTechLevel(game.TechCargo, 2.5)
|
||||||
assert.Equal(t, 4.0, sg.CarryingMass())
|
assert.Equal(t, 4.0, sg.CarryingMass())
|
||||||
assert.Equal(t, em+4.0, sg.FullMass(Freighter))
|
assert.Equal(t, em+4.0, sg.FullMass(Freighter))
|
||||||
}
|
}
|
||||||
@@ -85,21 +87,22 @@ func TestSpeed(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sg := &game.ShipGroup{
|
sg := &game.ShipGroup{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
State: "In_Orbit",
|
Tech: map[game.Tech]float64{
|
||||||
Drive: 1.0,
|
game.TechDrive: 1.0,
|
||||||
Weapons: 1.0,
|
game.TechWeapons: 1.0,
|
||||||
Shields: 1.0,
|
game.TechShields: 1.0,
|
||||||
Cargo: 1.0,
|
game.TechCargo: 1.0,
|
||||||
Load: 0.0,
|
},
|
||||||
|
Load: 0.0,
|
||||||
}
|
}
|
||||||
assert.Equal(t, 8.0, sg.Speed(Freighter))
|
assert.Equal(t, 8.0, sg.Speed(Freighter))
|
||||||
sg.Load = 5.0
|
sg.Load = 5.0
|
||||||
assert.Equal(t, 6.4, sg.Speed(Freighter))
|
assert.Equal(t, 6.4, sg.Speed(Freighter))
|
||||||
sg.Drive = 1.5
|
sg.SetTechLevel(game.TechDrive, 1.5)
|
||||||
assert.Equal(t, 9.6, sg.Speed(Freighter))
|
assert.Equal(t, 9.6, sg.Speed(Freighter))
|
||||||
sg.Load = 10
|
sg.Load = 10
|
||||||
sg.Cargo = 1.5
|
sg.SetTechLevel(game.TechCargo, 1.5)
|
||||||
assert.Equal(t, 9.0, sg.Speed(Freighter))
|
assert.Equal(t, 9.0, sg.Speed(Freighter))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,45 +117,19 @@ func TestBombingPower(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sg := game.ShipGroup{
|
sg := game.ShipGroup{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
State: "In_Orbit",
|
Tech: map[game.Tech]float64{
|
||||||
Drive: 1.0,
|
game.TechDrive: 1.0,
|
||||||
Weapons: 1.0,
|
game.TechWeapons: 1.0,
|
||||||
Shields: 1.0,
|
game.TechShields: 1.0,
|
||||||
Cargo: 1.0,
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
expectedBombingPower := 139.295
|
expectedBombingPower := 139.295
|
||||||
result := sg.BombingPower(&Gunship)
|
result := sg.BombingPower(&Gunship)
|
||||||
assert.Equal(t, expectedBombingPower, result)
|
assert.Equal(t, expectedBombingPower, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpgradeCost(t *testing.T) {
|
|
||||||
Cruiser := game.ShipType{
|
|
||||||
ShipTypeReport: game.ShipTypeReport{
|
|
||||||
Name: "Cruiser",
|
|
||||||
Drive: 15,
|
|
||||||
Armament: 1,
|
|
||||||
Weapons: 15,
|
|
||||||
Shields: 15,
|
|
||||||
Cargo: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
sg := game.ShipGroup{
|
|
||||||
Number: 1,
|
|
||||||
State: "In_Orbit",
|
|
||||||
Drive: 1.0,
|
|
||||||
Weapons: 1.0,
|
|
||||||
Shields: 1.0,
|
|
||||||
Cargo: 1.0,
|
|
||||||
}
|
|
||||||
upgradeCost := sg.UpgradeDriveCost(&Cruiser, 2.0) +
|
|
||||||
sg.UpgradeWeaponsCost(&Cruiser, 2.0) +
|
|
||||||
sg.UpgradeShieldsCost(&Cruiser, 2.0) +
|
|
||||||
sg.UpgradeCargoCost(&Cruiser, 2.0)
|
|
||||||
assert.Equal(t, 225., upgradeCost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDriveEffective(t *testing.T) {
|
func TestDriveEffective(t *testing.T) {
|
||||||
tc := []struct {
|
tc := []struct {
|
||||||
driveShipType float64
|
driveShipType float64
|
||||||
@@ -178,12 +155,13 @@ func TestDriveEffective(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sg := game.ShipGroup{
|
sg := game.ShipGroup{
|
||||||
Number: rand.UintN(4) + 1,
|
Number: rand.UintN(4) + 1,
|
||||||
State: "In_Orbit",
|
Tech: map[game.Tech]float64{
|
||||||
Drive: tc[i].driveTech,
|
game.TechDrive: tc[i].driveTech,
|
||||||
Weapons: rand.Float64()*5 + 1,
|
game.TechWeapons: rand.Float64()*5 + 1,
|
||||||
Shields: rand.Float64()*5 + 1,
|
game.TechShields: rand.Float64()*5 + 1,
|
||||||
Cargo: rand.Float64()*5 + 1,
|
game.TechCargo: rand.Float64()*5 + 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, tc[i].expectDriveEffective, sg.DriveEffective(&someShip))
|
assert.Equal(t, tc[i].expectDriveEffective, sg.DriveEffective(&someShip))
|
||||||
}
|
}
|
||||||
@@ -201,13 +179,14 @@ func TestShipGroupEqual(t *testing.T) {
|
|||||||
OwnerID: uuid.New(),
|
OwnerID: uuid.New(),
|
||||||
TypeID: uuid.New(),
|
TypeID: uuid.New(),
|
||||||
FleetID: &fleetId,
|
FleetID: &fleetId,
|
||||||
State: "In_Orbit",
|
|
||||||
CargoType: &mat,
|
CargoType: &mat,
|
||||||
Load: 123.45,
|
Load: 123.45,
|
||||||
Drive: 1.0,
|
Tech: map[game.Tech]float64{
|
||||||
Weapons: 1.0,
|
game.TechDrive: 1.0,
|
||||||
Shields: 1.0,
|
game.TechWeapons: 1.0,
|
||||||
Cargo: 1.0,
|
game.TechShields: 1.0,
|
||||||
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// essential properties
|
// essential properties
|
||||||
@@ -230,7 +209,10 @@ func TestShipGroupEqual(t *testing.T) {
|
|||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
left.State = "In_Space"
|
left.StateInSpace = &game.InSpace{
|
||||||
|
Origin: 1,
|
||||||
|
Range: 1,
|
||||||
|
}
|
||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
@@ -246,19 +228,23 @@ func TestShipGroupEqual(t *testing.T) {
|
|||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
left.Drive = 1.1
|
left.SetTechLevel(game.TechDrive, 1.1)
|
||||||
|
assert.Equal(t, 1.1, left.TechLevel(game.TechDrive))
|
||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
left.Weapons = 1.1
|
left.SetTechLevel(game.TechWeapons, 1.1)
|
||||||
|
assert.Equal(t, 1.1, left.TechLevel(game.TechWeapons))
|
||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
left.Shields = 1.1
|
left.SetTechLevel(game.TechShields, 1.1)
|
||||||
|
assert.Equal(t, 1.1, left.TechLevel(game.TechShields))
|
||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
right = *left
|
right = *left
|
||||||
left.Cargo = 1.1
|
left.SetTechLevel(game.TechCargo, 1.1)
|
||||||
|
assert.Equal(t, 1.1, left.TechLevel(game.TechCargo))
|
||||||
assert.False(t, left.Equal(right))
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
// non-essential properties
|
// non-essential properties
|
||||||
@@ -271,7 +257,7 @@ func TestShipGroupEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateShips(t *testing.T) {
|
func TestCreateShips(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
|
|
||||||
assert.ErrorContains(t,
|
assert.ErrorContains(t,
|
||||||
g.CreateShips(Race_0_idx, "Unknown_Ship_Type", R0_Planet_0_num, 2),
|
g.CreateShips(Race_0_idx, "Unknown_Ship_Type", R0_Planet_0_num, 2),
|
||||||
@@ -294,7 +280,7 @@ func TestCreateShips(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJoinEqualGroups(t *testing.T) {
|
func TestJoinEqualGroups(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
|
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2
|
||||||
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
|
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
|
||||||
@@ -302,7 +288,7 @@ func TestJoinEqualGroups(t *testing.T) {
|
|||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // (3)
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // (3)
|
||||||
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
|
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
|
||||||
|
|
||||||
g.Race[Race_0_idx].Drive = 1.5
|
g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 1.5)
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 9)) // 4 -> 6
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 9)) // 4 -> 6
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) // 5 -> 7
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) // 5 -> 7
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 4)) // (6)
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 4)) // (6)
|
||||||
@@ -310,7 +296,7 @@ func TestJoinEqualGroups(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
|
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
|
||||||
|
|
||||||
g.Race[Race_1_idx].Shields = 2.0
|
g.Race[Race_1_idx].SetTechLevel(game.TechShields, 2.0)
|
||||||
assert.NoError(t, g.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1))
|
assert.NoError(t, g.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1))
|
||||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3)
|
assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3)
|
||||||
|
|
||||||
@@ -330,16 +316,16 @@ func TestJoinEqualGroups(t *testing.T) {
|
|||||||
|
|
||||||
for sg := range g.ListShipGroups(Race_0_idx) {
|
for sg := range g.ListShipGroups(Race_0_idx) {
|
||||||
switch {
|
switch {
|
||||||
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.Drive == 1.1:
|
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.TechLevel(game.TechDrive) == 1.1:
|
||||||
assert.Equal(t, uint(7), sg.Number)
|
assert.Equal(t, uint(7), sg.Number)
|
||||||
assert.Equal(t, uint(2), sg.Index)
|
assert.Equal(t, uint(2), sg.Index)
|
||||||
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.Drive == 1.5:
|
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.TechLevel(game.TechDrive) == 1.5:
|
||||||
assert.Equal(t, uint(11), sg.Number)
|
assert.Equal(t, uint(11), sg.Number)
|
||||||
assert.Equal(t, uint(7), sg.Index)
|
assert.Equal(t, uint(7), sg.Index)
|
||||||
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.Drive == 1.1:
|
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.TechLevel(game.TechDrive) == 1.1:
|
||||||
assert.Equal(t, uint(2), sg.Number)
|
assert.Equal(t, uint(2), sg.Number)
|
||||||
assert.Equal(t, uint(3), sg.Index)
|
assert.Equal(t, uint(3), sg.Index)
|
||||||
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.Drive == 1.5:
|
case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.TechLevel(game.TechDrive) == 1.5:
|
||||||
assert.Equal(t, uint(13), sg.Number)
|
assert.Equal(t, uint(13), sg.Number)
|
||||||
assert.Equal(t, uint(6), sg.Index)
|
assert.Equal(t, uint(6), sg.Index)
|
||||||
default:
|
default:
|
||||||
@@ -349,10 +335,13 @@ func TestJoinEqualGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBreakGroup(t *testing.T) {
|
func TestBreakGroup(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 13)) // group #1 (0)
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 13)) // group #1 (0)
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) // group #2 (1) - In_Space
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) // group #2 (1) - In_Space
|
||||||
g.ShipGroups[1].State = "In_Space"
|
g.ShipGroups[1].StateInSpace = &game.InSpace{
|
||||||
|
Origin: 1,
|
||||||
|
Range: 1,
|
||||||
|
}
|
||||||
|
|
||||||
fleet := "R0_Fleet"
|
fleet := "R0_Fleet"
|
||||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 1, 0))
|
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 1, 0))
|
||||||
@@ -415,17 +404,17 @@ func TestBreakGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGiveawayGroup(t *testing.T) {
|
func TestGiveawayGroup(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0)
|
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0)
|
||||||
assert.NoError(t, g.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1)
|
assert.NoError(t, g.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1)
|
||||||
|
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 17)) // group #2 (2) - In_Space
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 17)) // group #2 (2) - In_Space
|
||||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, "R0_Fleet", 2, 0))
|
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, "R0_Fleet", 2, 0))
|
||||||
assert.NotNil(t, g.ShipGroups[2].FleetID)
|
assert.NotNil(t, g.ShipGroups[2].FleetID)
|
||||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||||
rng := 31.337
|
Origin: 2,
|
||||||
g.ShipGroups[2].Range = &rng
|
Range: 31.337,
|
||||||
g.ShipGroups[2].State = "In_Space"
|
}
|
||||||
g.ShipGroups[2].CargoType = game.CargoMaterial.Ref()
|
g.ShipGroups[2].CargoType = game.CargoMaterial.Ref()
|
||||||
g.ShipGroups[2].Load = 1.234
|
g.ShipGroups[2].Load = 1.234
|
||||||
|
|
||||||
@@ -462,16 +451,16 @@ func TestGiveawayGroup(t *testing.T) {
|
|||||||
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Shields, g.Race[Race_0_idx].ShipTypes[sto].Shields)
|
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Shields, g.Race[Race_0_idx].ShipTypes[sto].Shields)
|
||||||
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Cargo, g.Race[Race_0_idx].ShipTypes[sto].Cargo)
|
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Cargo, g.Race[Race_0_idx].ShipTypes[sto].Cargo)
|
||||||
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Armament, g.Race[Race_0_idx].ShipTypes[sto].Armament)
|
assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Armament, g.Race[Race_0_idx].ShipTypes[sto].Armament)
|
||||||
assert.Equal(t, g.ShipGroups[2].State, g.ShipGroups[3].State)
|
assert.Equal(t, g.ShipGroups[2].State(), g.ShipGroups[3].State())
|
||||||
assert.Equal(t, g.ShipGroups[2].CargoType, g.ShipGroups[3].CargoType)
|
assert.Equal(t, g.ShipGroups[2].CargoType, g.ShipGroups[3].CargoType)
|
||||||
assert.Equal(t, g.ShipGroups[2].Load, g.ShipGroups[3].Load)
|
assert.Equal(t, g.ShipGroups[2].Load, g.ShipGroups[3].Load)
|
||||||
assert.Equal(t, g.ShipGroups[2].Drive, g.ShipGroups[3].Drive)
|
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechDrive), g.ShipGroups[3].TechLevel(game.TechDrive))
|
||||||
assert.Equal(t, g.ShipGroups[2].Weapons, g.ShipGroups[3].Weapons)
|
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechWeapons), g.ShipGroups[3].TechLevel(game.TechWeapons))
|
||||||
assert.Equal(t, g.ShipGroups[2].Shields, g.ShipGroups[3].Shields)
|
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechShields), g.ShipGroups[3].TechLevel(game.TechShields))
|
||||||
assert.Equal(t, g.ShipGroups[2].Cargo, g.ShipGroups[3].Cargo)
|
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechCargo), g.ShipGroups[3].TechLevel(game.TechCargo))
|
||||||
assert.Equal(t, g.ShipGroups[2].Destination, g.ShipGroups[3].Destination)
|
assert.Equal(t, g.ShipGroups[2].Destination, g.ShipGroups[3].Destination)
|
||||||
assert.Equal(t, g.ShipGroups[2].Origin, g.ShipGroups[3].Origin)
|
assert.Equal(t, g.ShipGroups[2].StateInSpace, g.ShipGroups[3].StateInSpace)
|
||||||
assert.Equal(t, g.ShipGroups[2].Range, g.ShipGroups[3].Range)
|
assert.Equal(t, g.ShipGroups[2].StateUpgrade, g.ShipGroups[3].StateUpgrade)
|
||||||
assert.Equal(t, g.ShipGroups[3].OwnerID, g.Race[Race_1_idx].ID)
|
assert.Equal(t, g.ShipGroups[3].OwnerID, g.Race[Race_1_idx].ID)
|
||||||
assert.Equal(t, g.ShipGroups[3].TypeID, g.Race[Race_1_idx].ShipTypes[sti].ID)
|
assert.Equal(t, g.ShipGroups[3].TypeID, g.Race[Race_1_idx].ShipTypes[sti].ID)
|
||||||
assert.Equal(t, g.ShipGroups[3].Number, uint(11))
|
assert.Equal(t, g.ShipGroups[3].Number, uint(11))
|
||||||
@@ -483,7 +472,7 @@ func TestGiveawayGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadCargo(t *testing.T) {
|
func TestLoadCargo(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
|
|
||||||
// 1: idx = 0 / Ready to load
|
// 1: idx = 0 / Ready to load
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||||
@@ -493,10 +482,10 @@ func TestLoadCargo(t *testing.T) {
|
|||||||
|
|
||||||
// 3: idx = 2 / In_Space
|
// 3: idx = 2 / In_Space
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||||
rng := 31.337
|
Origin: 2,
|
||||||
g.ShipGroups[2].Range = &rng
|
Range: 31.337,
|
||||||
g.ShipGroups[2].State = "In_Space"
|
}
|
||||||
|
|
||||||
// 4: idx = 3 / loaded with COL
|
// 4: idx = 3 / loaded with COL
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||||
@@ -603,7 +592,7 @@ func TestLoadCargo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnloadCargo(t *testing.T) {
|
func TestUnloadCargo(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
|
|
||||||
// 1: idx = 0 / empty
|
// 1: idx = 0 / empty
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||||
@@ -613,10 +602,10 @@ func TestUnloadCargo(t *testing.T) {
|
|||||||
|
|
||||||
// 3: idx = 2 / In_Space
|
// 3: idx = 2 / In_Space
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||||
rng := 31.337
|
Origin: 2,
|
||||||
g.ShipGroups[2].Range = &rng
|
Range: 31.337,
|
||||||
g.ShipGroups[2].State = "In_Space"
|
}
|
||||||
|
|
||||||
// 4: idx = 3 / loaded with COL
|
// 4: idx = 3 / loaded with COL
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||||
@@ -699,17 +688,17 @@ func TestUnloadCargo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDisassembleGroup(t *testing.T) {
|
func TestDisassembleGroup(t *testing.T) {
|
||||||
g := copyGame()
|
g := newGame()
|
||||||
|
|
||||||
// 1: idx = 0 / empty
|
// 1: idx = 0 / empty
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||||
|
|
||||||
// 2: idx = 1 / In_Space
|
// 2: idx = 1 / In_Space
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||||
g.ShipGroups[1].Origin = &R0_Planet_2_num
|
g.ShipGroups[1].StateInSpace = &game.InSpace{
|
||||||
rng := 31.337
|
Origin: 2,
|
||||||
g.ShipGroups[1].Range = &rng
|
Range: 31.337,
|
||||||
g.ShipGroups[1].State = "In_Space"
|
}
|
||||||
|
|
||||||
// 3: idx = 2 / loaded with COL
|
// 3: idx = 2 / loaded with COL
|
||||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpgradeCalc struct {
|
||||||
|
Cost map[Tech]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc UpgradeCalc) UpgradeCost(ships uint) float64 {
|
||||||
|
var sum float64
|
||||||
|
for v := range maps.Values(uc.Cost) {
|
||||||
|
sum += v
|
||||||
|
}
|
||||||
|
return sum * float64(ships)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc UpgradeCalc) UpgradeMaxShips(resources float64) uint {
|
||||||
|
return uint(math.Floor(resources / uc.UpgradeCost(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) float64 {
|
||||||
|
if blockMass == 0 || targetBlockTech <= currentBlockTech {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass
|
||||||
|
}
|
||||||
|
|
||||||
|
func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo float64) UpgradeCalc {
|
||||||
|
uc := &UpgradeCalc{Cost: make(map[Tech]float64)}
|
||||||
|
if drive > 0 {
|
||||||
|
uc.Cost[TechDrive] = BlockUpgradeCost(st.DriveBlockMass(), sg.TechLevel(TechDrive), drive)
|
||||||
|
}
|
||||||
|
if weapons > 0 {
|
||||||
|
uc.Cost[TechWeapons] = BlockUpgradeCost(st.WeaponsBlockMass(), sg.TechLevel(TechWeapons), weapons)
|
||||||
|
}
|
||||||
|
if shields > 0 {
|
||||||
|
uc.Cost[TechShields] = BlockUpgradeCost(st.ShieldsBlockMass(), sg.TechLevel(TechShields), shields)
|
||||||
|
}
|
||||||
|
if cargo > 0 {
|
||||||
|
uc.Cost[TechCargo] = BlockUpgradeCost(st.CargoBlockMass(), sg.TechLevel(TechCargo), cargo)
|
||||||
|
}
|
||||||
|
return *uc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
|
||||||
|
ri, err := g.raceIndex(raceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return g.upgradeGroupInternal(ri, groupIndex, techInput, limitShips, limitLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) upgradeGroupInternal(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
|
||||||
|
sgi := -1
|
||||||
|
for i, sg := range g.listIndexShipGroups(ri) {
|
||||||
|
if sgi < 0 && sg.Index == groupIndex {
|
||||||
|
sgi = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sgi < 0 {
|
||||||
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sti int
|
||||||
|
if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
|
||||||
|
// hard to test, need manual game data invalidation
|
||||||
|
return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
|
||||||
|
}
|
||||||
|
st := g.Race[ri].ShipTypes[sti]
|
||||||
|
|
||||||
|
pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
||||||
|
if pl < 0 {
|
||||||
|
return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
|
||||||
|
}
|
||||||
|
if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
|
||||||
|
return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", g.Map.Planet[pl].Number, groupIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.ShipGroups[sgi].State() != StateInOrbit && g.ShipGroups[sgi].State() != StateUpgrade {
|
||||||
|
return e.NewShipsBusyError()
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeValidTech := map[string]Tech{
|
||||||
|
TechDrive.String(): TechDrive,
|
||||||
|
TechWeapons.String(): TechWeapons,
|
||||||
|
TechShields.String(): TechShields,
|
||||||
|
TechCargo.String(): TechCargo,
|
||||||
|
TechAll.String(): TechAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
techRequest, ok := upgradeValidTech[techInput]
|
||||||
|
if !ok {
|
||||||
|
return e.NewTechUnknownError(techInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockMasses map[Tech]float64 = map[Tech]float64{
|
||||||
|
TechDrive: st.DriveBlockMass(),
|
||||||
|
TechWeapons: st.WeaponsBlockMass(),
|
||||||
|
TechShields: st.ShieldsBlockMass(),
|
||||||
|
TechCargo: st.CargoBlockMass(),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case techRequest != TechAll && blockMasses[techRequest] == 0:
|
||||||
|
return e.NewUpgradeShipTechNotUsedError()
|
||||||
|
case techRequest == TechAll && limitLevel != 0:
|
||||||
|
return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetLevel := make(map[Tech]float64)
|
||||||
|
var sumLevels float64
|
||||||
|
for _, tech := range []Tech{TechDrive, TechWeapons, TechShields, TechCargo} {
|
||||||
|
if techRequest == TechAll || tech == techRequest {
|
||||||
|
if g.Race[ri].TechLevel(tech) < limitLevel {
|
||||||
|
return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), g.Race[ri].TechLevel(tech), limitLevel)
|
||||||
|
}
|
||||||
|
targetLevel[tech] = FutureUpgradeLevel(g.Race[ri].TechLevel(tech), g.ShipGroups[sgi].TechLevel(tech), limitLevel)
|
||||||
|
} else {
|
||||||
|
targetLevel[tech] = CurrentUpgradingLevel(g.ShipGroups[sgi], tech)
|
||||||
|
}
|
||||||
|
sumLevels += targetLevel[tech]
|
||||||
|
}
|
||||||
|
|
||||||
|
productionCapacity := PlanetProductionCapacity(g, g.Map.Planet[pl].Number)
|
||||||
|
if g.ShipGroups[sgi].State() == StateUpgrade {
|
||||||
|
// to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state
|
||||||
|
productionCapacity -= g.ShipGroups[sgi].StateUpgrade.Cost()
|
||||||
|
}
|
||||||
|
uc := GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo])
|
||||||
|
costForShip := uc.UpgradeCost(1)
|
||||||
|
if costForShip == 0 {
|
||||||
|
return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
shipsToUpgrade := g.ShipGroups[sgi].Number
|
||||||
|
// НЕ БОЛЕЕ УКАЗАННОГО
|
||||||
|
if limitShips > 0 && shipsToUpgrade > limitShips {
|
||||||
|
shipsToUpgrade = limitShips
|
||||||
|
}
|
||||||
|
|
||||||
|
maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity)
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. считаем стоимость модернизации одного корабля
|
||||||
|
2. считаем сколько кораблей можно модернизировать
|
||||||
|
3. если не хватает даже на 1 корабль, ограничиваемся одним кораблём и пересчитываем коэффициент пропорционально массе блоков
|
||||||
|
4. иначе, считаем истинное количество кораблей с учётом ограничения maxShips
|
||||||
|
*/
|
||||||
|
blockMassSum := st.EmptyMass()
|
||||||
|
|
||||||
|
coef := productionCapacity / costForShip
|
||||||
|
if maxUpgradableShips == 0 {
|
||||||
|
if limitLevel > 0 {
|
||||||
|
return e.NewUpgradeInsufficientResourcesError("ship cost=%.03f L=%.03f", costForShip, productionCapacity)
|
||||||
|
}
|
||||||
|
sumLevels = sumLevels * coef
|
||||||
|
for tech := range targetLevel {
|
||||||
|
if blockMasses[tech] > 0 {
|
||||||
|
proportional := sumLevels * (blockMasses[tech] / blockMassSum)
|
||||||
|
targetLevel[tech] = proportional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxUpgradableShips = 1
|
||||||
|
} else if maxUpgradableShips > shipsToUpgrade {
|
||||||
|
maxUpgradableShips = shipsToUpgrade
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
uc = GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo])
|
||||||
|
costForGroup := uc.UpgradeCost(maxUpgradableShips)
|
||||||
|
if costForGroup > productionCapacity {
|
||||||
|
e.NewGameStateError("cost recalculation: coef=%f cost(%d)=%f L=%f", coef, maxUpgradableShips, costForGroup, productionCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// break group if needed
|
||||||
|
if maxUpgradableShips < g.ShipGroups[sgi].Number {
|
||||||
|
if g.ShipGroups[sgi].State() == StateUpgrade {
|
||||||
|
return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", g.ShipGroups[sgi].Number, maxUpgradableShips)
|
||||||
|
}
|
||||||
|
nsgi, err := g.breakGroupSafe(ri, groupIndex, maxUpgradableShips)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sgi = nsgi
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, fill group upgrade prefs
|
||||||
|
for tech := range targetLevel {
|
||||||
|
if targetLevel[tech] > 0 {
|
||||||
|
g.ShipGroups[sgi] = UpgradeGroupPreference(g.ShipGroups[sgi], st, tech, targetLevel[tech])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 {
|
||||||
|
if sg.StateUpgrade == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref UpgradePreference) bool { return pref.Tech == tech })
|
||||||
|
if ti >= 0 {
|
||||||
|
return sg.StateUpgrade.UpgradeTech[ti].Level
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func FutureUpgradeLevel(raceLevel, groupLevel, limit float64) float64 {
|
||||||
|
target := limit
|
||||||
|
if target == 0 || target > raceLevel {
|
||||||
|
target = raceLevel
|
||||||
|
}
|
||||||
|
if groupLevel == target {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpgradeGroupPreference(sg ShipGroup, st ShipType, tech Tech, v float64) ShipGroup {
|
||||||
|
if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech) >= v {
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
var su InUpgrade
|
||||||
|
if sg.StateUpgrade != nil {
|
||||||
|
su = *sg.StateUpgrade
|
||||||
|
} else {
|
||||||
|
su = InUpgrade{UpgradeTech: []UpgradePreference{}}
|
||||||
|
}
|
||||||
|
ti := slices.IndexFunc(su.UpgradeTech, func(pref UpgradePreference) bool { return pref.Tech == tech })
|
||||||
|
if ti < 0 {
|
||||||
|
su.UpgradeTech = append(su.UpgradeTech, UpgradePreference{Tech: tech})
|
||||||
|
ti = len(su.UpgradeTech) - 1
|
||||||
|
}
|
||||||
|
su.UpgradeTech[ti].Level = v
|
||||||
|
su.UpgradeTech[ti].Cost = BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech), v) * float64(sg.Number)
|
||||||
|
|
||||||
|
sg.StateUpgrade = &su
|
||||||
|
return sg
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package game_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlockUpgradeCost(t *testing.T) {
|
||||||
|
assert.Equal(t, 00.0, game.BlockUpgradeCost(1, 1.0, 1.0))
|
||||||
|
assert.Equal(t, 25.0, game.BlockUpgradeCost(5, 1.0, 2.0))
|
||||||
|
assert.Equal(t, 50.0, game.BlockUpgradeCost(10, 1.0, 2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupUpgradeCost(t *testing.T) {
|
||||||
|
sg := game.ShipGroup{
|
||||||
|
Tech: map[game.Tech]float64{
|
||||||
|
game.TechDrive: 1.0,
|
||||||
|
game.TechWeapons: 1.0,
|
||||||
|
game.TechShields: 1.0,
|
||||||
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
}
|
||||||
|
assert.Equal(t, 225.0, game.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0).UpgradeCost(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpgradeMaxShips(t *testing.T) {
|
||||||
|
sg := game.ShipGroup{
|
||||||
|
Tech: map[game.Tech]float64{
|
||||||
|
game.TechDrive: 1.0,
|
||||||
|
game.TechWeapons: 1.0,
|
||||||
|
game.TechShields: 1.0,
|
||||||
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
|
Number: 10,
|
||||||
|
}
|
||||||
|
uc := game.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0)
|
||||||
|
assert.Equal(t, uint(4), uc.UpgradeMaxShips(1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCurrentUpgradingLevel(t *testing.T) {
|
||||||
|
sg := &game.ShipGroup{
|
||||||
|
StateUpgrade: nil,
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechDrive))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechCargo))
|
||||||
|
|
||||||
|
sg.StateUpgrade = &game.InUpgrade{
|
||||||
|
UpgradeTech: []game.UpgradePreference{
|
||||||
|
{Tech: game.TechDrive, Level: 1.5, Cost: 100.1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, 1.5, game.CurrentUpgradingLevel(*sg, game.TechDrive))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechCargo))
|
||||||
|
|
||||||
|
sg.StateUpgrade.UpgradeTech = append(sg.StateUpgrade.UpgradeTech, game.UpgradePreference{Tech: game.TechCargo, Level: 2.2, Cost: 200.2})
|
||||||
|
assert.Equal(t, 1.5, game.CurrentUpgradingLevel(*sg, game.TechDrive))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons))
|
||||||
|
assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields))
|
||||||
|
assert.Equal(t, 2.2, game.CurrentUpgradingLevel(*sg, game.TechCargo))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFutureUpgradeLevel(t *testing.T) {
|
||||||
|
assert.Equal(t, 0.0, game.FutureUpgradeLevel(2.0, 2.0, 2.0))
|
||||||
|
assert.Equal(t, 0.0, game.FutureUpgradeLevel(2.0, 2.0, 3.0))
|
||||||
|
assert.Equal(t, 1.5, game.FutureUpgradeLevel(1.5, 2.0, 3.0))
|
||||||
|
assert.Equal(t, 2.0, game.FutureUpgradeLevel(2.5, 1.0, 2.0))
|
||||||
|
assert.Equal(t, 2.5, game.FutureUpgradeLevel(2.5, 1.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpgradeGroupPreference(t *testing.T) {
|
||||||
|
sg := game.ShipGroup{
|
||||||
|
Number: 4,
|
||||||
|
Tech: game.TechSet{
|
||||||
|
game.TechDrive: 1.0,
|
||||||
|
game.TechWeapons: 1.0,
|
||||||
|
game.TechShields: 1.0,
|
||||||
|
game.TechCargo: 1.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Nil(t, sg.StateUpgrade)
|
||||||
|
sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechDrive, 0)
|
||||||
|
assert.Nil(t, sg.StateUpgrade)
|
||||||
|
|
||||||
|
sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechDrive, 2.0)
|
||||||
|
assert.NotNil(t, sg.StateUpgrade)
|
||||||
|
assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechDrive))
|
||||||
|
assert.Equal(t, 300., sg.StateUpgrade.Cost())
|
||||||
|
|
||||||
|
sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechWeapons, 2.0)
|
||||||
|
assert.NotNil(t, sg.StateUpgrade)
|
||||||
|
assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechWeapons))
|
||||||
|
assert.Equal(t, 600., sg.StateUpgrade.Cost())
|
||||||
|
|
||||||
|
sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechShields, 2.0)
|
||||||
|
assert.NotNil(t, sg.StateUpgrade)
|
||||||
|
assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechShields))
|
||||||
|
assert.Equal(t, 900., sg.StateUpgrade.Cost())
|
||||||
|
|
||||||
|
sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechCargo, 2.0)
|
||||||
|
assert.NotNil(t, sg.StateUpgrade)
|
||||||
|
assert.Equal(t, 0., sg.StateUpgrade.TechCost(game.TechCargo))
|
||||||
|
assert.Equal(t, 900., sg.StateUpgrade.Cost())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpgradeGroup(t *testing.T) {
|
||||||
|
g := newGame()
|
||||||
|
// group #1 - in_orbit, free to upgrade
|
||||||
|
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10))
|
||||||
|
// group #2 - in_space
|
||||||
|
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||||
|
g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||||
|
// group #3 - in_orbit, foreign planet
|
||||||
|
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||||
|
g.ShipGroups[2].Destination = R1_Planet_1_num
|
||||||
|
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup("UnknownRace", 1, "DRIVE", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 555, "DRIVE", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 2, "DRIVE", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrShipsBusy))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 3, "DRIVE", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "GUN", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrInputTechUnknown))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "CARGO", 0, 0),
|
||||||
|
e.GenericErrorText(e.ErrInputUpgradeShipTechNotUsed))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "ALL", 0, 2.0),
|
||||||
|
e.GenericErrorText(e.ErrInputUpgradeParameterNotAllowed))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 2.0),
|
||||||
|
e.GenericErrorText(e.ErrInputUpgradeTechLevelInsufficient))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 1.1),
|
||||||
|
e.GenericErrorText(e.ErrInputUpgradeShipsAlreadyUpToDate))
|
||||||
|
|
||||||
|
g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 10.0)
|
||||||
|
assert.Equal(t, 10.0, g.Race[Race_0_idx].TechLevel(game.TechDrive))
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 10.0),
|
||||||
|
e.GenericErrorText(e.ErrUpgradeInsufficientResources))
|
||||||
|
|
||||||
|
assert.NoError(t, g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 2, 1.2))
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
|
||||||
|
assert.Equal(t, uint(8), g.ShipGroups[0].Number)
|
||||||
|
assert.Equal(t, uint(2), g.ShipGroups[3].Number)
|
||||||
|
assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State())
|
||||||
|
assert.Equal(t, game.StateUpgrade, g.ShipGroups[3].State())
|
||||||
|
|
||||||
|
assert.ErrorContains(t,
|
||||||
|
g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3),
|
||||||
|
e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed))
|
||||||
|
}
|
||||||
@@ -25,10 +25,10 @@ type UninhabitedPlanet struct {
|
|||||||
|
|
||||||
type PlanetReport struct {
|
type PlanetReport struct {
|
||||||
UninhabitedPlanet
|
UninhabitedPlanet
|
||||||
Industry float64 `json:"industry"` // I - Промышленность
|
Industry float64 `json:"industry"` // I - Промышленность
|
||||||
Population float64 `json:"population"` // P - Население
|
Population float64 `json:"population"` // P - Население
|
||||||
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
||||||
Production ProductionType `json:"production"` // TODO: internal/report format
|
Production Production `json:"production"` // TODO: internal/report format
|
||||||
// Параметр "L" - Свободный производственный потенциал
|
// Параметр "L" - Свободный производственный потенциал
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +42,30 @@ type PlanetReportForeign struct {
|
|||||||
PlanetReport
|
PlanetReport
|
||||||
}
|
}
|
||||||
|
|
||||||
// Свободный производственный потенциал (L)
|
// TODO: delete func
|
||||||
// промышленность * 0.75 + население * 0.25
|
|
||||||
// TODO: за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
|
|
||||||
func (p Planet) ProductionCapacity() float64 {
|
func (p Planet) ProductionCapacity() float64 {
|
||||||
return p.Industry*0.75 + p.Population*0.25
|
return p.Industry*0.75 + p.Population*0.25
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Свободный производственный потенциал (L)
|
||||||
|
// промышленность * 0.75 + население * 0.25
|
||||||
|
// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
|
||||||
|
func PlanetProductionCapacity(g *Game, planetNumber uint) float64 {
|
||||||
|
p, err := g.PlanetByNumber(planetNumber)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var busyResources float64
|
||||||
|
for sg := range g.ShipsInUpgrade(p.Number) {
|
||||||
|
busyResources += sg.StateUpgrade.Cost()
|
||||||
|
}
|
||||||
|
return PlanetProduction(p.Industry, p.Population) - busyResources
|
||||||
|
}
|
||||||
|
|
||||||
|
func PlanetProduction(industry, population float64) float64 {
|
||||||
|
return industry*0.75 + population*0.25
|
||||||
|
}
|
||||||
|
|
||||||
// Производство промышленности
|
// Производство промышленности
|
||||||
// TODO: test on real values
|
// TODO: test on real values
|
||||||
func (p *Planet) IncreaseIndustry() {
|
func (p *Planet) IncreaseIndustry() {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package game_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlanetProduction(t *testing.T) {
|
||||||
|
assert.Equal(t, 1000., game.PlanetProduction(1000., 1000.))
|
||||||
|
assert.Equal(t, 750., game.PlanetProduction(1000., 0.))
|
||||||
|
assert.Equal(t, 250., game.PlanetProduction(0., 1000.))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanetProductionCapacity(t *testing.T) {
|
||||||
|
g := newGame()
|
||||||
|
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||||
|
assert.Equal(t, 100., game.PlanetProductionCapacity(g, R0_Planet_0_num))
|
||||||
|
g.ShipGroups[0] = game.UpgradeGroupPreference(g.ShipGroups[0], Cruiser, game.TechDrive, 1.6)
|
||||||
|
assert.Equal(t, 53.125, game.PlanetProductionCapacity(g, R0_Planet_0_num))
|
||||||
|
}
|
||||||
@@ -7,34 +7,34 @@ import (
|
|||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlanetProduction string
|
type ProductionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProductionNone PlanetProduction = "-"
|
ProductionNone ProductionType = "-"
|
||||||
ProductionMaterial PlanetProduction = "MAT" // Сырьё
|
ProductionMaterial ProductionType = "MAT" // Сырьё
|
||||||
ProductionCapital PlanetProduction = "CAP" // Промышленность
|
ProductionCapital ProductionType = "CAP" // Промышленность
|
||||||
|
|
||||||
ResearchDrive PlanetProduction = "DRIVE"
|
ResearchDrive ProductionType = "DRIVE"
|
||||||
ResearchWeapons PlanetProduction = "WEAPONS"
|
ResearchWeapons ProductionType = "WEAPONS"
|
||||||
ResearchShields PlanetProduction = "SHIELDS"
|
ResearchShields ProductionType = "SHIELDS"
|
||||||
ResearchCargo PlanetProduction = "CARGO"
|
ResearchCargo ProductionType = "CARGO"
|
||||||
|
|
||||||
ResearchScience PlanetProduction = "SCIENCE"
|
ResearchScience ProductionType = "SCIENCE"
|
||||||
ProductionShip PlanetProduction = "SHIP"
|
ProductionShip ProductionType = "SHIP"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductionType struct {
|
type Production struct {
|
||||||
Production PlanetProduction `json:"type"`
|
Type ProductionType `json:"type"`
|
||||||
SubjectID *uuid.UUID `json:"subjectId"`
|
SubjectID *uuid.UUID `json:"subjectId"`
|
||||||
Progress *float64 `json:"progress"`
|
Progress *float64 `json:"progress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PlanetProduction) AsType(subject uuid.UUID) ProductionType {
|
func (p ProductionType) AsType(subject uuid.UUID) Production {
|
||||||
switch p {
|
switch p {
|
||||||
case ResearchScience, ProductionShip:
|
case ResearchScience, ProductionShip:
|
||||||
return ProductionType{Production: p, SubjectID: &subject}
|
return Production{Type: p, SubjectID: &subject}
|
||||||
default:
|
default:
|
||||||
return ProductionType{Production: p, SubjectID: nil}
|
return Production{Type: p, SubjectID: nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subj
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var prod PlanetProduction
|
var prod ProductionType
|
||||||
switch PlanetProduction(prodType) {
|
switch ProductionType(prodType) {
|
||||||
case ProductionMaterial:
|
case ProductionMaterial:
|
||||||
prod = ProductionMaterial
|
prod = ProductionMaterial
|
||||||
case ProductionCapital:
|
case ProductionCapital:
|
||||||
@@ -67,7 +67,7 @@ func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subj
|
|||||||
return g.planetProductionInternal(ri, planetNumber, prod, subject)
|
return g.planetProductionInternal(ri, planetNumber, prod, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction, subj string) error {
|
func (g Game) planetProductionInternal(ri int, number int, prod ProductionType, subj string) error {
|
||||||
if number < 0 {
|
if number < 0 {
|
||||||
return e.NewPlanetNumberError(number)
|
return e.NewPlanetNumberError(number)
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction
|
|||||||
if i < 0 {
|
if i < 0 {
|
||||||
return e.NewEntityNotExistsError("ship type %w", subj)
|
return e.NewEntityNotExistsError("ship type %w", subj)
|
||||||
}
|
}
|
||||||
if g.Map.Planet[i].Production.Production == ProductionShip &&
|
if g.Map.Planet[i].Production.Type == ProductionShip &&
|
||||||
g.Map.Planet[i].Production.SubjectID != nil &&
|
g.Map.Planet[i].Production.SubjectID != nil &&
|
||||||
*g.Map.Planet[i].Production.SubjectID == g.Race[ri].ShipTypes[i].ID {
|
*g.Map.Planet[i].Production.SubjectID == g.Race[ri].ShipTypes[i].ID {
|
||||||
// Planet already produces this ship type, keeping progress intact
|
// Planet already produces this ship type, keeping progress intact
|
||||||
@@ -105,7 +105,7 @@ func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction
|
|||||||
var progress float64 = 0.
|
var progress float64 = 0.
|
||||||
g.Map.Planet[i].Production.Progress = &progress
|
g.Map.Planet[i].Production.Progress = &progress
|
||||||
}
|
}
|
||||||
if g.Map.Planet[i].Production.Production == ProductionShip {
|
if g.Map.Planet[i].Production.Type == ProductionShip {
|
||||||
if g.Map.Planet[i].Production.SubjectID == nil {
|
if g.Map.Planet[i].Production.SubjectID == nil {
|
||||||
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", g.Map.Planet[i].Number)
|
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", g.Map.Planet[i].Number)
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction
|
|||||||
extra := mat * progress
|
extra := mat * progress
|
||||||
g.Map.Planet[i].Material += extra
|
g.Map.Planet[i].Material += extra
|
||||||
}
|
}
|
||||||
g.Map.Planet[i].Production.Production = prod
|
g.Map.Planet[i].Production.Type = prod
|
||||||
g.Map.Planet[i].Production.SubjectID = subjectID
|
g.Map.Planet[i].Production.SubjectID = subjectID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type Race struct {
|
type Race struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
@@ -10,10 +12,7 @@ type Race struct {
|
|||||||
Vote uuid.UUID `json:"vote"`
|
Vote uuid.UUID `json:"vote"`
|
||||||
Relations []RaceRelation `json:"relations"`
|
Relations []RaceRelation `json:"relations"`
|
||||||
|
|
||||||
Drive float64 `json:"drive"`
|
Tech TechSet `json:"tech"`
|
||||||
Weapons float64 `json:"weapons"`
|
|
||||||
Shields float64 `json:"shields"`
|
|
||||||
Cargo float64 `json:"cargo"`
|
|
||||||
|
|
||||||
Sciences []Science `json:"science,omitempty"`
|
Sciences []Science `json:"science,omitempty"`
|
||||||
|
|
||||||
@@ -32,10 +31,19 @@ type RaceRelation struct {
|
|||||||
Relation Relation `json:"relation"`
|
Relation Relation `json:"relation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r Race) TechLevel(t Tech) float64 {
|
||||||
|
return r.Tech.Value(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refactor to separate method with *Race as parameter
|
||||||
|
func (r *Race) SetTechLevel(t Tech, v float64) {
|
||||||
|
r.Tech = r.Tech.Set(t, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (r Race) FlightDistance() float64 {
|
func (r Race) FlightDistance() float64 {
|
||||||
return r.Drive * 40
|
return r.TechLevel(TechDrive) * 40
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Race) VisibilityDistance() float64 {
|
func (r Race) VisibilityDistance() float64 {
|
||||||
return r.Drive * 30
|
return r.TechLevel(TechDrive) * 30
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (g Game) deleteScienceInternal(ri int, name string) error {
|
|||||||
return e.NewEntityNotExistsError("science %w", name)
|
return e.NewEntityNotExistsError("science %w", name)
|
||||||
}
|
}
|
||||||
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
||||||
return p.Production.Production == ResearchScience &&
|
return p.Production.Type == ResearchScience &&
|
||||||
p.Production.SubjectID != nil &&
|
p.Production.SubjectID != nil &&
|
||||||
*p.Production.SubjectID == g.Race[ri].Sciences[sc].ID
|
*p.Production.SubjectID == g.Race[ri].Sciences[sc].ID
|
||||||
}); pl >= 0 {
|
}); pl >= 0 {
|
||||||
|
|||||||
@@ -35,18 +35,45 @@ func (st ShipType) Equal(o ShipType) bool {
|
|||||||
st.Cargo == o.Cargo
|
st.Cargo == o.Cargo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st ShipType) EmptyMass() float64 {
|
func (st ShipType) BlockMass(t Tech) float64 {
|
||||||
shipMass := st.Drive + st.Shields + st.Cargo + st.WeaponsMass()
|
switch t {
|
||||||
return shipMass
|
case TechDrive:
|
||||||
|
return st.DriveBlockMass()
|
||||||
|
case TechWeapons:
|
||||||
|
return st.WeaponsBlockMass()
|
||||||
|
case TechShields:
|
||||||
|
return st.ShieldsBlockMass()
|
||||||
|
case TechCargo:
|
||||||
|
return st.CargoBlockMass()
|
||||||
|
default:
|
||||||
|
panic("BlockMass: unexpectec tech: " + t.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st ShipType) WeaponsMass() float64 {
|
func (st ShipType) DriveBlockMass() float64 {
|
||||||
|
return st.Drive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st ShipType) WeaponsBlockMass() float64 {
|
||||||
if st.Armament == 0 || st.Weapons == 0 {
|
if st.Armament == 0 || st.Weapons == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return float64(st.Armament+1) * (st.Weapons / 2)
|
return float64(st.Armament+1) * (st.Weapons / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st ShipType) ShieldsBlockMass() float64 {
|
||||||
|
return st.Shields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st ShipType) CargoBlockMass() float64 {
|
||||||
|
return st.Cargo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st ShipType) EmptyMass() float64 {
|
||||||
|
shipMass := st.DriveBlockMass() + st.ShieldsBlockMass() + st.CargoBlockMass() + st.WeaponsBlockMass()
|
||||||
|
return shipMass
|
||||||
|
}
|
||||||
|
|
||||||
// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType]
|
// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType]
|
||||||
func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
||||||
mat = st.EmptyMass()
|
mat = st.EmptyMass()
|
||||||
@@ -89,7 +116,7 @@ func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
|||||||
return e.NewEntityNotExistsError("ship type %w", name)
|
return e.NewEntityNotExistsError("ship type %w", name)
|
||||||
}
|
}
|
||||||
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
||||||
return p.Production.Production == ProductionShip &&
|
return p.Production.Type == ProductionShip &&
|
||||||
p.Production.SubjectID != nil &&
|
p.Production.SubjectID != nil &&
|
||||||
g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
|
g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
|
||||||
}); pl >= 0 {
|
}); pl >= 0 {
|
||||||
@@ -160,7 +187,7 @@ func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
|
|||||||
// switch planet productions to the new type
|
// switch planet productions to the new type
|
||||||
for pl := range g.Map.Planet {
|
for pl := range g.Map.Planet {
|
||||||
if g.Map.Planet[pl].Owner == g.Race[ri].ID &&
|
if g.Map.Planet[pl].Owner == g.Race[ri].ID &&
|
||||||
g.Map.Planet[pl].Production.Production == ProductionShip &&
|
g.Map.Planet[pl].Production.Type == ProductionShip &&
|
||||||
g.Map.Planet[pl].Production.SubjectID != nil &&
|
g.Map.Planet[pl].Production.SubjectID != nil &&
|
||||||
*g.Map.Planet[pl].Production.SubjectID == g.Race[ri].ShipTypes[st].ID {
|
*g.Map.Planet[pl].Production.SubjectID == g.Race[ri].ShipTypes[st].ID {
|
||||||
g.Map.Planet[pl].Production.SubjectID = &g.Race[ri].ShipTypes[tt].ID
|
g.Map.Planet[pl].Production.SubjectID = &g.Race[ri].ShipTypes[tt].ID
|
||||||
|
|||||||
Reference in New Issue
Block a user