cmd: send group
This commit is contained in:
@@ -74,12 +74,13 @@ func newGame() *game.Game {
|
||||
Race_1,
|
||||
},
|
||||
Map: game.Map{
|
||||
Width: 10,
|
||||
Height: 10,
|
||||
Width: 1000,
|
||||
Height: 1000,
|
||||
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)),
|
||||
controller.NewPlanet(3, "Planet_3", uuid.Nil, 500, 500, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,8 +47,11 @@ const (
|
||||
)
|
||||
|
||||
type InSpace struct {
|
||||
Origin uint `json:"origin"`
|
||||
Origin uint `json:"origin"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
// zero is for Launched status
|
||||
// TODO: calculate range dynamically
|
||||
Range float64 `json:"range"`
|
||||
}
|
||||
|
||||
@@ -136,6 +139,17 @@ func (sg ShipGroup) State() ShipGroupState {
|
||||
}
|
||||
}
|
||||
|
||||
func (sg ShipGroup) OnPlanet() (uint, bool) {
|
||||
switch sg.State() {
|
||||
case StateInOrbit:
|
||||
return sg.Destination, true
|
||||
case StateLaunched:
|
||||
return sg.StateInSpace.Origin, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func (sg ShipGroup) Equal(other ShipGroup) bool {
|
||||
return sg.OwnerID == other.OwnerID &&
|
||||
sg.TypeID == other.TypeID &&
|
||||
@@ -145,7 +159,7 @@ func (sg ShipGroup) Equal(other ShipGroup) bool {
|
||||
sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
|
||||
sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
|
||||
sg.CargoType == other.CargoType &&
|
||||
sg.Load == other.Load &&
|
||||
sg.Load/float64(sg.Number) == other.Load/float64(other.Number) &&
|
||||
sg.State() == other.State()
|
||||
}
|
||||
|
||||
@@ -471,7 +485,6 @@ func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships ui
|
||||
}
|
||||
*availableOnPlanet = *availableOnPlanet - toBeLoaded
|
||||
g.ShipGroups[sgi].Load += toBeLoaded
|
||||
// fmt.Println("capacity:", capacity, "loaded:", g.ShipGroups[sgi].Load, "free:", capacity-g.ShipGroups[sgi].Load)
|
||||
if g.ShipGroups[sgi].Load > 0 {
|
||||
g.ShipGroups[sgi].CargoType = &ct
|
||||
}
|
||||
@@ -706,6 +719,15 @@ func (g Game) listIndexShipGroups(ri int) iter.Seq2[int, ShipGroup] {
|
||||
}
|
||||
}
|
||||
|
||||
func MustShipGroup(g *Game, ri int, index uint) ShipGroup {
|
||||
for sg := range g.listShipGroups(ri) {
|
||||
if sg.Index == index {
|
||||
return sg
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("race i=%d have no group i=%d", ri, index))
|
||||
}
|
||||
|
||||
func maxUint(a, b uint) uint {
|
||||
if b > a {
|
||||
return b
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
func (g *Game) SendGroup(raceName string, groupIndex, planetNumber, quantity uint) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.sendGroupInternal(ri, groupIndex, planetNumber, quantity)
|
||||
}
|
||||
|
||||
func (g *Game) sendGroupInternal(ri int, groupIndex, planetNumber, quantity uint) 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]
|
||||
|
||||
if st.DriveBlockMass() == 0 {
|
||||
return e.NewSendShipHasNoDrivesError()
|
||||
}
|
||||
|
||||
sourcePlanet, ok := g.ShipGroups[sgi].OnPlanet()
|
||||
if !ok {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
if g.ShipGroups[sgi].Number < quantity {
|
||||
return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
||||
}
|
||||
|
||||
p1, ok := PlanetByNum(g, sourcePlanet)
|
||||
if !ok {
|
||||
return e.NewGameStateError("source planet #%d does not exists", g.ShipGroups[sgi].Destination)
|
||||
}
|
||||
p2, ok := PlanetByNum(g, planetNumber)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("destination planet #%d", planetNumber)
|
||||
}
|
||||
rangeToDestination := util.ShortDistance(g.Map.Width, g.Map.Height, p1.X, p1.Y, p2.X, p2.Y)
|
||||
if rangeToDestination > g.Race[ri].FlightDistance() {
|
||||
return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination)
|
||||
}
|
||||
|
||||
if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
|
||||
nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
|
||||
if sourcePlanet == planetNumber {
|
||||
fmt.Println("unsend: sgi=", sgi)
|
||||
g.ShipGroups[sgi] = UnsendShips(g.ShipGroups[sgi])
|
||||
g.joinEqualGroupsInternal(ri)
|
||||
return nil
|
||||
}
|
||||
|
||||
g.ShipGroups[sgi] = LaunchShips(g.ShipGroups[sgi], planetNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LaunchShips(sg ShipGroup, destination uint) ShipGroup {
|
||||
sg.StateInSpace = &InSpace{
|
||||
Origin: sg.Destination,
|
||||
}
|
||||
sg.Destination = destination
|
||||
return sg
|
||||
}
|
||||
|
||||
func UnsendShips(sg ShipGroup) ShipGroup {
|
||||
sg.Destination = sg.StateInSpace.Origin
|
||||
sg.StateInSpace = nil
|
||||
return sg
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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 TestSendGroup(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, unmovable
|
||||
g.CreateShipType(Race_0.Name, "Fortress", 0, 30, 100, 0, 50)
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup("UnknownRace", 1, 2, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 555, 2, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 1, 222, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 2, 1, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 3, 2, 0),
|
||||
e.GenericErrorText(e.ErrSendShipHasNoDrives))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 1, 2, 100),
|
||||
e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
|
||||
assert.ErrorContains(t,
|
||||
g.SendGroup(Race_0.Name, 1, 3, 0),
|
||||
e.GenericErrorText(e.ErrSendUnreachableDestination))
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 1, 2, 3)) // send 3 of 10
|
||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(7), g.ShipGroups[0].Number)
|
||||
assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State())
|
||||
assert.Equal(t, uint(3), g.ShipGroups[3].Number)
|
||||
assert.Equal(t, game.StateLaunched, g.ShipGroups[3].State())
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 2)) // un-send 2 of 3
|
||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(9), game.MustShipGroup(g, Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State())
|
||||
assert.Equal(t, uint(1), game.MustShipGroup(g, Race_0_idx, 4).Number)
|
||||
assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 4).State())
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 0)) // un-send the rest 1
|
||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State())
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 5, 2, 0))
|
||||
assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 5).State())
|
||||
}
|
||||
@@ -252,7 +252,10 @@ func TestShipGroupEqual(t *testing.T) {
|
||||
|
||||
left.Index = 2
|
||||
assert.True(t, left.Equal(right))
|
||||
|
||||
// dirty hack to equalize loads
|
||||
left.Number = 5
|
||||
left.Load = right.Load / float64(right.Number) * float64(left.Number)
|
||||
assert.True(t, left.Equal(right))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,3 +5,7 @@ type Map struct {
|
||||
Height uint32 `json:"height"`
|
||||
Planet []Planet `json:"planets"`
|
||||
}
|
||||
|
||||
func Destination(x1, y1, x2, y2 float64) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -128,3 +128,11 @@ func (g Game) renamePlanetInternal(ri int, number int, name string) error {
|
||||
g.Map.Planet[pl].Name = n
|
||||
return nil
|
||||
}
|
||||
|
||||
func PlanetByNum(g *Game, number uint) (Planet, bool) {
|
||||
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number })
|
||||
if pi < 0 {
|
||||
return Planet{}, false
|
||||
}
|
||||
return g.Map.Planet[pi], true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user