wip: refactor controller
This commit is contained in:
@@ -67,16 +67,16 @@ func TestEffectiveDefence(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectPlanetGroups(t *testing.T) {
|
||||
c, g := newCache()
|
||||
c, _ := newCache()
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 10)) // 0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 3)) // 1
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // 2
|
||||
g.ShipGroups[2].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} // 2 -> In_Space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) // 3
|
||||
g.ShipGroups[3].Destination = R1_Planet_1_num // 3 -> Planet_1
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 4
|
||||
g.ShipGroups[4].Destination = R0_Planet_0_num // 4 -> Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 10)) // 1 #0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 3)) // 2 #1
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // 3 #2
|
||||
c.ShipGroup(2).StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} // 3 #2 -> In_Space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) // 4 #3
|
||||
c.ShipGroup(3).Destination = R1_Planet_1_num // 4 #3 -> Planet_1
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 5 #4
|
||||
c.ShipGroup(4).Destination = R0_Planet_0_num // 5 #4 -> Planet_0
|
||||
|
||||
planetGroups := controller.CollectPlanetGroups(c)
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestFilterBattleOpponents(t *testing.T) {
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 2
|
||||
undefeatedShip := ship
|
||||
undefeatedShip.Shields = 100
|
||||
assert.NoError(t, c.CreateShipType(Race_1.Name, undefeatedShip.Name, undefeatedShip.Drive, int(undefeatedShip.Armament), undefeatedShip.Weapons, undefeatedShip.Shields, undefeatedShip.Cargo))
|
||||
assert.NoError(t, c.CreateShipType(Race_1_idx, undefeatedShip.Name, undefeatedShip.Drive, int(undefeatedShip.Armament), undefeatedShip.Weapons, undefeatedShip.Shields, undefeatedShip.Cargo))
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, undefeatedShip.Name, R1_Planet_1_num, 1)) // 3
|
||||
|
||||
cacheProbability := make(map[int]map[int]float64)
|
||||
@@ -121,10 +121,10 @@ func TestFilterBattleOpponents(t *testing.T) {
|
||||
assert.True(t, controller.FilterBattleOpponents(c, 1, 0, cacheProbability))
|
||||
|
||||
// Test: reace reations
|
||||
assert.NoError(t, c.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationPeace))
|
||||
assert.NoError(t, c.UpdateRelation(Race_0_idx, Race_1_idx, game.RelationPeace))
|
||||
assert.True(t, controller.FilterBattleOpponents(c, 0, 2, cacheProbability))
|
||||
assert.True(t, controller.FilterBattleOpponents(c, 2, 0, cacheProbability))
|
||||
assert.NoError(t, c.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
|
||||
assert.NoError(t, c.UpdateRelation(Race_0_idx, Race_1_idx, game.RelationWar))
|
||||
|
||||
assert.LessOrEqual(t, controller.DestructionProbability(Cruiser.Weapons, 1, undefeatedShip.Shields, 1, undefeatedShip.EmptyMass()), 0.)
|
||||
assert.True(t, controller.FilterBattleOpponents(c, 1, 3, cacheProbability))
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type Cache struct {
|
||||
g *game.Game
|
||||
cacheRaceIndexByID map[uuid.UUID]int
|
||||
cacheFleetIndexByID map[uuid.UUID]int
|
||||
raceIndexByShipGroupIndex map[int]int
|
||||
shipClassByShipGroupIndex map[int]*game.ShipType
|
||||
planetByPlanetNumber map[uint]*game.Planet
|
||||
@@ -27,53 +28,9 @@ func NewCache(g *game.Game) *Cache {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Cache) Relation(r1, r2 int) game.Relation {
|
||||
if c.cacheRelation == nil {
|
||||
c.cacheRelation = make(map[int]map[int]game.Relation)
|
||||
for r1 := range c.g.Race {
|
||||
for r2 := range c.g.Race {
|
||||
if r1 == r2 {
|
||||
continue
|
||||
}
|
||||
rel := slices.IndexFunc(c.g.Race[r1].Relations, func(r game.RaceRelation) bool { return r.RaceID == c.g.Race[r2].ID })
|
||||
if rel < 0 {
|
||||
panic(fmt.Sprintf("Relation: opponent not found idx=%d", r2))
|
||||
}
|
||||
c.updateRelationCache(r1, r2, c.g.Race[r1].Relations[rel].Relation)
|
||||
// if _, ok := c.cacheRelation[r1]; !ok {
|
||||
// c.cacheRelation[r1] = make(map[int]game.Relation)
|
||||
// }
|
||||
// c.cacheRelation[r1][r2] = c.g.Race[r1].Relations[rel].Relation
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no left race idx=%d", r1))
|
||||
}
|
||||
if v, ok := c.cacheRelation[r1][r2]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no right race idx=%d", r2))
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) updateRelationCache(r1, r2 int, rel game.Relation) {
|
||||
if r1 == r2 {
|
||||
return
|
||||
}
|
||||
if c.cacheRelation == nil {
|
||||
c.cacheRelation = make(map[int]map[int]game.Relation)
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
c.cacheRelation[r1] = make(map[int]game.Relation)
|
||||
}
|
||||
c.cacheRelation[r1][r2] = rel
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType {
|
||||
if c.shipClassByShipGroupIndex == nil {
|
||||
c.fillShipsAndGroups()
|
||||
if c.shipClassByShipGroupIndex == nil || len(c.shipClassByShipGroupIndex) == 0 {
|
||||
c.cacheShipsAndGroups()
|
||||
}
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
if v, ok := c.shipClassByShipGroupIndex[groupIndex]; ok {
|
||||
@@ -97,21 +54,7 @@ func (c *Cache) RaceIndex(ID uuid.UUID) int {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) unsafeDeleteShipGroup(i int) {
|
||||
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
|
||||
delete(c.raceIndexByShipGroupIndex, i)
|
||||
delete(c.shipClassByShipGroupIndex, i)
|
||||
}
|
||||
|
||||
// Internal
|
||||
|
||||
func (c *Cache) validateShipGroupIndex(i int) {
|
||||
if i >= len(c.g.ShipGroups) {
|
||||
panic(fmt.Sprintf("group index out of groups len: %d >= %d", i, len(c.g.ShipGroups)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) fillShipsAndGroups() {
|
||||
func (c *Cache) cacheShipsAndGroups() {
|
||||
if c.raceIndexByShipGroupIndex != nil {
|
||||
clear(c.raceIndexByShipGroupIndex)
|
||||
} else {
|
||||
@@ -133,6 +76,24 @@ func (c *Cache) fillShipsAndGroups() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) cacheShipGroup(groupIndex, ri int, class *game.ShipType) {
|
||||
if c.raceIndexByShipGroupIndex != nil {
|
||||
c.raceIndexByShipGroupIndex[groupIndex] = ri
|
||||
}
|
||||
if c.shipClassByShipGroupIndex != nil {
|
||||
c.shipClassByShipGroupIndex[groupIndex] = class
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) invalidateShipGroupCache() {
|
||||
if c.raceIndexByShipGroupIndex != nil {
|
||||
clear(c.raceIndexByShipGroupIndex)
|
||||
}
|
||||
if c.shipClassByShipGroupIndex != nil {
|
||||
clear(c.shipClassByShipGroupIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func ShipClassIndex(g *game.Game, ri int, classID uuid.UUID) (int, bool) {
|
||||
|
||||
@@ -31,7 +31,6 @@ type Repo interface {
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
param Param
|
||||
Repo Repo
|
||||
Cache *Cache
|
||||
}
|
||||
@@ -54,11 +53,16 @@ func NewController(config Config) (*Controller, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &Controller{
|
||||
param: *c,
|
||||
Repo: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRepoController(r Repo) *Controller {
|
||||
return &Controller{
|
||||
Repo: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) ExecuteState(consumer func(Repo)) error {
|
||||
if err := c.Repo.Lock(); err != nil {
|
||||
return fmt.Errorf("execute: lock failed: %s", err)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func (c *Cache) Race(i int) game.Race {
|
||||
c.validateRaceIndex(i)
|
||||
return c.g.Race[i]
|
||||
}
|
||||
|
||||
func (c *Cache) ListShipGroups(ri int) iter.Seq[*game.ShipGroup] {
|
||||
return c.listShipGroups(ri)
|
||||
}
|
||||
|
||||
func (c *Cache) ListFleets(ri int) iter.Seq[*game.Fleet] {
|
||||
return c.listFleets(ri)
|
||||
}
|
||||
|
||||
func (c *Cache) MustFleetID(ri int, name string) uuid.UUID {
|
||||
for f := range c.listFleets(ri) {
|
||||
if f.Name == name {
|
||||
return f.ID
|
||||
}
|
||||
}
|
||||
panic("fleet not found")
|
||||
}
|
||||
|
||||
func (c *Cache) MustShipGroup(ri int, index uint) *game.ShipGroup {
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.Index == index {
|
||||
return sg
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("race i=%d have no group i=%d", ri, index))
|
||||
}
|
||||
|
||||
func (c *Cache) MustShipClass(ri int, name string) *game.ShipType {
|
||||
st, _, ok := c.ShipClass(ri, name)
|
||||
if !ok {
|
||||
panic("ship class not foind")
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (c *Cache) PutPopulation(pn uint, v float64) {
|
||||
c.putPopulation(pn, v)
|
||||
}
|
||||
|
||||
func (c *Cache) PutColonists(pn uint, v float64) {
|
||||
c.putColonists(pn, v)
|
||||
}
|
||||
|
||||
func (c *Cache) PutMaterial(pn uint, v float64) {
|
||||
c.putMaterial(pn, v)
|
||||
}
|
||||
|
||||
func (c *Cache) RacetTechLevel(ri int, t game.Tech, v float64) {
|
||||
c.racetTechLevel(ri, t, v)
|
||||
}
|
||||
@@ -11,3 +11,10 @@ func validateTypeName(v string) (string, bool) {
|
||||
// TODO: special symbols AND include error check in all user-input test
|
||||
return s, false
|
||||
}
|
||||
|
||||
func maxUint(a, b uint) uint {
|
||||
if b > a {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -90,18 +90,22 @@ func newGame() *game.Game {
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func newCache() (*controller.Cache, *game.Game) {
|
||||
func newCache() (*controller.Cache, *controller.Controller) {
|
||||
g := newGame()
|
||||
c := controller.NewCache(g)
|
||||
return c, g
|
||||
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Gunship, 60, 3, 30, 100, 0))
|
||||
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Freighter, 8, 0, 0, 2, 10))
|
||||
assertNoError(c.CreateShipType(Race_0_idx, ShipType_Cruiser, Cruiser.Drive, int(Cruiser.Armament), Cruiser.Weapons, Cruiser.Shields, Cruiser.Cargo))
|
||||
|
||||
assertNoError(c.CreateShipType(Race_1_idx, Race_1_Gunship, 60, 3, 30, 100, 0))
|
||||
assertNoError(c.CreateShipType(Race_1_idx, Race_1_Freighter, 8, 0, 0, 2, 10))
|
||||
assertNoError(c.CreateShipType(Race_1_idx, ShipType_Cruiser, 15, 2, 15, 15, 0)) // same name - different type (why.)
|
||||
|
||||
ctl := controller.NewRepoController(nil)
|
||||
ctl.Cache = c
|
||||
|
||||
return c, ctl
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"math"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func (c *Cache) FleetState(fleetID uuid.UUID) (game.ShipGroupState, *uint, *game.InSpace) {
|
||||
fi := c.MustFleetIndex(fleetID)
|
||||
ri := c.RaceIndex(c.g.Fleets[fi].OwnerID)
|
||||
var state *game.ShipGroupState
|
||||
var onPlanet *uint
|
||||
var is *game.InSpace
|
||||
for sg := range c.FleetGroups(ri, fi) {
|
||||
if state == nil {
|
||||
s := sg.State()
|
||||
state = &s
|
||||
if planet, ok := sg.OnPlanet(); ok {
|
||||
onPlanet = &planet
|
||||
}
|
||||
is = sg.StateInSpace
|
||||
continue
|
||||
}
|
||||
if *state != sg.State() {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different states", c.g.Race[ri].Name, c.g.Fleets[fi].Name))
|
||||
}
|
||||
if planet, ok := sg.OnPlanet(); ok && onPlanet != nil && *onPlanet != planet {
|
||||
for sg := range c.FleetGroups(ri, fi) {
|
||||
fmt.Println("group", sg.Index, "fleet", sg.FleetID, c.g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination)
|
||||
}
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q are on different planets: %d <> %d", c.g.Race[ri].Name, c.g.Fleets[fi].Name, *onPlanet, planet))
|
||||
}
|
||||
if (is == nil && sg.StateInSpace != nil) || (is != nil && sg.StateInSpace == nil) {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q on_planet and in_space at the same time", c.g.Race[ri].Name, c.g.Fleets[fi].Name))
|
||||
}
|
||||
if is != nil && sg.StateInSpace != nil && !is.Equal(*sg.StateInSpace) {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different is_space states", c.g.Race[ri].Name, c.g.Fleets[fi].Name))
|
||||
}
|
||||
}
|
||||
if state == nil {
|
||||
panic(fmt.Sprintf("FleetState: race's %q fleet %q has no ships", c.g.Race[ri].Name, c.g.Fleets[fi].Name))
|
||||
}
|
||||
return *state, onPlanet, is
|
||||
}
|
||||
|
||||
// TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first.
|
||||
func (c *Cache) FleetSpeed(fl game.Fleet) float64 {
|
||||
result := math.MaxFloat64
|
||||
for sg := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(sg).FleetID == nil || *c.ShipGroup(sg).FleetID != fl.ID {
|
||||
continue
|
||||
}
|
||||
st := c.ShipGroupShipClass(sg)
|
||||
// st := g.mustShipType(g.ShipGroups[sg].TypeID)
|
||||
typeSpeed := c.ShipGroup(sg).Speed(st)
|
||||
if typeSpeed < result {
|
||||
result = typeSpeed
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Controller) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.JoinShipGroupToFleet(ri, fleetName, group, count)
|
||||
}
|
||||
|
||||
func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quantity uint) (err error) {
|
||||
c.validateRaceIndex(ri)
|
||||
name, ok := validateTypeName(fleetName)
|
||||
if !ok {
|
||||
return e.NewEntityTypeNameValidationError("%q", name)
|
||||
}
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
|
||||
// sgi := -1
|
||||
// var maxIndex uint
|
||||
// for i, sg := range g.listIndexShipGroups(ri) {
|
||||
// if sgi < 0 && sg.Index == groupIndex {
|
||||
// sgi = i
|
||||
// }
|
||||
// if sg.Index > maxIndex {
|
||||
// maxIndex = sg.Index
|
||||
// }
|
||||
// }
|
||||
// if sgi < 0 {
|
||||
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
// }
|
||||
|
||||
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).Number < quantity {
|
||||
return e.NewJoinFleetGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
||||
}
|
||||
|
||||
fi, ok := c.fleetIndex(ri, name)
|
||||
if !ok {
|
||||
fi, err = c.createFleet(ri, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
state, onPlanet, _ := c.FleetState(c.g.Fleets[fi].ID)
|
||||
if state != game.StateInOrbit || *onPlanet != c.ShipGroup(sgi).Destination {
|
||||
return e.NewShipsNotOnSamePlanetError("fleet: %s", fleetName)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: if g.ShipGroups[sgi].FleetID != nil { // delete old fleet if empty, ALSO mind breaking group }
|
||||
if quantity > 0 && quantity < c.ShipGroup(sgi).Number {
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
// newGroup := g.ShipGroups[sgi]
|
||||
// newGroup.Number -= quantity
|
||||
// g.ShipGroups[sgi].Number = quantity
|
||||
// newGroup.Index = maxIndex + 1
|
||||
// g.ShipGroups = append(g.ShipGroups, newGroup)
|
||||
}
|
||||
|
||||
// g.ShipGroups[sgi].FleetID = &g.Fleets[fi].ID
|
||||
c.ShipGroupFleet(sgi, &c.g.Fleets[fi].ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) JoinFleets(raceName, fleetSourceName, fleetTargetName string) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.JoinFleets(ri, fleetSourceName, fleetTargetName)
|
||||
}
|
||||
|
||||
func (c *Cache) JoinFleets(ri int, fleetSourceName, fleetTargetName string) (err error) {
|
||||
fiSource, ok := c.fleetIndex(ri, fleetSourceName)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("source fleet %s", fleetSourceName)
|
||||
}
|
||||
fiTarget, ok := c.fleetIndex(ri, fleetTargetName)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("target fleet %s", fleetTargetName)
|
||||
}
|
||||
srcState, planet1, _ := c.FleetState(c.g.Fleets[fiSource].ID)
|
||||
tgtState, planet2, _ := c.FleetState(c.g.Fleets[fiTarget].ID)
|
||||
if srcState != game.StateInOrbit || srcState != tgtState || *planet1 != *planet2 {
|
||||
return e.NewShipsNotOnSamePlanetError()
|
||||
}
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.FleetID != nil && *sg.FleetID == c.g.Fleets[fiSource].ID {
|
||||
sg.FleetID = &c.g.Fleets[fiTarget].ID
|
||||
}
|
||||
}
|
||||
return c.deleteFleetSafe(ri, fleetSourceName)
|
||||
}
|
||||
|
||||
func (c *Cache) createFleet(ri int, name string) (int, error) {
|
||||
c.validateRaceIndex(ri)
|
||||
n, ok := validateTypeName(name)
|
||||
if !ok {
|
||||
return 0, e.NewEntityTypeNameValidationError("%q", n)
|
||||
}
|
||||
if _, ok := c.fleetIndex(ri, n); ok {
|
||||
return 0, e.NewEntityTypeNameDuplicateError("fleet %w", n)
|
||||
}
|
||||
fleets := slices.Clone(c.g.Fleets)
|
||||
fleets = append(fleets, game.Fleet{
|
||||
ID: uuid.New(),
|
||||
OwnerID: c.g.Race[ri].ID,
|
||||
Name: n,
|
||||
})
|
||||
c.g.Fleets = fleets
|
||||
i := len(c.g.Fleets) - 1
|
||||
if c.cacheFleetIndexByID != nil {
|
||||
c.cacheFleetIndexByID[c.g.Fleets[i].ID] = i
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (c *Cache) deleteFleetSafe(ri int, name string) error {
|
||||
fi, ok := c.fleetIndex(ri, name)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("fleet %s", name)
|
||||
}
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.FleetID != nil && *(sg.FleetID) == c.g.Fleets[fi].ID {
|
||||
return e.NewEntityInUseError("fleet %s: race %s, group #%d", name, c.g.Race[ri].Name, sg.Number)
|
||||
}
|
||||
}
|
||||
if c.cacheFleetIndexByID != nil {
|
||||
delete(c.cacheFleetIndexByID, c.g.Fleets[fi].ID)
|
||||
}
|
||||
c.g.Fleets = append(c.g.Fleets[:fi], c.g.Fleets[fi+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Internal funcs
|
||||
|
||||
func (c *Cache) FleetIndex(ID uuid.UUID) (int, bool) {
|
||||
if c.cacheFleetIndexByID == nil {
|
||||
c.cacheFleetIndexByID = make(map[uuid.UUID]int)
|
||||
for i := range c.g.Fleets {
|
||||
c.cacheFleetIndexByID[c.g.Fleets[i].ID] = i
|
||||
}
|
||||
}
|
||||
if v, ok := c.cacheFleetIndexByID[ID]; ok {
|
||||
return v, true
|
||||
} else {
|
||||
return -1, false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rename / [fleetIndex]
|
||||
func (c *Cache) MustFleetIndex(ID uuid.UUID) int {
|
||||
if v, ok := c.FleetIndex(ID); ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("fleet not found by ID=%v", ID))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) FleetGroups(ri, fi int) iter.Seq[*game.ShipGroup] {
|
||||
c.validateRaceIndex(ri)
|
||||
c.validateFleetIndex(fi)
|
||||
return func(yield func(*game.ShipGroup) bool) {
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.FleetID != nil && *sg.FleetID == c.g.Fleets[fi].ID {
|
||||
if !yield(sg) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) listFleets(ri int) iter.Seq[*game.Fleet] {
|
||||
c.validateRaceIndex(ri)
|
||||
return func(yield func(*game.Fleet) bool) {
|
||||
for i := range c.g.Fleets {
|
||||
if c.g.Fleets[i].OwnerID == c.g.Race[ri].ID {
|
||||
if !yield(&c.g.Fleets[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) fleetIndex(ri int, name string) (int, bool) {
|
||||
c.validateRaceIndex(ri)
|
||||
if i := slices.IndexFunc(c.g.Fleets, func(f game.Fleet) bool { return f.OwnerID == c.g.Race[ri].ID && f.Name == name }); i < 0 {
|
||||
return -1, false
|
||||
} else {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) mustFleetIndex(ri int, name string) int {
|
||||
if v, ok := c.fleetIndex(ri, name); ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("fleet not found: race_idx=%d name=%q", ri, name))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) validateFleetIndex(i int) {
|
||||
if i >= len(c.g.Fleets) {
|
||||
panic(fmt.Sprintf("race index out of range: %d >= %d", i, len(c.g.Fleets)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
func (c *Controller) SendFleet(raceName, fleetName string, planetNumber uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, ok := c.Cache.fleetIndex(ri, fleetName)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("fleet %q", fleetName)
|
||||
}
|
||||
return c.Cache.SendFleet(ri, fi, planetNumber)
|
||||
}
|
||||
|
||||
func (c *Cache) SendFleet(ri, fi int, planetNumber uint) error {
|
||||
c.validateRaceIndex(ri)
|
||||
c.validateFleetIndex(fi)
|
||||
state, sourcePlanet, _ := c.FleetState(c.g.Fleets[fi].ID)
|
||||
if game.StateInOrbit != state && game.StateLaunched != state {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
p1, ok := c.Planet(*sourcePlanet)
|
||||
// p1, ok := PlanetByNum(g, *sourcePlanet)
|
||||
if !ok {
|
||||
return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
|
||||
}
|
||||
p2, ok := c.Planet(planetNumber)
|
||||
// p2, ok := PlanetByNum(g, planetNumber)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("destination planet #%d", planetNumber)
|
||||
}
|
||||
rangeToDestination := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X, p1.Y, p2.X, p2.Y)
|
||||
if rangeToDestination > c.g.Race[ri].FlightDistance() {
|
||||
return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination)
|
||||
}
|
||||
|
||||
for sg := range c.FleetGroups(ri, fi) {
|
||||
st := c.ShipType(ri, sg.TypeID)
|
||||
// st, ok := ShipClass(g, ri, sg.TypeID)
|
||||
// if !ok {
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", sg.TypeID)
|
||||
// }
|
||||
if st.DriveBlockMass() == 0 {
|
||||
return e.NewSendShipHasNoDrivesError("Class=%s", st.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if *sourcePlanet == planetNumber {
|
||||
c.UnsendFleet(ri, fi)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.LaunchFleet(ri, fi, planetNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) LaunchFleet(ri, fi int, destination uint) {
|
||||
c.validateRaceIndex(ri)
|
||||
c.validateFleetIndex(fi)
|
||||
for sg := range c.FleetGroups(ri, fi) {
|
||||
c.LaunchShips(*sg, destination)
|
||||
// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
// if sgi < 0 {
|
||||
// panic(fmt.Sprintf("LauncgFleet: cannot find ship group index=%d", sg.Index))
|
||||
// }
|
||||
// g.ShipGroups[sgi] = c.LaunchShips(sg, destination)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) UnsendFleet(ri, fi int) {
|
||||
c.validateRaceIndex(ri)
|
||||
c.validateFleetIndex(fi)
|
||||
for sg := range c.FleetGroups(ri, fi) {
|
||||
c.UnsendShips(*sg)
|
||||
// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
// if sgi < 0 {
|
||||
// panic(fmt.Sprintf("UnsendFleet: cannot find ship group index=%d", sg.Index))
|
||||
// }
|
||||
// g.ShipGroups[sgi] = c.UnsendShips(sg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSendFleet(t *testing.T) {
|
||||
c, g := newCache()
|
||||
// group #1 - in_orbit Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1))
|
||||
// group #2 - in_space (later)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
// group #3 - in_orbit Planet_0, unmovable
|
||||
g.CreateShipType(Race_0.Name, "Fortress", 0, 50, 30, 100, 0)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1))
|
||||
// group #4 - in_orbit Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2))
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetSending := "R0_Fleet_one"
|
||||
fleetInSpace := "R0_Fleet_inSpace"
|
||||
fleetUnmovable := "R0_Fleet_unmovable"
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 1, 0))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 3, 0))
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetInSpace, 2, 0))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 3, 0))
|
||||
// group #2 - in_space
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet("UnknownRace", fleetSending, 2),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, "UnknownFleet", 2),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetInSpace, 2),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetSending, 200),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetSending, 3),
|
||||
e.GenericErrorText(e.ErrSendUnreachableDestination))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetUnmovable, 2),
|
||||
e.GenericErrorText(e.ErrSendShipHasNoDrives))
|
||||
|
||||
assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 2))
|
||||
// fi := slices.IndexFunc(slices.Collect(c.ListFleets(Race_0_idx)), func(f *game.Fleet) bool { return f.Name == fleetSending })
|
||||
state, _, _ := c.FleetState(c.MustFleetID(Race_0_idx, fleetSending))
|
||||
assert.Equal(t, game.StateLaunched, state)
|
||||
for sg := range c.FleetGroups(Race_0_idx, c.MustFleetIndex(c.MustFleetID(Race_0_idx, fleetSending))) {
|
||||
assert.Equal(t, game.StateLaunched, sg.State())
|
||||
}
|
||||
|
||||
assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 0))
|
||||
// fi = slices.IndexFunc(slices.Collect(c.ListFleets(Race_0_idx)), func(f *game.Fleet) bool { return f.Name == fleetSending })
|
||||
state, _, _ = c.FleetState(c.MustFleetID(Race_0_idx, fleetSending))
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
for sg := range c.FleetGroups(Race_0_idx, c.MustFleetIndex(c.MustFleetID(Race_0_idx, fleetSending))) {
|
||||
assert.Equal(t, game.StateInOrbit, sg.State())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"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) {
|
||||
c, g := newCache()
|
||||
var groupIndex uint = 1
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, " ", groupIndex, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
|
||||
// creating ShipGroup
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 5))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 6),
|
||||
e.GenericErrorText(e.ErrJoinFleetGroupNumberNotEnough))
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetOne := "R0_Fleet_one"
|
||||
fleetTwo := "R0_Fleet_two"
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
fleets := slices.Collect(c.ListFleets(Race_0_idx))
|
||||
groups := slices.Collect(c.ListShipGroups(Race_0_idx))
|
||||
assert.Len(t, groups, 1)
|
||||
gi := 0
|
||||
assert.Len(t, fleets, 1)
|
||||
assert.Equal(t, fleets[0].Name, fleetOne)
|
||||
state, _, _ := c.FleetState(fleets[0].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
|
||||
// create another ShipGroup
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
groupIndex = 2
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTwo, groupIndex, 2))
|
||||
fleets = slices.Collect(c.ListFleets(Race_0_idx))
|
||||
groups = slices.Collect(c.ListShipGroups(Race_0_idx))
|
||||
assert.Len(t, groups, 3)
|
||||
assert.Len(t, fleets, 2)
|
||||
assert.Equal(t, fleets[1].Name, fleetTwo)
|
||||
state, _, _ = c.FleetState(fleets[1].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
gi = 2
|
||||
assert.Len(t, groups, 3)
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[1].ID, *groups[gi].FleetID)
|
||||
assert.Equal(t, uint(2), groups[gi].Number)
|
||||
assert.Equal(t, uint(3), groups[gi].Index)
|
||||
|
||||
gi = 1
|
||||
assert.Nil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, uint(1), groups[gi].Number)
|
||||
assert.Equal(t, uint(2), groups[gi].Index)
|
||||
|
||||
groupIndex = groups[gi].Index
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
fleets = slices.Collect(c.ListFleets(Race_0_idx))
|
||||
assert.Len(t, fleets, 2)
|
||||
groups = slices.Collect(c.ListShipGroups(Race_0_idx))
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
state, _, _ = c.FleetState(fleets[0].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
// group not In_Orbit
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7))
|
||||
gi = 3
|
||||
c.ShipGroup(gi).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 1,
|
||||
}
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, c.ShipGroup(gi).Index, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
c.ShipGroup(gi).StateInSpace = nil
|
||||
|
||||
// existing fleet not on the same planet or in_orbit
|
||||
c.ShipGroup(0).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 1,
|
||||
}
|
||||
c.ShipGroup(2).StateInSpace = c.ShipGroup(0).StateInSpace
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, c.ShipGroup(gi).Index, 0),
|
||||
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
}
|
||||
|
||||
func TestJoinFleets(t *testing.T) {
|
||||
c, g := newCache()
|
||||
// creating ShipGroup #1 at Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
||||
// creating ShipGroup #2 at Planet_2
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_2_num, 2)) // group #2
|
||||
// creating ShipGroup #3 at Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // group #3
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetPlanet2 := "R0_Fleet_On_Planet_2"
|
||||
fleetSource := "R0_Fleet_one"
|
||||
fleetTarget := "R0_Fleet_two"
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSource, 1, 0))
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTarget, 3, 0))
|
||||
assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSource, fleetTarget))
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetPlanet2, 2, 0))
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetPlanet2, fleetTarget),
|
||||
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
}
|
||||
@@ -12,7 +12,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
|
||||
g.Age += 1
|
||||
|
||||
// 01. Корабли, где это возможно, объединяются в группы.
|
||||
game.JoinEqualGroups(g)
|
||||
c.Cache.CmdJoinEqualGroups()
|
||||
|
||||
// 02. Враждующие корабли вступают в схватку.
|
||||
battles := ProduceBattles(c.Cache)
|
||||
|
||||
@@ -27,3 +27,42 @@ func (c *Cache) MustPlanet(planetNumber uint) *game.Planet {
|
||||
panic(fmt.Sprintf("Planet: not found by number=%d", planetNumber))
|
||||
}
|
||||
}
|
||||
|
||||
// Свободный производственный потенциал (L)
|
||||
// промышленность * 0.75 + население * 0.25
|
||||
// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
|
||||
func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 {
|
||||
p := c.MustPlanet(planetNumber)
|
||||
// p, err := g.PlanetByNumber(planetNumber)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
var busyResources float64
|
||||
for sg := range c.g.ShipsInUpgrade(p.Number) {
|
||||
busyResources += sg.StateUpgrade.Cost()
|
||||
}
|
||||
return game.PlanetProduction(p.Industry, p.Population) - busyResources
|
||||
}
|
||||
|
||||
// Internal fincs
|
||||
|
||||
func (c *Cache) putPopulation(pn uint, v float64) {
|
||||
c.MustPlanet(pn).Population = v
|
||||
}
|
||||
|
||||
func (c *Cache) putColonists(pn uint, v float64) {
|
||||
c.MustPlanet(pn).Colonists = v
|
||||
}
|
||||
|
||||
func (c *Cache) putMaterial(pn uint, v float64) {
|
||||
c.MustPlanet(pn).Material = v
|
||||
}
|
||||
|
||||
func UnloadColonists(p game.Planet, v float64) game.Planet {
|
||||
p.Population += v * 8
|
||||
if p.Population > p.Size {
|
||||
p.Colonists += (p.Population - p.Size) / 8
|
||||
p.Population = p.Size
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
+111
-45
@@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
@@ -8,69 +9,129 @@ import (
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
// func (c *Cache) CmdRelation(hostRace, opponentRace string) (game.RaceRelation, error) {
|
||||
// ri, err := c.raceIndex(hostRace)
|
||||
// if err != nil {
|
||||
// return game.RaceRelation{}, err
|
||||
// }
|
||||
// other, err := c.raceIndex(opponentRace)
|
||||
// if err != nil {
|
||||
// return game.RaceRelation{}, err
|
||||
// }
|
||||
// if ri == other {
|
||||
// return game.RaceRelation{
|
||||
// RaceID: c.g.Race[ri].ID,
|
||||
// Relation: game.RelationPeace,
|
||||
// }, nil
|
||||
// }
|
||||
// rel := slices.IndexFunc(c.g.Race[ri].Relations, func(r game.RaceRelation) bool { return r.RaceID == c.g.Race[other].ID })
|
||||
// if rel < 0 {
|
||||
// return game.RaceRelation{}, e.NewGameStateError("Relation: opponent not found")
|
||||
// }
|
||||
// return c.g.Race[ri].Relations[rel], nil
|
||||
func (c Controller) Relation(from, to string) (game.Relation, error) {
|
||||
r1, err := c.Cache.raceIndex(from)
|
||||
if err != nil {
|
||||
return game.Relation(""), err
|
||||
}
|
||||
r2, err := c.Cache.raceIndex(to)
|
||||
if err != nil {
|
||||
return game.Relation(""), err
|
||||
}
|
||||
return c.Cache.Relation(r1, r2), nil
|
||||
}
|
||||
|
||||
// // return g.relationInternal(ri, other)
|
||||
// }
|
||||
|
||||
func (c *Cache) UpdateRelation(race, opponent string, rel game.Relation) error {
|
||||
r1, err := c.raceIndex(race)
|
||||
func (c Controller) UpdateRelation(race, opponent string, rel game.Relation) error {
|
||||
ri, err := c.Cache.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var r2 int
|
||||
var other int
|
||||
if race == opponent {
|
||||
r2 = r1
|
||||
} else if r2, err = c.raceIndex(opponent); err != nil {
|
||||
other = ri
|
||||
} else if other, err = c.Cache.raceIndex(opponent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r2 == r1 {
|
||||
return c.Cache.UpdateRelation(ri, other, rel)
|
||||
}
|
||||
|
||||
func (c *Cache) Relation(r1, r2 int) game.Relation {
|
||||
if c.cacheRelation == nil {
|
||||
c.cacheRelation = make(map[int]map[int]game.Relation)
|
||||
for r1 := range c.g.Race {
|
||||
for r2 := range c.g.Race {
|
||||
if r1 == r2 {
|
||||
continue
|
||||
}
|
||||
rel := slices.IndexFunc(c.g.Race[r1].Relations, func(r game.RaceRelation) bool { return r.RaceID == c.g.Race[r2].ID })
|
||||
if rel < 0 {
|
||||
panic(fmt.Sprintf("Relation: opponent not found idx=%d", r2))
|
||||
}
|
||||
c.updateRelationCache(r1, r2, c.g.Race[r1].Relations[rel].Relation)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no left race idx=%d", r1))
|
||||
}
|
||||
if v, ok := c.cacheRelation[r1][r2]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no right race idx=%d", r2))
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) updateRelationCache(r1, r2 int, rel game.Relation) {
|
||||
if r1 == r2 {
|
||||
return
|
||||
}
|
||||
if c.cacheRelation == nil {
|
||||
c.cacheRelation = make(map[int]map[int]game.Relation)
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
c.cacheRelation[r1] = make(map[int]game.Relation)
|
||||
}
|
||||
c.cacheRelation[r1][r2] = rel
|
||||
}
|
||||
|
||||
func (c *Cache) GiveVotes(race, recipient string) error {
|
||||
ri, err := c.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rec, err := c.raceIndex(recipient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.g.Race[ri].Vote = c.g.Race[rec].ID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) Voted(ri int) int {
|
||||
c.validateRaceIndex(ri)
|
||||
return c.RaceIndex(c.g.Race[ri].Vote)
|
||||
}
|
||||
|
||||
func (c *Cache) UpdateRelation(ri, other int, rel game.Relation) (err error) {
|
||||
defer func() {
|
||||
if err == nil && c.cacheRelation != nil {
|
||||
c.updateRelationCache(ri, other, rel)
|
||||
}
|
||||
}()
|
||||
for o := range c.g.Race[ri].Relations {
|
||||
switch {
|
||||
case ri == other:
|
||||
c.g.Race[ri].Relations[o].Relation = rel
|
||||
case c.g.Race[ri].Relations[o].RaceID == c.g.Race[other].ID:
|
||||
c.g.Race[ri].Relations[o].Relation = rel
|
||||
return nil
|
||||
}
|
||||
for o := range c.g.Race[r1].Relations {
|
||||
if c.g.Race[r1].Relations[o].RaceID == c.g.Race[r2].ID {
|
||||
c.g.Race[r1].Relations[o].Relation = rel
|
||||
if c.cacheRelation != nil {
|
||||
c.updateRelationCache(r1, r2, rel)
|
||||
}
|
||||
return nil
|
||||
if ri != other {
|
||||
err = e.NewGameStateError("UpdateRelation: opponent not found")
|
||||
}
|
||||
// switch {
|
||||
// case r1 == r2:
|
||||
// c.g.Race[r1].Relations[o].Relation = rel
|
||||
// case c.g.Race[r1].Relations[o].RaceID == c.g.Race[r2].ID:
|
||||
return
|
||||
|
||||
// for o := range c.g.Race[r1].Relations {
|
||||
// if c.g.Race[r1].Relations[o].RaceID == c.g.Race[r2].ID {
|
||||
// c.g.Race[r1].Relations[o].Relation = rel
|
||||
// if c.cacheRelation != nil {
|
||||
// c.updateRelationCache(r1, r2, rel)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
}
|
||||
return e.NewGameStateError("UpdateRelation: opponent not found")
|
||||
// if r1 != r2 {
|
||||
// }
|
||||
// return e.NewGameStateError("UpdateRelation: opponent not found")
|
||||
// }
|
||||
// return nil
|
||||
// return g.updateRelationInternal(ri, other, rel)
|
||||
}
|
||||
|
||||
func (c *Cache) validateRaceIndex(i int) {
|
||||
if i >= len(c.g.Race) {
|
||||
panic(fmt.Sprintf("race index out of range: %d >= %d", i, len(c.g.Race)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) raceIndex(name string) (int, error) {
|
||||
@@ -80,3 +141,8 @@ func (c *Cache) raceIndex(name string) (int, error) {
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (c *Cache) racetTechLevel(ri int, t game.Tech, v float64) {
|
||||
c.validateFleetIndex(ri)
|
||||
c.g.Race[ri].Tech = c.g.Race[ri].Tech.Set(t, v)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGiveVotes(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
|
||||
assert.Equal(t, c.Voted(Race_0_idx), Race_0_idx)
|
||||
assert.Equal(t, c.Voted(Race_1_idx), Race_1_idx)
|
||||
|
||||
assert.NoError(t, c.GiveVotes(Race_0.Name, Race_1.Name))
|
||||
assert.Equal(t, Race_1_idx, c.Voted(Race_0_idx))
|
||||
assert.Equal(t, Race_1_idx, c.Voted(Race_1_idx))
|
||||
|
||||
assert.ErrorContains(t, c.GiveVotes("UnknownRace", Race_1.Name), e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t, c.GiveVotes(Race_0.Name, "UnknownRace"), e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -8,6 +9,14 @@ import (
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func (c *Controller) CreateShipType(raceName, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.CreateShipType(ri, typeName, drive, ammo, weapons, shileds, cargo)
|
||||
}
|
||||
|
||||
func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) {
|
||||
i := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name })
|
||||
if i < 0 {
|
||||
@@ -16,25 +25,17 @@ func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) {
|
||||
return &c.g.Race[ri].ShipTypes[i], i, true
|
||||
}
|
||||
|
||||
func (c *Cache) CreateShipType(raceName, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error {
|
||||
ri, err := c.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.createShipTypeInternal(ri, typeName, drive, ammo, weapons, shileds, cargo)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) createShipTypeInternal(ri int, name string, drive float64, ammo int, weapons, shileds, cargo float64) (int, error) {
|
||||
func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error {
|
||||
c.validateRaceIndex(ri)
|
||||
if err := checkShipTypeValues(drive, ammo, weapons, shileds, cargo); err != nil {
|
||||
return -1, err
|
||||
return err
|
||||
}
|
||||
n, ok := validateTypeName(name)
|
||||
n, ok := validateTypeName(typeName)
|
||||
if !ok {
|
||||
return -1, e.NewEntityTypeNameValidationError("%q", n)
|
||||
return e.NewEntityTypeNameValidationError("%q", n)
|
||||
}
|
||||
if st := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name }); st >= 0 {
|
||||
return -1, e.NewEntityTypeNameDuplicateError("ship type %w", c.g.Race[ri].ShipTypes[st].Name)
|
||||
if st := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == typeName }); st >= 0 {
|
||||
return e.NewEntityTypeNameDuplicateError("ship type %w", c.g.Race[ri].ShipTypes[st].Name)
|
||||
}
|
||||
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes, game.ShipType{
|
||||
ID: uuid.New(),
|
||||
@@ -47,7 +48,124 @@ func (c *Cache) createShipTypeInternal(ri int, name string, drive float64, ammo
|
||||
Armament: uint(ammo),
|
||||
},
|
||||
})
|
||||
return len(c.g.Race[ri].ShipTypes) - 1, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) MergeShipType(race, name, targetName string) error {
|
||||
ri, err := c.Cache.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.MergeShipType(ri, name, targetName)
|
||||
}
|
||||
|
||||
func (c *Cache) MergeShipType(ri int, name, targetName string) error {
|
||||
c.validateRaceIndex(ri)
|
||||
st, sti, ok := c.ShipClass(ri, name)
|
||||
|
||||
// st := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name })
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("source ship type %w", name)
|
||||
}
|
||||
if name == targetName {
|
||||
return e.NewEntityTypeNameEqualityError("ship type %q", targetName)
|
||||
}
|
||||
tt, _, ok := c.ShipClass(ri, name)
|
||||
// tt := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == targetName })
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("target ship type %w", name)
|
||||
}
|
||||
if !st.Equal(*tt) {
|
||||
return e.NewMergeShipTypeNotEqualError()
|
||||
}
|
||||
|
||||
// switch planet productions to the new type
|
||||
for pl := range c.g.Map.Planet {
|
||||
if c.g.Map.Planet[pl].Owner == c.g.Race[ri].ID &&
|
||||
c.g.Map.Planet[pl].Production.Type == game.ProductionShip &&
|
||||
c.g.Map.Planet[pl].Production.SubjectID != nil &&
|
||||
*c.g.Map.Planet[pl].Production.SubjectID == st.ID {
|
||||
|
||||
c.g.Map.Planet[pl].Production.SubjectID = &tt.ID
|
||||
}
|
||||
}
|
||||
|
||||
// switch ship groups to the new type
|
||||
for sg := range c.g.ShipGroups {
|
||||
if c.g.ShipGroups[sg].OwnerID == c.g.Race[ri].ID && c.g.ShipGroups[sg].TypeID == st.ID {
|
||||
c.g.ShipGroups[sg].TypeID = tt.ID
|
||||
}
|
||||
}
|
||||
|
||||
// remove the source type
|
||||
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes[:sti], c.g.Race[ri].ShipTypes[sti+1:]...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) DeleteShipType(raceName, typeName string) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.DeleteShipType(ri, typeName)
|
||||
}
|
||||
|
||||
func (c *Cache) DeleteShipType(ri int, name string) error {
|
||||
c.validateRaceIndex(ri)
|
||||
st, i, ok := c.ShipClass(ri, name)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("ship type %w", name)
|
||||
}
|
||||
// st := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name })
|
||||
// if st < 0 {
|
||||
// return e.NewEntityNotExistsError("ship type %w", name)
|
||||
// }
|
||||
if pl := slices.IndexFunc(c.g.Map.Planet, func(p game.Planet) bool {
|
||||
return p.Production.Type == game.ProductionShip &&
|
||||
p.Production.SubjectID != nil &&
|
||||
st.ID == *p.Production.SubjectID
|
||||
}); pl >= 0 {
|
||||
return e.NewDeleteShipTypePlanetProductionError(c.g.Map.Planet[pl].Name)
|
||||
}
|
||||
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.TypeID == st.ID {
|
||||
return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index)
|
||||
}
|
||||
}
|
||||
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes[:i], c.g.Race[ri].ShipTypes[i+1:]...)
|
||||
// FIXME: update cache ON ALL FUNCs IN THIS FILE
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShipTypes used for tests only
|
||||
func (c *Controller) ShipTypes(race string) ([]*game.ShipType, error) {
|
||||
ri, err := c.Cache.raceIndex(race)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Cache.ShipTypes(ri), nil
|
||||
}
|
||||
|
||||
// ShipTypes used for tests only
|
||||
func (c *Cache) ShipTypes(ri int) []*game.ShipType {
|
||||
c.validateRaceIndex(ri)
|
||||
result := make([]*game.ShipType, len(c.g.Race[ri].ShipTypes))
|
||||
for i := range c.g.Race[ri].ShipTypes {
|
||||
result[i] = &c.g.Race[ri].ShipTypes[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Cache) ShipType(ri int, ID uuid.UUID) *game.ShipType {
|
||||
c.validateRaceIndex(ri)
|
||||
for i := range c.g.Race[ri].ShipTypes {
|
||||
if c.g.Race[ri].ShipTypes[i].ID == ID {
|
||||
return &c.g.Race[ri].ShipTypes[i]
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("ship_class not found: race_id=%d id=%v", ri, ID))
|
||||
}
|
||||
|
||||
func checkShipTypeValues(d float64, a int, w, s, c float64) error {
|
||||
|
||||
@@ -3,7 +3,10 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
@@ -23,8 +26,9 @@ func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quan
|
||||
return e.NewEntityNotOwnedError("planet #%d", planetNumber)
|
||||
}
|
||||
|
||||
// FIXME: move maxindex to appendShipGroup
|
||||
nextIndex := c.ShipGroupMaxIndex(ri) + 1
|
||||
c.g.ShipGroups = append(c.g.ShipGroups, game.ShipGroup{
|
||||
c.appendShipGroup(ri, class, &game.ShipGroup{
|
||||
Index: nextIndex,
|
||||
OwnerID: c.g.Race[ri].ID,
|
||||
TypeID: class.ID,
|
||||
@@ -37,12 +41,6 @@ func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quan
|
||||
game.TechCargo: c.g.Race[ri].TechLevel(game.TechCargo),
|
||||
},
|
||||
})
|
||||
if c.raceIndexByShipGroupIndex != nil {
|
||||
c.raceIndexByShipGroupIndex[len(c.g.ShipGroups)-1] = ri
|
||||
}
|
||||
if c.shipClassByShipGroupIndex != nil {
|
||||
c.shipClassByShipGroupIndex[len(c.g.ShipGroups)-1] = class
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,6 +50,19 @@ func (c *Cache) ShipGroup(groupIndex int) *game.ShipGroup {
|
||||
return &c.g.ShipGroups[groupIndex]
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupFleet(groupIndex int, fID *uuid.UUID) {
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
c.g.ShipGroups[groupIndex].FleetID = fID
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupShipsNumber(groupIndex int, number uint) {
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
if c.g.ShipGroups[groupIndex].Number > 0 {
|
||||
c.g.ShipGroups[groupIndex].Load = c.g.ShipGroups[groupIndex].Load / float64(c.g.ShipGroups[groupIndex].Number) * float64(number)
|
||||
}
|
||||
c.g.ShipGroups[groupIndex].Number = number
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupsIndex() iter.Seq[int] {
|
||||
return func(yield func(int) bool) {
|
||||
for i := range c.g.ShipGroups {
|
||||
@@ -73,10 +84,10 @@ func (c *Cache) ShipGroupMaxIndex(ri int) uint {
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupOwnerRaceIndex(groupIndex int) int {
|
||||
if c.raceIndexByShipGroupIndex == nil {
|
||||
c.fillShipsAndGroups()
|
||||
}
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
if len(c.raceIndexByShipGroupIndex) == 0 {
|
||||
c.cacheShipsAndGroups()
|
||||
}
|
||||
if v, ok := c.raceIndexByShipGroupIndex[groupIndex]; ok {
|
||||
return v
|
||||
} else {
|
||||
@@ -105,3 +116,527 @@ func (c *Cache) DeleteKilledShipGroups() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) JoinEqualGroups(raceName string) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Cache.JoinEqualGroups(ri)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) CmdJoinEqualGroups() {
|
||||
for i := range c.g.Race {
|
||||
c.JoinEqualGroups(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) JoinEqualGroups(ri int) {
|
||||
c.validateRaceIndex(ri)
|
||||
shipGroups := slices.Collect(c.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
|
||||
}
|
||||
|
||||
toDelete := make([]int, 0)
|
||||
for i := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(i).OwnerID == c.g.Race[ri].ID {
|
||||
toDelete = append(toDelete, i)
|
||||
}
|
||||
}
|
||||
|
||||
// c.g.ShipGroups = slices.DeleteFunc(c.g.ShipGroups, func(v game.ShipGroup) bool { return v.OwnerID == c.g.Race[ri].ID })
|
||||
for _, idx := range toDelete {
|
||||
c.unsafeDeleteShipGroup(idx)
|
||||
}
|
||||
|
||||
// c.g.ShipGroups = append(c.g.ShipGroups, shipGroups...)
|
||||
for i := range shipGroups {
|
||||
c.g.ShipGroups = append(c.g.ShipGroups, *shipGroups[i])
|
||||
}
|
||||
|
||||
c.invalidateShipGroupCache()
|
||||
}
|
||||
|
||||
func (c *Controller) BreakGroup(raceName string, groupIndex, quantity uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.BreakGroup(ri, groupIndex, quantity)
|
||||
}
|
||||
|
||||
func (c *Controller) DisassembleGroup(raceName string, groupIndex, quantity uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.DisassembleGroup(ri, groupIndex, quantity)
|
||||
}
|
||||
|
||||
func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error {
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).Number < quantity {
|
||||
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
||||
}
|
||||
|
||||
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
||||
if !ok {
|
||||
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
}
|
||||
|
||||
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
||||
// if pl < 0 {
|
||||
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
// }
|
||||
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// var sti int
|
||||
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
||||
// // hard to test, need manual game data invalidation
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
||||
// }
|
||||
|
||||
if quantity > 0 && quantity < c.ShipGroup(sgi).Number {
|
||||
// make new group for disassembly
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).CargoType != nil {
|
||||
ct := *c.ShipGroup(sgi).CargoType
|
||||
load := c.ShipGroup(sgi).Load
|
||||
switch ct {
|
||||
case game.CargoColonist:
|
||||
if p.Owner == c.g.Race[ri].ID {
|
||||
pn := UnloadColonists(*p, load)
|
||||
p = &pn
|
||||
}
|
||||
case game.CargoMaterial:
|
||||
p.Material += load
|
||||
case game.CargoCapital:
|
||||
p.Capital += load
|
||||
}
|
||||
}
|
||||
|
||||
p.Material += c.ShipGroup(sgi).EmptyMass(st)
|
||||
|
||||
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
||||
c.unsafeDeleteShipGroup(sgi)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ct, ok := game.CargoTypeSet[cargoType]
|
||||
if !ok {
|
||||
return e.NewCargoTypeInvalidError(cargoType)
|
||||
}
|
||||
return c.Cache.LoadCargo(ri, groupIndex, ct, ships, quantity)
|
||||
}
|
||||
|
||||
// Корабль может нести только один тип груза одновременно.
|
||||
// Возможные типы груза - это колонисты, сырье и промышленность.
|
||||
// Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется.
|
||||
func (c *Cache) LoadCargo(ri int, groupIndex uint, ct game.CargoType, ships uint, quantity float64) error {
|
||||
if ships == 0 && quantity > 0 {
|
||||
return e.NewCargoQuantityWithoutGroupBreakError()
|
||||
}
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
||||
if !ok {
|
||||
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
}
|
||||
|
||||
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
||||
// if pl < 0 {
|
||||
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
// }
|
||||
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
||||
return e.NewEntityNotOwnedError("planet #%d", p.Number)
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// var sti int
|
||||
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
||||
// // hard to test, need manual game data invalidation
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
||||
// }
|
||||
if st.Cargo < 1 {
|
||||
return e.NewNoCargoBayError("ship_type %q", st.Name)
|
||||
}
|
||||
if c.ShipGroup(sgi).CargoType != nil && *c.ShipGroup(sgi).CargoType != ct {
|
||||
return e.NewCargoLoadNotEqualError("cargo: %v", *c.ShipGroup(sgi).CargoType)
|
||||
}
|
||||
if ships > 0 && ships < c.ShipGroup(sgi).Number {
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
capacity := c.ShipGroup(sgi).CargoCapacity(st)
|
||||
freeShipGroupCargoLoad := capacity - c.ShipGroup(sgi).Load
|
||||
if freeShipGroupCargoLoad == 0 {
|
||||
return e.NewCargoLoadNoSpaceLeftError()
|
||||
}
|
||||
var availableOnPlanet *float64
|
||||
switch ct {
|
||||
case game.CargoMaterial:
|
||||
availableOnPlanet = &p.Material
|
||||
case game.CargoCapital:
|
||||
availableOnPlanet = &p.Capital
|
||||
case game.CargoColonist:
|
||||
availableOnPlanet = &p.Colonists
|
||||
default:
|
||||
return e.NewGameStateError("CargoType not accepted: %v", ct)
|
||||
}
|
||||
if quantity > *availableOnPlanet || *availableOnPlanet == 0 {
|
||||
return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", p.Number, ct, *availableOnPlanet)
|
||||
}
|
||||
toBeLoaded := quantity
|
||||
if quantity == 0 {
|
||||
toBeLoaded = *availableOnPlanet
|
||||
}
|
||||
if toBeLoaded > freeShipGroupCargoLoad {
|
||||
toBeLoaded = freeShipGroupCargoLoad
|
||||
}
|
||||
*availableOnPlanet = *availableOnPlanet - toBeLoaded
|
||||
c.ShipGroup(sgi).Load += toBeLoaded
|
||||
if c.ShipGroup(sgi).Load > 0 {
|
||||
c.ShipGroup(sgi).CargoType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.UnloadCargo(ri, groupIndex, ships, quantity)
|
||||
}
|
||||
|
||||
// Промышленность и Сырье могут быть выгружены на любой планете.
|
||||
// Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты.
|
||||
func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float64) error {
|
||||
c.validateRaceIndex(ri)
|
||||
if ships == 0 && quantity > 0 {
|
||||
return e.NewCargoQuantityWithoutGroupBreakError()
|
||||
}
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// var sti int
|
||||
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
||||
// // hard to test, need manual game data invalidation
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
||||
// }
|
||||
if st.Cargo < 1 {
|
||||
return e.NewNoCargoBayError("ship_type %q", st.Name)
|
||||
}
|
||||
if c.ShipGroup(sgi).CargoType == nil || c.ShipGroup(sgi).Load == 0 {
|
||||
return e.NewCargoUnloadEmptyError()
|
||||
}
|
||||
ct := *c.ShipGroup(sgi).CargoType
|
||||
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
||||
if !ok {
|
||||
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
}
|
||||
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
||||
// if pl < 0 {
|
||||
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
// }
|
||||
if ct == game.CargoColonist {
|
||||
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
||||
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
||||
}
|
||||
if p.Owner == uuid.Nil {
|
||||
p.Owner = c.g.Race[ri].ID
|
||||
}
|
||||
}
|
||||
var availableOnPlanet *float64
|
||||
switch ct {
|
||||
case game.CargoMaterial:
|
||||
availableOnPlanet = &p.Material
|
||||
case game.CargoCapital:
|
||||
availableOnPlanet = &p.Capital
|
||||
case game.CargoColonist:
|
||||
availableOnPlanet = &p.Colonists
|
||||
default:
|
||||
return e.NewGameStateError("CargoType not accepted: %v", ct)
|
||||
}
|
||||
if ships > 0 && ships < c.ShipGroup(sgi).Number {
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
toBeUnloaded := quantity
|
||||
if quantity == 0 {
|
||||
toBeUnloaded = c.ShipGroup(sgi).Load
|
||||
}
|
||||
if toBeUnloaded > c.ShipGroup(sgi).Load {
|
||||
return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load)
|
||||
}
|
||||
*availableOnPlanet += toBeUnloaded
|
||||
c.ShipGroup(sgi).Load -= toBeUnloaded
|
||||
if c.ShipGroup(sgi).Load == 0 {
|
||||
c.ShipGroup(sgi).CargoType = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
riAccept, err := c.Cache.raceIndex(raceAcceptor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.GiveawayGroup(ri, riAccept, groupIndex, quantity)
|
||||
}
|
||||
|
||||
func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err error) {
|
||||
if ri == riAccept {
|
||||
return e.NewSameRaceError(c.g.Race[riAccept].Name)
|
||||
}
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
if c.ShipGroup(sgi).Number < quantity {
|
||||
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
||||
}
|
||||
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// var sti int
|
||||
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
||||
// // hard to test, need manual game data invalidation
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
||||
// }
|
||||
|
||||
var stAcc int
|
||||
if stAcc = slices.IndexFunc(c.g.Race[riAccept].ShipTypes, func(v game.ShipType) bool { return v.Name == st.Name }); stAcc >= 0 &&
|
||||
!st.Equal(c.g.Race[riAccept].ShipTypes[stAcc]) {
|
||||
return e.NewGiveawayGroupShipsTypeNotEqualError("race %w, ship type %w", c.g.Race[riAccept].Name, c.g.Race[riAccept].ShipTypes[stAcc].Name)
|
||||
}
|
||||
if stAcc < 0 {
|
||||
err = c.CreateShipType(riAccept,
|
||||
st.Name,
|
||||
st.Drive,
|
||||
int(st.Armament),
|
||||
st.Weapons,
|
||||
st.Shields,
|
||||
st.Cargo)
|
||||
stAcc = len(c.g.Race[ri].ShipTypes) - 1
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// maxIndex := c.ShipGroupMaxIndex(riAccept)
|
||||
// var maxIndex uint
|
||||
// for sg := range g.listShipGroups(riAccept) {
|
||||
// if sg.Index > maxIndex {
|
||||
// maxIndex = sg.Index
|
||||
// }
|
||||
// }
|
||||
|
||||
c.appendShipGroup(ri, st, &game.ShipGroup{
|
||||
// Index: maxIndex + 1,
|
||||
OwnerID: c.g.Race[riAccept].ID,
|
||||
TypeID: c.g.Race[riAccept].ShipTypes[stAcc].ID,
|
||||
Number: uint(quantity),
|
||||
|
||||
CargoType: c.ShipGroup(sgi).CargoType,
|
||||
Load: c.ShipGroup(sgi).Load,
|
||||
|
||||
Tech: maps.Clone(c.ShipGroup(sgi).Tech),
|
||||
|
||||
Destination: c.ShipGroup(sgi).Destination,
|
||||
StateInSpace: c.ShipGroup(sgi).StateInSpace,
|
||||
StateUpgrade: c.ShipGroup(sgi).StateUpgrade,
|
||||
})
|
||||
|
||||
// g.ShipGroups = append(g.ShipGroups, game.ShipGroup{
|
||||
// Index: maxIndex + 1,
|
||||
// OwnerID: g.Race[riAccept].ID,
|
||||
// TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
||||
// Number: uint(quantity),
|
||||
|
||||
// CargoType: c.ShipGroup(sgi).CargoType,
|
||||
// Load: c.ShipGroup(sgi).Load,
|
||||
|
||||
// Tech: maps.Clone(c.ShipGroup(sgi).Tech),
|
||||
|
||||
// Destination: c.ShipGroup(sgi).Destination,
|
||||
// StateInSpace: c.ShipGroup(sgi).StateInSpace,
|
||||
// StateUpgrade: c.ShipGroup(sgi).StateUpgrade,
|
||||
// })
|
||||
|
||||
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
|
||||
c.unsafeDeleteShipGroup(sgi)
|
||||
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
||||
} else {
|
||||
c.ShipGroup(sgi).Number -= quantity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) BreakGroup(ri int, groupIndex, quantity uint) error {
|
||||
c.validateRaceIndex(ri)
|
||||
sgi := -1
|
||||
for i := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroupOwnerRaceIndex(i) == ri && c.ShipGroup(i).Index == groupIndex {
|
||||
sgi = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if sgi < 0 {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).Number < quantity {
|
||||
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
||||
}
|
||||
|
||||
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
|
||||
c.ShipGroupFleet(sgi, nil)
|
||||
} else {
|
||||
if _, err := c.breakGroupSafe(ri, groupIndex, quantity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) {
|
||||
c.validateRaceIndex(ri)
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return -1, e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
// maxIndex := c.ShipGroupMaxIndex(ri)
|
||||
if c.ShipGroup(sgi).Number < newGroupShips {
|
||||
return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", c.ShipGroup(sgi).Index, c.ShipGroup(sgi).Number, newGroupShips)
|
||||
}
|
||||
newGroup := *c.ShipGroup(sgi)
|
||||
if c.ShipGroup(sgi).CargoType != nil {
|
||||
newGroup.Load = c.ShipGroup(sgi).Load / float64(c.ShipGroup(sgi).Number) * float64(newGroupShips)
|
||||
// c.ShipGroup(sgi).Load -= newGroup.Load
|
||||
}
|
||||
newGroup.Number = newGroupShips
|
||||
c.ShipGroupShipsNumber(sgi, c.ShipGroup(sgi).Number-newGroup.Number)
|
||||
// c.ShipGroup(sgi).Number -= newGroup.Number
|
||||
// newGroup.Index = maxIndex + 1
|
||||
newGroup.FleetID = nil
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// c.g.ShipGroups = append(c.g.ShipGroups, newGroup)
|
||||
return c.appendShipGroup(ri, st, &newGroup), nil
|
||||
}
|
||||
|
||||
// Internal funcs
|
||||
|
||||
func (c *Cache) appendShipGroup(ri int, class *game.ShipType, sg *game.ShipGroup) int {
|
||||
c.validateRaceIndex(ri)
|
||||
sg.Index = c.ShipGroupMaxIndex(ri) + 1
|
||||
c.g.ShipGroups = append(c.g.ShipGroups, *sg)
|
||||
i := len(c.g.ShipGroups) - 1
|
||||
c.cacheShipGroup(i, ri, class)
|
||||
return i
|
||||
}
|
||||
|
||||
func (c *Cache) raceShipGroupIndex(ri int, index uint) (int, bool) {
|
||||
c.validateRaceIndex(ri)
|
||||
for i := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroupOwnerRaceIndex(i) == ri && c.ShipGroup(i).Index == index {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] {
|
||||
c.validateRaceIndex(ri)
|
||||
return func(yield func(*game.ShipGroup) bool) {
|
||||
for i := range c.g.ShipGroups {
|
||||
if ri == c.ShipGroupOwnerRaceIndex(i) {
|
||||
if !yield(&c.g.ShipGroups[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] {
|
||||
return func(yield func(*game.ShipGroup) bool) {
|
||||
for sg := range c.g.ShipGroups {
|
||||
if c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade {
|
||||
if !yield(&c.g.ShipGroups[sg]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) unsafeDeleteShipGroup(i int) {
|
||||
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
|
||||
delete(c.raceIndexByShipGroupIndex, i)
|
||||
delete(c.shipClassByShipGroupIndex, i)
|
||||
}
|
||||
|
||||
func (c *Cache) validateShipGroupIndex(i int) {
|
||||
if i >= len(c.g.ShipGroups) {
|
||||
panic(fmt.Sprintf("group index out of range: %d >= %d", i, len(c.g.ShipGroups)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
func (c *Controller) SendGroup(raceName string, groupIndex, planetNumber, quantity uint) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.SendGroup(ri, groupIndex, planetNumber, quantity)
|
||||
}
|
||||
|
||||
func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error {
|
||||
c.validateRaceIndex(ri)
|
||||
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
|
||||
// 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 := c.ShipGroup(sgi).OnPlanet()
|
||||
if !ok {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
if c.ShipGroup(sgi).Number < quantity {
|
||||
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
||||
}
|
||||
|
||||
p1 := c.MustPlanet(sourcePlanet)
|
||||
// p1, ok := PlanetByNum(g, sourcePlanet)
|
||||
// if !ok {
|
||||
// return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
|
||||
// }
|
||||
p2 := c.MustPlanet(planetNumber)
|
||||
// p2, ok := PlanetByNum(g, planetNumber)
|
||||
// if !ok {
|
||||
// return e.NewEntityNotExistsError("destination planet #%d", planetNumber)
|
||||
// }
|
||||
rangeToDestination := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X, p1.Y, p2.X, p2.Y)
|
||||
if rangeToDestination > c.g.Race[ri].FlightDistance() {
|
||||
return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination)
|
||||
}
|
||||
|
||||
if quantity > 0 && quantity < c.ShipGroup(sgi).Number {
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
|
||||
if sourcePlanet == planetNumber {
|
||||
c.UnsendShips(*c.ShipGroup(sgi))
|
||||
c.JoinEqualGroups(ri)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.LaunchShips(*c.ShipGroup(sgi), planetNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) LaunchShips(sg game.ShipGroup, destination uint) *game.ShipGroup {
|
||||
for i := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(i).OwnerID == sg.OwnerID && c.ShipGroup(i).Index == sg.Index {
|
||||
state := c.g.ShipGroups[i].State()
|
||||
if state != game.StateInOrbit && state != game.StateLaunched {
|
||||
panic("state invalid")
|
||||
}
|
||||
c.g.ShipGroups[i] = LaunchShips(sg, destination)
|
||||
return &c.g.ShipGroups[i]
|
||||
}
|
||||
}
|
||||
panic("ship group not found")
|
||||
}
|
||||
|
||||
func (c *Cache) UnsendShips(sg game.ShipGroup) *game.ShipGroup {
|
||||
for i := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(i).OwnerID == sg.OwnerID && c.ShipGroup(i).Index == sg.Index {
|
||||
c.g.ShipGroups[i] = UnsendShips(sg)
|
||||
return &c.g.ShipGroups[i]
|
||||
}
|
||||
}
|
||||
panic("ship group not found")
|
||||
}
|
||||
|
||||
func LaunchShips(sg game.ShipGroup, destination uint) game.ShipGroup {
|
||||
sg.StateInSpace = &game.InSpace{
|
||||
Origin: sg.Destination,
|
||||
}
|
||||
sg.Destination = destination
|
||||
return sg
|
||||
}
|
||||
|
||||
func UnsendShips(sg game.ShipGroup) game.ShipGroup {
|
||||
sg.Destination = sg.StateInSpace.Origin
|
||||
sg.StateInSpace = nil
|
||||
return sg
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package controller_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) {
|
||||
c, g := newCache()
|
||||
// group #1 - in_orbit, free to upgrade
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10))
|
||||
// group #2 - in_space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
// group #3 - in_orbit, unmovable
|
||||
g.CreateShipType(Race_0.Name, "Fortress", 0, 50, 30, 100, 0)
|
||||
assert.NoError(t, c.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(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(7), c.ShipGroup(0).Number)
|
||||
assert.Equal(t, game.StateInOrbit, c.ShipGroup(0).State())
|
||||
assert.Equal(t, uint(3), c.ShipGroup(3).Number)
|
||||
assert.Equal(t, game.StateLaunched, c.ShipGroup(3).State())
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 2)) // un-send 2 of 3
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(9), c.MustShipGroup(Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateInOrbit, c.MustShipGroup(Race_0_idx, 5).State())
|
||||
assert.Equal(t, uint(1), c.MustShipGroup(Race_0_idx, 4).Number)
|
||||
assert.Equal(t, game.StateLaunched, c.MustShipGroup(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(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, uint(10), c.MustShipGroup(Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateInOrbit, c.MustShipGroup(Race_0_idx, 5).State())
|
||||
|
||||
assert.NoError(t, g.SendGroup(Race_0.Name, 5, 2, 0))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, uint(10), c.MustShipGroup(Race_0_idx, 5).Number)
|
||||
assert.Equal(t, game.StateLaunched, c.MustShipGroup(Race_0_idx, 5).State())
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/number"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateShips(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
|
||||
assert.ErrorContains(t,
|
||||
c.CreateShips(Race_0_idx, "Unknown_Ship_Type", R0_Planet_0_num, 2),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
c.CreateShips(Race_0_idx, Race_0_Gunship, R1_Planet_1_num, 2),
|
||||
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 1)
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(1)), 1)
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 2)
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(1)), 2)
|
||||
}
|
||||
|
||||
func TestJoinEqualGroups(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6)) // (2)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // (3)
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
|
||||
|
||||
// g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 1.5)
|
||||
c.RacetTechLevel(Race_0_idx, game.TechDrive, 1.5)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 9)) // 4 -> 6
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) // 5 -> 7
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 4)) // (6)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 4)) // (7)
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
|
||||
// g.Race[Race_1_idx].SetTechLevel(game.TechShields, 2.0)
|
||||
c.RacetTechLevel(Race_1_idx, game.TechShields, 2.0)
|
||||
assert.NoError(t, c.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_1_idx)), 3)
|
||||
|
||||
c.JoinEqualGroups(Race_0_idx)
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_1_idx)), 3)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
|
||||
shipTypeID := func(ri int, name string) uuid.UUID {
|
||||
class, _, ok := c.ShipClass(ri, name)
|
||||
if !ok {
|
||||
t.Fatalf("ShipType not found: %s", name)
|
||||
return uuid.Nil
|
||||
}
|
||||
return class.ID
|
||||
}
|
||||
|
||||
for sg := range c.ListShipGroups(Race_0_idx) {
|
||||
switch {
|
||||
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.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.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.TechLevel(game.TechDrive) == 1.5:
|
||||
assert.Equal(t, uint(13), sg.Number)
|
||||
assert.Equal(t, uint(6), sg.Index)
|
||||
default:
|
||||
t.Error("not all ship groups covered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreakGroup(t *testing.T) {
|
||||
c, g := newCache()
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 13)) // group #1 (0)
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) // group #2 (1) - In_Space
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{
|
||||
Origin: 1,
|
||||
Range: 1,
|
||||
}
|
||||
|
||||
fleet := "R0_Fleet"
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 1, 0))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.BreakGroup("UnknownRace", 1, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.BreakGroup(Race_0.Name, 555, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.BreakGroup(Race_0.Name, 1, 17),
|
||||
e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
|
||||
assert.ErrorContains(t,
|
||||
g.BreakGroup(Race_0.Name, 2, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 2)
|
||||
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 1)
|
||||
|
||||
// group #1 -> group #3 (5 new, 8 left)
|
||||
assert.NoError(t, c.BreakGroup(Race_0_idx, 1, 5)) // group #3 (2)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, uint(8), c.ShipGroup(0).Number)
|
||||
assert.NotNil(t, c.ShipGroup(0).FleetID)
|
||||
assert.Equal(t, uint(5), c.ShipGroup(2).Number)
|
||||
assert.Equal(t, uint(3), c.ShipGroup(2).Index)
|
||||
assert.Nil(t, c.ShipGroup(2).FleetID)
|
||||
assert.Nil(t, c.ShipGroup(2).CargoType)
|
||||
|
||||
// group #1 -> group #4 (2 new, 6 left)
|
||||
c.ShipGroup(0).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(0).Load = 32.8 // 8 ships
|
||||
assert.NoError(t, c.BreakGroup(Race_0_idx, 1, 2)) // group #4 (3)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(6), c.ShipGroup(0).Number)
|
||||
assert.NotNil(t, c.ShipGroup(0).FleetID)
|
||||
assert.Equal(t, uint(2), c.ShipGroup(3).Number)
|
||||
assert.Equal(t, uint(4), c.ShipGroup(3).Index)
|
||||
assert.Nil(t, c.ShipGroup(3).FleetID)
|
||||
assert.NoError(t, c.JoinShipGroupToFleet(Race_0_idx, fleet, 4, 0))
|
||||
assert.NotNil(t, c.ShipGroup(3).FleetID)
|
||||
|
||||
assert.Equal(t, game.CargoColonist.Ref(), c.ShipGroup(0).CargoType)
|
||||
assert.Equal(t, 24.6, number.Fixed3(c.ShipGroup(0).Load))
|
||||
assert.Equal(t, game.CargoColonist.Ref(), c.ShipGroup(3).CargoType)
|
||||
assert.Equal(t, 8.2, number.Fixed3(c.ShipGroup(3).Load))
|
||||
|
||||
// group #1 -> MAX 6 off the fleet
|
||||
assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 6)) // group #1 (0)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(6), c.ShipGroup(0).Number)
|
||||
assert.Nil(t, c.ShipGroup(0).FleetID)
|
||||
|
||||
// group #4 -> ALL off the fleet
|
||||
assert.NoError(t, g.BreakGroup(Race_0.Name, 4, 0)) // group #1 (0)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(2), c.ShipGroup(3).Number)
|
||||
assert.Nil(t, c.ShipGroup(3).FleetID)
|
||||
}
|
||||
|
||||
func TestGiveawayGroup(t *testing.T) {
|
||||
c, g := newCache()
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0)
|
||||
assert.NoError(t, c.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1)
|
||||
|
||||
assert.NoError(t, c.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, c.ShipGroup(2).FleetID)
|
||||
c.ShipGroup(2).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
c.ShipGroup(2).CargoType = game.CargoMaterial.Ref()
|
||||
c.ShipGroup(2).Load = 1.234
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 2)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_1_idx)), 1)
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup("UnknownRace", Race_1.Name, 2, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup(Race_0.Name, "UnknownRace", 2, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup(Race_0.Name, Race_0.Name, 2, 0),
|
||||
e.GenericErrorText(e.ErrInputSameRace))
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup(Race_0.Name, Race_1.Name, 555, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup(Race_0.Name, Race_1.Name, 2, 18),
|
||||
e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
|
||||
assert.ErrorContains(t,
|
||||
g.GiveawayGroup(Race_0.Name, Race_1.Name, 1, 0),
|
||||
e.GenericErrorText(e.ErrGiveawayGroupShipsTypeNotEqual))
|
||||
|
||||
assert.NoError(t, g.GiveawayGroup(Race_0.Name, Race_1.Name, 2, 11)) // group #2 (3)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 2)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_1_idx)), 2)
|
||||
// sto := slices.IndexFunc(g.Race[Race_0_idx].ShipTypes, func(st game.ShipType) bool { return st.Name == Race_0_Gunship })
|
||||
// sti := slices.IndexFunc(g.Race[Race_1_idx].ShipTypes, func(st game.ShipType) bool { return st.Name == Race_0_Gunship })
|
||||
|
||||
c.MustShipClass(Race_0_idx, Race_0_Gunship)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Name, c.MustShipClass(Race_0_idx, Race_0_Gunship).Name)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Drive, c.MustShipClass(Race_0_idx, Race_0_Gunship).Drive)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Weapons, c.MustShipClass(Race_0_idx, Race_0_Gunship).Weapons)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Shields, c.MustShipClass(Race_0_idx, Race_0_Gunship).Shields)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Cargo, c.MustShipClass(Race_0_idx, Race_0_Gunship).Cargo)
|
||||
assert.Equal(t, c.MustShipClass(Race_1_idx, Race_0_Gunship).Armament, c.MustShipClass(Race_0_idx, Race_0_Gunship).Armament)
|
||||
assert.Equal(t, c.ShipGroup(2).State(), c.ShipGroup(3).State())
|
||||
assert.Equal(t, c.ShipGroup(2).CargoType, c.ShipGroup(3).CargoType)
|
||||
assert.Equal(t, c.ShipGroup(2).Load, c.ShipGroup(3).Load)
|
||||
assert.Equal(t, c.ShipGroup(2).TechLevel(game.TechDrive), c.ShipGroup(3).TechLevel(game.TechDrive))
|
||||
assert.Equal(t, c.ShipGroup(2).TechLevel(game.TechWeapons), c.ShipGroup(3).TechLevel(game.TechWeapons))
|
||||
assert.Equal(t, c.ShipGroup(2).TechLevel(game.TechShields), c.ShipGroup(3).TechLevel(game.TechShields))
|
||||
assert.Equal(t, c.ShipGroup(2).TechLevel(game.TechCargo), c.ShipGroup(3).TechLevel(game.TechCargo))
|
||||
assert.Equal(t, c.ShipGroup(2).Destination, c.ShipGroup(3).Destination)
|
||||
assert.Equal(t, c.ShipGroup(2).StateInSpace, c.ShipGroup(3).StateInSpace)
|
||||
assert.Equal(t, c.ShipGroup(2).StateUpgrade, c.ShipGroup(3).StateUpgrade)
|
||||
assert.Equal(t, c.ShipGroup(3).OwnerID, Race_1_ID)
|
||||
assert.Equal(t, c.ShipGroup(3).TypeID, c.MustShipClass(Race_1_idx, Race_0_Gunship).ID)
|
||||
assert.Equal(t, c.ShipGroup(3).Number, uint(11))
|
||||
assert.Nil(t, c.ShipGroup(3).FleetID)
|
||||
|
||||
assert.NoError(t, g.GiveawayGroup(Race_1.Name, Race_0.Name, 2, 11))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_1_idx)), 1)
|
||||
}
|
||||
|
||||
func TestLoadCargo(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
// 1: idx = 0 / Ready to load
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
|
||||
// 2: idx = 1 / Has no cargo bay
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1))
|
||||
|
||||
// 3: idx = 2 / In_Space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
c.ShipGroup(2).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 4: idx = 3 / loaded with COL
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(3).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(3).Load = 1.234
|
||||
|
||||
// 5: idx = 4 / on foreign planet
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(4).Destination = R1_Planet_1_num
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 5)
|
||||
|
||||
// tests
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo("UnknownRace", 1, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 1, "GOLD", 0, 0),
|
||||
e.GenericErrorText(e.ErrInputCargoTypeInvalid))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 555, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 3, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 5, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 2, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputNoCargoBay))
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 4, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputCargoLoadNotEqual))
|
||||
|
||||
// initial planet is empty
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputCargoLoadNotEnough))
|
||||
// add cargo to planet
|
||||
// g.Map.Planet[0].Material = 100
|
||||
c.PutMaterial(R0_Planet_0_num, 100)
|
||||
// not enough on the planet
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 101),
|
||||
e.GenericErrorText(e.ErrInputCargoLoadNotEnough))
|
||||
// quantity > ships
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 1),
|
||||
e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak))
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 5)
|
||||
|
||||
// break group and load maximum
|
||||
assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 0))
|
||||
assert.Equal(t, 58.0, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 6)
|
||||
assert.Nil(t, c.ShipGroup(0).CargoType)
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(5).CargoType)
|
||||
assert.Equal(t, uint(9), c.ShipGroup(0).Number)
|
||||
assert.Equal(t, 0.0, c.ShipGroup(0).Load)
|
||||
assert.Equal(t, uint(2), c.ShipGroup(5).Number)
|
||||
assert.Equal(t, 42.0, c.ShipGroup(5).Load)
|
||||
|
||||
// break group and load limited
|
||||
assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 18))
|
||||
assert.Equal(t, 40.0, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
assert.Nil(t, c.ShipGroup(0).CargoType)
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(6).CargoType)
|
||||
assert.Equal(t, uint(7), c.ShipGroup(0).Number)
|
||||
assert.Equal(t, 0.0, c.ShipGroup(0).Load)
|
||||
assert.Equal(t, uint(2), c.ShipGroup(6).Number)
|
||||
assert.Equal(t, 18.0, c.ShipGroup(6).Load)
|
||||
|
||||
// add cargo to planet
|
||||
// g.Map.Planet[0].Material = 100
|
||||
c.PutMaterial(R0_Planet_0_num, 100)
|
||||
// loading all available cargo
|
||||
assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
assert.Equal(t, 0.0, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Equal(t, 100.0, c.ShipGroup(0).Load) // free: 131.0
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(0).CargoType)
|
||||
|
||||
// add cargo to planet
|
||||
// g.Map.Planet[0].Material = 200
|
||||
c.PutMaterial(R0_Planet_0_num, 200)
|
||||
assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 31))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
assert.Equal(t, 169.0, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Equal(t, 131.0, c.ShipGroup(0).Load) // free: 100.0
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(0).CargoType)
|
||||
|
||||
// load to maximum cargo space left
|
||||
assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 0))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
assert.Equal(t, 153.0, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Equal(t, 147.0, c.ShipGroup(0).Load) // free: 0.0
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(0).CargoType)
|
||||
|
||||
// ship group is full
|
||||
assert.ErrorContains(t,
|
||||
g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0),
|
||||
e.GenericErrorText(e.ErrInputCargoLoadNoSpaceLeft))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
}
|
||||
|
||||
func TestUnloadCargo(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
// 1: idx = 0 / empty
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||
|
||||
// 2: idx = 1 / Has no cargo bay
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1))
|
||||
|
||||
// 3: idx = 2 / In_Space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
c.ShipGroup(2).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 4: idx = 3 / loaded with COL
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(3).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(3).Load = 1.234
|
||||
|
||||
// 5: idx = 4 / on foreign planet / loaded with COL
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(4).Destination = R1_Planet_1_num
|
||||
c.ShipGroup(4).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(4).Load = 1.234
|
||||
|
||||
// 6: idx = 5 / on foreign planet / loaded with MAT
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(5).Destination = R1_Planet_1_num
|
||||
c.ShipGroup(5).CargoType = game.CargoMaterial.Ref()
|
||||
c.ShipGroup(5).Load = 100.0
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 6)
|
||||
|
||||
// tests
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo("UnknownRace", 1, 0, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 555, 0, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 3, 0, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 2, 0, 0),
|
||||
e.GenericErrorText(e.ErrInputNoCargoBay))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 1, 0, 0),
|
||||
e.GenericErrorText(e.ErrInputCargoUnloadEmpty))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 5, 0, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||
c.ShipGroup(0).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(0).Load = 100
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 1, 11, 101),
|
||||
e.GenericErrorText(e.ErrInputCargoUnoadNotEnough))
|
||||
assert.ErrorContains(t,
|
||||
g.UnloadCargo(Race_0.Name, 1, 0, 1),
|
||||
e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak))
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 6)
|
||||
|
||||
// unload MAT on foreign planet / break group
|
||||
assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 0))
|
||||
assert.Equal(t, 27.273, number.Fixed3(c.MustPlanet(R1_Planet_1_num).Material))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 7)
|
||||
assert.Equal(t, uint(3), c.ShipGroup(6).Number)
|
||||
assert.Nil(t, c.ShipGroup(6).CargoType)
|
||||
assert.Equal(t, 0.0, c.ShipGroup(6).Load)
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(5).CargoType)
|
||||
assert.Equal(t, uint(8), c.ShipGroup(5).Number)
|
||||
assert.Equal(t, 72.727, number.Fixed3(c.ShipGroup(5).Load))
|
||||
|
||||
// unload MAT on foreign planet / break group / limited MAT
|
||||
assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 20.0))
|
||||
assert.Equal(t, 47.273, number.Fixed3(c.MustPlanet(R1_Planet_1_num).Material))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 8)
|
||||
assert.Equal(t, uint(3), c.ShipGroup(7).Number)
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(7).CargoType)
|
||||
assert.Equal(t, 7.273, number.Fixed3(c.ShipGroup(7).Load))
|
||||
assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(5).CargoType)
|
||||
assert.Equal(t, uint(5), c.ShipGroup(5).Number)
|
||||
assert.Equal(t, 45.455, number.Fixed3(c.ShipGroup(5).Load))
|
||||
|
||||
// unload ALL
|
||||
assert.NoError(t, g.UnloadCargo(Race_0.Name, 1, 0, 0))
|
||||
assert.Equal(t, 100.0, number.Fixed3(c.MustPlanet(R0_Planet_0_num).Colonists))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 8)
|
||||
assert.Equal(t, uint(10), c.ShipGroup(0).Number)
|
||||
assert.Nil(t, c.ShipGroup(0).CargoType)
|
||||
assert.Equal(t, 0.0, number.Fixed3(c.ShipGroup(0).Load))
|
||||
}
|
||||
|
||||
func TestDisassembleGroup(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
// 1: idx = 0 / empty
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||
|
||||
// 2: idx = 1 / In_Space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 31.337,
|
||||
}
|
||||
|
||||
// 3: idx = 2 / loaded with COL
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
|
||||
c.ShipGroup(2).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(2).Load = 80.0
|
||||
|
||||
// 4: idx = 3 / on foreign planet / loaded with MAT
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(3).Destination = R1_Planet_1_num
|
||||
c.ShipGroup(3).CargoType = game.CargoMaterial.Ref()
|
||||
c.ShipGroup(3).Load = 100.0
|
||||
|
||||
// 5: idx = 4 / on foreign planet / loaded with COL
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
|
||||
c.ShipGroup(4).Destination = R1_Planet_1_num
|
||||
c.ShipGroup(4).CargoType = game.CargoColonist.Ref()
|
||||
c.ShipGroup(4).Load = 2.345
|
||||
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 5)
|
||||
|
||||
// tests
|
||||
assert.ErrorContains(t,
|
||||
g.DisassembleGroup("UnknownRace", 1, 0),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.DisassembleGroup(Race_0.Name, 555, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.DisassembleGroup(Race_0.Name, 2, 0),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.DisassembleGroup(Race_0.Name, 3, 12),
|
||||
e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
|
||||
|
||||
groupEmptyMass := c.ShipGroup(4).EmptyMass(c.MustShipClass(Race_0_idx, Race_0_Freighter))
|
||||
// groupEmptyMass := c.ShipGroup(4).EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
|
||||
// groupLoadCOL := c.ShipGroup(4).Load
|
||||
planetMAT := c.MustPlanet(R1_Planet_1_num).Material
|
||||
planetCOL := c.MustPlanet(R1_Planet_1_num).Colonists
|
||||
|
||||
assert.NoError(t, g.DisassembleGroup(Race_0.Name, 5, 0))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, planetMAT+groupEmptyMass, c.MustPlanet(R1_Planet_1_num).Material)
|
||||
assert.Equal(t, planetCOL, c.MustPlanet(R1_Planet_1_num).Colonists)
|
||||
|
||||
groupEmptyMass = c.ShipGroup(3).EmptyMass(c.MustShipClass(Race_0_idx, Race_0_Freighter))
|
||||
// groupEmptyMass = c.ShipGroup(3).EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
|
||||
groupLoadMAT := c.ShipGroup(3).Load
|
||||
planetMAT = c.MustPlanet(R1_Planet_1_num).Material
|
||||
assert.NoError(t, g.DisassembleGroup(Race_0.Name, 4, 0))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, planetMAT+groupEmptyMass+groupLoadMAT, c.MustPlanet(R1_Planet_1_num).Material)
|
||||
|
||||
groupEmptyMass = c.ShipGroup(2).EmptyMass(c.MustShipClass(Race_0_idx, Race_0_Freighter))
|
||||
// groupEmptyMass = c.ShipGroup(2).EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
|
||||
planetMAT = c.MustPlanet(R0_Planet_0_num).Material
|
||||
planetCOL = c.MustPlanet(R0_Planet_0_num).Colonists
|
||||
planetPOP := 90.0
|
||||
|
||||
// g.Map.Planet[0].Population = planetPOP
|
||||
c.PutPopulation(R0_Planet_0_num, planetPOP)
|
||||
var shipsDisassembling uint = 3
|
||||
groupEmptyMass = groupEmptyMass / float64(c.ShipGroup(2).Number) * float64(shipsDisassembling)
|
||||
newGroupUnloadedCOL := c.ShipGroup(2).Load / float64(c.ShipGroup(2).Number) * float64(shipsDisassembling)
|
||||
expectPOPIncrease := newGroupUnloadedCOL * 8
|
||||
freePOPLeft := c.MustPlanet(R0_Planet_0_num).Size - c.MustPlanet(R0_Planet_0_num).Population
|
||||
expectAddedCOL := (expectPOPIncrease - freePOPLeft) / 8
|
||||
expectAddedPOP := freePOPLeft
|
||||
assert.NoError(t, g.DisassembleGroup(Race_0.Name, 3, shipsDisassembling))
|
||||
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 3)
|
||||
assert.Equal(t, planetCOL+expectAddedCOL, c.MustPlanet(R0_Planet_0_num).Colonists)
|
||||
assert.Equal(t, planetPOP+expectAddedPOP, c.MustPlanet(R0_Planet_0_num).Population)
|
||||
assert.Equal(t, planetMAT+groupEmptyMass, c.MustPlanet(R0_Planet_0_num).Material)
|
||||
assert.Equal(t, uint(7), c.ShipGroup(2).Number)
|
||||
assert.Equal(t, 56.0, c.ShipGroup(2).Load)
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func (c *Controller) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
|
||||
ri, err := c.Cache.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.UpgradeGroup(ri, groupIndex, techInput, limitShips, limitLevel)
|
||||
}
|
||||
|
||||
func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
|
||||
c.validateRaceIndex(ri)
|
||||
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
||||
if !ok {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
|
||||
// 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 == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
||||
// // hard to test, need manual game data invalidation
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
||||
// }
|
||||
// st := g.Race[ri].ShipTypes[sti]
|
||||
|
||||
if s := c.ShipGroup(sgi).State(); s != game.StateInOrbit && s != game.StateUpgrade {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
pl := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
||||
|
||||
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
||||
// if pl < 0 {
|
||||
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
||||
// }
|
||||
if pl.Owner != uuid.Nil && pl.Owner != c.g.Race[ri].ID {
|
||||
return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", pl.Number, groupIndex)
|
||||
}
|
||||
|
||||
upgradeValidTech := map[string]game.Tech{
|
||||
game.TechDrive.String(): game.TechDrive,
|
||||
game.TechWeapons.String(): game.TechWeapons,
|
||||
game.TechShields.String(): game.TechShields,
|
||||
game.TechCargo.String(): game.TechCargo,
|
||||
game.TechAll.String(): game.TechAll,
|
||||
}
|
||||
|
||||
techRequest, ok := upgradeValidTech[techInput]
|
||||
if !ok {
|
||||
return e.NewTechUnknownError(techInput)
|
||||
}
|
||||
|
||||
var blockMasses map[game.Tech]float64 = map[game.Tech]float64{
|
||||
game.TechDrive: st.DriveBlockMass(),
|
||||
game.TechWeapons: st.WeaponsBlockMass(),
|
||||
game.TechShields: st.ShieldsBlockMass(),
|
||||
game.TechCargo: st.CargoBlockMass(),
|
||||
}
|
||||
|
||||
switch {
|
||||
case techRequest != game.TechAll && blockMasses[techRequest] == 0:
|
||||
return e.NewUpgradeShipTechNotUsedError()
|
||||
case techRequest == game.TechAll && limitLevel != 0:
|
||||
return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel)
|
||||
}
|
||||
|
||||
targetLevel := make(map[game.Tech]float64)
|
||||
var sumLevels float64
|
||||
for _, tech := range []game.Tech{game.TechDrive, game.TechWeapons, game.TechShields, game.TechCargo} {
|
||||
if techRequest == game.TechAll || tech == techRequest {
|
||||
if c.g.Race[ri].TechLevel(tech) < limitLevel {
|
||||
return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), c.g.Race[ri].TechLevel(tech), limitLevel)
|
||||
}
|
||||
targetLevel[tech] = game.FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), c.ShipGroup(sgi).TechLevel(tech), limitLevel)
|
||||
} else {
|
||||
targetLevel[tech] = game.CurrentUpgradingLevel(c.g.ShipGroups[sgi], tech)
|
||||
}
|
||||
sumLevels += targetLevel[tech]
|
||||
}
|
||||
|
||||
productionCapacity := c.PlanetProductionCapacity(pl.Number)
|
||||
if c.ShipGroup(sgi).State() == game.StateUpgrade {
|
||||
// to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state
|
||||
productionCapacity -= c.ShipGroup(sgi).StateUpgrade.Cost()
|
||||
}
|
||||
uc := game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo])
|
||||
costForShip := uc.UpgradeCost(1)
|
||||
if costForShip == 0 {
|
||||
return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel)
|
||||
}
|
||||
|
||||
shipsToUpgrade := c.ShipGroup(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 = game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.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 < c.ShipGroup(sgi).Number {
|
||||
if c.ShipGroup(sgi).State() == game.StateUpgrade {
|
||||
return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", c.ShipGroup(sgi).Number, maxUpgradableShips)
|
||||
}
|
||||
nsgi, err := c.breakGroupSafe(ri, groupIndex, maxUpgradableShips)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sgi = nsgi
|
||||
}
|
||||
|
||||
// finally, fill group upgrade prefs
|
||||
for tech := range targetLevel {
|
||||
if targetLevel[tech] > 0 {
|
||||
c.upgradeShipGroup(sgi, tech, targetLevel[tech])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func CurrentUpgradingLevel(sg game.ShipGroup, tech game.Tech) float64 {
|
||||
// if sg.StateUpgrade == nil {
|
||||
// return 0
|
||||
// }
|
||||
// ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref game.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 (c *Cache) upgradeShipGroup(sgi int, tech game.Tech, v float64) {
|
||||
sg := *(c.ShipGroup(sgi))
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
// if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech) >= v {
|
||||
// return
|
||||
// }
|
||||
// var su game.InUpgrade
|
||||
// if sg.StateUpgrade != nil {
|
||||
// su = *sg.StateUpgrade
|
||||
// } else {
|
||||
// su = game.InUpgrade{UpgradeTech: []game.UpgradePreference{}}
|
||||
// }
|
||||
// ti := slices.IndexFunc(su.UpgradeTech, func(pref game.UpgradePreference) bool { return pref.Tech == tech })
|
||||
// if ti < 0 {
|
||||
// su.UpgradeTech = append(su.UpgradeTech, game.UpgradePreference{Tech: tech})
|
||||
// ti = len(su.UpgradeTech) - 1
|
||||
// }
|
||||
// su.UpgradeTech[ti].Level = v
|
||||
// su.UpgradeTech[ti].Cost = game.BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech), v) * float64(sg.Number)
|
||||
|
||||
// sg.StateUpgrade = &su
|
||||
|
||||
c.g.ShipGroups[sgi] = game.UpgradeGroupPreference(sg, *st, tech, v)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package controller_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) {
|
||||
c, g := newCache()
|
||||
// group #1 - in_orbit, free to upgrade
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10))
|
||||
// group #2 - in_space
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
// group #3 - in_orbit, foreign planet
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
c.ShipGroup(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)
|
||||
c.RacetTechLevel(Race_0_idx, game.TechDrive, 10.0)
|
||||
assert.Equal(t, 10.0, c.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(c.ListShipGroups(Race_0_idx)), 4)
|
||||
assert.Equal(t, uint(8), c.ShipGroup(0).Number)
|
||||
assert.Equal(t, uint(2), c.ShipGroup(3).Number)
|
||||
assert.Equal(t, game.StateInOrbit, c.ShipGroup(0).State())
|
||||
assert.Equal(t, game.StateUpgrade, c.ShipGroup(3).State())
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3),
|
||||
e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed))
|
||||
}
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
func JoinEqualGroups(configure func(*controller.Param), race string) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) {
|
||||
err = joinEqualGroups(r, g, race)
|
||||
err = joinEqualGroups(c, r, g, race)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func joinEqualGroups(r controller.Repo, g *game.Game, race string) error {
|
||||
if err := g.JoinEqualGroups(race); err != nil {
|
||||
func joinEqualGroups(c *controller.Controller, r controller.Repo, g *game.Game, race string) error {
|
||||
if err := c.JoinEqualGroups(race); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.SaveState(g)
|
||||
|
||||
@@ -5,12 +5,11 @@ import (
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
mg "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJoinEqualGroups(t *testing.T) {
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
c(t, func(p func(*controller.Param), g func() *controller.Controller) {
|
||||
err := game.JoinEqualGroups(p, "race_01")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -5,17 +5,17 @@ import (
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func CreateShipType(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64, armament int) (err error) {
|
||||
func CreateShipType(configure func(*controller.Param), race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) {
|
||||
err = createShipType(r, g, race, typeName, drive, weapons, shields, cargo, armament)
|
||||
err = createShipType(c, r, g, race, typeName, drive, ammo, weapons, shields, cargo)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createShipType(r controller.Repo, g *game.Game, race, typeName string, d, w, s, c float64, a int) error {
|
||||
if err := g.CreateShipType(race, typeName, d, w, s, c, a); err != nil {
|
||||
func createShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) error {
|
||||
if err := c.CreateShipType(race, typeName, drive, ammo, weapons, shields, cargo); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.SaveState(g)
|
||||
@@ -24,14 +24,14 @@ func createShipType(r controller.Repo, g *game.Game, race, typeName string, d, w
|
||||
func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) {
|
||||
err = mergeShipType(r, g, race, source, target)
|
||||
err = mergeShipType(c, r, g, race, source, target)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func mergeShipType(r controller.Repo, g *game.Game, race, source, target string) error {
|
||||
if err := g.MergeShipType(race, source, target); err != nil {
|
||||
func mergeShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, source, target string) error {
|
||||
if err := c.MergeShipType(race, source, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.SaveState(g)
|
||||
@@ -40,14 +40,14 @@ func mergeShipType(r controller.Repo, g *game.Game, race, source, target string)
|
||||
func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) {
|
||||
err = deleteShipType(r, g, race, typeName)
|
||||
err = deleteShipType(c, r, g, race, typeName)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func deleteShipType(r controller.Repo, g *game.Game, race, typeName string) error {
|
||||
if err := g.DeleteShipType(race, typeName); err != nil {
|
||||
func deleteShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string) error {
|
||||
if err := c.DeleteShipType(race, typeName); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.SaveState(g)
|
||||
|
||||
@@ -15,14 +15,14 @@ import (
|
||||
func TestCreateShipType(t *testing.T) {
|
||||
race := "race_01"
|
||||
typeName := "Drone"
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
err := game.DeleteShipType(p, race, typeName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.CreateShipType(p, unknownRaceName, " "+typeName+" ", 1, 0, 0, 0, 0) // TODO: test on dead race
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = game.CreateShipType(p, race, " "+typeName+" ", 1, 0, 0, 0, 0)
|
||||
assert.NoError(t, err)
|
||||
st, err := g().ShipTypes(race)
|
||||
st, err := ctrl().ShipTypes(race)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, st, 1)
|
||||
assert.Equal(t, st[0].Name, typeName)
|
||||
@@ -36,7 +36,7 @@ func TestCreateShipType(t *testing.T) {
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = game.DeleteShipType(p, race, typeName)
|
||||
assert.NoError(t, err)
|
||||
st, err = g().ShipTypes(race)
|
||||
st, err = ctrl().ShipTypes(race)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, st, 0)
|
||||
})
|
||||
@@ -82,12 +82,12 @@ func TestCreateShipTypeValidation(t *testing.T) {
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
for i, tc := range table {
|
||||
if tc.err == "" {
|
||||
err := game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.w, tc.s, tc.c, tc.a)
|
||||
err := game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.NoError(t, err)
|
||||
err = game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.w, tc.s, tc.c, tc.a)
|
||||
err = game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate))
|
||||
} else {
|
||||
err := game.CreateShipType(p, race, tc.name, tc.d, tc.w, tc.s, tc.c, tc.a)
|
||||
err := game.CreateShipType(p, race, tc.name, tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func TestCreateShipTypeValidation(t *testing.T) {
|
||||
|
||||
func TestMergeShipType(t *testing.T) {
|
||||
race := "race_01"
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
err := game.CreateShipType(p, race, "Drone", 1, 0, 0, 0, 0)
|
||||
assert.NoError(t, err)
|
||||
err = game.CreateShipType(p, race, "Spy", 1, 0, 0, 0, 0)
|
||||
@@ -109,7 +109,7 @@ func TestMergeShipType(t *testing.T) {
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.MergeShipType(p, race, "Spy", "Drone")
|
||||
assert.NoError(t, err)
|
||||
st, err := g().ShipTypes(race)
|
||||
st, err := ctrl().ShipTypes(race)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, st, 2)
|
||||
err = game.MergeShipType(p, race, "Drone", "Cruiser")
|
||||
|
||||
@@ -7,20 +7,20 @@ import (
|
||||
|
||||
func DeclareWar(configure func(*controller.Param), from, to string) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(r, g, from, to, game.RelationWar) })
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(c, r, g, from, to, game.RelationWar) })
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func DeclarePeace(configure func(*controller.Param), from, to string) (err error) {
|
||||
control(configure, func(c *controller.Controller) {
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(r, g, from, to, game.RelationPeace) })
|
||||
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(c, r, g, from, to, game.RelationPeace) })
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func updateRelation(r controller.Repo, g *game.Game, hostRace, opponentRace string, rel game.Relation) error {
|
||||
if err := g.UpdateRelation(hostRace, opponentRace, rel); err != nil {
|
||||
func updateRelation(c *controller.Controller, r controller.Repo, g *game.Game, hostRace, opponentRace string, rel game.Relation) error {
|
||||
if err := c.UpdateRelation(hostRace, opponentRace, rel); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.SaveState(g)
|
||||
|
||||
@@ -12,33 +12,33 @@ import (
|
||||
)
|
||||
|
||||
func TestDeclarePeaceAndWarSingle(t *testing.T) {
|
||||
g(t, func(f func(*controller.Param), g func() *mg.Game) {
|
||||
c(t, func(f func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
hostRace := "race_05"
|
||||
opponentRace := "race_01"
|
||||
|
||||
r, err := g().Relation(hostRace, opponentRace)
|
||||
r, err := ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationWar, r.Relation)
|
||||
assert.Equal(t, mg.RelationWar, r)
|
||||
|
||||
r, err = g().Relation(unknownRaceName, opponentRace) // TODO: test on dead race
|
||||
r, err = ctrl().Relation(unknownRaceName, opponentRace) // TODO: test on dead race
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
r, err = g().Relation(hostRace, unknownRaceName) // TODO: test on dead race
|
||||
r, err = ctrl().Relation(hostRace, unknownRaceName) // TODO: test on dead race
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
|
||||
assert.NoError(t, game.DeclarePeace(f, hostRace, opponentRace))
|
||||
r, err = g().Relation(hostRace, opponentRace)
|
||||
r, err = ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationPeace, r.Relation)
|
||||
assert.Equal(t, mg.RelationPeace, r)
|
||||
|
||||
assert.NoError(t, game.DeclareWar(f, hostRace, opponentRace))
|
||||
r, err = g().Relation(hostRace, opponentRace)
|
||||
r, err = ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationWar, r.Relation)
|
||||
assert.Equal(t, mg.RelationWar, r)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeclarePeaceAndWarAll(t *testing.T) {
|
||||
g(t, func(f func(*controller.Param), g func() *mg.Game) {
|
||||
c(t, func(f func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
hostRace := "race_07"
|
||||
|
||||
for i := range testRaceCount {
|
||||
@@ -46,9 +46,9 @@ func TestDeclarePeaceAndWarAll(t *testing.T) {
|
||||
if opponentRace == hostRace {
|
||||
continue
|
||||
}
|
||||
r, err := g().Relation(hostRace, opponentRace)
|
||||
r, err := ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationWar, r.Relation)
|
||||
assert.Equal(t, mg.RelationWar, r)
|
||||
}
|
||||
|
||||
assert.NoError(t, game.DeclarePeace(f, hostRace, hostRace))
|
||||
@@ -58,9 +58,9 @@ func TestDeclarePeaceAndWarAll(t *testing.T) {
|
||||
if opponentRace == hostRace {
|
||||
continue
|
||||
}
|
||||
r, err := g().Relation(hostRace, opponentRace)
|
||||
r, err := ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationPeace, r.Relation)
|
||||
assert.Equal(t, mg.RelationPeace, r)
|
||||
}
|
||||
|
||||
assert.NoError(t, game.DeclareWar(f, hostRace, hostRace))
|
||||
@@ -70,9 +70,9 @@ func TestDeclarePeaceAndWarAll(t *testing.T) {
|
||||
if opponentRace == hostRace {
|
||||
continue
|
||||
}
|
||||
r, err := g().Relation(hostRace, opponentRace)
|
||||
r, err := ctrl().Relation(hostRace, opponentRace)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mg.RelationWar, r.Relation)
|
||||
assert.Equal(t, mg.RelationWar, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,3 +52,33 @@ func g(t *testing.T, f func(p func(*controller.Param), g func() *mg.Game)) {
|
||||
}
|
||||
f(p, g)
|
||||
}
|
||||
|
||||
func c(t *testing.T, f func(p func(*controller.Param), g func() *controller.Controller)) {
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
races := make([]string, testRaceCount)
|
||||
for i := range testRaceCount {
|
||||
races[i] = raceNum(i)
|
||||
}
|
||||
p := func(p *controller.Param) { p.StoragePath = root }
|
||||
_, err := game.GenerateGame(p, races)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: GenerateGame", err)
|
||||
return
|
||||
}
|
||||
g := func() *controller.Controller {
|
||||
c, err := controller.NewController(p)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: NewController", err)
|
||||
return nil
|
||||
}
|
||||
g, err := game.LoadState(p)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: LoadState", err)
|
||||
return nil
|
||||
}
|
||||
c.Cache = controller.NewCache(g)
|
||||
return c
|
||||
}
|
||||
f(p, g)
|
||||
}
|
||||
|
||||
+222
-220
@@ -1,14 +1,16 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"math"
|
||||
"slices"
|
||||
import "github.com/google/uuid"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
)
|
||||
// import (
|
||||
// "fmt"
|
||||
// "iter"
|
||||
// "math"
|
||||
// "slices"
|
||||
|
||||
// "github.com/google/uuid"
|
||||
// e "github.com/iliadenisov/galaxy/internal/error"
|
||||
// )
|
||||
|
||||
type Fleet struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
@@ -16,231 +18,231 @@ type Fleet struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func FleetState(g *Game, fleetID uuid.UUID) (ShipGroupState, *uint, *InSpace) {
|
||||
fi := slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.ID == fleetID })
|
||||
if fi < 0 {
|
||||
panic("FleetState: fleet id not found: " + fleetID.String())
|
||||
}
|
||||
ri := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == g.Fleets[fi].OwnerID })
|
||||
if ri < 0 {
|
||||
panic("FleetState: race id not found: " + g.Fleets[fi].OwnerID.String())
|
||||
}
|
||||
var state *ShipGroupState
|
||||
var onPlanet *uint
|
||||
var is *InSpace
|
||||
for sg := range FleetGroups(g, ri, fi) {
|
||||
if state == nil {
|
||||
s := sg.State()
|
||||
state = &s
|
||||
if planet, ok := sg.OnPlanet(); ok {
|
||||
onPlanet = &planet
|
||||
}
|
||||
is = sg.StateInSpace
|
||||
continue
|
||||
}
|
||||
if *state != sg.State() {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different states", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
}
|
||||
if planet, ok := sg.OnPlanet(); ok && onPlanet != nil && *onPlanet != planet {
|
||||
for sg := range FleetGroups(g, ri, fi) {
|
||||
fmt.Println("group", sg.Index, "fleet", sg.FleetID, g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination)
|
||||
}
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q are on different planets: %d <> %d", g.Race[ri].Name, g.Fleets[fi].Name, *onPlanet, planet))
|
||||
}
|
||||
if (is == nil && sg.StateInSpace != nil) || (is != nil && sg.StateInSpace == nil) {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q on_planet and in_space at the same time", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
}
|
||||
if is != nil && sg.StateInSpace != nil && !is.Equal(*sg.StateInSpace) {
|
||||
panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different is_space states", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
}
|
||||
}
|
||||
if state == nil {
|
||||
panic(fmt.Sprintf("FleetState: race's %q fleet %q has no ships", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
}
|
||||
return *state, onPlanet, is
|
||||
}
|
||||
// func FleetState(g *Game, fleetID uuid.UUID) (ShipGroupState, *uint, *InSpace) {
|
||||
// fi := slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.ID == fleetID })
|
||||
// if fi < 0 {
|
||||
// panic("FleetState: fleet id not found: " + fleetID.String())
|
||||
// }
|
||||
// ri := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == g.Fleets[fi].OwnerID })
|
||||
// if ri < 0 {
|
||||
// panic("FleetState: race id not found: " + g.Fleets[fi].OwnerID.String())
|
||||
// }
|
||||
// var state *ShipGroupState
|
||||
// var onPlanet *uint
|
||||
// var is *InSpace
|
||||
// for sg := range FleetGroups(g, ri, fi) {
|
||||
// if state == nil {
|
||||
// s := sg.State()
|
||||
// state = &s
|
||||
// if planet, ok := sg.OnPlanet(); ok {
|
||||
// onPlanet = &planet
|
||||
// }
|
||||
// is = sg.StateInSpace
|
||||
// continue
|
||||
// }
|
||||
// if *state != sg.State() {
|
||||
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different states", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
// }
|
||||
// if planet, ok := sg.OnPlanet(); ok && onPlanet != nil && *onPlanet != planet {
|
||||
// for sg := range FleetGroups(g, ri, fi) {
|
||||
// fmt.Println("group", sg.Index, "fleet", sg.FleetID, g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination)
|
||||
// }
|
||||
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q are on different planets: %d <> %d", g.Race[ri].Name, g.Fleets[fi].Name, *onPlanet, planet))
|
||||
// }
|
||||
// if (is == nil && sg.StateInSpace != nil) || (is != nil && sg.StateInSpace == nil) {
|
||||
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q on_planet and in_space at the same time", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
// }
|
||||
// if is != nil && sg.StateInSpace != nil && !is.Equal(*sg.StateInSpace) {
|
||||
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different is_space states", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
// }
|
||||
// }
|
||||
// if state == nil {
|
||||
// panic(fmt.Sprintf("FleetState: race's %q fleet %q has no ships", g.Race[ri].Name, g.Fleets[fi].Name))
|
||||
// }
|
||||
// return *state, onPlanet, is
|
||||
// }
|
||||
|
||||
// TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first.
|
||||
func (g Game) FleetSpeed(fl Fleet) float64 {
|
||||
result := math.MaxFloat64
|
||||
for sg := range g.ShipGroups {
|
||||
if g.ShipGroups[sg].FleetID == nil || *g.ShipGroups[sg].FleetID != fl.ID {
|
||||
continue
|
||||
}
|
||||
st := g.mustShipType(g.ShipGroups[sg].TypeID)
|
||||
typeSpeed := g.ShipGroups[sg].Speed(st)
|
||||
if typeSpeed < result {
|
||||
result = typeSpeed
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
// // TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first.
|
||||
// func (g Game) FleetSpeed(fl Fleet) float64 {
|
||||
// result := math.MaxFloat64
|
||||
// for sg := range g.ShipGroups {
|
||||
// if g.ShipGroups[sg].FleetID == nil || *g.ShipGroups[sg].FleetID != fl.ID {
|
||||
// continue
|
||||
// }
|
||||
// st := g.mustShipType(g.ShipGroups[sg].TypeID)
|
||||
// typeSpeed := g.ShipGroups[sg].Speed(st)
|
||||
// if typeSpeed < result {
|
||||
// result = typeSpeed
|
||||
// }
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
||||
func (g *Game) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.joinShipGroupToFleetInternal(ri, fleetName, group, count)
|
||||
}
|
||||
// func (g *Game) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error {
|
||||
// ri, err := g.raceIndex(raceName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return g.joinShipGroupToFleetInternal(ri, fleetName, group, count)
|
||||
// }
|
||||
|
||||
func (g *Game) JoinFleets(raceName, fleetSourceName, fleetTargetName string) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.joinFleetsInternal(ri, fleetSourceName, fleetTargetName)
|
||||
}
|
||||
// func (g *Game) JoinFleets(raceName, fleetSourceName, fleetTargetName string) error {
|
||||
// ri, err := g.raceIndex(raceName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return g.joinFleetsInternal(ri, fleetSourceName, fleetTargetName)
|
||||
// }
|
||||
|
||||
func (g *Game) joinShipGroupToFleetInternal(ri int, fleetName string, groupIndex, quantity uint) (err error) {
|
||||
name, ok := validateTypeName(fleetName)
|
||||
if !ok {
|
||||
return e.NewEntityTypeNameValidationError("%q", name)
|
||||
}
|
||||
sgi := -1
|
||||
var maxIndex uint
|
||||
for i, sg := range g.listIndexShipGroups(ri) {
|
||||
if sgi < 0 && sg.Index == groupIndex {
|
||||
sgi = i
|
||||
}
|
||||
if sg.Index > maxIndex {
|
||||
maxIndex = sg.Index
|
||||
}
|
||||
}
|
||||
if sgi < 0 {
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
// func (g *Game) joinShipGroupToFleetInternal(ri int, fleetName string, groupIndex, quantity uint) (err error) {
|
||||
// name, ok := validateTypeName(fleetName)
|
||||
// if !ok {
|
||||
// return e.NewEntityTypeNameValidationError("%q", name)
|
||||
// }
|
||||
// sgi := -1
|
||||
// var maxIndex uint
|
||||
// for i, sg := range g.listIndexShipGroups(ri) {
|
||||
// if sgi < 0 && sg.Index == groupIndex {
|
||||
// sgi = i
|
||||
// }
|
||||
// if sg.Index > maxIndex {
|
||||
// maxIndex = sg.Index
|
||||
// }
|
||||
// }
|
||||
// if sgi < 0 {
|
||||
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
// }
|
||||
|
||||
if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
// if g.ShipGroups[sgi].State() != StateInOrbit {
|
||||
// return e.NewShipsBusyError()
|
||||
// }
|
||||
|
||||
if g.ShipGroups[sgi].Number < quantity {
|
||||
return e.NewJoinFleetGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
||||
}
|
||||
// if g.ShipGroups[sgi].Number < quantity {
|
||||
// return e.NewJoinFleetGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
||||
// }
|
||||
|
||||
fi := g.fleetIndex(ri, name)
|
||||
if fi < 0 {
|
||||
fi, err = g.createFleet(ri, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
state, onPlanet, _ := FleetState(g, g.Fleets[fi].ID)
|
||||
if state != StateInOrbit || *onPlanet != g.ShipGroups[sgi].Destination {
|
||||
return e.NewShipsNotOnSamePlanetError("fleet: %s", fleetName)
|
||||
}
|
||||
}
|
||||
// fi := g.fleetIndex(ri, name)
|
||||
// if fi < 0 {
|
||||
// fi, err = g.createFleet(ri, name)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// } else {
|
||||
// state, onPlanet, _ := FleetState(g, g.Fleets[fi].ID)
|
||||
// if state != StateInOrbit || *onPlanet != g.ShipGroups[sgi].Destination {
|
||||
// return e.NewShipsNotOnSamePlanetError("fleet: %s", fleetName)
|
||||
// }
|
||||
// }
|
||||
|
||||
// FIXME: if g.ShipGroups[sgi].FleetID != nil { // delete old fleet if empty, ALSO mind breaking group }
|
||||
if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
|
||||
// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// sgi = nsgi
|
||||
newGroup := g.ShipGroups[sgi]
|
||||
newGroup.Number -= quantity
|
||||
g.ShipGroups[sgi].Number = quantity
|
||||
newGroup.Index = maxIndex + 1
|
||||
g.ShipGroups = append(g.ShipGroups, newGroup)
|
||||
}
|
||||
// // FIXME: if g.ShipGroups[sgi].FleetID != nil { // delete old fleet if empty, ALSO mind breaking group }
|
||||
// if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
|
||||
// // nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
|
||||
// // if err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // sgi = nsgi
|
||||
// newGroup := g.ShipGroups[sgi]
|
||||
// newGroup.Number -= quantity
|
||||
// g.ShipGroups[sgi].Number = quantity
|
||||
// newGroup.Index = maxIndex + 1
|
||||
// g.ShipGroups = append(g.ShipGroups, newGroup)
|
||||
// }
|
||||
|
||||
g.ShipGroups[sgi].FleetID = &g.Fleets[fi].ID
|
||||
return nil
|
||||
}
|
||||
// g.ShipGroups[sgi].FleetID = &g.Fleets[fi].ID
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (g *Game) createFleet(ri int, name string) (int, error) {
|
||||
n, ok := validateTypeName(name)
|
||||
if !ok {
|
||||
return 0, e.NewEntityTypeNameValidationError("%q", n)
|
||||
}
|
||||
if fl := g.fleetIndex(ri, n); fl >= 0 {
|
||||
return 0, e.NewEntityTypeNameDuplicateError("fleet %w", g.Fleets[fl].Name)
|
||||
}
|
||||
fleets := slices.Clone(g.Fleets)
|
||||
fleets = append(fleets, Fleet{
|
||||
ID: uuid.New(),
|
||||
OwnerID: g.Race[ri].ID,
|
||||
Name: n,
|
||||
})
|
||||
g.Fleets = fleets
|
||||
return len(g.Fleets) - 1, nil
|
||||
}
|
||||
// func (g *Game) createFleet(ri int, name string) (int, error) {
|
||||
// n, ok := validateTypeName(name)
|
||||
// if !ok {
|
||||
// return 0, e.NewEntityTypeNameValidationError("%q", n)
|
||||
// }
|
||||
// if fl := g.fleetIndex(ri, n); fl >= 0 {
|
||||
// return 0, e.NewEntityTypeNameDuplicateError("fleet %w", g.Fleets[fl].Name)
|
||||
// }
|
||||
// fleets := slices.Clone(g.Fleets)
|
||||
// fleets = append(fleets, Fleet{
|
||||
// ID: uuid.New(),
|
||||
// OwnerID: g.Race[ri].ID,
|
||||
// Name: n,
|
||||
// })
|
||||
// g.Fleets = fleets
|
||||
// return len(g.Fleets) - 1, nil
|
||||
// }
|
||||
|
||||
func (g *Game) joinFleetsInternal(ri int, fleetSourceName, fleetTargetName string) (err error) {
|
||||
fiSource := g.fleetIndex(ri, fleetSourceName)
|
||||
if fiSource < 0 {
|
||||
return e.NewEntityNotExistsError("source fleet %s", fleetSourceName)
|
||||
}
|
||||
fiTarget := g.fleetIndex(ri, fleetTargetName)
|
||||
if fiTarget < 0 {
|
||||
return e.NewEntityNotExistsError("target fleet %s", fleetTargetName)
|
||||
}
|
||||
srcState, planet1, _ := FleetState(g, g.Fleets[fiSource].ID)
|
||||
tgtState, planet2, _ := FleetState(g, g.Fleets[fiTarget].ID)
|
||||
if srcState != StateInOrbit || srcState != tgtState || *planet1 != *planet2 {
|
||||
return e.NewShipsNotOnSamePlanetError()
|
||||
}
|
||||
for sgi, sg := range g.listIndexShipGroups(ri) {
|
||||
if sg.FleetID != nil && *sg.FleetID == g.Fleets[fiSource].ID {
|
||||
g.ShipGroups[sgi].FleetID = &g.Fleets[fiTarget].ID
|
||||
}
|
||||
}
|
||||
return g.deleteFleetSafe(ri, fleetSourceName)
|
||||
}
|
||||
// func (g *Game) joinFleetsInternal(ri int, fleetSourceName, fleetTargetName string) (err error) {
|
||||
// fiSource := g.fleetIndex(ri, fleetSourceName)
|
||||
// if fiSource < 0 {
|
||||
// return e.NewEntityNotExistsError("source fleet %s", fleetSourceName)
|
||||
// }
|
||||
// fiTarget := g.fleetIndex(ri, fleetTargetName)
|
||||
// if fiTarget < 0 {
|
||||
// return e.NewEntityNotExistsError("target fleet %s", fleetTargetName)
|
||||
// }
|
||||
// srcState, planet1, _ := FleetState(g, g.Fleets[fiSource].ID)
|
||||
// tgtState, planet2, _ := FleetState(g, g.Fleets[fiTarget].ID)
|
||||
// if srcState != StateInOrbit || srcState != tgtState || *planet1 != *planet2 {
|
||||
// return e.NewShipsNotOnSamePlanetError()
|
||||
// }
|
||||
// for sgi, sg := range g.listIndexShipGroups(ri) {
|
||||
// if sg.FleetID != nil && *sg.FleetID == g.Fleets[fiSource].ID {
|
||||
// g.ShipGroups[sgi].FleetID = &g.Fleets[fiTarget].ID
|
||||
// }
|
||||
// }
|
||||
// return g.deleteFleetSafe(ri, fleetSourceName)
|
||||
// }
|
||||
|
||||
func (g *Game) deleteFleetSafe(ri int, name string) error {
|
||||
fi := g.fleetIndex(ri, name)
|
||||
if fi < 0 {
|
||||
return e.NewEntityNotExistsError("fleet %s", name)
|
||||
}
|
||||
for sgi := range g.ShipGroups {
|
||||
if g.ShipGroups[sgi].FleetID != nil && *g.ShipGroups[sgi].FleetID == g.Fleets[fi].ID {
|
||||
return e.NewEntityInUseError("fleet %s: race %s, group #%d", name, g.Race[ri].Name, g.ShipGroups[sgi].Number)
|
||||
}
|
||||
}
|
||||
g.Fleets = append(g.Fleets[:fi], g.Fleets[fi+1:]...)
|
||||
return nil
|
||||
}
|
||||
// func (g *Game) deleteFleetSafe(ri int, name string) error {
|
||||
// fi := g.fleetIndex(ri, name)
|
||||
// if fi < 0 {
|
||||
// return e.NewEntityNotExistsError("fleet %s", name)
|
||||
// }
|
||||
// for sgi := range g.ShipGroups {
|
||||
// if g.ShipGroups[sgi].FleetID != nil && *g.ShipGroups[sgi].FleetID == g.Fleets[fi].ID {
|
||||
// return e.NewEntityInUseError("fleet %s: race %s, group #%d", name, g.Race[ri].Name, g.ShipGroups[sgi].Number)
|
||||
// }
|
||||
// }
|
||||
// g.Fleets = append(g.Fleets[:fi], g.Fleets[fi+1:]...)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (g Game) fleetIndex(ri int, name string) int {
|
||||
return slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.OwnerID == g.Race[ri].ID && f.Name == name })
|
||||
}
|
||||
// func (g Game) fleetIndex(ri int, name string) int {
|
||||
// return slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.OwnerID == g.Race[ri].ID && f.Name == name })
|
||||
// }
|
||||
|
||||
func (g Game) listFleets(ri int) iter.Seq[Fleet] {
|
||||
return func(yield func(Fleet) bool) {
|
||||
for _, fl := range g.listIndexFleets(ri) {
|
||||
if !yield(fl) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// func (g Game) listFleets(ri int) iter.Seq[Fleet] {
|
||||
// return func(yield func(Fleet) bool) {
|
||||
// for _, fl := range g.listIndexFleets(ri) {
|
||||
// if !yield(fl) {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func (g Game) listIndexFleets(ri int) iter.Seq2[int, Fleet] {
|
||||
return func(yield func(int, Fleet) bool) {
|
||||
for i := range g.Fleets {
|
||||
if g.Fleets[i].OwnerID == g.Race[ri].ID {
|
||||
if !yield(i, g.Fleets[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// func (g Game) listIndexFleets(ri int) iter.Seq2[int, Fleet] {
|
||||
// return func(yield func(int, Fleet) bool) {
|
||||
// for i := range g.Fleets {
|
||||
// if g.Fleets[i].OwnerID == g.Race[ri].ID {
|
||||
// if !yield(i, g.Fleets[i]) {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func FleetGroups(g *Game, ri, fi int) iter.Seq[ShipGroup] {
|
||||
if len(g.Fleets) < fi+1 {
|
||||
panic(fmt.Sprintf("FleetGroups: game fleets index %d invalid: len=%d", fi, len(g.Fleets)))
|
||||
}
|
||||
return func(yield func(ShipGroup) bool) {
|
||||
for sg := range g.listShipGroups(ri) {
|
||||
if sg.FleetID != nil && *sg.FleetID == g.Fleets[fi].ID {
|
||||
if !yield(sg) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// func FleetGroups(g *Game, ri, fi int) iter.Seq[ShipGroup] {
|
||||
// if len(g.Fleets) < fi+1 {
|
||||
// panic(fmt.Sprintf("FleetGroups: game fleets index %d invalid: len=%d", fi, len(g.Fleets)))
|
||||
// }
|
||||
// return func(yield func(ShipGroup) bool) {
|
||||
// for sg := range g.listShipGroups(ri) {
|
||||
// if sg.FleetID != nil && *sg.FleetID == g.Fleets[fi].ID {
|
||||
// if !yield(sg) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
// import (
|
||||
// "fmt"
|
||||
// "slices"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
// e "github.com/iliadenisov/galaxy/internal/error"
|
||||
// "github.com/iliadenisov/galaxy/internal/util"
|
||||
// )
|
||||
|
||||
func (g *Game) SendFleet(raceName, fleetName string, planetNumber uint) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi := g.fleetIndex(ri, fleetName)
|
||||
if fi < 0 {
|
||||
return e.NewEntityNotExistsError("fleet %q", fleetName)
|
||||
}
|
||||
return g.sendFleetInternal(ri, fi, planetNumber)
|
||||
}
|
||||
// func (g *Game) SendFleet(raceName, fleetName string, planetNumber uint) error {
|
||||
// ri, err := g.raceIndex(raceName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// fi := g.fleetIndex(ri, fleetName)
|
||||
// if fi < 0 {
|
||||
// return e.NewEntityNotExistsError("fleet %q", fleetName)
|
||||
// }
|
||||
// return g.sendFleetInternal(ri, fi, planetNumber)
|
||||
// }
|
||||
|
||||
func (g *Game) sendFleetInternal(ri, fi int, planetNumber uint) error {
|
||||
state, sourcePlanet, _ := FleetState(g, g.Fleets[fi].ID)
|
||||
if StateInOrbit != state && StateLaunched != state {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
// func (g *Game) sendFleetInternal(ri, fi int, planetNumber uint) error {
|
||||
// state, sourcePlanet, _ := FleetState(g, g.Fleets[fi].ID)
|
||||
// if StateInOrbit != state && StateLaunched != state {
|
||||
// return e.NewShipsBusyError()
|
||||
// }
|
||||
|
||||
p1, ok := PlanetByNum(g, *sourcePlanet)
|
||||
if !ok {
|
||||
return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// p1, ok := PlanetByNum(g, *sourcePlanet)
|
||||
// if !ok {
|
||||
// return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
|
||||
// }
|
||||
// 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)
|
||||
// }
|
||||
|
||||
for sg := range FleetGroups(g, ri, fi) {
|
||||
st, ok := ShipClass(g, ri, sg.TypeID)
|
||||
if !ok {
|
||||
return e.NewGameStateError("not found: ShipType ID=%v", sg.TypeID)
|
||||
}
|
||||
if st.DriveBlockMass() == 0 {
|
||||
return e.NewSendShipHasNoDrivesError("Class=%s", st.Name)
|
||||
}
|
||||
}
|
||||
// for sg := range FleetGroups(g, ri, fi) {
|
||||
// st, ok := ShipClass(g, ri, sg.TypeID)
|
||||
// if !ok {
|
||||
// return e.NewGameStateError("not found: ShipType ID=%v", sg.TypeID)
|
||||
// }
|
||||
// if st.DriveBlockMass() == 0 {
|
||||
// return e.NewSendShipHasNoDrivesError("Class=%s", st.Name)
|
||||
// }
|
||||
// }
|
||||
|
||||
if *sourcePlanet == planetNumber {
|
||||
UnsendFleet(g, ri, fi)
|
||||
return nil
|
||||
}
|
||||
// if *sourcePlanet == planetNumber {
|
||||
// UnsendFleet(g, ri, fi)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
LaunchFleet(g, ri, fi, planetNumber)
|
||||
// LaunchFleet(g, ri, fi, planetNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func LaunchFleet(g *Game, ri, fi int, destination uint) {
|
||||
for sg := range FleetGroups(g, ri, fi) {
|
||||
sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
if sgi < 0 {
|
||||
panic(fmt.Sprintf("LauncgFleet: cannot find ship group index=%d", sg.Index))
|
||||
}
|
||||
g.ShipGroups[sgi] = LaunchShips(sg, destination)
|
||||
}
|
||||
}
|
||||
// func LaunchFleet(g *Game, ri, fi int, destination uint) {
|
||||
// for sg := range FleetGroups(g, ri, fi) {
|
||||
// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
// if sgi < 0 {
|
||||
// panic(fmt.Sprintf("LauncgFleet: cannot find ship group index=%d", sg.Index))
|
||||
// }
|
||||
// g.ShipGroups[sgi] = LaunchShips(sg, destination)
|
||||
// }
|
||||
// }
|
||||
|
||||
func UnsendFleet(g *Game, ri, fi int) {
|
||||
for sg := range FleetGroups(g, ri, fi) {
|
||||
sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
if sgi < 0 {
|
||||
panic(fmt.Sprintf("UnsendFleet: cannot find ship group index=%d", sg.Index))
|
||||
}
|
||||
g.ShipGroups[sgi] = UnsendShips(sg)
|
||||
}
|
||||
}
|
||||
// func UnsendFleet(g *Game, ri, fi int) {
|
||||
// for sg := range FleetGroups(g, ri, fi) {
|
||||
// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index })
|
||||
// if sgi < 0 {
|
||||
// panic(fmt.Sprintf("UnsendFleet: cannot find ship group index=%d", sg.Index))
|
||||
// }
|
||||
// g.ShipGroups[sgi] = UnsendShips(sg)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
// import (
|
||||
// "slices"
|
||||
// "testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
// e "github.com/iliadenisov/galaxy/internal/error"
|
||||
// "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
func TestSendFleet(t *testing.T) {
|
||||
g := newGame()
|
||||
// group #1 - in_orbit Planet_0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1))
|
||||
// group #2 - in_space (later)
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
// group #3 - in_orbit Planet_0, 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))
|
||||
// group #4 - in_orbit Planet_0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2))
|
||||
// func TestSendFleet(t *testing.T) {
|
||||
// g := newGame()
|
||||
// // group #1 - in_orbit Planet_0
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1))
|
||||
// // group #2 - in_space (later)
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
// // group #3 - in_orbit Planet_0, unmovable
|
||||
// g.CreateShipType(Race_0.Name, "Fortress", 0, 50, 30, 100, 0)
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1))
|
||||
// // group #4 - in_orbit Planet_0
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2))
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
// // ensure race has no Fleets
|
||||
// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetSending := "R0_Fleet_one"
|
||||
fleetInSpace := "R0_Fleet_inSpace"
|
||||
fleetUnmovable := "R0_Fleet_unmovable"
|
||||
// fleetSending := "R0_Fleet_one"
|
||||
// fleetInSpace := "R0_Fleet_inSpace"
|
||||
// fleetUnmovable := "R0_Fleet_unmovable"
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 1, 0))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 3, 0))
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 1, 0))
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 3, 0))
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetInSpace, 2, 0))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 3, 0))
|
||||
// group #2 - in_space
|
||||
g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetInSpace, 2, 0))
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 3, 0))
|
||||
// // group #2 - in_space
|
||||
// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet("UnknownRace", fleetSending, 2),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, "UnknownFleet", 2),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetInSpace, 2),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetSending, 200),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetSending, 3),
|
||||
e.GenericErrorText(e.ErrSendUnreachableDestination))
|
||||
assert.ErrorContains(t,
|
||||
g.SendFleet(Race_0.Name, fleetUnmovable, 2),
|
||||
e.GenericErrorText(e.ErrSendShipHasNoDrives))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet("UnknownRace", fleetSending, 2),
|
||||
// e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet(Race_0.Name, "UnknownFleet", 2),
|
||||
// e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet(Race_0.Name, fleetInSpace, 2),
|
||||
// e.GenericErrorText(e.ErrShipsBusy))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet(Race_0.Name, fleetSending, 200),
|
||||
// e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet(Race_0.Name, fleetSending, 3),
|
||||
// e.GenericErrorText(e.ErrSendUnreachableDestination))
|
||||
// assert.ErrorContains(t,
|
||||
// g.SendFleet(Race_0.Name, fleetUnmovable, 2),
|
||||
// e.GenericErrorText(e.ErrSendShipHasNoDrives))
|
||||
|
||||
assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 2))
|
||||
fi := slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending })
|
||||
state, _, _ := game.FleetState(g, g.Fleets[fi].ID)
|
||||
assert.Equal(t, game.StateLaunched, state)
|
||||
for sg := range game.FleetGroups(g, Race_0_idx, fi) {
|
||||
assert.Equal(t, game.StateLaunched, sg.State())
|
||||
}
|
||||
// assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 2))
|
||||
// fi := slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending })
|
||||
// state, _, _ := game.FleetState(g, g.Fleets[fi].ID)
|
||||
// assert.Equal(t, game.StateLaunched, state)
|
||||
// for sg := range game.FleetGroups(g, Race_0_idx, fi) {
|
||||
// assert.Equal(t, game.StateLaunched, sg.State())
|
||||
// }
|
||||
|
||||
assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 0))
|
||||
fi = slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending })
|
||||
state, _, _ = game.FleetState(g, g.Fleets[fi].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
for sg := range game.FleetGroups(g, Race_0_idx, fi) {
|
||||
assert.Equal(t, game.StateInOrbit, sg.State())
|
||||
}
|
||||
}
|
||||
// assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 0))
|
||||
// fi = slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending })
|
||||
// state, _, _ = game.FleetState(g, g.Fleets[fi].ID)
|
||||
// assert.Equal(t, game.StateInOrbit, state)
|
||||
// for sg := range game.FleetGroups(g, Race_0_idx, fi) {
|
||||
// assert.Equal(t, game.StateInOrbit, sg.State())
|
||||
// }
|
||||
// }
|
||||
|
||||
+113
-113
@@ -1,136 +1,136 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
// import (
|
||||
// "slices"
|
||||
// "testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
// 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 := newGame()
|
||||
var groupIndex uint = 1
|
||||
// func TestJoinShipGroupToFleet(t *testing.T) {
|
||||
// g := newGame()
|
||||
// var groupIndex uint = 1
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, " ", groupIndex, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinShipGroupToFleet(Race_0.Name, " ", groupIndex, 0),
|
||||
// e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 0),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 0),
|
||||
// e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
|
||||
// creating ShipGroup
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 5))
|
||||
// // creating ShipGroup
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 5))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 6),
|
||||
e.GenericErrorText(e.ErrJoinFleetGroupNumberNotEnough))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 6),
|
||||
// e.GenericErrorText(e.ErrJoinFleetGroupNumberNotEnough))
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
// // ensure race has no Fleets
|
||||
// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetOne := "R0_Fleet_one"
|
||||
fleetTwo := "R0_Fleet_two"
|
||||
// fleetOne := "R0_Fleet_one"
|
||||
// fleetTwo := "R0_Fleet_two"
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
fleets := slices.Collect(g.ListFleets(Race_0_idx))
|
||||
groups := slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
assert.Len(t, groups, 1)
|
||||
gi := 0
|
||||
assert.Len(t, fleets, 1)
|
||||
assert.Equal(t, fleets[0].Name, fleetOne)
|
||||
state, _, _ := game.FleetState(g, fleets[0].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
// fleets := slices.Collect(g.ListFleets(Race_0_idx))
|
||||
// groups := slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
// assert.Len(t, groups, 1)
|
||||
// gi := 0
|
||||
// assert.Len(t, fleets, 1)
|
||||
// assert.Equal(t, fleets[0].Name, fleetOne)
|
||||
// state, _, _ := game.FleetState(g, fleets[0].ID)
|
||||
// assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
// assert.NotNil(t, groups[gi].FleetID)
|
||||
// assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
|
||||
// create another ShipGroup
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
groupIndex = 2
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTwo, groupIndex, 2))
|
||||
fleets = slices.Collect(g.ListFleets(Race_0_idx))
|
||||
groups = slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
assert.Len(t, groups, 3)
|
||||
gi = 1
|
||||
assert.Len(t, fleets, 2)
|
||||
assert.Equal(t, fleets[1].Name, fleetTwo)
|
||||
state, _, _ = game.FleetState(g, fleets[1].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
// // create another ShipGroup
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
// groupIndex = 2
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTwo, groupIndex, 2))
|
||||
// fleets = slices.Collect(g.ListFleets(Race_0_idx))
|
||||
// groups = slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
// assert.Len(t, groups, 3)
|
||||
// gi = 1
|
||||
// assert.Len(t, fleets, 2)
|
||||
// assert.Equal(t, fleets[1].Name, fleetTwo)
|
||||
// state, _, _ = game.FleetState(g, fleets[1].ID)
|
||||
// assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[1].ID, *groups[gi].FleetID)
|
||||
assert.Equal(t, uint(2), groups[gi].Number)
|
||||
assert.Equal(t, uint(2), groups[gi].Index)
|
||||
// assert.NotNil(t, groups[gi].FleetID)
|
||||
// assert.Equal(t, fleets[1].ID, *groups[gi].FleetID)
|
||||
// assert.Equal(t, uint(2), groups[gi].Number)
|
||||
// assert.Equal(t, uint(2), groups[gi].Index)
|
||||
|
||||
gi = 2
|
||||
assert.Nil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, uint(1), groups[gi].Number)
|
||||
assert.Equal(t, uint(3), groups[gi].Index)
|
||||
// gi = 2
|
||||
// assert.Nil(t, groups[gi].FleetID)
|
||||
// assert.Equal(t, uint(1), groups[gi].Number)
|
||||
// assert.Equal(t, uint(3), groups[gi].Index)
|
||||
|
||||
groupIndex = groups[gi].Index
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
fleets = slices.Collect(g.ListFleets(Race_0_idx))
|
||||
assert.Len(t, fleets, 2)
|
||||
groups = slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
assert.NotNil(t, groups[gi].FleetID)
|
||||
assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
state, _, _ = game.FleetState(g, fleets[0].ID)
|
||||
assert.Equal(t, game.StateInOrbit, state)
|
||||
// groupIndex = groups[gi].Index
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0))
|
||||
// fleets = slices.Collect(g.ListFleets(Race_0_idx))
|
||||
// assert.Len(t, fleets, 2)
|
||||
// groups = slices.Collect(g.ListShipGroups(Race_0_idx))
|
||||
// assert.NotNil(t, groups[gi].FleetID)
|
||||
// assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
|
||||
// state, _, _ = game.FleetState(g, fleets[0].ID)
|
||||
// assert.Equal(t, game.StateInOrbit, state)
|
||||
|
||||
// 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].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].StateInSpace = nil
|
||||
// // 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].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].StateInSpace = nil
|
||||
|
||||
// existing fleet not on the same planet or in_orbit
|
||||
g.ShipGroups[0].StateInSpace = &game.InSpace{
|
||||
Origin: 2,
|
||||
Range: 1,
|
||||
}
|
||||
g.ShipGroups[2].StateInSpace = g.ShipGroups[0].StateInSpace
|
||||
assert.ErrorContains(t,
|
||||
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0),
|
||||
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
}
|
||||
// // existing fleet not on the same planet or in_orbit
|
||||
// g.ShipGroups[0].StateInSpace = &game.InSpace{
|
||||
// Origin: 2,
|
||||
// Range: 1,
|
||||
// }
|
||||
// g.ShipGroups[2].StateInSpace = g.ShipGroups[0].StateInSpace
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0),
|
||||
// e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
// }
|
||||
|
||||
func TestJoinFleets(t *testing.T) {
|
||||
g := newGame()
|
||||
// creating ShipGroup #1 at Planet_0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
||||
// creating ShipGroup #2 at Planet_2
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_2_num, 2)) // group #2
|
||||
// creating ShipGroup #3 at Planet_0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // group #3
|
||||
// func TestJoinFleets(t *testing.T) {
|
||||
// g := newGame()
|
||||
// // creating ShipGroup #1 at Planet_0
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1
|
||||
// // creating ShipGroup #2 at Planet_2
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_2_num, 2)) // group #2
|
||||
// // creating ShipGroup #3 at Planet_0
|
||||
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // group #3
|
||||
|
||||
// ensure race has no Fleets
|
||||
assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
// // ensure race has no Fleets
|
||||
// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0)
|
||||
|
||||
fleetPlanet2 := "R0_Fleet_On_Planet_2"
|
||||
fleetSource := "R0_Fleet_one"
|
||||
fleetTarget := "R0_Fleet_two"
|
||||
// fleetPlanet2 := "R0_Fleet_On_Planet_2"
|
||||
// fleetSource := "R0_Fleet_one"
|
||||
// fleetTarget := "R0_Fleet_two"
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSource, 1, 0))
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTarget, 3, 0))
|
||||
assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSource, fleetTarget))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
// e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSource, 1, 0))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
|
||||
// e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTarget, 3, 0))
|
||||
// assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSource, fleetTarget))
|
||||
|
||||
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetPlanet2, 2, 0))
|
||||
assert.ErrorContains(t,
|
||||
g.JoinFleets(Race_0.Name, fleetPlanet2, fleetTarget),
|
||||
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
}
|
||||
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetPlanet2, 2, 0))
|
||||
// assert.ErrorContains(t,
|
||||
// g.JoinFleets(Race_0.Name, fleetPlanet2, fleetTarget),
|
||||
// e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
|
||||
// }
|
||||
|
||||
@@ -3,15 +3,17 @@ package game
|
||||
import "iter"
|
||||
|
||||
func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error {
|
||||
return g.createShips(ri, shipTypeName, int(planetNumber), quantity)
|
||||
return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity)
|
||||
}
|
||||
|
||||
func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] {
|
||||
return g.listShipGroups(ri)
|
||||
return func(yield func(ShipGroup) bool) {}
|
||||
// return g.listShipGroups(ri)
|
||||
}
|
||||
|
||||
func (g Game) ListFleets(ri int) iter.Seq[Fleet] {
|
||||
return g.listFleets(ri)
|
||||
return func(yield func(Fleet) bool) {}
|
||||
// return g.listFleets(ri)
|
||||
}
|
||||
|
||||
func (g Game) MustPlanetByNumber(num uint) Planet {
|
||||
|
||||
@@ -90,12 +90,12 @@ func newGame() *game.Game {
|
||||
},
|
||||
},
|
||||
}
|
||||
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_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.)
|
||||
// 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
|
||||
}
|
||||
|
||||
+472
-479
File diff suppressed because it is too large
Load Diff
@@ -1,93 +1,93 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"slices"
|
||||
// import (
|
||||
// "slices"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
// 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) 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)
|
||||
}
|
||||
// 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]
|
||||
// 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()
|
||||
}
|
||||
// if st.DriveBlockMass() == 0 {
|
||||
// return e.NewSendShipHasNoDrivesError()
|
||||
// }
|
||||
|
||||
sourcePlanet, ok := g.ShipGroups[sgi].OnPlanet()
|
||||
if !ok {
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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", sourcePlanet)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// p1, ok := PlanetByNum(g, sourcePlanet)
|
||||
// if !ok {
|
||||
// return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
|
||||
// }
|
||||
// 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 quantity > 0 && quantity < g.ShipGroups[sgi].Number {
|
||||
// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// sgi = nsgi
|
||||
// }
|
||||
|
||||
if sourcePlanet == planetNumber {
|
||||
g.ShipGroups[sgi] = UnsendShips(g.ShipGroups[sgi])
|
||||
g.joinEqualGroupsInternal(ri)
|
||||
return nil
|
||||
}
|
||||
// if sourcePlanet == planetNumber {
|
||||
// g.ShipGroups[sgi] = UnsendShips(g.ShipGroups[sgi])
|
||||
// g.joinEqualGroupsInternal(ri)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
g.ShipGroups[sgi] = LaunchShips(g.ShipGroups[sgi], planetNumber)
|
||||
// g.ShipGroups[sgi] = LaunchShips(g.ShipGroups[sgi], planetNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func LaunchShips(sg ShipGroup, destination uint) ShipGroup {
|
||||
sg.StateInSpace = &InSpace{
|
||||
Origin: sg.Destination,
|
||||
}
|
||||
sg.Destination = destination
|
||||
return sg
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// func UnsendShips(sg ShipGroup) ShipGroup {
|
||||
// sg.Destination = sg.StateInSpace.Origin
|
||||
// sg.StateInSpace = nil
|
||||
// return sg
|
||||
// }
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
// import (
|
||||
// "slices"
|
||||
// "testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
// e "github.com/iliadenisov/galaxy/internal/error"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
// "github.com/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))
|
||||
// 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, 50, 30, 100, 0)
|
||||
// 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.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, 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, 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, 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())
|
||||
}
|
||||
// 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())
|
||||
// }
|
||||
|
||||
+514
-517
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,6 @@ import (
|
||||
"maps"
|
||||
"math"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
)
|
||||
|
||||
type UpgradeCalc struct {
|
||||
@@ -49,157 +46,157 @@ func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, 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) 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)
|
||||
}
|
||||
// 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]
|
||||
// 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)
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
// 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,
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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(),
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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]
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// shipsToUpgrade := g.ShipGroups[sgi].Number
|
||||
// // НЕ БОЛЕЕ УКАЗАННОГО
|
||||
// if limitShips > 0 && shipsToUpgrade > limitShips {
|
||||
// shipsToUpgrade = limitShips
|
||||
// }
|
||||
|
||||
maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity)
|
||||
// maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity)
|
||||
|
||||
/*
|
||||
1. считаем стоимость модернизации одного корабля
|
||||
2. считаем сколько кораблей можно модернизировать
|
||||
3. если не хватает даже на 1 корабль, ограничиваемся одним кораблём и пересчитываем коэффициент пропорционально массе блоков
|
||||
4. иначе, считаем истинное количество кораблей с учётом ограничения maxShips
|
||||
*/
|
||||
blockMassSum := st.EmptyMass()
|
||||
// /*
|
||||
// 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
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// // 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
|
||||
}
|
||||
// // 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])
|
||||
}
|
||||
}
|
||||
// // 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
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 {
|
||||
if sg.StateUpgrade == nil {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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"
|
||||
)
|
||||
@@ -111,59 +109,59 @@ func TestUpgradeGroupPreference(t *testing.T) {
|
||||
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
|
||||
// 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))
|
||||
// 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))
|
||||
// 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.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))
|
||||
}
|
||||
// assert.ErrorContains(t,
|
||||
// g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3),
|
||||
// e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed))
|
||||
// }
|
||||
|
||||
+70
-70
@@ -1,10 +1,10 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"slices"
|
||||
// "slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
// e "github.com/iliadenisov/galaxy/internal/error"
|
||||
)
|
||||
|
||||
type Race struct {
|
||||
@@ -38,7 +38,7 @@ func (r Race) TechLevel(t Tech) float64 {
|
||||
return r.Tech.Value(t)
|
||||
}
|
||||
|
||||
// TODO: refactor to separate method with *Race as parameter
|
||||
// TODO: remove func, move to Cache
|
||||
func (r *Race) SetTechLevel(t Tech, v float64) {
|
||||
r.Tech = r.Tech.Set(t, v)
|
||||
}
|
||||
@@ -51,74 +51,74 @@ func (r Race) VisibilityDistance() float64 {
|
||||
return r.TechLevel(TechDrive) * 30
|
||||
}
|
||||
|
||||
func (g Game) Relation(hostRace, opponentRace string) (RaceRelation, error) {
|
||||
ri, err := g.raceIndex(hostRace)
|
||||
if err != nil {
|
||||
return RaceRelation{}, err
|
||||
}
|
||||
other, err := g.raceIndex(opponentRace)
|
||||
if err != nil {
|
||||
return RaceRelation{}, err
|
||||
}
|
||||
return g.relationInternal(ri, other)
|
||||
}
|
||||
// func (g Game) Relation(hostRace, opponentRace string) (RaceRelation, error) {
|
||||
// ri, err := g.raceIndex(hostRace)
|
||||
// if err != nil {
|
||||
// return RaceRelation{}, err
|
||||
// }
|
||||
// other, err := g.raceIndex(opponentRace)
|
||||
// if err != nil {
|
||||
// return RaceRelation{}, err
|
||||
// }
|
||||
// return g.relationInternal(ri, other)
|
||||
// }
|
||||
|
||||
func (g Game) UpdateRelation(race, opponent string, rel Relation) error {
|
||||
ri, err := g.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var other int
|
||||
if race == opponent {
|
||||
other = ri
|
||||
} else if other, err = g.raceIndex(opponent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.updateRelationInternal(ri, other, rel)
|
||||
}
|
||||
// func (g Game) UpdateRelation(race, opponent string, rel Relation) error {
|
||||
// ri, err := g.raceIndex(race)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// var other int
|
||||
// if race == opponent {
|
||||
// other = ri
|
||||
// } else if other, err = g.raceIndex(opponent); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return g.updateRelationInternal(ri, other, rel)
|
||||
// }
|
||||
|
||||
func (g *Game) GiveVotes(race, recipient string) error {
|
||||
ri, err := g.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rec, err := g.raceIndex(recipient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Race[ri].Vote = g.Race[rec].ID
|
||||
return nil
|
||||
}
|
||||
// func (g *Game) GiveVotes(race, recipient string) error {
|
||||
// ri, err := g.raceIndex(race)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// rec, err := g.raceIndex(recipient)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// g.Race[ri].Vote = g.Race[rec].ID
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (g Game) relationInternal(ri, other int) (RaceRelation, error) {
|
||||
if ri == other {
|
||||
return RaceRelation{
|
||||
RaceID: g.Race[ri].ID,
|
||||
Relation: RelationPeace,
|
||||
}, nil
|
||||
}
|
||||
rel := slices.IndexFunc(g.Race[ri].Relations, func(r RaceRelation) bool { return r.RaceID == g.Race[other].ID })
|
||||
if rel < 0 {
|
||||
return RaceRelation{}, e.NewGameStateError("Relation: opponent not found")
|
||||
}
|
||||
return g.Race[ri].Relations[rel], nil
|
||||
}
|
||||
// func (g Game) relationInternal(ri, other int) (RaceRelation, error) {
|
||||
// if ri == other {
|
||||
// return RaceRelation{
|
||||
// RaceID: g.Race[ri].ID,
|
||||
// Relation: RelationPeace,
|
||||
// }, nil
|
||||
// }
|
||||
// rel := slices.IndexFunc(g.Race[ri].Relations, func(r RaceRelation) bool { return r.RaceID == g.Race[other].ID })
|
||||
// if rel < 0 {
|
||||
// return RaceRelation{}, e.NewGameStateError("Relation: opponent not found")
|
||||
// }
|
||||
// return g.Race[ri].Relations[rel], nil
|
||||
// }
|
||||
|
||||
func (g Game) updateRelationInternal(ri, other int, rel Relation) error {
|
||||
for o := range g.Race[ri].Relations {
|
||||
switch {
|
||||
case ri == other:
|
||||
g.Race[ri].Relations[o].Relation = rel
|
||||
case g.Race[ri].Relations[o].RaceID == g.Race[other].ID:
|
||||
g.Race[ri].Relations[o].Relation = rel
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if ri != other {
|
||||
return e.NewGameStateError("UpdateRelation: opponent not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func (g Game) updateRelationInternal(ri, other int, rel Relation) error {
|
||||
// for o := range g.Race[ri].Relations {
|
||||
// switch {
|
||||
// case ri == other:
|
||||
// g.Race[ri].Relations[o].Relation = rel
|
||||
// case g.Race[ri].Relations[o].RaceID == g.Race[other].ID:
|
||||
// g.Race[ri].Relations[o].Relation = rel
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
// if ri != other {
|
||||
// return e.NewGameStateError("UpdateRelation: opponent not found")
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
@@ -1,22 +1 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGiveVotes(t *testing.T) {
|
||||
g := newGame()
|
||||
|
||||
assert.Equal(t, g.Race[Race_0_idx].ID, g.Race[Race_0_idx].Vote)
|
||||
assert.Equal(t, g.Race[Race_1_idx].ID, g.Race[Race_1_idx].Vote)
|
||||
|
||||
assert.NoError(t, g.GiveVotes(Race_0.Name, Race_1.Name))
|
||||
assert.Equal(t, g.Race[Race_1_idx].ID, g.Race[Race_0_idx].Vote)
|
||||
assert.Equal(t, g.Race[Race_1_idx].ID, g.Race[Race_1_idx].Vote)
|
||||
|
||||
assert.ErrorContains(t, g.GiveVotes("UnknownRace", Race_1.Name), e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t, g.GiveVotes(Race_0.Name, "UnknownRace"), e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
}
|
||||
|
||||
+100
-100
@@ -102,116 +102,116 @@ func (g Game) shipTypesInternal(ri int) []ShipType {
|
||||
return g.Race[ri].ShipTypes
|
||||
}
|
||||
|
||||
func (g Game) DeleteShipType(raceName, typeName string) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.deleteShipTypeInternal(ri, typeName)
|
||||
}
|
||||
// func (g Game) DeleteShipType(raceName, typeName string) error {
|
||||
// ri, err := g.raceIndex(raceName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return g.deleteShipTypeInternal(ri, typeName)
|
||||
// }
|
||||
|
||||
func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
||||
st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
|
||||
if st < 0 {
|
||||
return e.NewEntityNotExistsError("ship type %w", name)
|
||||
}
|
||||
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
||||
return p.Production.Type == ProductionShip &&
|
||||
p.Production.SubjectID != nil &&
|
||||
g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
|
||||
}); pl >= 0 {
|
||||
return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name)
|
||||
}
|
||||
for sg := range g.listShipGroups(ri) {
|
||||
if sg.TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||
return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index)
|
||||
}
|
||||
}
|
||||
g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
||||
return nil
|
||||
}
|
||||
// func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
||||
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
|
||||
// if st < 0 {
|
||||
// return e.NewEntityNotExistsError("ship type %w", name)
|
||||
// }
|
||||
// if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
|
||||
// return p.Production.Type == ProductionShip &&
|
||||
// p.Production.SubjectID != nil &&
|
||||
// g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
|
||||
// }); pl >= 0 {
|
||||
// return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name)
|
||||
// }
|
||||
// for sg := range g.listShipGroups(ri) {
|
||||
// if sg.TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||
// return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index)
|
||||
// }
|
||||
// }
|
||||
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// TODO: D A W S C
|
||||
func (g Game) CreateShipType(raceName, typeName string, d, w, s, c float64, a int) error {
|
||||
ri, err := g.raceIndex(raceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = g.createShipTypeInternal(ri, typeName, d, w, s, c, a)
|
||||
return err
|
||||
}
|
||||
// func (g Game) CreateShipType(raceName, typeName string, d, w, s, c float64, a int) error {
|
||||
// ri, err := g.raceIndex(raceName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// _, err = g.createShipTypeInternal(ri, typeName, d, w, s, c, a)
|
||||
// return err
|
||||
// }
|
||||
|
||||
func (g Game) createShipTypeInternal(ri int, name string, d, w, s, c float64, a int) (int, error) {
|
||||
if err := checkShipTypeValues(d, w, s, c, a); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
n, ok := validateTypeName(name)
|
||||
if !ok {
|
||||
return -1, e.NewEntityTypeNameValidationError("%q", n)
|
||||
}
|
||||
if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }); st >= 0 {
|
||||
return -1, e.NewEntityTypeNameDuplicateError("ship type %w", g.Race[ri].ShipTypes[st].Name)
|
||||
}
|
||||
g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes, ShipType{
|
||||
ID: uuid.New(),
|
||||
ShipTypeReport: ShipTypeReport{
|
||||
Name: n,
|
||||
Drive: d,
|
||||
Weapons: w,
|
||||
Shields: s,
|
||||
Cargo: c,
|
||||
Armament: uint(a),
|
||||
},
|
||||
})
|
||||
return len(g.Race[ri].ShipTypes) - 1, nil
|
||||
}
|
||||
// func (g Game) createShipTypeInternal(ri int, name string, d, w, s, c float64, a int) (int, error) {
|
||||
// if err := checkShipTypeValues(d, w, s, c, a); err != nil {
|
||||
// return -1, err
|
||||
// }
|
||||
// n, ok := validateTypeName(name)
|
||||
// if !ok {
|
||||
// return -1, e.NewEntityTypeNameValidationError("%q", n)
|
||||
// }
|
||||
// if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }); st >= 0 {
|
||||
// return -1, e.NewEntityTypeNameDuplicateError("ship type %w", g.Race[ri].ShipTypes[st].Name)
|
||||
// }
|
||||
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes, ShipType{
|
||||
// ID: uuid.New(),
|
||||
// ShipTypeReport: ShipTypeReport{
|
||||
// Name: n,
|
||||
// Drive: d,
|
||||
// Weapons: w,
|
||||
// Shields: s,
|
||||
// Cargo: c,
|
||||
// Armament: uint(a),
|
||||
// },
|
||||
// })
|
||||
// return len(g.Race[ri].ShipTypes) - 1, nil
|
||||
// }
|
||||
|
||||
func (g Game) MergeShipType(race, name, targetName string) error {
|
||||
ri, err := g.raceIndex(race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.mergeShipTypeInternal(ri, name, targetName)
|
||||
}
|
||||
// func (g Game) MergeShipType(race, name, targetName string) error {
|
||||
// ri, err := g.raceIndex(race)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return g.mergeShipTypeInternal(ri, name, targetName)
|
||||
// }
|
||||
|
||||
func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
|
||||
st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
|
||||
if st < 0 {
|
||||
return e.NewEntityNotExistsError("source ship type %w", name)
|
||||
}
|
||||
if name == targetName {
|
||||
return e.NewEntityTypeNameEqualityError("ship type %q", targetName)
|
||||
}
|
||||
tt := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == targetName })
|
||||
if tt < 0 {
|
||||
return e.NewEntityNotExistsError("target ship type %w", name)
|
||||
}
|
||||
if !g.Race[ri].ShipTypes[st].Equal(g.Race[ri].ShipTypes[tt]) {
|
||||
return e.NewMergeShipTypeNotEqualError()
|
||||
}
|
||||
// func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
|
||||
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
|
||||
// if st < 0 {
|
||||
// return e.NewEntityNotExistsError("source ship type %w", name)
|
||||
// }
|
||||
// if name == targetName {
|
||||
// return e.NewEntityTypeNameEqualityError("ship type %q", targetName)
|
||||
// }
|
||||
// tt := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == targetName })
|
||||
// if tt < 0 {
|
||||
// return e.NewEntityNotExistsError("target ship type %w", name)
|
||||
// }
|
||||
// if !g.Race[ri].ShipTypes[st].Equal(g.Race[ri].ShipTypes[tt]) {
|
||||
// return e.NewMergeShipTypeNotEqualError()
|
||||
// }
|
||||
|
||||
// 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.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
|
||||
}
|
||||
}
|
||||
// // 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.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
|
||||
// }
|
||||
// }
|
||||
|
||||
// switch ship groups to the new type
|
||||
for sg := range g.ShipGroups {
|
||||
if g.ShipGroups[sg].OwnerID == g.Race[ri].ID && g.ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||
g.ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID
|
||||
}
|
||||
}
|
||||
// // switch ship groups to the new type
|
||||
// for sg := range g.ShipGroups {
|
||||
// if g.ShipGroups[sg].OwnerID == g.Race[ri].ID && g.ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||
// g.ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID
|
||||
// }
|
||||
// }
|
||||
|
||||
// remove the source type
|
||||
g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
||||
// // remove the source type
|
||||
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func checkShipTypeValues(d, w, s, c float64, a int) error {
|
||||
if !checkShipTypeValueDWSC(d) {
|
||||
|
||||
Reference in New Issue
Block a user