cmd: join ship groups
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "github.com/iliadenisov/galaxy/pkg/model/game"
|
||||||
|
|
||||||
|
func JoinEqualGroups(configure func(*Param), race string) (err error) {
|
||||||
|
control(configure, func(c *ctrl) {
|
||||||
|
c.execute(func(r Repo, g game.Game) {
|
||||||
|
err = joinEqualGroups(r, g, race)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinEqualGroups(r Repo, g game.Game, race string) error {
|
||||||
|
if err := g.JoinEqualGroups(race); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.SaveState(g)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package game_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/iliadenisov/galaxy/pkg/game"
|
||||||
|
mg "github.com/iliadenisov/galaxy/pkg/model/game"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJoinEqualGroups(t *testing.T) {
|
||||||
|
g(t, func(p func(*game.Param), g func() mg.Game) {
|
||||||
|
err := game.JoinEqualGroups(p, "race_01")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
|||||||
Shields: 1,
|
Shields: 1,
|
||||||
Cargo: 1,
|
Cargo: 1,
|
||||||
}
|
}
|
||||||
gameMap.Planet = append(gameMap.Planet, newPlanet(
|
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
||||||
planetCount,
|
planetCount,
|
||||||
m.HomePlanets[i].HW.RandomName(),
|
m.HomePlanets[i].HW.RandomName(),
|
||||||
raceID,
|
raceID,
|
||||||
@@ -79,7 +79,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
|||||||
))
|
))
|
||||||
planetCount++
|
planetCount++
|
||||||
for dw := range m.HomePlanets[i].DW {
|
for dw := range m.HomePlanets[i].DW {
|
||||||
gameMap.Planet = append(gameMap.Planet, newPlanet(
|
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
||||||
planetCount,
|
planetCount,
|
||||||
m.HomePlanets[i].DW[dw].RandomName(),
|
m.HomePlanets[i].DW[dw].RandomName(),
|
||||||
raceID,
|
raceID,
|
||||||
@@ -101,7 +101,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range m.FreePlanets {
|
for i := range m.FreePlanets {
|
||||||
gameMap.Planet = append(gameMap.Planet, newPlanet(
|
gameMap.Planet = append(gameMap.Planet, NewPlanet(
|
||||||
planetCount,
|
planetCount,
|
||||||
m.FreePlanets[i].RandomName(),
|
m.FreePlanets[i].RandomName(),
|
||||||
uuid.Nil,
|
uuid.Nil,
|
||||||
@@ -125,7 +125,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.ProductionType) game.Planet {
|
||||||
return game.Planet{
|
return game.Planet{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
PlanetReport: game.PlanetReport{
|
PlanetReport: game.PlanetReport{
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ type Game struct {
|
|||||||
Age uint `json:"turn"` // Game's turn number
|
Age uint `json:"turn"` // Game's turn number
|
||||||
Map Map `json:"map"`
|
Map Map `json:"map"`
|
||||||
Race []Race `json:"races"`
|
Race []Race `json:"races"`
|
||||||
|
ShipGroups []ShipGroup `json:"shipGroup,omitempty"`
|
||||||
|
Fleets []Fleet `json:"fleet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Game) Votes(raceID uuid.UUID) float64 {
|
func (g Game) Votes(raceID uuid.UUID) float64 {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber int, quantity int) error {
|
||||||
|
return g.createShips(ri, shipTypeName, planetNumber, quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] {
|
||||||
|
return g.listShipGroups(ri)
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
e "github.com/iliadenisov/galaxy/pkg/error"
|
||||||
|
"github.com/iliadenisov/galaxy/pkg/number"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CargoType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CargoNone CargoType = "-"
|
||||||
|
CargoColonist CargoType = "COL" // Колонисты
|
||||||
|
CargoMaterial CargoType = "MAT" // Сырьё
|
||||||
|
CargoCapital CargoType = "CAP" // Промышленность
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShipGroup struct {
|
||||||
|
Index uint `json:"index"` // Group index (ordered)
|
||||||
|
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
||||||
|
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
||||||
|
FleetID *uuid.UUID `json:"fleetId,omitempty"` // ShipType link
|
||||||
|
Number uint `json:"number"` // Number (quantity) ships of Type
|
||||||
|
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"`
|
||||||
|
|
||||||
|
// TODO: append AND TEST: Destination, Origin, Range
|
||||||
|
Destination uint `json:"destination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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.CargoType == other.CargoType &&
|
||||||
|
sg.Load == other.Load &&
|
||||||
|
sg.State == other.State
|
||||||
|
}
|
||||||
|
|
||||||
|
// Грузоподъёмность
|
||||||
|
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
|
||||||
|
return sg.Cargo * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Масса перевозимого груза -
|
||||||
|
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
|
||||||
|
func (sg ShipGroup) CarryingMass() float64 {
|
||||||
|
return sg.Load / sg.Cargo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Полная масса -
|
||||||
|
// массу корабля самого по себе плюс масса перевозимого груза.
|
||||||
|
func (sg ShipGroup) FullMass(st *ShipType) float64 {
|
||||||
|
return st.EmptyMass() + sg.CarryingMass()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Эффективность двигателя -
|
||||||
|
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
|
||||||
|
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
|
||||||
|
return st.Drive * sg.Drive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корабли перемещаются за один ход на количество световых лет, равное
|
||||||
|
// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля.
|
||||||
|
func (sg ShipGroup) Speed(st *ShipType) float64 {
|
||||||
|
return sg.DriveEffective(st) * 20 / sg.FullMass(st)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 {
|
||||||
|
return (1 - sg.Drive/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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 {
|
||||||
|
return (1 - sg.Shields/shields) * 10 * st.Shields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 {
|
||||||
|
return (1 - sg.Cargo/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.) *
|
||||||
|
st.Weapons *
|
||||||
|
sg.Weapons *
|
||||||
|
float64(st.Armament) *
|
||||||
|
float64(sg.Number)
|
||||||
|
return number.Fixed3(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) JoinEqualGroups(raceName string) error {
|
||||||
|
ri, err := g.raceIndex(raceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.joinEqualGroupsInternal(ri)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) joinEqualGroupsInternal(ri int) {
|
||||||
|
shipGroups := slices.Collect(g.listShipGroups(ri))
|
||||||
|
origin := len(shipGroups)
|
||||||
|
if origin < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(shipGroups)-1; i++ {
|
||||||
|
for j := len(shipGroups) - 1; j > i; j-- {
|
||||||
|
if shipGroups[i].Equal(shipGroups[j]) {
|
||||||
|
shipGroups[i].Index = maxUint(shipGroups[i].Index, shipGroups[j].Index)
|
||||||
|
shipGroups[i].Number += shipGroups[j].Number
|
||||||
|
shipGroups = append(shipGroups[:j], shipGroups[j+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(shipGroups) == origin {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.ShipGroups = slices.DeleteFunc(g.ShipGroups, func(v ShipGroup) bool { return v.OwnerID == g.Race[ri].ID })
|
||||||
|
g.ShipGroups = append(g.ShipGroups, shipGroups...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quantity int) error {
|
||||||
|
st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == shipTypeName })
|
||||||
|
if st < 0 {
|
||||||
|
return e.NewEntityNotExistsError("ship type %w", shipTypeName)
|
||||||
|
}
|
||||||
|
pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(planetNumber) })
|
||||||
|
if pl < 0 {
|
||||||
|
return e.NewEntityNotExistsError("planet #%d", planetNumber)
|
||||||
|
}
|
||||||
|
if g.Map.Planet[pl].Owner != g.Race[ri].ID {
|
||||||
|
return e.NewEntityNotOwnedError("planet %#d", planetNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxIndex uint
|
||||||
|
for sg := range g.listShipGroups(ri) {
|
||||||
|
if sg.Index > maxIndex {
|
||||||
|
maxIndex = sg.Index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.ShipGroups = append(g.ShipGroups, ShipGroup{
|
||||||
|
Index: maxIndex + 1,
|
||||||
|
OwnerID: g.Race[ri].ID,
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Game) listShipGroups(ri int) iter.Seq[ShipGroup] {
|
||||||
|
return func(yield func(ShipGroup) bool) {
|
||||||
|
for sg := range g.ShipGroups {
|
||||||
|
if g.ShipGroups[sg].OwnerID == g.Race[ri].ID {
|
||||||
|
if !yield(g.ShipGroups[sg]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxUint(a, b uint) uint {
|
||||||
|
if b > a {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
package game_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand/v2"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
e "github.com/iliadenisov/galaxy/pkg/error"
|
||||||
|
gg "github.com/iliadenisov/galaxy/pkg/game"
|
||||||
|
"github.com/iliadenisov/galaxy/pkg/model/game"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCargoCapacity(t *testing.T) {
|
||||||
|
test := func(cargoSize float64, expectCapacity float64) {
|
||||||
|
ship := game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Drive: 1,
|
||||||
|
Armament: 1,
|
||||||
|
Weapons: 1,
|
||||||
|
Shields: 1,
|
||||||
|
Cargo: cargoSize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sg := game.ShipGroup{
|
||||||
|
Number: 1,
|
||||||
|
State: "In_Orbit",
|
||||||
|
Drive: 1.5,
|
||||||
|
Weapons: 1.1,
|
||||||
|
Shields: 2.0,
|
||||||
|
Cargo: 1.0,
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
|
||||||
|
}
|
||||||
|
test(1, 1.05)
|
||||||
|
test(5, 6.25)
|
||||||
|
test(10, 15)
|
||||||
|
test(50, 175)
|
||||||
|
test(100, 600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCarryingAndFullMass(t *testing.T) {
|
||||||
|
Freighter := &game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Name: "Freighter",
|
||||||
|
Drive: 8,
|
||||||
|
Armament: 0,
|
||||||
|
Weapons: 0,
|
||||||
|
Shields: 2,
|
||||||
|
Cargo: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sg := &game.ShipGroup{
|
||||||
|
Number: 1,
|
||||||
|
State: "In_Orbit",
|
||||||
|
Drive: 1.0,
|
||||||
|
Weapons: 1.0,
|
||||||
|
Shields: 1.0,
|
||||||
|
Cargo: 1.0,
|
||||||
|
Load: 0.0,
|
||||||
|
}
|
||||||
|
em := Freighter.EmptyMass()
|
||||||
|
assert.Equal(t, 0.0, sg.CarryingMass())
|
||||||
|
assert.Equal(t, em, sg.FullMass(Freighter))
|
||||||
|
|
||||||
|
sg.Load = 10.0
|
||||||
|
assert.Equal(t, 10.0, sg.CarryingMass())
|
||||||
|
assert.Equal(t, em+10.0, sg.FullMass(Freighter))
|
||||||
|
|
||||||
|
sg.Cargo = 2.5
|
||||||
|
assert.Equal(t, 4.0, sg.CarryingMass())
|
||||||
|
assert.Equal(t, em+4.0, sg.FullMass(Freighter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpeed(t *testing.T) {
|
||||||
|
Freighter := &game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Name: "Freighter",
|
||||||
|
Drive: 8,
|
||||||
|
Armament: 0,
|
||||||
|
Weapons: 0,
|
||||||
|
Shields: 2,
|
||||||
|
Cargo: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sg := &game.ShipGroup{
|
||||||
|
Number: 1,
|
||||||
|
State: "In_Orbit",
|
||||||
|
Drive: 1.0,
|
||||||
|
Weapons: 1.0,
|
||||||
|
Shields: 1.0,
|
||||||
|
Cargo: 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
|
||||||
|
assert.Equal(t, 9.6, sg.Speed(Freighter))
|
||||||
|
sg.Load = 10
|
||||||
|
sg.Cargo = 1.5
|
||||||
|
assert.Equal(t, 9.0, sg.Speed(Freighter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBombingPower(t *testing.T) {
|
||||||
|
Gunship := game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Drive: 60.0,
|
||||||
|
Armament: 3,
|
||||||
|
Weapons: 30.0,
|
||||||
|
Shields: 100.0,
|
||||||
|
Cargo: 0.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sg := game.ShipGroup{
|
||||||
|
Number: 1,
|
||||||
|
State: "In_Orbit",
|
||||||
|
Drive: 1.0,
|
||||||
|
Weapons: 1.0,
|
||||||
|
Shields: 1.0,
|
||||||
|
Cargo: 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
|
||||||
|
driveTech float64
|
||||||
|
expectDriveEffective float64
|
||||||
|
}{
|
||||||
|
{1, 1, 1},
|
||||||
|
{1, 2, 2},
|
||||||
|
{2, 1, 2},
|
||||||
|
{0, 1, 0},
|
||||||
|
{0, 1.5, 0},
|
||||||
|
{0, 10, 0},
|
||||||
|
{1.5, 1.5, 2.25},
|
||||||
|
}
|
||||||
|
for i := range tc {
|
||||||
|
someShip := game.ShipType{
|
||||||
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
|
Drive: tc[i].driveShipType,
|
||||||
|
Armament: rand.UintN(30) + 1,
|
||||||
|
Weapons: rand.Float64()*30 + 1,
|
||||||
|
Shields: rand.Float64()*100 + 1,
|
||||||
|
Cargo: rand.Float64()*20 + 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc[i].expectDriveEffective, sg.DriveEffective(&someShip))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShipGroupEqual(t *testing.T) {
|
||||||
|
fleetId := uuid.New()
|
||||||
|
someUUID := uuid.New()
|
||||||
|
mat := game.CargoMaterial
|
||||||
|
cap := game.CargoCapital
|
||||||
|
left := &game.ShipGroup{
|
||||||
|
Index: 1,
|
||||||
|
Number: 1,
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// essential properties
|
||||||
|
right := *left
|
||||||
|
assert.True(t, left.Equal(right))
|
||||||
|
|
||||||
|
left.OwnerID = someUUID
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.TypeID = someUUID
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.FleetID = &someUUID
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.FleetID = nil
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.State = "In_Space"
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.CargoType = &cap
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.CargoType = nil
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.Load = 45.123
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.Drive = 1.1
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.Weapons = 1.1
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.Shields = 1.1
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
right = *left
|
||||||
|
left.Cargo = 1.1
|
||||||
|
assert.False(t, left.Equal(right))
|
||||||
|
|
||||||
|
// non-essential properties
|
||||||
|
right = *left
|
||||||
|
|
||||||
|
left.Index = 2
|
||||||
|
assert.True(t, left.Equal(right))
|
||||||
|
left.Number = 5
|
||||||
|
assert.True(t, left.Equal(right))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJoinEqualGroups(t *testing.T) {
|
||||||
|
g := &game.Game{
|
||||||
|
Race: make([]game.Race, 2),
|
||||||
|
}
|
||||||
|
raceIdx := 0
|
||||||
|
g.Race[raceIdx] = game.Race{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: "Race_0",
|
||||||
|
Drive: 1.1,
|
||||||
|
Weapons: 1.2,
|
||||||
|
Shields: 1.3,
|
||||||
|
Cargo: 1.4,
|
||||||
|
}
|
||||||
|
g.Race[1] = game.Race{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: "Race_1",
|
||||||
|
Drive: 2.1,
|
||||||
|
Weapons: 2.2,
|
||||||
|
Shields: 2.3,
|
||||||
|
Cargo: 2.4,
|
||||||
|
}
|
||||||
|
g.Map = game.Map{
|
||||||
|
Width: 10,
|
||||||
|
Height: 10,
|
||||||
|
Planet: make([]game.Planet, 3),
|
||||||
|
}
|
||||||
|
g.Map.Planet[0] = gg.NewPlanet(0, "Planet_0", g.Race[0].ID, 0, 0, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil))
|
||||||
|
g.Map.Planet[1] = gg.NewPlanet(1, "Planet_1", g.Race[1].ID, 1, 1, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil))
|
||||||
|
g.Map.Planet[2] = gg.NewPlanet(1, "Planet_2", g.Race[0].ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil))
|
||||||
|
|
||||||
|
err := g.CreateShipType("Race_0", "R0_Gunship", 60, 30, 100, 0, 3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = g.CreateShipType("Race_0", "R0_Freighter", 8, 0, 2, 10, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = g.CreateShipType("Race_1", "R1_Gunship", 60, 30, 100, 0, 3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = g.CreateShipType("Race_1", "R1_Freighter", 8, 0, 2, 10, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = g.CreateShips(raceIdx, "Freighter", 0, 2)
|
||||||
|
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Gunship", 1, 2)
|
||||||
|
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||||
|
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Freighter", 0, 1) // 1 -> 2
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 1)
|
||||||
|
|
||||||
|
err = g.CreateShips(1, "R1_Freighter", 1, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(1)), 1)
|
||||||
|
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Freighter", 0, 6) // (2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 2)
|
||||||
|
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Gunship", 0, 2) // (3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 3)
|
||||||
|
|
||||||
|
err = g.CreateShips(1, "R1_Gunship", 1, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(1)), 2)
|
||||||
|
|
||||||
|
g.Race[raceIdx].Drive = 1.5
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Gunship", 0, 9) // 4 -> 6
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 4)
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Freighter", 0, 7) // 5 -> 7
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 5)
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Gunship", 0, 4) // (6)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 6)
|
||||||
|
err = g.CreateShips(raceIdx, "R0_Freighter", 0, 4) // (7)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 7)
|
||||||
|
|
||||||
|
g.Race[1].Shields = 2.0
|
||||||
|
err = g.CreateShips(1, "R1_Freighter", 1, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(1)), 3)
|
||||||
|
|
||||||
|
err = g.JoinEqualGroups("Race_0")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(1)), 3)
|
||||||
|
assert.Len(t, slices.Collect(g.ListShipGroups(raceIdx)), 4)
|
||||||
|
|
||||||
|
shipTypeID := func(ri int, name string) uuid.UUID {
|
||||||
|
st := slices.IndexFunc(g.Race[ri].ShipTypes, func(v game.ShipType) bool { return v.Name == name })
|
||||||
|
if st < 0 {
|
||||||
|
t.Fatalf("ShipType not found: %s", name)
|
||||||
|
return uuid.Nil
|
||||||
|
}
|
||||||
|
return g.Race[ri].ShipTypes[st].ID
|
||||||
|
}
|
||||||
|
|
||||||
|
for sg := range g.ListShipGroups(raceIdx) {
|
||||||
|
switch {
|
||||||
|
case sg.TypeID == shipTypeID(raceIdx, "R0_Freighter") && sg.Drive == 1.1:
|
||||||
|
assert.Equal(t, uint(7), sg.Number)
|
||||||
|
assert.Equal(t, uint(2), sg.Index)
|
||||||
|
case sg.TypeID == shipTypeID(raceIdx, "R0_Freighter") && sg.Drive == 1.5:
|
||||||
|
assert.Equal(t, uint(11), sg.Number)
|
||||||
|
assert.Equal(t, uint(7), sg.Index)
|
||||||
|
case sg.TypeID == shipTypeID(raceIdx, "R0_Gunship") && sg.Drive == 1.1:
|
||||||
|
assert.Equal(t, uint(2), sg.Number)
|
||||||
|
assert.Equal(t, uint(3), sg.Index)
|
||||||
|
case sg.TypeID == shipTypeID(raceIdx, "R0_Gunship") && sg.Drive == 1.5:
|
||||||
|
assert.Equal(t, uint(13), sg.Number)
|
||||||
|
assert.Equal(t, uint(6), sg.Index)
|
||||||
|
default:
|
||||||
|
t.Error("not all ship groups covered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,6 @@ type Race struct {
|
|||||||
Sciences []Science `json:"science,omitempty"`
|
Sciences []Science `json:"science,omitempty"`
|
||||||
|
|
||||||
ShipTypes []ShipType `json:"shipType,omitempty"`
|
ShipTypes []ShipType `json:"shipType,omitempty"`
|
||||||
ShipGroups []ShipGroup `json:"shipGroup,omitempty"`
|
|
||||||
Fleets []Fleet `json:"fleet,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Relation string
|
type Relation string
|
||||||
|
|||||||
+18
-73
@@ -1,12 +1,12 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
e "github.com/iliadenisov/galaxy/pkg/error"
|
e "github.com/iliadenisov/galaxy/pkg/error"
|
||||||
"github.com/iliadenisov/galaxy/pkg/number"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShipTypeReport struct {
|
type ShipTypeReport struct {
|
||||||
@@ -28,19 +28,8 @@ type ShipTypeReportForeign struct {
|
|||||||
ShipTypeReport
|
ShipTypeReport
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShipGroup struct {
|
|
||||||
TypeID uuid.UUID `json:"id"`
|
|
||||||
Type ShipType `json:"-"` // TODO: fill upon load from store
|
|
||||||
Number uint `json:"number"`
|
|
||||||
State string `json:"state"` // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade
|
|
||||||
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
|
|
||||||
Drive float64 `json:"drive"`
|
|
||||||
Weapons float64 `json:"weapons"`
|
|
||||||
Shields float64 `json:"shields"`
|
|
||||||
Cargo float64 `json:"cargo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fleet struct {
|
type Fleet struct {
|
||||||
|
OwnerID uuid.UUID `json:"ownerId"`
|
||||||
ShipGroups []ShipGroup `json:"group"`
|
ShipGroups []ShipGroup `json:"group"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,71 +60,27 @@ func (st ShipType) ProductionCost() (mat float64, pop float64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Грузоподъёмность
|
|
||||||
func (sg ShipGroup) CargoCapacity() float64 {
|
|
||||||
return sg.Drive * (sg.Type.Cargo + (sg.Type.Cargo*sg.Type.Cargo)/20)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Масса перевозимого груза"
|
|
||||||
func (sg ShipGroup) CarryingMass() float64 {
|
|
||||||
return sg.Load / sg.Cargo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sg ShipGroup) FullMass() float64 {
|
|
||||||
return sg.Type.EmptyMass() + sg.CarryingMass()
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Эффективность двигателя"
|
|
||||||
// равна мощности Двигателей умноженной на текущий технологический уровень блока Двигателей
|
|
||||||
func (sg ShipGroup) DriveEffective() float64 {
|
|
||||||
return sg.Type.Drive * sg.Drive
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test this
|
// TODO: test this
|
||||||
func (sg ShipGroup) Speed() float64 {
|
func (g Game) FleetSpeed(fl *Fleet) float64 {
|
||||||
return sg.DriveEffective() * 20 / sg.FullMass()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeDriveCost(drive float64) float64 {
|
|
||||||
return (1 - sg.Drive/drive) * 10 * sg.Type.Drive
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test on other values
|
|
||||||
func (sg ShipGroup) UpgradeWeaponsCost(weapons float64) float64 {
|
|
||||||
return (1 - sg.Weapons/weapons) * 10 * sg.Type.WeaponsMass()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeShieldsCost(shields float64) float64 {
|
|
||||||
return (1 - sg.Shields/shields) * 10 * sg.Type.Shields
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sg ShipGroup) UpgradeCargoCost(cargo float64) float64 {
|
|
||||||
return (1 - sg.Cargo/cargo) * 10 * sg.Type.Cargo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Мощность бомбардировки
|
|
||||||
// TODO: maybe rounding must be done only for display?
|
|
||||||
func (sg ShipGroup) BombingPower() float64 {
|
|
||||||
// return math.Sqrt(sg.Type.Weapons * sg.Weapons)
|
|
||||||
result := (math.Sqrt(sg.Type.Weapons*sg.Weapons)/10. + 1.) *
|
|
||||||
sg.Type.Weapons *
|
|
||||||
sg.Weapons *
|
|
||||||
float64(sg.Type.Armament) *
|
|
||||||
float64(sg.Number)
|
|
||||||
return number.Fixed3(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test this
|
|
||||||
func (fl Fleet) Speed() float64 {
|
|
||||||
result := math.MaxFloat64
|
result := math.MaxFloat64
|
||||||
for _, sg := range fl.ShipGroups {
|
for _, sg := range fl.ShipGroups {
|
||||||
if sg.Speed() < result {
|
st := g.mustShipType(sg.TypeID)
|
||||||
result = sg.Speed()
|
if sg.Speed(st) < result {
|
||||||
|
result = sg.Speed(st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g Game) mustShipType(id uuid.UUID) *ShipType {
|
||||||
|
for ri := range g.Race {
|
||||||
|
if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == id }); st >= 0 {
|
||||||
|
return &g.Race[ri].ShipTypes[st]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("mustShipType: ShipType not found: %v", id))
|
||||||
|
}
|
||||||
|
|
||||||
func (g Game) ShipTypes(raceName string) ([]ShipType, error) {
|
func (g Game) ShipTypes(raceName string) ([]ShipType, error) {
|
||||||
ri, err := g.raceIndex(raceName)
|
ri, err := g.raceIndex(raceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,9 +185,9 @@ func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// switch ship groups to the new type
|
// switch ship groups to the new type
|
||||||
for sg := range g.Race[ri].ShipGroups {
|
for sg := range g.ShipGroups {
|
||||||
if g.Race[ri].ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID {
|
if g.ShipGroups[sg].OwnerID == g.Race[ri].ID && g.ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||||
g.Race[ri].ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID
|
g.ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShipType(t *testing.T) {
|
func TestEmptyMass(t *testing.T) {
|
||||||
Freighter := game.ShipType{
|
Freighter := game.ShipType{
|
||||||
ShipTypeReport: game.ShipTypeReport{
|
ShipTypeReport: game.ShipTypeReport{
|
||||||
Name: "Freighter",
|
Name: "Freighter",
|
||||||
@@ -43,72 +43,4 @@ func TestShipType(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, 45., Cruiser.EmptyMass())
|
assert.Equal(t, 45., Cruiser.EmptyMass())
|
||||||
|
|
||||||
sg := game.ShipGroup{
|
|
||||||
Type: Cruiser,
|
|
||||||
Number: 1,
|
|
||||||
State: "In_Orbit",
|
|
||||||
Drive: 1.0,
|
|
||||||
Weapons: 1.0,
|
|
||||||
Shields: 1.0,
|
|
||||||
Cargo: 1.0,
|
|
||||||
}
|
|
||||||
upgradeCost := sg.UpgradeDriveCost(2.0) +
|
|
||||||
sg.UpgradeWeaponsCost(2.0) +
|
|
||||||
sg.UpgradeShieldsCost(2.0) +
|
|
||||||
sg.UpgradeCargoCost(2.0)
|
|
||||||
assert.Equal(t, 225., upgradeCost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCargoCapacity(t *testing.T) {
|
|
||||||
test := func(cargoSize float64, expectCapacity float64) {
|
|
||||||
ship := game.ShipType{
|
|
||||||
ShipTypeReport: game.ShipTypeReport{
|
|
||||||
Drive: 1,
|
|
||||||
Armament: 1,
|
|
||||||
Weapons: 1,
|
|
||||||
Shields: 1,
|
|
||||||
Cargo: cargoSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sg := game.ShipGroup{
|
|
||||||
Type: ship,
|
|
||||||
Number: 1,
|
|
||||||
State: "In_Orbit",
|
|
||||||
Drive: 1.0,
|
|
||||||
Weapons: 1.0,
|
|
||||||
Shields: 1.0,
|
|
||||||
Cargo: 1.0,
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectCapacity, sg.CargoCapacity())
|
|
||||||
}
|
|
||||||
test(1, 1.05)
|
|
||||||
test(5, 6.25)
|
|
||||||
test(10, 15)
|
|
||||||
test(50, 175)
|
|
||||||
test(100, 600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBombingPower(t *testing.T) {
|
|
||||||
Gunship := game.ShipType{
|
|
||||||
ShipTypeReport: game.ShipTypeReport{
|
|
||||||
Drive: 60.0,
|
|
||||||
Armament: 3,
|
|
||||||
Weapons: 30.0,
|
|
||||||
Shields: 100.0,
|
|
||||||
Cargo: 0.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sg := game.ShipGroup{
|
|
||||||
Type: Gunship,
|
|
||||||
Number: 1,
|
|
||||||
State: "In_Orbit",
|
|
||||||
Drive: 1.0,
|
|
||||||
Weapons: 1.0,
|
|
||||||
Shields: 1.0,
|
|
||||||
Cargo: 1.0,
|
|
||||||
}
|
|
||||||
expectedBombingPower := 139.295
|
|
||||||
result := sg.BombingPower()
|
|
||||||
assert.Equal(t, expectedBombingPower, result)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user