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}
|
||||
g.Race[i] = game.Race{
|
||||
ID: raceID,
|
||||
Name: races[i],
|
||||
Vote: raceID,
|
||||
Drive: 1,
|
||||
Weapons: 1,
|
||||
Shields: 1,
|
||||
Cargo: 1,
|
||||
ID: raceID,
|
||||
Name: races[i],
|
||||
Vote: raceID,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1,
|
||||
game.TechWeapons: 1,
|
||||
game.TechShields: 1,
|
||||
game.TechCargo: 1,
|
||||
},
|
||||
}
|
||||
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
||||
planetCount,
|
||||
@@ -125,7 +127,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
||||
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{
|
||||
Owner: owner,
|
||||
PlanetReport: game.PlanetReport{
|
||||
|
||||
@@ -20,6 +20,8 @@ const (
|
||||
ErrShipsBusy = 5007
|
||||
ErrShipsNotOnSamePlanet = 5008
|
||||
ErrGiveawayGroupShipsTypeNotEqual = 5009
|
||||
ErrUpgradeGroupNumberNotEnough = 5010
|
||||
ErrUpgradeInsufficientResources = 5011
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,6 +51,13 @@ const (
|
||||
ErrInputCargoUnloadEmpty
|
||||
ErrInputCargoUnoadNotEnough
|
||||
ErrInputBreakGroupIllegalNumber
|
||||
ErrInputTechUnknown
|
||||
ErrInputTechInvalidMixing
|
||||
ErrInputUpgradeShipTechNotUsed
|
||||
ErrInputUpgradeParameterNotAllowed
|
||||
ErrInputUpgradeShipsAlreadyUpToDate
|
||||
ErrInputUpgradeGroupBreakNotAllowed
|
||||
ErrInputUpgradeTechLevelInsufficient
|
||||
)
|
||||
|
||||
func GenericErrorText(code int) string {
|
||||
@@ -131,6 +140,24 @@ func GenericErrorText(code int) string {
|
||||
return "Ships not on the same planet"
|
||||
case ErrGiveawayGroupShipsTypeNotEqual:
|
||||
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:
|
||||
return fmt.Sprintf("Undescribed error with code %d", code)
|
||||
}
|
||||
|
||||
@@ -131,3 +131,39 @@ func NewShipsNotOnSamePlanetError(arg ...any) error {
|
||||
func NewGiveawayGroupShipsTypeNotEqualError(arg ...any) error {
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJoinShipGroupToFleet(t *testing.T) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
var groupIndex uint = 1
|
||||
|
||||
assert.ErrorContains(t,
|
||||
@@ -85,11 +86,14 @@ func TestJoinShipGroupToFleet(t *testing.T) {
|
||||
// group not In_Orbit
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7))
|
||||
gi = 3
|
||||
g.ShipGroups[gi].State = "In_Space"
|
||||
g.ShipGroups[gi].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 1,
|
||||
}
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0),
|
||||
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
|
||||
g.Fleets[0].Destination = R0_Planet_2_num
|
||||
@@ -100,7 +104,7 @@ func TestJoinShipGroupToFleet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJoinFleets(t *testing.T) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
// creating ShipGroup at Planet_0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
||||
// creating ShipGroup at Planet_2
|
||||
|
||||
@@ -2,6 +2,9 @@ package game
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -9,6 +12,22 @@ import (
|
||||
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 {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Age uint `json:"turn"` // Game's turn number
|
||||
@@ -29,6 +48,26 @@ func (g Game) Votes(raceID uuid.UUID) float64 {
|
||||
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) {
|
||||
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name })
|
||||
if i < 0 {
|
||||
|
||||
@@ -10,37 +10,26 @@ import (
|
||||
|
||||
var (
|
||||
Race_0 = game.Race{
|
||||
ID: uuid.New(),
|
||||
Name: "Race_0",
|
||||
Drive: 1.1,
|
||||
Weapons: 1.2,
|
||||
Shields: 1.3,
|
||||
Cargo: 1.4,
|
||||
ID: uuid.New(),
|
||||
Name: "Race_0",
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.1,
|
||||
game.TechWeapons: 1.2,
|
||||
game.TechShields: 1.3,
|
||||
game.TechCargo: 1.4,
|
||||
},
|
||||
}
|
||||
Race_1 = game.Race{
|
||||
ID: uuid.New(),
|
||||
Name: "Race_1",
|
||||
Drive: 2.1,
|
||||
Weapons: 2.2,
|
||||
Shields: 2.3,
|
||||
Cargo: 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)),
|
||||
ID: uuid.New(),
|
||||
Name: "Race_1",
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 2.1,
|
||||
game.TechWeapons: 2.2,
|
||||
game.TechShields: 2.3,
|
||||
game.TechCargo: 2.4,
|
||||
},
|
||||
}
|
||||
Game = &game.Game{
|
||||
Race: []game.Race{
|
||||
Race_0,
|
||||
Race_1,
|
||||
},
|
||||
Map: Map,
|
||||
}
|
||||
|
||||
Race_0_idx = 0
|
||||
Race_0_Gunship = "R0_Gunship"
|
||||
Race_0_Freighter = "R0_Freighter"
|
||||
@@ -59,25 +48,47 @@ var (
|
||||
Race_1_Cruiser_idx = 2
|
||||
|
||||
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) {
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("init assertion failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func copyGame() *game.Game {
|
||||
g := *Game
|
||||
return &g
|
||||
func newGame() *game.Game {
|
||||
g := &game.Game{
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"math"
|
||||
@@ -35,50 +36,128 @@ func (ct CargoType) String() string {
|
||||
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 {
|
||||
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
||||
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
||||
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
||||
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
||||
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"`
|
||||
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
|
||||
|
||||
Drive float64 `json:"drive"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
Tech TechSet `json:"tech"`
|
||||
|
||||
// TODO: TEST: Destination, Origin, Range
|
||||
Destination uint `json:"destination"`
|
||||
Origin *uint `json:"origin,omitempty"`
|
||||
Range *float64 `json:"range,omitempty"`
|
||||
Destination uint `json:"destination"`
|
||||
StateInSpace *InSpace `json:"stateInSpace,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 {
|
||||
return sg.OwnerID == other.OwnerID &&
|
||||
sg.TypeID == other.TypeID &&
|
||||
sg.FleetID == other.FleetID &&
|
||||
sg.Drive == other.Drive &&
|
||||
sg.Weapons == other.Weapons &&
|
||||
sg.Shields == other.Shields &&
|
||||
sg.Cargo == other.Cargo &&
|
||||
sg.TechLevel(TechDrive) == other.TechLevel(TechDrive) &&
|
||||
sg.TechLevel(TechWeapons) == other.TechLevel(TechWeapons) &&
|
||||
sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
|
||||
sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
|
||||
sg.CargoType == other.CargoType &&
|
||||
sg.Load == other.Load &&
|
||||
sg.State == other.State
|
||||
sg.State() == other.State()
|
||||
}
|
||||
|
||||
// Грузоподъёмность
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return (1 - sg.Drive/drive) * 10 * st.Drive
|
||||
return (1 - sg.TechLevel(TechDrive)/drive) * 10 * st.Drive
|
||||
}
|
||||
|
||||
// TODO: test on other values
|
||||
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 {
|
||||
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 {
|
||||
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?
|
||||
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
|
||||
// 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 *
|
||||
sg.Weapons *
|
||||
sg.TechLevel(TechWeapons) *
|
||||
float64(st.Armament) *
|
||||
float64(sg.Number)
|
||||
return number.Fixed3(result)
|
||||
@@ -173,7 +252,7 @@ func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -257,7 +336,7 @@ func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity
|
||||
if sgi < 0 {
|
||||
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()
|
||||
}
|
||||
var sti int
|
||||
@@ -336,7 +415,7 @@ func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships ui
|
||||
if sgi < 0 {
|
||||
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()
|
||||
}
|
||||
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,
|
||||
TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
||||
Number: uint(quantity),
|
||||
State: g.ShipGroups[sgi].State,
|
||||
|
||||
CargoType: g.ShipGroups[sgi].CargoType,
|
||||
Load: g.ShipGroups[sgi].Load,
|
||||
|
||||
Drive: g.ShipGroups[sgi].Drive,
|
||||
Weapons: g.ShipGroups[sgi].Weapons,
|
||||
Shields: g.ShipGroups[sgi].Shields,
|
||||
Cargo: g.ShipGroups[sgi].Cargo,
|
||||
Tech: maps.Clone(g.ShipGroups[sgi].Tech),
|
||||
|
||||
Destination: g.ShipGroups[sgi].Destination,
|
||||
Origin: g.ShipGroups[sgi].Origin,
|
||||
Range: g.ShipGroups[sgi].Range,
|
||||
Destination: g.ShipGroups[sgi].Destination,
|
||||
StateInSpace: g.ShipGroups[sgi].StateInSpace,
|
||||
StateUpgrade: g.ShipGroups[sgi].StateUpgrade,
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -599,11 +674,12 @@ func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quanti
|
||||
TypeID: g.Race[ri].ShipTypes[st].ID,
|
||||
Destination: g.Map.Planet[pl].Number,
|
||||
Number: uint(quantity),
|
||||
State: "In_Orbit",
|
||||
Drive: g.Race[ri].Drive,
|
||||
Weapons: g.Race[ri].Weapons,
|
||||
Shields: g.Race[ri].Shields,
|
||||
Cargo: g.Race[ri].Cargo,
|
||||
Tech: map[Tech]float64{
|
||||
TechDrive: g.Race[ri].TechLevel(TechDrive),
|
||||
TechWeapons: g.Race[ri].TechLevel(TechWeapons),
|
||||
TechShields: g.Race[ri].TechLevel(TechShields),
|
||||
TechCargo: g.Race[ri].TechLevel(TechCargo),
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ func TestCargoCapacity(t *testing.T) {
|
||||
},
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: 1,
|
||||
State: "In_Orbit",
|
||||
Drive: 1.5,
|
||||
Weapons: 1.1,
|
||||
Shields: 2.0,
|
||||
Cargo: 1.0,
|
||||
Number: 1,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.5,
|
||||
game.TechWeapons: 1.1,
|
||||
game.TechShields: 2.0,
|
||||
game.TechCargo: 1.0,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
|
||||
}
|
||||
@@ -52,13 +53,14 @@ func TestCarryingAndFullMass(t *testing.T) {
|
||||
},
|
||||
}
|
||||
sg := &game.ShipGroup{
|
||||
Number: 1,
|
||||
State: "In_Orbit",
|
||||
Drive: 1.0,
|
||||
Weapons: 1.0,
|
||||
Shields: 1.0,
|
||||
Cargo: 1.0,
|
||||
Load: 0.0,
|
||||
Number: 1,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
game.TechShields: 1.0,
|
||||
game.TechCargo: 1.0,
|
||||
},
|
||||
Load: 0.0,
|
||||
}
|
||||
em := Freighter.EmptyMass()
|
||||
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, 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, em+4.0, sg.FullMass(Freighter))
|
||||
}
|
||||
@@ -85,21 +87,22 @@ func TestSpeed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
sg := &game.ShipGroup{
|
||||
Number: 1,
|
||||
State: "In_Orbit",
|
||||
Drive: 1.0,
|
||||
Weapons: 1.0,
|
||||
Shields: 1.0,
|
||||
Cargo: 1.0,
|
||||
Load: 0.0,
|
||||
Number: 1,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
game.TechShields: 1.0,
|
||||
game.TechCargo: 1.0,
|
||||
},
|
||||
Load: 0.0,
|
||||
}
|
||||
assert.Equal(t, 8.0, sg.Speed(Freighter))
|
||||
sg.Load = 5.0
|
||||
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))
|
||||
sg.Load = 10
|
||||
sg.Cargo = 1.5
|
||||
sg.SetTechLevel(game.TechCargo, 1.5)
|
||||
assert.Equal(t, 9.0, sg.Speed(Freighter))
|
||||
}
|
||||
|
||||
@@ -114,45 +117,19 @@ func TestBombingPower(t *testing.T) {
|
||||
},
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: 1,
|
||||
State: "In_Orbit",
|
||||
Drive: 1.0,
|
||||
Weapons: 1.0,
|
||||
Shields: 1.0,
|
||||
Cargo: 1.0,
|
||||
Number: 1,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
game.TechShields: 1.0,
|
||||
game.TechCargo: 1.0,
|
||||
},
|
||||
}
|
||||
expectedBombingPower := 139.295
|
||||
result := sg.BombingPower(&Gunship)
|
||||
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) {
|
||||
tc := []struct {
|
||||
driveShipType float64
|
||||
@@ -178,12 +155,13 @@ func TestDriveEffective(t *testing.T) {
|
||||
},
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: rand.UintN(4) + 1,
|
||||
State: "In_Orbit",
|
||||
Drive: tc[i].driveTech,
|
||||
Weapons: rand.Float64()*5 + 1,
|
||||
Shields: rand.Float64()*5 + 1,
|
||||
Cargo: rand.Float64()*5 + 1,
|
||||
Number: rand.UintN(4) + 1,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: tc[i].driveTech,
|
||||
game.TechWeapons: rand.Float64()*5 + 1,
|
||||
game.TechShields: rand.Float64()*5 + 1,
|
||||
game.TechCargo: rand.Float64()*5 + 1,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, tc[i].expectDriveEffective, sg.DriveEffective(&someShip))
|
||||
}
|
||||
@@ -201,13 +179,14 @@ func TestShipGroupEqual(t *testing.T) {
|
||||
OwnerID: uuid.New(),
|
||||
TypeID: uuid.New(),
|
||||
FleetID: &fleetId,
|
||||
State: "In_Orbit",
|
||||
CargoType: &mat,
|
||||
Load: 123.45,
|
||||
Drive: 1.0,
|
||||
Weapons: 1.0,
|
||||
Shields: 1.0,
|
||||
Cargo: 1.0,
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
game.TechShields: 1.0,
|
||||
game.TechCargo: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
// essential properties
|
||||
@@ -230,7 +209,10 @@ func TestShipGroupEqual(t *testing.T) {
|
||||
assert.False(t, left.Equal(right))
|
||||
|
||||
right = *left
|
||||
left.State = "In_Space"
|
||||
left.StateInSpace = &game.InSpace{
|
||||
Origin: 1,
|
||||
Range: 1,
|
||||
}
|
||||
assert.False(t, left.Equal(right))
|
||||
|
||||
right = *left
|
||||
@@ -246,19 +228,23 @@ func TestShipGroupEqual(t *testing.T) {
|
||||
assert.False(t, left.Equal(right))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
// non-essential properties
|
||||
@@ -271,7 +257,7 @@ func TestShipGroupEqual(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateShips(t *testing.T) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
|
||||
assert.ErrorContains(t,
|
||||
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) {
|
||||
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_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_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_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)
|
||||
@@ -310,7 +296,7 @@ func TestJoinEqualGroups(t *testing.T) {
|
||||
|
||||
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.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) {
|
||||
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(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(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(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(6), sg.Index)
|
||||
default:
|
||||
@@ -349,10 +335,13 @@ func TestJoinEqualGroups(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_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"
|
||||
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) {
|
||||
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_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.JoinShipGroupToFleet(Race_0.Name, "R0_Fleet", 2, 0))
|
||||
assert.NotNil(t, g.ShipGroups[2].FleetID)
|
||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
||||
rng := 31.337
|
||||
g.ShipGroups[2].Range = &rng
|
||||
g.ShipGroups[2].State = "In_Space"
|
||||
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
g.ShipGroups[2].CargoType = game.CargoMaterial.Ref()
|
||||
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].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.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].Load, g.ShipGroups[3].Load)
|
||||
assert.Equal(t, g.ShipGroups[2].Drive, g.ShipGroups[3].Drive)
|
||||
assert.Equal(t, g.ShipGroups[2].Weapons, g.ShipGroups[3].Weapons)
|
||||
assert.Equal(t, g.ShipGroups[2].Shields, g.ShipGroups[3].Shields)
|
||||
assert.Equal(t, g.ShipGroups[2].Cargo, g.ShipGroups[3].Cargo)
|
||||
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechDrive), g.ShipGroups[3].TechLevel(game.TechDrive))
|
||||
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechWeapons), g.ShipGroups[3].TechLevel(game.TechWeapons))
|
||||
assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechShields), g.ShipGroups[3].TechLevel(game.TechShields))
|
||||
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].Origin, g.ShipGroups[3].Origin)
|
||||
assert.Equal(t, g.ShipGroups[2].Range, g.ShipGroups[3].Range)
|
||||
assert.Equal(t, g.ShipGroups[2].StateInSpace, g.ShipGroups[3].StateInSpace)
|
||||
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].TypeID, g.Race[Race_1_idx].ShipTypes[sti].ID)
|
||||
assert.Equal(t, g.ShipGroups[3].Number, uint(11))
|
||||
@@ -483,7 +472,7 @@ func TestGiveawayGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadCargo(t *testing.T) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
|
||||
// 1: idx = 0 / Ready to load
|
||||
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
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
||||
rng := 31.337
|
||||
g.ShipGroups[2].Range = &rng
|
||||
g.ShipGroups[2].State = "In_Space"
|
||||
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 4: idx = 3 / loaded with COL
|
||||
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) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
|
||||
// 1: idx = 0 / empty
|
||||
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
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
g.ShipGroups[2].Origin = &R0_Planet_2_num
|
||||
rng := 31.337
|
||||
g.ShipGroups[2].Range = &rng
|
||||
g.ShipGroups[2].State = "In_Space"
|
||||
g.ShipGroups[2].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 4: idx = 3 / loaded with COL
|
||||
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) {
|
||||
g := copyGame()
|
||||
g := newGame()
|
||||
|
||||
// 1: idx = 0 / empty
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||
|
||||
// 2: idx = 1 / In_Space
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
g.ShipGroups[1].Origin = &R0_Planet_2_num
|
||||
rng := 31.337
|
||||
g.ShipGroups[1].Range = &rng
|
||||
g.ShipGroups[1].State = "In_Space"
|
||||
g.ShipGroups[1].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 3: idx = 2 / loaded with COL
|
||||
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 {
|
||||
UninhabitedPlanet
|
||||
Industry float64 `json:"industry"` // I - Промышленность
|
||||
Population float64 `json:"population"` // P - Население
|
||||
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
||||
Production ProductionType `json:"production"` // TODO: internal/report format
|
||||
Industry float64 `json:"industry"` // I - Промышленность
|
||||
Population float64 `json:"population"` // P - Население
|
||||
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
|
||||
Production Production `json:"production"` // TODO: internal/report format
|
||||
// Параметр "L" - Свободный производственный потенциал
|
||||
}
|
||||
|
||||
@@ -42,13 +42,30 @@ type PlanetReportForeign struct {
|
||||
PlanetReport
|
||||
}
|
||||
|
||||
// Свободный производственный потенциал (L)
|
||||
// промышленность * 0.75 + население * 0.25
|
||||
// TODO: за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
|
||||
// TODO: delete func
|
||||
func (p Planet) ProductionCapacity() float64 {
|
||||
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
|
||||
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"
|
||||
)
|
||||
|
||||
type PlanetProduction string
|
||||
type ProductionType string
|
||||
|
||||
const (
|
||||
ProductionNone PlanetProduction = "-"
|
||||
ProductionMaterial PlanetProduction = "MAT" // Сырьё
|
||||
ProductionCapital PlanetProduction = "CAP" // Промышленность
|
||||
ProductionNone ProductionType = "-"
|
||||
ProductionMaterial ProductionType = "MAT" // Сырьё
|
||||
ProductionCapital ProductionType = "CAP" // Промышленность
|
||||
|
||||
ResearchDrive PlanetProduction = "DRIVE"
|
||||
ResearchWeapons PlanetProduction = "WEAPONS"
|
||||
ResearchShields PlanetProduction = "SHIELDS"
|
||||
ResearchCargo PlanetProduction = "CARGO"
|
||||
ResearchDrive ProductionType = "DRIVE"
|
||||
ResearchWeapons ProductionType = "WEAPONS"
|
||||
ResearchShields ProductionType = "SHIELDS"
|
||||
ResearchCargo ProductionType = "CARGO"
|
||||
|
||||
ResearchScience PlanetProduction = "SCIENCE"
|
||||
ProductionShip PlanetProduction = "SHIP"
|
||||
ResearchScience ProductionType = "SCIENCE"
|
||||
ProductionShip ProductionType = "SHIP"
|
||||
)
|
||||
|
||||
type ProductionType struct {
|
||||
Production PlanetProduction `json:"type"`
|
||||
SubjectID *uuid.UUID `json:"subjectId"`
|
||||
Progress *float64 `json:"progress"`
|
||||
type Production struct {
|
||||
Type ProductionType `json:"type"`
|
||||
SubjectID *uuid.UUID `json:"subjectId"`
|
||||
Progress *float64 `json:"progress"`
|
||||
}
|
||||
|
||||
func (p PlanetProduction) AsType(subject uuid.UUID) ProductionType {
|
||||
func (p ProductionType) AsType(subject uuid.UUID) Production {
|
||||
switch p {
|
||||
case ResearchScience, ProductionShip:
|
||||
return ProductionType{Production: p, SubjectID: &subject}
|
||||
return Production{Type: p, SubjectID: &subject}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
var prod PlanetProduction
|
||||
switch PlanetProduction(prodType) {
|
||||
var prod ProductionType
|
||||
switch ProductionType(prodType) {
|
||||
case ProductionMaterial:
|
||||
prod = ProductionMaterial
|
||||
case ProductionCapital:
|
||||
@@ -67,7 +67,7 @@ func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subj
|
||||
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 {
|
||||
return e.NewPlanetNumberError(number)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction
|
||||
if i < 0 {
|
||||
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 == g.Race[ri].ShipTypes[i].ID {
|
||||
// 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.
|
||||
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 {
|
||||
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
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package game
|
||||
|
||||
import "github.com/google/uuid"
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Race struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
@@ -10,10 +12,7 @@ type Race struct {
|
||||
Vote uuid.UUID `json:"vote"`
|
||||
Relations []RaceRelation `json:"relations"`
|
||||
|
||||
Drive float64 `json:"drive"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
Tech TechSet `json:"tech"`
|
||||
|
||||
Sciences []Science `json:"science,omitempty"`
|
||||
|
||||
@@ -32,10 +31,19 @@ type RaceRelation struct {
|
||||
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 {
|
||||
return r.Drive * 40
|
||||
return r.TechLevel(TechDrive) * 40
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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 == g.Race[ri].Sciences[sc].ID
|
||||
}); pl >= 0 {
|
||||
|
||||
@@ -35,18 +35,45 @@ func (st ShipType) Equal(o ShipType) bool {
|
||||
st.Cargo == o.Cargo
|
||||
}
|
||||
|
||||
func (st ShipType) EmptyMass() float64 {
|
||||
shipMass := st.Drive + st.Shields + st.Cargo + st.WeaponsMass()
|
||||
return shipMass
|
||||
func (st ShipType) BlockMass(t Tech) float64 {
|
||||
switch t {
|
||||
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 {
|
||||
return 0
|
||||
}
|
||||
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]
|
||||
func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
||||
mat = st.EmptyMass()
|
||||
@@ -89,7 +116,7 @@ func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
||||
return e.NewEntityNotExistsError("ship type %w", name)
|
||||
}
|
||||
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 &&
|
||||
g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
|
||||
}); pl >= 0 {
|
||||
@@ -160,7 +187,7 @@ func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
|
||||
// switch planet productions to the new type
|
||||
for pl := range g.Map.Planet {
|
||||
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 == g.Race[ri].ShipTypes[st].ID {
|
||||
g.Map.Planet[pl].Production.SubjectID = &g.Race[ri].ShipTypes[tt].ID
|
||||
|
||||
Reference in New Issue
Block a user