refactor: game funcs moved to controller

This commit is contained in:
Ilia Denisov
2026-01-15 14:42:04 +02:00
parent fe8a8d4150
commit 16aba8435d
47 changed files with 1023 additions and 3093 deletions
+1 -5
View File
@@ -1,10 +1,6 @@
package controller
import (
// "github.com/iliadenisov/galaxy/internal/controller"
// "github.com/iliadenisov/galaxy/internal/game/battle"
"github.com/iliadenisov/galaxy/internal/model/game"
)
import "github.com/iliadenisov/galaxy/internal/model/game"
func TransformBattle(c *Cache, b *Battle) *game.BattleReport {
r := &game.BattleReport{
+23 -32
View File
@@ -9,13 +9,13 @@ 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
cacheRelation map[int]map[int]game.Relation
g *game.Game
cacheRaceIndexByID map[uuid.UUID]int
cacheFleetIndexByID map[uuid.UUID]int
cacheRaceIndexByShipGroupIndex map[int]int
cacheShipClassByShipGroupIndex map[int]*game.ShipType
cachePlanetByPlanetNumber map[uint]*game.Planet
cacheRelation map[int]map[int]game.Relation
}
func NewCache(g *game.Game) *Cache {
@@ -29,11 +29,11 @@ func NewCache(g *game.Game) *Cache {
}
func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType {
if c.shipClassByShipGroupIndex == nil || len(c.shipClassByShipGroupIndex) == 0 {
if len(c.cacheShipClassByShipGroupIndex) == 0 {
c.cacheShipsAndGroups()
}
c.validateShipGroupIndex(groupIndex)
if v, ok := c.shipClassByShipGroupIndex[groupIndex]; ok {
if v, ok := c.cacheShipClassByShipGroupIndex[groupIndex]; ok {
return v
} else {
panic(fmt.Sprintf("ShipClassByShipGroupIndex: group not found by index=%v", groupIndex))
@@ -55,43 +55,34 @@ func (c *Cache) RaceIndex(ID uuid.UUID) int {
}
func (c *Cache) cacheShipsAndGroups() {
if c.raceIndexByShipGroupIndex != nil {
clear(c.raceIndexByShipGroupIndex)
if c.cacheRaceIndexByShipGroupIndex != nil {
clear(c.cacheRaceIndexByShipGroupIndex)
} else {
c.raceIndexByShipGroupIndex = make(map[int]int)
c.cacheRaceIndexByShipGroupIndex = make(map[int]int)
}
if c.shipClassByShipGroupIndex != nil {
clear(c.shipClassByShipGroupIndex)
if c.cacheShipClassByShipGroupIndex != nil {
clear(c.cacheShipClassByShipGroupIndex)
} else {
c.shipClassByShipGroupIndex = make(map[int]*game.ShipType)
c.cacheShipClassByShipGroupIndex = make(map[int]*game.ShipType)
}
for groupIndex := range c.g.ShipGroups {
ri := c.RaceIndex(c.g.ShipGroups[groupIndex].OwnerID)
c.raceIndexByShipGroupIndex[groupIndex] = ri
c.cacheRaceIndexByShipGroupIndex[groupIndex] = ri
sti, ok := ShipClassIndex(c.g, ri, c.g.ShipGroups[groupIndex].TypeID)
if !ok {
panic(fmt.Sprintf("CollectPlanetGroups: ship class not found for race=%q group=%v", c.g.Race[ri].Name, c.g.ShipGroups[groupIndex].Index))
}
c.shipClassByShipGroupIndex[groupIndex] = &c.g.Race[ri].ShipTypes[sti]
}
}
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
c.cacheShipClassByShipGroupIndex[groupIndex] = &c.g.Race[ri].ShipTypes[sti]
}
}
func (c *Cache) invalidateShipGroupCache() {
if c.raceIndexByShipGroupIndex != nil {
clear(c.raceIndexByShipGroupIndex)
}
if c.shipClassByShipGroupIndex != nil {
clear(c.shipClassByShipGroupIndex)
}
clear(c.cacheRaceIndexByShipGroupIndex)
clear(c.cacheShipClassByShipGroupIndex)
}
func (c *Cache) invalidateFleetCache() {
clear(c.cacheFleetIndexByID)
}
// Helpers
@@ -13,10 +13,14 @@ func (c *Cache) Race(i int) game.Race {
return c.g.Race[i]
}
func (c *Cache) ListShipGroups(ri int) iter.Seq[*game.ShipGroup] {
func (c *Cache) RaceShipGroups(ri int) iter.Seq[*game.ShipGroup] {
return c.listShipGroups(ri)
}
func (c *Cache) RaceScience(ri int) []game.Science {
return c.raceScience(ri)
}
func (c *Cache) ListFleets(ri int) iter.Seq[*game.Fleet] {
return c.listFleets(ri)
}
@@ -42,7 +46,7 @@ func (c *Cache) MustShipGroup(ri int, index uint) *game.ShipGroup {
func (c *Cache) MustShipClass(ri int, name string) *game.ShipType {
st, _, ok := c.ShipClass(ri, name)
if !ok {
panic("ship class not foind")
panic("ship class not found")
}
return st
}
@@ -59,6 +63,6 @@ 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)
func (c *Cache) RaceTechLevel(ri int, t game.Tech, v float64) {
c.raceTechLevel(ri, t, v)
}
+31 -37
View File
@@ -57,7 +57,6 @@ func (c *Cache) FleetSpeed(fl game.Fleet) float64 {
continue
}
st := c.ShipGroupShipClass(sg)
// st := g.mustShipType(g.ShipGroups[sg].TypeID)
typeSpeed := c.ShipGroup(sg).Speed(st)
if typeSpeed < result {
result = typeSpeed
@@ -85,20 +84,6 @@ func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quant
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()
}
@@ -107,6 +92,12 @@ func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quant
return e.NewJoinFleetGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
}
var oldFleetID *uuid.UUID
if c.ShipGroup(sgi).FleetID != nil {
fID := *c.ShipGroup(sgi).FleetID
oldFleetID = &fID
}
fi, ok := c.fleetIndex(ri, name)
if !ok {
fi, err = c.createFleet(ri, name)
@@ -120,22 +111,35 @@ func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quant
}
}
// 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)
c.ShipGroupJoinFleet(sgi, &c.g.Fleets[fi].ID)
if oldFleetID != nil {
keepOldFleet := false
for sg := range c.listShipGroups(ri) {
if sg.FleetID != nil && *sg.FleetID == *oldFleetID {
keepOldFleet = true
break
}
}
if !keepOldFleet {
oldFleetIndex, ok := c.FleetIndex(*oldFleetID)
if !ok {
return e.NewGameStateError("old fleet index not found by ID=%v", *oldFleetID)
}
if err := c.deleteFleetSafe(ri, c.g.Fleets[oldFleetIndex].Name); err != nil {
return err
}
}
}
return nil
}
@@ -176,7 +180,7 @@ func (c *Cache) createFleet(ri int, name string) (int, error) {
return 0, e.NewEntityTypeNameValidationError("%q", n)
}
if _, ok := c.fleetIndex(ri, n); ok {
return 0, e.NewEntityTypeNameDuplicateError("fleet %w", n)
return 0, e.NewEntityTypeNameDuplicateError("fleet %q", n)
}
fleets := slices.Clone(c.g.Fleets)
fleets = append(fleets, game.Fleet{
@@ -202,17 +206,15 @@ func (c *Cache) deleteFleetSafe(ri int, name string) error {
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:]...)
c.cacheFleetIndexByID = nil
return nil
}
// Internal funcs
func (c *Cache) FleetIndex(ID uuid.UUID) (int, bool) {
if c.cacheFleetIndexByID == nil {
if len(c.cacheFleetIndexByID) == 0 {
c.cacheFleetIndexByID = make(map[uuid.UUID]int)
for i := range c.g.Fleets {
c.cacheFleetIndexByID[c.g.Fleets[i].ID] = i
@@ -270,16 +272,8 @@ func (c *Cache) fleetIndex(ri int, name string) (int, bool) {
}
}
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)))
panic(fmt.Sprintf("fleet index out of range: %d >= %d", i, len(c.g.Fleets)))
}
}
+3 -19
View File
@@ -27,12 +27,10 @@ func (c *Cache) SendFleet(ri, fi int, planetNumber uint) error {
}
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)
}
@@ -42,11 +40,7 @@ func (c *Cache) SendFleet(ri, fi int, planetNumber uint) error {
}
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)
// }
st := c.MustShipType(ri, sg.TypeID)
if st.DriveBlockMass() == 0 {
return e.NewSendShipHasNoDrivesError("Class=%s", st.Name)
}
@@ -66,12 +60,7 @@ 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)
c.LaunchShips(sg, destination)
}
}
@@ -79,11 +68,6 @@ 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)
c.UnsendShips(sg)
}
}
+14 -3
View File
@@ -27,15 +27,28 @@ func TestSendFleet(t *testing.T) {
fleetSending := "R0_Fleet_one"
fleetInSpace := "R0_Fleet_inSpace"
fleetUnmovable := "R0_Fleet_unmovable"
fleetUnmovable2 := "R0_Fleet_unmovable2"
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 1, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 1)
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 3, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 1)
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetInSpace, 2, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 2)
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 3, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 3)
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable2, 4, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 4)
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 4, 0))
assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 3)
// 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),
@@ -57,7 +70,6 @@ func TestSendFleet(t *testing.T) {
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))) {
@@ -65,7 +77,6 @@ func TestSendFleet(t *testing.T) {
}
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))) {
+15 -18
View File
@@ -36,7 +36,7 @@ func TestJoinShipGroupToFleet(t *testing.T) {
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))
groups := slices.Collect(c.RaceShipGroups(Race_0_idx))
assert.Len(t, groups, 1)
gi := 0
assert.Len(t, fleets, 1)
@@ -52,7 +52,7 @@ func TestJoinShipGroupToFleet(t *testing.T) {
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))
groups = slices.Collect(c.RaceShipGroups(Race_0_idx))
assert.Len(t, groups, 3)
assert.Len(t, fleets, 2)
assert.Equal(t, fleets[1].Name, fleetTwo)
@@ -75,7 +75,7 @@ func TestJoinShipGroupToFleet(t *testing.T) {
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))
groups = slices.Collect(c.RaceShipGroups(Race_0_idx))
assert.NotNil(t, groups[gi].FleetID)
assert.Equal(t, fleets[0].ID, *groups[gi].FleetID)
state, _, _ = c.FleetState(fleets[0].ID)
@@ -94,11 +94,8 @@ func TestJoinShipGroupToFleet(t *testing.T) {
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
c.ShipGroup(0).StateInSpace = &game.InSpace{Origin: 2, Range: 1}
c.ShipGroup(1).StateInSpace = c.ShipGroup(0).StateInSpace
assert.ErrorContains(t,
g.JoinShipGroupToFleet(Race_0.Name, fleetOne, c.ShipGroup(gi).Index, 0),
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
@@ -116,22 +113,22 @@ func TestJoinFleets(t *testing.T) {
// 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"
fleetOnPlanet2 := "R0_Fleet_On_Planet_2"
fleetSourceOne := "R0_Fleet_one"
fleetTargetTwo := "R0_Fleet_two"
assert.ErrorContains(t,
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSource, 1, 0))
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSourceOne, 1, 0))
assert.ErrorContains(t,
g.JoinFleets(Race_0.Name, fleetSource, fleetTarget),
g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo),
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, fleetTargetTwo, 3, 0))
assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo))
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetPlanet2, 2, 0))
assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOnPlanet2, 2, 0))
assert.ErrorContains(t,
g.JoinFleets(Race_0.Name, fleetPlanet2, fleetTarget),
g.JoinFleets(Race_0.Name, fleetOnPlanet2, fleetTargetTwo),
e.GenericErrorText(e.ErrShipsNotOnSamePlanet))
}
+140 -22
View File
@@ -2,29 +2,160 @@ package controller
import (
"fmt"
"slices"
"strings"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
)
func (c *Controller) RenamePlanet(raceName string, planetNumber int, typeName string) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
return c.Cache.RenamePlanet(ri, planetNumber, typeName)
}
func (c *Cache) RenamePlanet(ri int, number int, name string) error {
n, ok := validateTypeName(name)
if !ok {
return e.NewEntityTypeNameValidationError("%q", n)
}
if number < 0 {
return e.NewPlanetNumberError(number)
}
p, ok := c.Planet(uint(number))
if !ok {
return e.NewEntityNotExistsError("planet #%d", number)
}
if p.Owner != c.g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", number)
}
c.g.Map.Planet[c.MustPlanetIndex(p.Number)].Name = n
return nil
}
func (c *Controller) PlanetProduction(raceName string, planetNumber int, prodType, subject string) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
var prod game.ProductionType
switch game.ProductionType(strings.ToUpper(prodType)) {
case game.ProductionMaterial:
prod = game.ProductionMaterial
case game.ProductionCapital:
prod = game.ProductionCapital
case game.ResearchDrive:
prod = game.ResearchDrive
case game.ResearchWeapons:
prod = game.ResearchWeapons
case game.ResearchShields:
prod = game.ResearchShields
case game.ResearchCargo:
prod = game.ResearchCargo
case game.ResearchScience:
prod = game.ResearchScience
case game.ProductionShip:
prod = game.ProductionShip
default:
return e.NewProductionInvalidError(prodType)
}
return c.Cache.PlanetProduction(ri, planetNumber, prod, subject)
}
func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, subj string) error {
c.validateRaceIndex(ri)
if number < 0 {
return e.NewPlanetNumberError(number)
}
p, ok := c.Planet(uint(number))
if !ok {
return e.NewEntityNotExistsError("planet #%d", number)
}
if p.Owner != c.g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", number)
}
i := c.MustPlanetIndex(p.Number)
var subjectID *uuid.UUID
if (prod == game.ResearchScience || prod == game.ProductionShip) && subj == "" {
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
}
if prod == game.ResearchScience {
i := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == subj })
if i < 0 {
return e.NewEntityNotExistsError("science %q", subj)
}
subjectID = &c.g.Race[ri].Sciences[i].ID
}
if prod == game.ProductionShip {
st, _, ok := c.ShipClass(ri, subj)
if !ok {
return e.NewEntityNotExistsError("ship type %q", subj)
}
if p.Production.Type == game.ProductionShip &&
p.Production.SubjectID != nil &&
*p.Production.SubjectID == st.ID {
// Planet already produces this ship type, keeping progress intact
return nil
}
subjectID = &st.ID
var progress float64 = 0.
c.g.Map.Planet[i].Production.Progress = &progress
} else {
c.g.Map.Planet[i].Production.Progress = nil
}
if p.Production.Type == game.ProductionShip && prod != game.ProductionShip {
if p.Production.SubjectID == nil {
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", p.Number)
}
s := *p.Production.SubjectID
if p.Production.Progress == nil {
return e.NewGameStateError("planet #%d produces ship but Progress is empty", p.Number)
}
progress := *p.Production.Progress
st, ok := c.ShipType(ri, s)
if !ok {
return e.NewGameStateError("planet #%d produces ship but ShipType was not found for race %s", p.Number, c.g.Race[ri].Name)
}
mat, _ := st.ProductionCost()
extra := mat * progress
c.g.Map.Planet[i].Material += extra
}
c.g.Map.Planet[i].Production.Type = prod
c.g.Map.Planet[i].Production.SubjectID = subjectID
return nil
}
func (c *Cache) Planet(planetNumber uint) (*game.Planet, bool) {
if c.planetByPlanetNumber == nil {
c.planetByPlanetNumber = make(map[uint]*game.Planet)
if c.cachePlanetByPlanetNumber == nil {
c.cachePlanetByPlanetNumber = make(map[uint]*game.Planet)
for p := range c.g.Map.Planet {
c.planetByPlanetNumber[c.g.Map.Planet[p].Number] = &c.g.Map.Planet[p]
c.cachePlanetByPlanetNumber[c.g.Map.Planet[p].Number] = &c.g.Map.Planet[p]
}
}
if v, ok := c.planetByPlanetNumber[planetNumber]; ok {
if v, ok := c.cachePlanetByPlanetNumber[planetNumber]; ok {
return v, true
} else {
return nil, false
}
}
func (c *Cache) MustPlanet(planetNumber uint) *game.Planet {
if v, ok := c.Planet(planetNumber); ok {
func (c *Cache) MustPlanet(pn uint) *game.Planet {
if v, ok := c.Planet(pn); ok {
return v
} else {
panic(fmt.Sprintf("Planet: not found by number=%d", planetNumber))
panic(fmt.Sprintf("planet not found by number=%d", pn))
}
}
func (c *Cache) MustPlanetIndex(pn uint) int {
if idx := slices.IndexFunc(c.g.Map.Planet, func(p game.Planet) bool { return p.Number == pn }); idx < 0 {
panic(fmt.Sprintf("planet not found by number=%d", pn))
} else {
return idx
}
}
@@ -33,18 +164,14 @@ func (c *Cache) MustPlanet(planetNumber uint) *game.Planet {
// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
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) {
for sg := range c.shipGroupsInUpgrade(p.Number) {
busyResources += sg.StateUpgrade.Cost()
}
return game.PlanetProduction(p.Industry, p.Population) - busyResources
}
// Internal fincs
// Internal funcs
func (c *Cache) putPopulation(pn uint, v float64) {
c.MustPlanet(pn).Population = v
@@ -57,12 +184,3 @@ func (c *Cache) putColonists(pn uint, v float64) {
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
}
+120
View File
@@ -0,0 +1,120 @@
package controller_test
import (
"testing"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestRenamePlanet(t *testing.T) {
c, g := newCache()
assert.Equal(t, "Planet_0", c.MustPlanet(R0_Planet_0_num).Name)
assert.NoError(t, g.RenamePlanet(Race_0.Name, int(R0_Planet_0_num), "Home_World"))
assert.Equal(t, "Home_World", c.MustPlanet(R0_Planet_0_num).Name)
assert.ErrorContains(t,
g.RenamePlanet("UnknownRace", int(R0_Planet_0_num), "Home_World"),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.RenamePlanet(Race_0.Name, -1, "Home_World"),
e.GenericErrorText(e.ErrInputPlanetNumber))
assert.ErrorContains(t,
g.RenamePlanet(Race_0.Name, 500, "Home_World"),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.RenamePlanet(Race_0.Name, int(R1_Planet_1_num), "Home_World"),
e.GenericErrorText(e.ErrInputEntityNotOwned))
}
func TestPlanetProduction(t *testing.T) {
c, g := newCache()
first := "Drive_Shields"
assert.NoError(t, g.CreateScience(Race_0.Name, first, 0.4, 0, 0.6, 0))
assert.Len(t, c.RaceScience(Race_0_idx), 1)
scID := c.RaceScience(Race_0_idx)[0].ID
pn := int(R0_Planet_0_num)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "MAT", ""))
assert.Equal(t, game.ProductionMaterial, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "CAP", ""))
assert.Equal(t, game.ProductionCapital, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "Weapons", "500"))
assert.Equal(t, game.ResearchWeapons, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "cargo", ""))
assert.Equal(t, game.ResearchCargo, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIELDS", first))
assert.Equal(t, game.ResearchShields, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "DrivE", ""))
assert.Equal(t, game.ResearchDrive, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "Science", first))
assert.Equal(t, game.ResearchScience, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.Nil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.Equal(t, scID, *c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIP", Race_0_Gunship))
assert.Equal(t, game.ProductionShip, c.MustPlanet(R0_Planet_0_num).Production.Type)
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress)
assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
stID := c.MustShipClass(Race_0_idx, Race_0_Gunship).ID
assert.Equal(t, stID, *c.MustPlanet(R0_Planet_0_num).Production.SubjectID)
pn = int(R0_Planet_2_num)
assert.ErrorContains(t,
g.PlanetProduction("UnknownRace", pn, "DRIVE", ""),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, pn, "Hyperdrive", ""),
e.GenericErrorText(e.ErrInputProductionInvalid))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, -1, "DRIVE", ""),
e.GenericErrorText(e.ErrInputPlanetNumber))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, 500, "DRIVE", ""),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, int(R1_Planet_1_num), "DRIVE", ""),
e.GenericErrorText(e.ErrInputEntityNotOwned))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, pn, "Science", ""),
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, pn, "SHIP", ""),
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, pn, "Science", "Winning"),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.PlanetProduction(Race_0.Name, pn, "SHIP", "Drone"),
e.GenericErrorText(e.ErrInputEntityNotExists))
}
func TestPlanetProductionCapacity(t *testing.T) {
c, _ := newCache()
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
assert.Equal(t, 100., c.PlanetProductionCapacity(R0_Planet_0_num))
c.UpgradeShipGroup(0, game.TechDrive, 1.6)
assert.Equal(t, 53.125, c.PlanetProductionCapacity(R0_Planet_0_num))
}
+2 -13
View File
@@ -115,17 +115,6 @@ func (c *Cache) UpdateRelation(ri, other int, rel game.Relation) (err error) {
err = e.NewGameStateError("UpdateRelation: opponent not found")
}
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")
}
func (c *Cache) validateRaceIndex(i int) {
@@ -142,7 +131,7 @@ 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)
func (c *Cache) raceTechLevel(ri int, t game.Tech, v float64) {
c.validateRaceIndex(ri)
c.g.Race[ri].Tech = c.g.Race[ri].Tech.Set(t, v)
}
+84
View File
@@ -0,0 +1,84 @@
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) SetRoute(raceName, loadType string, origin, destination uint) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
rt, ok := game.RouteTypeSet[loadType]
if !ok {
return e.NewCargoTypeInvalidError(loadType)
}
return c.Cache.SetRoute(ri, rt, origin, destination)
}
func (c *Cache) SetRoute(ri int, rt game.RouteType, origin, destination uint) error {
c.validateRaceIndex(ri)
p1, ok := c.Planet(origin)
if !ok {
return e.NewEntityNotExistsError("origin planet #%d", origin)
}
if p1.Owner != c.g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", origin)
}
p2, ok := c.Planet(destination)
if !ok {
return e.NewEntityNotExistsError("destination planet #%d", destination)
}
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)
}
c.SetPlanetRoute(rt, origin, destination)
return nil
}
func (c *Controller) RemoveRoute(raceName, loadType string, origin uint) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
rt, ok := game.RouteTypeSet[loadType]
if !ok {
return e.NewCargoTypeInvalidError(loadType)
}
return c.Cache.RemoveRoute(ri, rt, origin)
}
func (c *Cache) RemoveRoute(ri int, rt game.RouteType, origin uint) error {
c.validateRaceIndex(ri)
p1, ok := c.Planet(origin)
if !ok {
return e.NewEntityNotExistsError("origin planet #%d", origin)
}
if p1.Owner != c.g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", origin)
}
c.RemovePlanetRoute(rt, origin)
return nil
}
func (c *Cache) SetPlanetRoute(rt game.RouteType, origin, destination uint) {
pi := c.MustPlanetIndex(origin)
if c.g.Map.Planet[pi].Route == nil {
c.g.Map.Planet[pi].Route = make(map[game.RouteType]uint)
}
c.g.Map.Planet[pi].Route[rt] = destination
}
func (c *Cache) RemovePlanetRoute(rt game.RouteType, origin uint) {
pi := c.MustPlanetIndex(origin)
if c.g.Map.Planet[pi].Route != nil {
delete(c.g.Map.Planet[pi].Route, rt)
}
}
+91
View File
@@ -0,0 +1,91 @@
package controller_test
import (
"testing"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestSetRoute(t *testing.T) {
c, g := newCache()
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteMaterial)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "COL", 0, 2))
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteMaterial)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "MAT", 0, 2))
assert.Contains(t, c.MustPlanet(0).Route, game.RouteMaterial)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "CAP", 0, 2))
assert.Contains(t, c.MustPlanet(0).Route, game.RouteMaterial)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "EMP", 0, 2))
assert.Contains(t, c.MustPlanet(0).Route, game.RouteMaterial)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteEmpty)
assert.ErrorContains(t,
g.SetRoute("UnknownRace", "COL", 0, 2),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "IND", 0, 2),
e.GenericErrorText(e.ErrInputCargoTypeInvalid))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 500, 2),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 1, 2),
e.GenericErrorText(e.ErrInputEntityNotOwned))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 0, 3),
e.GenericErrorText(e.ErrSendUnreachableDestination))
}
func TestRemoveRoute(t *testing.T) {
c, g := newCache()
assert.NoError(t, g.SetRoute(Race_0.Name, "COL", 0, 2))
assert.NoError(t, g.SetRoute(Race_0.Name, "CAP", 0, 2))
assert.NoError(t, g.SetRoute(Race_0.Name, "EMP", 2, 0))
assert.Contains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.Contains(t, c.MustPlanet(2).Route, game.RouteEmpty)
assert.NoError(t, g.RemoveRoute(Race_0.Name, "COL", 0))
assert.NotContains(t, c.MustPlanet(0).Route, game.RouteColonist)
assert.Contains(t, c.MustPlanet(0).Route, game.RouteCapital)
assert.NoError(t, g.RemoveRoute(Race_0.Name, "EMP", 2))
assert.NotContains(t, c.MustPlanet(2).Route, game.RouteEmpty)
assert.ErrorContains(t,
g.RemoveRoute("UnknownRace", "COL", 0),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "IND", 0),
e.GenericErrorText(e.ErrInputCargoTypeInvalid))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "COL", 500),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "COL", 1),
e.GenericErrorText(e.ErrInputEntityNotOwned))
}
+88
View File
@@ -0,0 +1,88 @@
package controller
import (
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
)
func (c *Controller) CreateScience(raceName, typeName string, drive, weapons, shields, cargo float64) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
return c.Cache.CreateScience(ri, typeName, drive, weapons, shields, cargo)
}
func (c *Cache) CreateScience(ri int, name string, drive, weapons, shileds, cargo float64) error {
c.validateRaceIndex(ri)
n, ok := validateTypeName(name)
if !ok {
return e.NewEntityTypeNameValidationError("%q", n)
}
if sc := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == n }); sc >= 0 {
return e.NewEntityTypeNameDuplicateError("science %q", c.g.Race[ri].Sciences[sc].Name)
}
if drive < 0 {
return e.NewDriveValueError(drive)
}
if weapons < 0 {
return e.NewWeaponsValueError(weapons)
}
if shileds < 0 {
return e.NewShieldsValueError(shileds)
}
if cargo < 0 {
return e.NewCargoValueError(cargo)
}
sum := drive + weapons + shileds + cargo
if sum != 1 {
return e.NewScienceSumValuesError("D=%f W=%f S=%f C=%f sum=%f", drive, weapons, shileds, cargo, sum)
}
c.g.Race[ri].Sciences = append(c.g.Race[ri].Sciences, game.Science{
ID: uuid.New(),
ScienceReport: game.ScienceReport{
Name: n,
Drive: drive,
Weapons: weapons,
Shields: shileds,
Cargo: cargo,
},
})
return nil
}
func (c *Controller) DeleteScience(raceName, typeName string) error {
ri, err := c.Cache.raceIndex(raceName)
if err != nil {
return err
}
return c.Cache.DeleteScience(ri, typeName)
}
func (c *Cache) DeleteScience(ri int, name string) error {
c.validateRaceIndex(ri)
sc := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == name })
if sc < 0 {
return e.NewEntityNotExistsError("science %q", name)
}
if pl := slices.IndexFunc(c.g.Map.Planet, func(p game.Planet) bool {
return p.Production.Type == game.ResearchScience &&
p.Production.SubjectID != nil &&
*p.Production.SubjectID == c.g.Race[ri].Sciences[sc].ID
}); pl >= 0 {
return e.NewDeleteSciencePlanetProductionError(c.g.Map.Planet[pl].Name)
}
c.g.Race[ri].Sciences = append(c.g.Race[ri].Sciences[:sc], c.g.Race[ri].Sciences[sc+1:]...)
return nil
}
// Internal func
func (c *Cache) raceScience(ri int) []game.Science {
c.validateRaceIndex(ri)
return c.g.Race[ri].Sciences
}
+99
View File
@@ -0,0 +1,99 @@
package controller_test
import (
"testing"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/stretchr/testify/assert"
)
func TestCreateScience(t *testing.T) {
// TODO: test on dead race
c, g := newCache()
first := "Drive_Shields"
second := "Hyperdrive"
assert.Len(t, c.RaceScience(Race_0_idx), 0)
assert.NoError(t, g.CreateScience(Race_0.Name, first, 0.4, 0, 0.6, 0))
assert.Len(t, c.RaceScience(Race_0_idx), 1)
sc := c.RaceScience(Race_0_idx)[0]
assert.NoError(t, uuid.Validate(sc.ID.String()))
assert.Equal(t, first, sc.Name)
assert.Equal(t, 0.4, sc.Drive)
assert.Equal(t, 0., sc.Weapons)
assert.Equal(t, 0.6, sc.Shields)
assert.Equal(t, 0., sc.Cargo)
assert.ErrorContains(t,
g.CreateScience("UnknownRace", second, 0.4, 0, 0.6, 0),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, " ", 0.4, 0, 0.6, 0),
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, first, 0.4, 0, 0.6, 0),
e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, -0.1, 0, 1.1, 0),
e.GenericErrorText(e.ErrInputDriveValue))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 1.5, -0.5, 0, 0),
e.GenericErrorText(e.ErrInputWeaponsValue))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 1.3, 0, -0.3, 0),
e.GenericErrorText(e.ErrInputShieldsValue))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 0, 1.07, 0, -0.07),
e.GenericErrorText(e.ErrInputCargoValue))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 0.26, 0.25, 0.25, 0.25),
e.GenericErrorText(e.ErrInputScienceSumValues))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 0.25, 0.26, 0.25, 0.25),
e.GenericErrorText(e.ErrInputScienceSumValues))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 0.25, 0.25, 0.26, 0.25),
e.GenericErrorText(e.ErrInputScienceSumValues))
assert.ErrorContains(t,
g.CreateScience(Race_0.Name, second, 0.25, 0.25, 0.25, 0.26),
e.GenericErrorText(e.ErrInputScienceSumValues))
assert.NoError(t, g.CreateScience(Race_0.Name, second, 0.25, 0.25, 0.25, 0.25))
assert.Len(t, c.RaceScience(Race_0_idx), 2)
sc = c.RaceScience(Race_0_idx)[1]
assert.NoError(t, uuid.Validate(sc.ID.String()))
assert.Equal(t, second, sc.Name)
assert.Equal(t, 0.25, sc.Drive)
assert.Equal(t, 0.25, sc.Weapons)
assert.Equal(t, 0.25, sc.Shields)
assert.Equal(t, 0.25, sc.Cargo)
}
func TestDeleteScience(t *testing.T) {
// TODO: test on dead race
// TODO: test with existing ship group
// TODO: test with planet production busy with science
c, g := newCache()
first := "Drive_Shields"
second := "Hyperdrive"
assert.Len(t, c.RaceScience(Race_0_idx), 0)
assert.NoError(t, g.CreateScience(Race_0.Name, first, 0.4, 0, 0.6, 0))
assert.NoError(t, g.CreateScience(Race_0.Name, second, 0.25, 0.25, 0.25, 0.25))
assert.Len(t, c.RaceScience(Race_0_idx), 2)
assert.NoError(t, g.DeleteScience(Race_0.Name, first))
assert.Len(t, c.RaceScience(Race_0_idx), 1)
g.PlanetProduction(Race_0.Name, int(R0_Planet_0_num), "SCIENCE", second)
assert.ErrorContains(t,
g.DeleteScience("UnknownRace", second),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.DeleteScience(Race_0.Name, first),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.DeleteScience(Race_0.Name, second),
e.GenericErrorText(e.ErrDeleteSciencePlanetProduction))
}
+38 -26
View File
@@ -17,14 +17,6 @@ func (c *Controller) CreateShipType(raceName, typeName string, drive float64, am
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 {
return nil, -1, false
}
return &c.g.Race[ri].ShipTypes[i], i, true
}
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 {
@@ -35,19 +27,21 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int,
return e.NewEntityTypeNameValidationError("%q", n)
}
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)
return e.NewEntityTypeNameDuplicateError("ship type %q", c.g.Race[ri].ShipTypes[st].Name)
}
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes, game.ShipType{
ID: uuid.New(),
ShipTypeReport: game.ShipTypeReport{
Name: n,
Drive: drive,
Armament: uint(ammo),
Weapons: weapons,
Shields: shileds,
Cargo: cargo,
Armament: uint(ammo),
},
})
c.invalidateShipGroupCache()
c.invalidateFleetCache()
return nil
}
@@ -63,18 +57,19 @@ 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)
return e.NewEntityNotExistsError("source ship type %q", name)
}
if name == targetName {
tt, _, ok := c.ShipClass(ri, targetName)
if !ok {
return e.NewEntityNotExistsError("target ship type %q", name)
}
if st.Name == tt.Name {
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()
}
@@ -100,6 +95,9 @@ func (c *Cache) MergeShipType(ri int, name, targetName string) error {
// remove the source type
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes[:sti], c.g.Race[ri].ShipTypes[sti+1:]...)
c.invalidateShipGroupCache()
c.invalidateFleetCache()
return nil
}
@@ -115,12 +113,8 @@ 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)
return e.NewEntityNotExistsError("ship type %q", 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 &&
@@ -135,7 +129,10 @@ func (c *Cache) DeleteShipType(ri int, name string) error {
}
}
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
c.invalidateShipGroupCache()
c.invalidateFleetCache()
return nil
}
@@ -158,13 +155,28 @@ func (c *Cache) ShipTypes(ri int) []*game.ShipType {
return result
}
func (c *Cache) ShipType(ri int, ID uuid.UUID) *game.ShipType {
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 {
return nil, -1, false
}
return &c.g.Race[ri].ShipTypes[i], i, true
}
func (c *Cache) ShipType(ri int, ID uuid.UUID) (*game.ShipType, bool) {
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]
return &c.g.Race[ri].ShipTypes[i], true
}
}
return nil, false
}
func (c *Cache) MustShipType(ri int, ID uuid.UUID) *game.ShipType {
if v, ok := c.ShipType(ri, ID); ok {
return v
}
panic(fmt.Sprintf("ship_class not found: race_id=%d id=%v", ri, ID))
}
+46 -113
View File
@@ -14,8 +14,7 @@ import (
func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error {
class, _, ok := c.ShipClass(ri, shipTypeName)
if !ok {
return e.NewEntityNotExistsError("ship class %w", shipTypeName)
return e.NewEntityNotExistsError("ship class q", shipTypeName)
}
p, ok := c.Planet(planetNumber)
@@ -26,10 +25,7 @@ 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.appendShipGroup(ri, class, &game.ShipGroup{
Index: nextIndex,
c.appendShipGroup(ri, &game.ShipGroup{
OwnerID: c.g.Race[ri].ID,
TypeID: class.ID,
Destination: p.Number,
@@ -50,9 +46,10 @@ func (c *Cache) ShipGroup(groupIndex int) *game.ShipGroup {
return &c.g.ShipGroups[groupIndex]
}
func (c *Cache) ShipGroupFleet(groupIndex int, fID *uuid.UUID) {
func (c *Cache) ShipGroupJoinFleet(groupIndex int, fID *uuid.UUID) {
c.validateShipGroupIndex(groupIndex)
c.g.ShipGroups[groupIndex].FleetID = fID
c.invalidateFleetCache()
}
func (c *Cache) ShipGroupShipsNumber(groupIndex int, number uint) {
@@ -85,10 +82,10 @@ func (c *Cache) ShipGroupMaxIndex(ri int) uint {
func (c *Cache) ShipGroupOwnerRaceIndex(groupIndex int) int {
c.validateShipGroupIndex(groupIndex)
if len(c.raceIndexByShipGroupIndex) == 0 {
if len(c.cacheRaceIndexByShipGroupIndex) == 0 {
c.cacheShipsAndGroups()
}
if v, ok := c.raceIndexByShipGroupIndex[groupIndex]; ok {
if v, ok := c.cacheRaceIndexByShipGroupIndex[groupIndex]; ok {
return v
} else {
panic(fmt.Sprintf("ShipGroupRace: group not found by index=%v", groupIndex))
@@ -134,21 +131,25 @@ func (c *Cache) CmdJoinEqualGroups() {
func (c *Cache) JoinEqualGroups(ri int) {
c.validateRaceIndex(ri)
shipGroups := slices.Collect(c.listShipGroups(ri))
origin := len(shipGroups)
raceGroups := make([]game.ShipGroup, 0)
for sg := range c.listShipGroups(ri) {
raceGroups = append(raceGroups, *sg)
}
origin := len(raceGroups)
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:]...)
for i := 0; i < len(raceGroups)-1; i++ {
for j := len(raceGroups) - 1; j > i; j-- {
if raceGroups[i].Equal(raceGroups[j]) {
raceGroups[i].Index = maxUint(raceGroups[i].Index, raceGroups[j].Index)
raceGroups[i].Number += raceGroups[j].Number
raceGroups = append(raceGroups[:j], raceGroups[j+1:]...)
}
}
}
if len(shipGroups) == origin {
if len(raceGroups) == origin {
return
}
@@ -159,14 +160,16 @@ func (c *Cache) JoinEqualGroups(ri int) {
}
}
// c.g.ShipGroups = slices.DeleteFunc(c.g.ShipGroups, func(v game.ShipGroup) bool { return v.OwnerID == c.g.Race[ri].ID })
slices.Sort(toDelete)
slices.Reverse(toDelete)
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()
for i := range raceGroups {
c.appendShipGroup(ri, &raceGroups[i])
}
c.invalidateShipGroupCache()
@@ -202,22 +205,13 @@ func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error {
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
}
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
pl, 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)
// }
p := *pl
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
@@ -234,8 +228,7 @@ func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error {
switch ct {
case game.CargoColonist:
if p.Owner == c.g.Race[ri].ID {
pn := UnloadColonists(*p, load)
p = &pn
p = game.UnloadColonists(p, load)
}
case game.CargoMaterial:
p.Material += load
@@ -246,9 +239,10 @@ func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error {
p.Material += c.ShipGroup(sgi).EmptyMass(st)
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
c.unsafeDeleteShipGroup(sgi)
c.g.Map.Planet[c.MustPlanetIndex(p.Number)] = p
return nil
}
@@ -283,19 +277,11 @@ func (c *Cache) LoadCargo(ri int, groupIndex uint, ct game.CargoType, ships uint
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)
}
@@ -366,11 +352,6 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6
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)
}
@@ -382,10 +363,6 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6
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)
@@ -451,16 +428,11 @@ func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err
}
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)
return e.NewGiveawayGroupShipsTypeNotEqualError("race %q, ship type %q", c.g.Race[riAccept].Name, c.g.Race[riAccept].ShipTypes[stAcc].Name)
}
if stAcc < 0 {
err = c.CreateShipType(riAccept,
@@ -470,55 +442,20 @@ func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err
st.Weapons,
st.Shields,
st.Cargo)
stAcc = len(c.g.Race[ri].ShipTypes) - 1
if err != nil {
return err
}
stAcc = len(c.g.Race[riAccept].ShipTypes) - 1
}
// 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,
// })
sg := *(c.ShipGroup(sgi))
sg.TypeID = c.g.Race[riAccept].ShipTypes[stAcc].ID
sg.Number = uint(quantity)
sg.Tech = maps.Clone(sg.Tech)
c.appendShipGroup(riAccept, &sg)
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
}
@@ -548,7 +485,7 @@ func (c *Cache) BreakGroup(ri int, groupIndex, quantity uint) error {
}
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
c.ShipGroupFleet(sgi, nil)
c.ShipGroupJoinFleet(sgi, nil)
} else {
if _, err := c.breakGroupSafe(ri, groupIndex, quantity); err != nil {
return err
@@ -564,33 +501,29 @@ func (c *Cache) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int
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
return c.appendShipGroup(ri, &newGroup), nil
}
// Internal funcs
func (c *Cache) appendShipGroup(ri int, class *game.ShipType, sg *game.ShipGroup) int {
func (c *Cache) appendShipGroup(ri int, sg *game.ShipGroup) int {
c.validateRaceIndex(ri)
sg.Index = c.ShipGroupMaxIndex(ri) + 1
sg.OwnerID = c.g.Race[ri].ID
sg.FleetID = nil
c.g.ShipGroups = append(c.g.ShipGroups, *sg)
i := len(c.g.ShipGroups) - 1
c.cacheShipGroup(i, ri, class)
c.invalidateShipGroupCache()
return i
}
@@ -630,9 +563,9 @@ func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup]
}
func (c *Cache) unsafeDeleteShipGroup(i int) {
c.validateShipGroupIndex(i)
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
delete(c.raceIndexByShipGroupIndex, i)
delete(c.shipClassByShipGroupIndex, i)
c.invalidateShipGroupCache()
}
func (c *Cache) validateShipGroupIndex(i int) {
+20 -35
View File
@@ -23,23 +23,6 @@ func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error
}
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()
}
@@ -53,16 +36,14 @@ func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error
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)
// }
p1, ok := c.Planet(sourcePlanet)
if !ok {
return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
}
p2, ok := c.Planet(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)
@@ -76,35 +57,39 @@ func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error
sgi = nsgi
}
if sourcePlanet == planetNumber {
c.UnsendShips(*c.ShipGroup(sgi))
if p1.Number == p2.Number {
c.UnsendShips(c.ShipGroup(sgi))
c.JoinEqualGroups(ri)
return nil
}
c.LaunchShips(*c.ShipGroup(sgi), planetNumber)
c.LaunchShips(c.ShipGroup(sgi), planetNumber)
return nil
}
func (c *Cache) LaunchShips(sg game.ShipGroup, destination uint) *game.ShipGroup {
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()
state := c.ShipGroup(i).State()
if state != game.StateInOrbit && state != game.StateLaunched {
panic("state invalid")
}
c.g.ShipGroups[i] = LaunchShips(sg, destination)
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 {
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)
state := c.ShipGroup(i).State()
if state != game.StateLaunched {
panic("state invalid")
}
c.g.ShipGroups[i] = UnsendShips(*sg)
return &c.g.ShipGroups[i]
}
}
+11 -11
View File
@@ -45,26 +45,26 @@ func TestSendGroup(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 4)
assert.Equal(t, uint(9), c.MustShipGroup(Race_0_idx, 1).Number)
assert.Equal(t, game.StateInOrbit, c.MustShipGroup(Race_0_idx, 1).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.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3)
assert.Equal(t, uint(10), c.MustShipGroup(Race_0_idx, 1).Number)
assert.Equal(t, game.StateInOrbit, c.MustShipGroup(Race_0_idx, 1).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())
assert.NoError(t, g.SendGroup(Race_0.Name, 1, 2, 0))
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3)
assert.Equal(t, uint(10), c.MustShipGroup(Race_0_idx, 1).Number)
assert.Equal(t, game.StateLaunched, c.MustShipGroup(Race_0_idx, 1).State())
}
+50 -61
View File
@@ -22,16 +22,16 @@ func TestCreateShips(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
assert.Len(t, slices.Collect(c.RaceShipGroups(1)), 2)
}
func TestJoinEqualGroups(t *testing.T) {
@@ -43,48 +43,47 @@ func TestJoinEqualGroups(t *testing.T) {
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)
c.RaceTechLevel(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)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 7)
// g.Race[Race_1_idx].SetTechLevel(game.TechShields, 2.0)
c.RacetTechLevel(Race_1_idx, game.TechShields, 2.0)
c.RaceTechLevel(Race_1_idx, game.TechShields, 2.0)
assert.Equal(t, 2.0, c.Race(Race_1_idx).Tech[game.TechShields])
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)
assert.Len(t, slices.Collect(c.RaceShipGroups(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)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 3)
assert.Len(t, slices.Collect(c.RaceShipGroups(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)
t.Fatalf("ship_class not found: %s", name)
return uuid.Nil
}
return class.ID
}
for sg := range c.ListShipGroups(Race_0_idx) {
for sg := range c.RaceShipGroups(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)
assert.Equal(t, uint(1), 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)
assert.Equal(t, uint(4), 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)
assert.Equal(t, uint(2), 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)
assert.Equal(t, uint(3), sg.Index)
default:
t.Error("not all ship groups covered")
}
@@ -116,12 +115,12 @@ func TestBreakGroup(t *testing.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.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -133,7 +132,7 @@ func TestBreakGroup(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -149,13 +148,13 @@ func TestBreakGroup(t *testing.T) {
// 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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 4)
assert.Equal(t, uint(2), c.ShipGroup(3).Number)
assert.Nil(t, c.ShipGroup(3).FleetID)
}
@@ -175,8 +174,8 @@ func TestGiveawayGroup(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 2)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1)
assert.ErrorContains(t,
g.GiveawayGroup("UnknownRace", Race_1.Name, 2, 0),
@@ -198,12 +197,9 @@ func TestGiveawayGroup(t *testing.T) {
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 })
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 2)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 2)
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)
@@ -226,8 +222,8 @@ func TestGiveawayGroup(t *testing.T) {
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)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1)
}
func TestLoadCargo(t *testing.T) {
@@ -255,7 +251,7 @@ func TestLoadCargo(t *testing.T) {
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)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 5)
// tests
assert.ErrorContains(t,
@@ -285,7 +281,6 @@ func TestLoadCargo(t *testing.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,
@@ -296,12 +291,12 @@ func TestLoadCargo(t *testing.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)
assert.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -312,7 +307,7 @@ func TestLoadCargo(t *testing.T) {
// 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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -321,27 +316,25 @@ func TestLoadCargo(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -350,7 +343,7 @@ func TestLoadCargo(t *testing.T) {
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)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 7)
}
func TestUnloadCargo(t *testing.T) {
@@ -386,7 +379,7 @@ func TestUnloadCargo(t *testing.T) {
c.ShipGroup(5).CargoType = game.CargoMaterial.Ref()
c.ShipGroup(5).Load = 100.0
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 6)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 6)
// tests
assert.ErrorContains(t,
@@ -416,12 +409,12 @@ func TestUnloadCargo(t *testing.T) {
g.UnloadCargo(Race_0.Name, 1, 0, 1),
e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak))
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 6)
assert.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
@@ -432,7 +425,7 @@ func TestUnloadCargo(t *testing.T) {
// 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.Len(t, slices.Collect(c.RaceShipGroups(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))
@@ -443,7 +436,7 @@ func TestUnloadCargo(t *testing.T) {
// 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.Len(t, slices.Collect(c.RaceShipGroups(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))
@@ -479,7 +472,7 @@ func TestDisassembleGroup(t *testing.T) {
c.ShipGroup(4).CargoType = game.CargoColonist.Ref()
c.ShipGroup(4).Load = 2.345
assert.Len(t, slices.Collect(c.ListShipGroups(Race_0_idx)), 5)
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 5)
// tests
assert.ErrorContains(t,
@@ -496,41 +489,37 @@ func TestDisassembleGroup(t *testing.T) {
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.Len(t, slices.Collect(c.RaceShipGroups(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.Len(t, slices.Collect(c.RaceShipGroups(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)
assert.Equal(t, planetPOP, c.MustPlanet(R0_Planet_0_num).Population)
var quantity uint = 3
groupEmptyMass = groupEmptyMass / float64(c.ShipGroup(2).Number) * float64(quantity)
newGroupUnloadedCOL := c.ShipGroup(2).Load / float64(c.ShipGroup(2).Number) * float64(quantity)
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.NoError(t, g.DisassembleGroup(Race_0.Name, 3, quantity))
assert.Len(t, slices.Collect(c.RaceShipGroups(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)
+2 -65
View File
@@ -22,33 +22,11 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi
}
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)
}
@@ -160,56 +138,15 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi
// finally, fill group upgrade prefs
for tech := range targetLevel {
if targetLevel[tech] > 0 {
c.upgradeShipGroup(sgi, tech, targetLevel[tech])
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) {
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)
}
+2 -105
View File
@@ -9,108 +9,6 @@ import (
"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
@@ -150,15 +48,14 @@ func TestUpgradeGroup(t *testing.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)
c.RaceTechLevel(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.Len(t, slices.Collect(c.RaceShipGroups(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())