From 16aba8435daf579b8981930fba759fc03762f3ea Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Thu, 15 Jan 2026 14:42:04 +0200 Subject: [PATCH] refactor: game funcs moved to controller --- internal/controller/battle_transform.go | 6 +- internal/controller/cache.go | 55 +- internal/controller/controller_export_test.go | 12 +- internal/controller/fleet.go | 68 ++- internal/controller/fleet_send.go | 22 +- internal/controller/fleet_send_test.go | 17 +- internal/controller/fleet_test.go | 33 +- internal/controller/planet.go | 162 +++++- internal/controller/planet_test.go | 120 ++++ internal/controller/race.go | 15 +- internal/controller/route.go | 84 +++ internal/controller/route_test.go | 91 ++++ internal/controller/science.go | 88 +++ internal/controller/science_test.go | 99 ++++ internal/controller/ship_class.go | 64 ++- internal/controller/ship_group.go | 159 ++---- internal/controller/ship_group_send.go | 55 +- internal/controller/ship_group_send_test.go | 22 +- internal/controller/ship_group_test.go | 111 ++-- internal/controller/ship_group_upgrade.go | 67 +-- .../controller/ship_group_upgrade_test.go | 107 +--- internal/game/cmd_planet.go | 6 +- internal/game/cmd_production.go | 6 +- internal/game/cmd_science.go | 12 +- internal/game/cmd_science_test.go | 40 +- internal/game/cmd_ship_type_test.go | 4 +- internal/game/controller_test.go | 4 +- internal/model/game/fleet.go | 239 -------- internal/model/game/game.go | 44 -- internal/model/game/game_export_test.go | 38 +- internal/model/game/game_test.go | 129 ++--- internal/model/game/group.go | 514 ----------------- internal/model/game/group_send.go | 93 ---- internal/model/game/group_send_test.go | 69 --- internal/model/game/group_test.go | 515 ------------------ internal/model/game/group_upgrade.go | 152 ------ internal/model/game/group_upgrade_test.go | 70 +-- internal/model/game/map.go | 4 - internal/model/game/planet.go | 52 -- internal/model/game/planet_test.go | 8 - internal/model/game/production.go | 92 ---- internal/model/game/race.go | 84 +-- internal/model/game/race_test.go | 34 ++ internal/model/game/route.go | 91 +--- internal/model/game/route_test.go | 91 ---- internal/model/game/science.go | 84 --- internal/model/game/ship.go | 184 ------- 47 files changed, 1023 insertions(+), 3093 deletions(-) create mode 100644 internal/controller/planet_test.go create mode 100644 internal/controller/route.go create mode 100644 internal/controller/route_test.go create mode 100644 internal/controller/science.go create mode 100644 internal/controller/science_test.go delete mode 100644 internal/model/game/group_send.go delete mode 100644 internal/model/game/group_send_test.go delete mode 100644 internal/model/game/route_test.go diff --git a/internal/controller/battle_transform.go b/internal/controller/battle_transform.go index 353dc29..afafb9b 100644 --- a/internal/controller/battle_transform.go +++ b/internal/controller/battle_transform.go @@ -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{ diff --git a/internal/controller/cache.go b/internal/controller/cache.go index 3403042..95cc363 100644 --- a/internal/controller/cache.go +++ b/internal/controller/cache.go @@ -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 diff --git a/internal/controller/controller_export_test.go b/internal/controller/controller_export_test.go index 07718e3..8dc378b 100644 --- a/internal/controller/controller_export_test.go +++ b/internal/controller/controller_export_test.go @@ -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) } diff --git a/internal/controller/fleet.go b/internal/controller/fleet.go index e09a9a6..16a3ab1 100644 --- a/internal/controller/fleet.go +++ b/internal/controller/fleet.go @@ -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))) } } diff --git a/internal/controller/fleet_send.go b/internal/controller/fleet_send.go index 1d8e180..1b8436b 100644 --- a/internal/controller/fleet_send.go +++ b/internal/controller/fleet_send.go @@ -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) } } diff --git a/internal/controller/fleet_send_test.go b/internal/controller/fleet_send_test.go index ad28259..276251f 100644 --- a/internal/controller/fleet_send_test.go +++ b/internal/controller/fleet_send_test.go @@ -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))) { diff --git a/internal/controller/fleet_test.go b/internal/controller/fleet_test.go index 829ea1a..e92de2d 100644 --- a/internal/controller/fleet_test.go +++ b/internal/controller/fleet_test.go @@ -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)) } diff --git a/internal/controller/planet.go b/internal/controller/planet.go index 92250b9..86897aa 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -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 -} diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go new file mode 100644 index 0000000..63c068d --- /dev/null +++ b/internal/controller/planet_test.go @@ -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)) +} diff --git a/internal/controller/race.go b/internal/controller/race.go index c5e28f1..cdb8ce8 100644 --- a/internal/controller/race.go +++ b/internal/controller/race.go @@ -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) } diff --git a/internal/controller/route.go b/internal/controller/route.go new file mode 100644 index 0000000..10cf808 --- /dev/null +++ b/internal/controller/route.go @@ -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) + } +} diff --git a/internal/controller/route_test.go b/internal/controller/route_test.go new file mode 100644 index 0000000..6b18062 --- /dev/null +++ b/internal/controller/route_test.go @@ -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)) +} diff --git a/internal/controller/science.go b/internal/controller/science.go new file mode 100644 index 0000000..028aad6 --- /dev/null +++ b/internal/controller/science.go @@ -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 +} diff --git a/internal/controller/science_test.go b/internal/controller/science_test.go new file mode 100644 index 0000000..001895b --- /dev/null +++ b/internal/controller/science_test.go @@ -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)) +} diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index 44de6e1..fc10641 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -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)) } diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index b1be59b..30dffc4 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -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) { diff --git a/internal/controller/ship_group_send.go b/internal/controller/ship_group_send.go index 5074b56..ff26688 100644 --- a/internal/controller/ship_group_send.go +++ b/internal/controller/ship_group_send.go @@ -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] } } diff --git a/internal/controller/ship_group_send_test.go b/internal/controller/ship_group_send_test.go index 9f121e9..43595b9 100644 --- a/internal/controller/ship_group_send_test.go +++ b/internal/controller/ship_group_send_test.go @@ -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()) } diff --git a/internal/controller/ship_group_test.go b/internal/controller/ship_group_test.go index 18dcd94..cfbe9f6 100644 --- a/internal/controller/ship_group_test.go +++ b/internal/controller/ship_group_test.go @@ -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) diff --git a/internal/controller/ship_group_upgrade.go b/internal/controller/ship_group_upgrade.go index 9bb836a..1aaf7c7 100644 --- a/internal/controller/ship_group_upgrade.go +++ b/internal/controller/ship_group_upgrade.go @@ -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) } diff --git a/internal/controller/ship_group_upgrade_test.go b/internal/controller/ship_group_upgrade_test.go index 0dc1748..7510096 100644 --- a/internal/controller/ship_group_upgrade_test.go +++ b/internal/controller/ship_group_upgrade_test.go @@ -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()) diff --git a/internal/game/cmd_planet.go b/internal/game/cmd_planet.go index 20d5900..3e43835 100644 --- a/internal/game/cmd_planet.go +++ b/internal/game/cmd_planet.go @@ -8,14 +8,14 @@ import ( func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) { control(configure, func(c *controller.Controller) { c.ExecuteGame(func(r controller.Repo, g *game.Game) { - err = renamePlanet(r, g, race, number, name) + err = renamePlanet(c, r, g, race, number, name) }) }) return } -func renamePlanet(r controller.Repo, g *game.Game, race string, number int, name string) error { - if err := g.RenamePlanet(race, number, name); err != nil { +func renamePlanet(c *controller.Controller, r controller.Repo, g *game.Game, race string, number int, name string) error { + if err := c.RenamePlanet(race, number, name); err != nil { return err } return r.SaveState(g) diff --git a/internal/game/cmd_production.go b/internal/game/cmd_production.go index 87ec8d3..6b4d387 100644 --- a/internal/game/cmd_production.go +++ b/internal/game/cmd_production.go @@ -8,14 +8,14 @@ import ( func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) { control(configure, func(c *controller.Controller) { c.ExecuteGame(func(r controller.Repo, g *game.Game) { - err = planetProduction(r, g, race, planetNumber, prodType, subject) + err = planetProduction(c, r, g, race, planetNumber, prodType, subject) }) }) return } -func planetProduction(r controller.Repo, g *game.Game, race string, planetNumber int, prodType, subject string) error { - if err := g.PlanetProduction(race, planetNumber, prodType, subject); err != nil { +func planetProduction(c *controller.Controller, r controller.Repo, g *game.Game, race string, planetNumber int, prodType, subject string) error { + if err := c.PlanetProduction(race, planetNumber, prodType, subject); err != nil { return err } return r.SaveState(g) diff --git a/internal/game/cmd_science.go b/internal/game/cmd_science.go index 9e91c81..0dcbd70 100644 --- a/internal/game/cmd_science.go +++ b/internal/game/cmd_science.go @@ -8,14 +8,14 @@ import ( func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) { control(configure, func(c *controller.Controller) { c.ExecuteGame(func(r controller.Repo, g *game.Game) { - err = createScience(r, g, race, typeName, drive, weapons, shields, cargo) + err = createScience(c, r, g, race, typeName, drive, weapons, shields, cargo) }) }) return } -func createScience(r controller.Repo, g *game.Game, race, typeName string, d, w, s, c float64) error { - if err := g.CreateScience(race, typeName, d, w, s, c); err != nil { +func createScience(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string, drive, weapons, shields, cargo float64) error { + if err := c.CreateScience(race, typeName, drive, weapons, shields, cargo); err != nil { return err } return r.SaveState(g) @@ -24,14 +24,14 @@ func createScience(r controller.Repo, g *game.Game, race, typeName string, d, w, func DeleteScience(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 = deleteScience(r, g, race, typeName) + err = deleteScience(c, r, g, race, typeName) }) }) return } -func deleteScience(r controller.Repo, g *game.Game, race, typeName string) error { - if err := g.DeleteScience(race, typeName); err != nil { +func deleteScience(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string) error { + if err := c.DeleteScience(race, typeName); err != nil { return err } return r.SaveState(g) diff --git a/internal/game/cmd_science_test.go b/internal/game/cmd_science_test.go index f2daab3..f843472 100644 --- a/internal/game/cmd_science_test.go +++ b/internal/game/cmd_science_test.go @@ -5,40 +5,19 @@ import ( "testing" "github.com/iliadenisov/galaxy/internal/controller" + "github.com/iliadenisov/galaxy/internal/game" e "github.com/iliadenisov/galaxy/internal/error" - "github.com/iliadenisov/galaxy/internal/game" - mg "github.com/iliadenisov/galaxy/internal/model/game" "github.com/stretchr/testify/assert" ) func TestCreateScience(t *testing.T) { - race := "race_01" typeName := "First Step" - g(t, func(p func(*controller.Param), g func() *mg.Game) { - err := game.DeleteScience(p, race, typeName) - assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists)) - err = game.CreateScience(p, unknownRaceName, " "+typeName+" ", 1, 0, 0, 0) // TODO: test on dead race + c(t, func(p func(*controller.Param), g func() *controller.Controller) { + err := g().CreateScience(unknownRaceName, " "+typeName+" ", 1, 0, 0, 0) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) - err = game.CreateScience(p, race, " "+typeName+" ", 1, 0, 0, 0) - assert.NoError(t, err) - sc, err := g().Sciences(race) - assert.NoError(t, err) - assert.Len(t, sc, 1) - assert.Equal(t, sc[0].Name, typeName) - assert.Equal(t, sc[0].Drive, 1.) - assert.Equal(t, sc[0].Weapons, 0.) - assert.Equal(t, sc[0].Shields, 0.) - assert.Equal(t, sc[0].Cargo, 0.) - // TODO: test delete with existing ship group - // TODO: test delete with planet production busy with science - err = game.DeleteScience(p, unknownRaceName, typeName) // TODO: test with actial rip race + err = g().DeleteScience(unknownRaceName, typeName) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) - err = game.DeleteScience(p, race, typeName) - assert.NoError(t, err) - sc, err = g().Sciences(race) - assert.NoError(t, err) - assert.Len(t, sc, 0) }) } @@ -74,13 +53,14 @@ func TestCreateScienceValidation(t *testing.T) { {typeName, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputCargoValue)}, {typeName, 0, 1, 1, -1, e.GenericErrorText(e.ErrInputCargoValue)}, } - g(t, func(p func(*controller.Param), g func() *mg.Game) { + c(t, func(p func(*controller.Param), g func() *controller.Controller) { for i, tc := range table { if tc.err == "" { - err := game.CreateScience(p, race, tc.name+strconv.Itoa(i), tc.d, tc.w, tc.s, tc.c) - assert.NoError(t, err) - err = game.CreateScience(p, race, tc.name+strconv.Itoa(i), tc.d, tc.w, tc.s, tc.c) - assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate)) + n := tc.name + strconv.Itoa(i) + err := game.CreateScience(p, race, n, tc.d, tc.w, tc.s, tc.c) + assert.NoError(t, err, "for name=%q", n) + err = game.CreateScience(p, race, n, tc.d, tc.w, tc.s, tc.c) + assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate), "for name=%q", n) } else { err := game.CreateScience(p, race, tc.name, tc.d, tc.w, tc.s, tc.c) assert.ErrorContains(t, err, tc.err) diff --git a/internal/game/cmd_ship_type_test.go b/internal/game/cmd_ship_type_test.go index 146b83c..7f329b2 100644 --- a/internal/game/cmd_ship_type_test.go +++ b/internal/game/cmd_ship_type_test.go @@ -96,7 +96,7 @@ func TestCreateShipTypeValidation(t *testing.T) { func TestMergeShipType(t *testing.T) { race := "race_01" - c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) { + c(t, func(p func(*controller.Param), ctl 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 := ctrl().ShipTypes(race) + st, err := ctl().ShipTypes(race) assert.NoError(t, err) assert.Len(t, st, 2) err = game.MergeShipType(p, race, "Drone", "Cruiser") diff --git a/internal/game/controller_test.go b/internal/game/controller_test.go index 45f1b36..103e098 100644 --- a/internal/game/controller_test.go +++ b/internal/game/controller_test.go @@ -66,7 +66,7 @@ func c(t *testing.T, f func(p func(*controller.Param), g func() *controller.Cont assert.FailNow(t, "c: GenerateGame", err) return } - g := func() *controller.Controller { + ctl := func() *controller.Controller { c, err := controller.NewController(p) if err != nil { assert.FailNow(t, "c: NewController", err) @@ -80,5 +80,5 @@ func c(t *testing.T, f func(p func(*controller.Param), g func() *controller.Cont c.Cache = controller.NewCache(g) return c } - f(p, g) + f(p, ctl) } diff --git a/internal/model/game/fleet.go b/internal/model/game/fleet.go index b9946f5..fa1e36e 100644 --- a/internal/model/game/fleet.go +++ b/internal/model/game/fleet.go @@ -2,247 +2,8 @@ package game import "github.com/google/uuid" -// import ( -// "fmt" -// "iter" -// "math" -// "slices" - -// "github.com/google/uuid" -// e "github.com/iliadenisov/galaxy/internal/error" -// ) - type Fleet struct { ID uuid.UUID `json:"id"` OwnerID uuid.UUID `json:"ownerId"` 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 -// } - -// // 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) 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) -// } - -// 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) -// } - -// 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) -// } - -// 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) 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) 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) 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 -// } -// } -// } -// } -// } diff --git a/internal/model/game/game.go b/internal/model/game/game.go index 28a725e..fe062f0 100644 --- a/internal/model/game/game.go +++ b/internal/model/game/game.go @@ -3,13 +3,9 @@ package game import ( "encoding/json" "fmt" - "iter" "maps" - "slices" - "strings" "github.com/google/uuid" - e "github.com/iliadenisov/galaxy/internal/error" ) type TechSet map[Tech]float64 @@ -48,46 +44,6 @@ func (g Game) Votes(raceID uuid.UUID) float64 { return pop / 1000. } -func (g Game) PlanetByNumber(number uint) (Planet, error) { - pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number }) - if pi < 0 { - return Planet{}, e.NewGameStateError("PlanetByNumber: planet with number=%d not found", number) - } - return g.Map.Planet[pi], nil -} - -func (g Game) ShipsInUpgrade(planetNumber uint) iter.Seq[ShipGroup] { - return func(yield func(ShipGroup) bool) { - for sg := range g.ShipGroups { - if g.ShipGroups[sg].Destination == planetNumber && g.ShipGroups[sg].State() == StateUpgrade { - if !yield(g.ShipGroups[sg]) { - break - } - } - } - } -} - -func (g Game) raceIndex(name string) (int, error) { - i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name }) - if i < 0 { - return i, e.NewRaceUnknownError(name) - } - return i, nil -} - -// ----------------------------------------------------------------------------- - -// validateTypeName always return v without leading and trailing spaces -func validateTypeName(v string) (string, bool) { - s := strings.TrimSpace(v) - if len(s) > 0 { - return s, true - } - // TODO: special symbols AND include error check in all user-input test - return s, false -} - func (g Game) MarshalBinary() (data []byte, err error) { return json.Marshal(&g) } diff --git a/internal/model/game/game_export_test.go b/internal/model/game/game_export_test.go index fd81624..13c7b64 100644 --- a/internal/model/game/game_export_test.go +++ b/internal/model/game/game_export_test.go @@ -1,25 +1,25 @@ package game -import "iter" +// import "iter" -func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error { - return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity) -} +// func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error { +// return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity) +// } -func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] { - return func(yield func(ShipGroup) bool) {} - // return g.listShipGroups(ri) -} +// func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] { +// return func(yield func(ShipGroup) bool) {} +// // return g.listShipGroups(ri) +// } -func (g Game) ListFleets(ri int) iter.Seq[Fleet] { - return func(yield func(Fleet) bool) {} - // return g.listFleets(ri) -} +// func (g Game) ListFleets(ri int) iter.Seq[Fleet] { +// return func(yield func(Fleet) bool) {} +// // return g.listFleets(ri) +// } -func (g Game) MustPlanetByNumber(num uint) Planet { - p, err := g.PlanetByNumber(num) - if err != nil { - panic(err) - } - return p -} +// func (g Game) MustPlanetByNumber(num uint) Planet { +// p, err := g.PlanetByNumber(num) +// if err != nil { +// panic(err) +// } +// return p +// } diff --git a/internal/model/game/game_test.go b/internal/model/game/game_test.go index 25e25ee..23f890b 100644 --- a/internal/model/game/game_test.go +++ b/internal/model/game/game_test.go @@ -1,101 +1,54 @@ package game_test import ( - "fmt" + "testing" - "github.com/google/uuid" - "github.com/iliadenisov/galaxy/internal/controller" "github.com/iliadenisov/galaxy/internal/model/game" + "github.com/stretchr/testify/assert" ) -var ( - Race_0 = game.Race{ - ID: Race_0_ID, - Vote: Race_0_ID, - Name: "Race_0", - Tech: map[game.Tech]float64{ - game.TechDrive: 1.1, - game.TechWeapons: 1.2, - game.TechShields: 1.3, - game.TechCargo: 1.4, - }, - Relations: []game.RaceRelation{{RaceID: Race_1_ID, Relation: game.RelationWar}}, - } - Race_1 = game.Race{ - ID: Race_1_ID, - Vote: Race_1_ID, - Name: "Race_1", - Tech: map[game.Tech]float64{ - game.TechDrive: 2.1, - game.TechWeapons: 2.2, - game.TechShields: 2.3, - game.TechCargo: 2.4, - }, - Relations: []game.RaceRelation{{RaceID: Race_0_ID, Relation: game.RelationPeace}}, - } +func TestTechSet(t *testing.T) { + s := ts.Set(game.TechDrive, 10.5) + assert.Equal(t, 1.1, ts.Value(game.TechDrive)) + assert.Equal(t, 1.2, ts.Value(game.TechWeapons)) + assert.Equal(t, 1.3, ts.Value(game.TechShields)) + assert.Equal(t, 1.4, ts.Value(game.TechCargo)) - Race_0_ID = uuid.New() - Race_0_idx = 0 - Race_0_Gunship = "R0_Gunship" - Race_0_Freighter = "R0_Freighter" - R0_Planet_0_num uint = 0 - R0_Planet_2_num uint = 2 - Race_0_Gunship_idx = 0 - Race_0_Freighter_idx = 1 - Race_0_Cruiser_idx = 2 + assert.Equal(t, 10.5, s.Value(game.TechDrive)) + assert.Equal(t, 1.2, s.Value(game.TechWeapons)) + assert.Equal(t, 1.3, s.Value(game.TechShields)) + assert.Equal(t, 1.4, s.Value(game.TechCargo)) - Race_1_ID = uuid.New() - Race_1_idx = 1 - Race_1_Gunship = "R1_Gunship" - Race_1_Freighter = "R1_Freighter" - R1_Planet_1_num uint = 1 - Race_1_Gunship_idx = 0 - Race_1_Freighter_idx = 1 - Race_1_Cruiser_idx = 2 + s = s.Set(game.TechWeapons, 5.7) + assert.Equal(t, 1.1, ts.Value(game.TechDrive)) + assert.Equal(t, 1.2, ts.Value(game.TechWeapons)) + assert.Equal(t, 1.3, ts.Value(game.TechShields)) + assert.Equal(t, 1.4, ts.Value(game.TechCargo)) - ShipType_Cruiser = "Cruiser" + assert.Equal(t, 10.5, s.Value(game.TechDrive)) + assert.Equal(t, 5.7, s.Value(game.TechWeapons)) + assert.Equal(t, 1.3, s.Value(game.TechShields)) + assert.Equal(t, 1.4, s.Value(game.TechCargo)) - Cruiser = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Cruiser", - Drive: 15, - Armament: 1, - Weapons: 15, - Shields: 15, - Cargo: 0, - }, - } -) + s = s.Set(game.TechShields, 2.13) + assert.Equal(t, 1.1, ts.Value(game.TechDrive)) + assert.Equal(t, 1.2, ts.Value(game.TechWeapons)) + assert.Equal(t, 1.3, ts.Value(game.TechShields)) + assert.Equal(t, 1.4, ts.Value(game.TechCargo)) -func assertNoError(err error) { - if err != nil { - panic(fmt.Sprintf("init assertion failed: %v", err)) - } -} - -func newGame() *game.Game { - g := &game.Game{ - Race: []game.Race{ - Race_0, - Race_1, - }, - Map: game.Map{ - Width: 1000, - Height: 1000, - Planet: []game.Planet{ - controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 0, 0, 100, 100, 100, 0, game.ProductionNone.AsType(uuid.Nil)), - controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 1, 1, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), - controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), - controller.NewPlanet(3, "Planet_3", uuid.Nil, 500, 500, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), - }, - }, - } - // 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 + assert.Equal(t, 10.5, s.Value(game.TechDrive)) + assert.Equal(t, 5.7, s.Value(game.TechWeapons)) + assert.Equal(t, 2.13, s.Value(game.TechShields)) + assert.Equal(t, 1.4, s.Value(game.TechCargo)) + + s = s.Set(game.TechCargo, 3.1415926) + assert.Equal(t, 1.1, ts.Value(game.TechDrive)) + assert.Equal(t, 1.2, ts.Value(game.TechWeapons)) + assert.Equal(t, 1.3, ts.Value(game.TechShields)) + assert.Equal(t, 1.4, ts.Value(game.TechCargo)) + + assert.Equal(t, 10.5, s.Value(game.TechDrive)) + assert.Equal(t, 5.7, s.Value(game.TechWeapons)) + assert.Equal(t, 2.13, s.Value(game.TechShields)) + assert.Equal(t, 3.1415926, s.Value(game.TechCargo)) } diff --git a/internal/model/game/group.go b/internal/model/game/group.go index b3d832f..8ccef82 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -229,517 +229,3 @@ func (sg ShipGroup) BombingPower(st *ShipType) float64 { float64(sg.Number) return number.Fixed3(result) } - -// JoinEqualGroups iterates over all races and joins their respective equal ship groups. -// Used in turn production. -// func JoinEqualGroups(g *Game) { -// // for i := range g.Race { -// // g.joinEqualGroupsInternal(i) -// // } -// } - -// func (g *Game) JoinEqualGroups(raceName string) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// g.joinEqualGroupsInternal(ri) -// return nil -// } - -// func (g *Game) joinEqualGroupsInternal(ri int) { -// shipGroups := slices.Collect(maps.Values(maps.Collect(g.listIndexShipGroups(ri)))) -// origin := len(shipGroups) -// if origin < 2 { -// return -// } -// for i := 0; i < len(shipGroups)-1; i++ { -// for j := len(shipGroups) - 1; j > i; j-- { -// if shipGroups[i].Equal(shipGroups[j]) { -// shipGroups[i].Index = maxUint(shipGroups[i].Index, shipGroups[j].Index) -// shipGroups[i].Number += shipGroups[j].Number -// shipGroups = append(shipGroups[:j], shipGroups[j+1:]...) -// } -// } -// } -// if len(shipGroups) == origin { -// return -// } -// g.ShipGroups = slices.DeleteFunc(g.ShipGroups, func(v ShipGroup) bool { return v.OwnerID == g.Race[ri].ID }) -// g.ShipGroups = append(g.ShipGroups, shipGroups...) -// } - -// func (g *Game) BreakGroup(raceName string, groupIndex, quantity uint) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// return g.breakGroupInternal(ri, groupIndex, quantity) -// } - -// func (g *Game) DisassembleGroup(raceName string, groupIndex, quantity uint) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// return g.disassembleGroupInternal(ri, groupIndex, quantity) -// } - -// func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error { -// 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].Number < quantity { -// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity) -// } - -// 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) -// } -// 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) -// } - -// if quantity > 0 && quantity < g.ShipGroups[sgi].Number { -// // make new group for disassembly -// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity) -// if err != nil { -// return err -// } -// sgi = nsgi -// } - -// if g.ShipGroups[sgi].CargoType != nil { -// ct := *g.ShipGroups[sgi].CargoType -// load := g.ShipGroups[sgi].Load -// switch ct { -// case CargoColonist: -// if g.Map.Planet[pl].Owner == g.Race[ri].ID { -// g.Map.Planet[pl] = UnloadColonists(g.Map.Planet[pl], load) -// } -// case CargoMaterial: -// g.Map.Planet[pl].Material += load -// case CargoCapital: -// g.Map.Planet[pl].Capital += load -// } -// } - -// g.Map.Planet[pl].Material += g.ShipGroups[sgi].EmptyMass(&g.Race[ri].ShipTypes[sti]) - -// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...) - -// return nil -// } - -// func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// ct, ok := CargoTypeSet[cargoType] -// if !ok { -// return e.NewCargoTypeInvalidError(cargoType) -// } -// return g.loadCargoInternal(ri, groupIndex, ct, ships, quantity) -// } - -// func (g *Game) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// return g.unloadCargoInternal(ri, groupIndex, ships, quantity) -// } - -// // Промышленность и Сырье могут быть выгружены на любой планете. -// // Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты. -// func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity float64) error { -// if ships == 0 && quantity > 0 { -// return e.NewCargoQuantityWithoutGroupBreakError() -// } -// 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) -// } -// if g.ShipGroups[sgi].State() != StateInOrbit { -// return e.NewShipsBusyError() -// } -// 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) -// } -// if g.Race[ri].ShipTypes[sti].Cargo < 1 { -// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name) -// } -// if g.ShipGroups[sgi].CargoType == nil || g.ShipGroups[sgi].Load == 0 { -// return e.NewCargoUnloadEmptyError() -// } -// ct := *g.ShipGroups[sgi].CargoType -// 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 ct == CargoColonist { -// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID { -// return e.NewEntityNotOwnedError("planet #%d unload %v", g.Map.Planet[pl].Number, ct) -// } -// if g.Map.Planet[pl].Owner == uuid.Nil { -// g.Map.Planet[pl].Owner = g.Race[ri].ID -// } -// } -// var availableOnPlanet *float64 -// switch ct { -// case CargoMaterial: -// availableOnPlanet = &g.Map.Planet[pl].Material -// case CargoCapital: -// availableOnPlanet = &g.Map.Planet[pl].Capital -// case CargoColonist: -// availableOnPlanet = &g.Map.Planet[pl].Colonists -// default: -// return e.NewGameStateError("CargoType not accepted: %v", ct) -// } -// if ships > 0 && ships < g.ShipGroups[sgi].Number { -// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships) -// if err != nil { -// return err -// } -// sgi = nsgi -// } -// toBeUnloaded := quantity -// if quantity == 0 { -// toBeUnloaded = g.ShipGroups[sgi].Load -// } -// if toBeUnloaded > g.ShipGroups[sgi].Load { -// return e.NewCargoUnoadNotEnoughError("load: %.03f", g.ShipGroups[sgi].Load) -// } -// *availableOnPlanet += toBeUnloaded -// g.ShipGroups[sgi].Load -= toBeUnloaded -// if g.ShipGroups[sgi].Load == 0 { -// g.ShipGroups[sgi].CargoType = nil -// } -// return nil -// } - -// // Корабль может нести только один тип груза одновременно. -// // Возможные типы груза - это колонисты, сырье и промышленность. -// // Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется. -// func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships uint, quantity float64) error { -// if ships == 0 && quantity > 0 { -// return e.NewCargoQuantityWithoutGroupBreakError() -// } -// 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) -// } -// if g.ShipGroups[sgi].State() != StateInOrbit { -// return e.NewShipsBusyError() -// } -// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination }) -// 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", g.Map.Planet[pl].Number) -// } -// 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) -// } -// if g.Race[ri].ShipTypes[sti].Cargo < 1 { -// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name) -// } -// if g.ShipGroups[sgi].CargoType != nil && *g.ShipGroups[sgi].CargoType != ct { -// return e.NewCargoLoadNotEqualError("cargo: %v", *g.ShipGroups[sgi].CargoType) -// } -// if ships > 0 && ships < g.ShipGroups[sgi].Number { -// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships) -// if err != nil { -// return err -// } -// sgi = nsgi -// } -// capacity := g.ShipGroups[sgi].CargoCapacity(&g.Race[ri].ShipTypes[sti]) -// freeShipGroupCargoLoad := capacity - g.ShipGroups[sgi].Load -// if freeShipGroupCargoLoad == 0 { -// return e.NewCargoLoadNoSpaceLeftError() -// } -// var availableOnPlanet *float64 -// switch ct { -// case CargoMaterial: -// availableOnPlanet = &g.Map.Planet[pl].Material -// case CargoCapital: -// availableOnPlanet = &g.Map.Planet[pl].Capital -// case CargoColonist: -// availableOnPlanet = &g.Map.Planet[pl].Colonists -// default: -// return e.NewGameStateError("CargoType not accepted: %v", ct) -// } -// if quantity > *availableOnPlanet || *availableOnPlanet == 0 { -// return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", g.Map.Planet[pl].Number, ct, *availableOnPlanet) -// } -// toBeLoaded := quantity -// if quantity == 0 { -// toBeLoaded = *availableOnPlanet -// } -// if toBeLoaded > freeShipGroupCargoLoad { -// toBeLoaded = freeShipGroupCargoLoad -// } -// *availableOnPlanet = *availableOnPlanet - toBeLoaded -// g.ShipGroups[sgi].Load += toBeLoaded -// if g.ShipGroups[sgi].Load > 0 { -// g.ShipGroups[sgi].CargoType = &ct -// } -// return nil -// } - -// func (g *Game) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// riAccept, err := g.raceIndex(raceAcceptor) -// if err != nil { -// return err -// } -// return g.giveawayGroupInternal(ri, riAccept, groupIndex, quantity) -// } - -// func (g *Game) giveawayGroupInternal(ri, riAccept int, groupIndex, quantity uint) (err error) { -// if ri == riAccept { -// return e.NewSameRaceError(g.Race[riAccept].Name) -// } -// 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) -// } -// if g.ShipGroups[sgi].Number < quantity { -// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity) -// } - -// 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) -// } - -// var stAcc int -// if stAcc = slices.IndexFunc(g.Race[riAccept].ShipTypes, func(st ShipType) bool { return st.Name == g.Race[ri].ShipTypes[sti].Name }); stAcc >= 0 && -// !g.Race[ri].ShipTypes[sti].Equal(g.Race[riAccept].ShipTypes[stAcc]) { -// return e.NewGiveawayGroupShipsTypeNotEqualError("race %w, ship type %w", g.Race[riAccept].Name, g.Race[riAccept].ShipTypes[stAcc].Name) -// } -// if stAcc < 0 { -// stAcc, err = g.createShipTypeInternal(riAccept, -// g.Race[ri].ShipTypes[sti].Name, -// g.Race[ri].ShipTypes[sti].Drive, -// g.Race[ri].ShipTypes[sti].Weapons, -// g.Race[ri].ShipTypes[sti].Shields, -// g.Race[ri].ShipTypes[sti].Cargo, -// int(g.Race[ri].ShipTypes[sti].Armament)) -// if err != nil { -// return err -// } -// } - -// var maxIndex uint -// for sg := range g.listShipGroups(riAccept) { -// if sg.Index > maxIndex { -// maxIndex = sg.Index -// } -// } - -// g.ShipGroups = append(g.ShipGroups, ShipGroup{ -// Index: maxIndex + 1, -// OwnerID: g.Race[riAccept].ID, -// TypeID: g.Race[riAccept].ShipTypes[stAcc].ID, -// Number: uint(quantity), - -// CargoType: g.ShipGroups[sgi].CargoType, -// Load: g.ShipGroups[sgi].Load, - -// Tech: maps.Clone(g.ShipGroups[sgi].Tech), - -// Destination: g.ShipGroups[sgi].Destination, -// StateInSpace: g.ShipGroups[sgi].StateInSpace, -// StateUpgrade: g.ShipGroups[sgi].StateUpgrade, -// }) - -// if quantity == 0 || quantity == g.ShipGroups[sgi].Number { -// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...) -// } else { -// g.ShipGroups[sgi].Number -= quantity -// } - -// return nil -// } - -// func (g *Game) breakGroupInternal(ri int, groupIndex, quantity uint) error { -// 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].Number < quantity { -// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity) -// } - -// if quantity == 0 || quantity == g.ShipGroups[sgi].Number { -// g.ShipGroups[sgi].FleetID = nil -// } else { -// if _, err := g.breakGroupSafe(ri, groupIndex, quantity); err != nil { -// return err -// } -// } - -// return nil -// } - -// func (g *Game) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) { -// 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 -1, e.NewEntityNotExistsError("group #%d", groupIndex) -// } -// if g.ShipGroups[sgi].Number < newGroupShips { -// return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", g.ShipGroups[sgi].Index, g.ShipGroups[sgi].Number, newGroupShips) -// } -// newGroup := g.ShipGroups[sgi] -// if g.ShipGroups[sgi].CargoType != nil { -// newGroup.Load = g.ShipGroups[sgi].Load / float64(g.ShipGroups[sgi].Number) * float64(newGroupShips) -// g.ShipGroups[sgi].Load -= newGroup.Load -// } -// newGroup.Number = newGroupShips -// g.ShipGroups[sgi].Number -= newGroup.Number -// newGroup.Index = maxIndex + 1 -// newGroup.FleetID = nil -// g.ShipGroups = append(g.ShipGroups, newGroup) -// return len(g.ShipGroups) - 1, nil -// } - -// func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quantity int) error { -// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == shipTypeName }) -// if st < 0 { -// return e.NewEntityNotExistsError("ship type %w", shipTypeName) -// } -// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(planetNumber) }) -// if pl < 0 { -// return e.NewEntityNotExistsError("planet #%d", planetNumber) -// } -// if g.Map.Planet[pl].Owner != g.Race[ri].ID { -// return e.NewEntityNotOwnedError("planet #%d", planetNumber) -// } - -// var maxIndex uint -// for _, sg := range g.listIndexShipGroups(ri) { -// if sg.Index > maxIndex { -// maxIndex = sg.Index -// } -// } -// g.ShipGroups = append(g.ShipGroups, ShipGroup{ -// Index: maxIndex + 1, -// OwnerID: g.Race[ri].ID, -// TypeID: g.Race[ri].ShipTypes[st].ID, -// Destination: g.Map.Planet[pl].Number, -// Number: uint(quantity), -// Tech: map[Tech]float64{ -// TechDrive: g.Race[ri].TechLevel(TechDrive), -// TechWeapons: g.Race[ri].TechLevel(TechWeapons), -// TechShields: g.Race[ri].TechLevel(TechShields), -// TechCargo: g.Race[ri].TechLevel(TechCargo), -// }, -// }) -// return nil -// } - -// func (g Game) listShipGroups(ri int) iter.Seq[ShipGroup] { -// return func(yield func(ShipGroup) bool) { -// for _, sg := range g.listIndexShipGroups(ri) { -// if !yield(sg) { -// return -// } -// } -// } -// } - -// func (g Game) listIndexShipGroups(ri int) iter.Seq2[int, ShipGroup] { -// return func(yield func(int, ShipGroup) bool) { -// for i := range g.ShipGroups { -// if g.ShipGroups[i].OwnerID == g.Race[ri].ID { -// if !yield(i, g.ShipGroups[i]) { -// return -// } -// } -// } -// } -// } - -// func MustShipGroup(g *Game, ri int, index uint) ShipGroup { -// for sg := range g.listShipGroups(ri) { -// if sg.Index == index { -// return sg -// } -// } -// panic(fmt.Sprintf("race i=%d have no group i=%d", ri, index)) -// } - -// func maxUint(a, b uint) uint { -// if b > a { -// return b -// } -// return a -// } diff --git a/internal/model/game/group_send.go b/internal/model/game/group_send.go deleted file mode 100644 index ea1d18e..0000000 --- a/internal/model/game/group_send.go +++ /dev/null @@ -1,93 +0,0 @@ -package game - -// import ( -// "slices" - -// e "github.com/iliadenisov/galaxy/internal/error" -// "github.com/iliadenisov/galaxy/internal/util" -// ) - -// func (g *Game) SendGroup(raceName string, groupIndex, planetNumber, quantity uint) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// return g.sendGroupInternal(ri, groupIndex, planetNumber, quantity) -// } - -// func (g *Game) sendGroupInternal(ri int, groupIndex, planetNumber, quantity uint) error { -// sgi := -1 -// for i, sg := range g.listIndexShipGroups(ri) { -// if sgi < 0 && sg.Index == groupIndex { -// sgi = i -// } -// } -// if sgi < 0 { -// return e.NewEntityNotExistsError("group #%d", groupIndex) -// } - -// var sti int -// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 { -// // hard to test, need manual game data invalidation -// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID) -// } -// st := g.Race[ri].ShipTypes[sti] - -// if st.DriveBlockMass() == 0 { -// return e.NewSendShipHasNoDrivesError() -// } - -// sourcePlanet, ok := g.ShipGroups[sgi].OnPlanet() -// if !ok { -// return e.NewShipsBusyError() -// } - -// if g.ShipGroups[sgi].Number < quantity { -// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity) -// } - -// p1, ok := PlanetByNum(g, sourcePlanet) -// if !ok { -// return e.NewGameStateError("source planet #%d does not exists", 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 sourcePlanet == planetNumber { -// g.ShipGroups[sgi] = UnsendShips(g.ShipGroups[sgi]) -// g.joinEqualGroupsInternal(ri) -// return nil -// } - -// g.ShipGroups[sgi] = LaunchShips(g.ShipGroups[sgi], planetNumber) - -// return nil -// } - -// func LaunchShips(sg ShipGroup, destination uint) ShipGroup { -// sg.StateInSpace = &InSpace{ -// Origin: sg.Destination, -// } -// sg.Destination = destination -// return sg -// } - -// func UnsendShips(sg ShipGroup) ShipGroup { -// sg.Destination = sg.StateInSpace.Origin -// sg.StateInSpace = nil -// return sg -// } diff --git a/internal/model/game/group_send_test.go b/internal/model/game/group_send_test.go deleted file mode 100644 index 4efcca2..0000000 --- a/internal/model/game/group_send_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package game_test - -// import ( -// "slices" -// "testing" - -// e "github.com/iliadenisov/galaxy/internal/error" - -// "github.com/iliadenisov/galaxy/internal/model/game" -// "github.com/stretchr/testify/assert" -// ) - -// func TestSendGroup(t *testing.T) { -// g := newGame() -// // group #1 - in_orbit, free to upgrade -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10)) -// // group #2 - in_space -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) -// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} -// // group #3 - in_orbit, unmovable -// g.CreateShipType(Race_0.Name, "Fortress", 0, 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.NoError(t, g.SendGroup(Race_0.Name, 1, 2, 3)) // send 3 of 10 -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(7), g.ShipGroups[0].Number) -// assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State()) -// assert.Equal(t, uint(3), g.ShipGroups[3].Number) -// assert.Equal(t, game.StateLaunched, g.ShipGroups[3].State()) - -// assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 2)) // un-send 2 of 3 -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(9), game.MustShipGroup(g, Race_0_idx, 5).Number) -// assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State()) -// assert.Equal(t, uint(1), game.MustShipGroup(g, Race_0_idx, 4).Number) -// assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 4).State()) - -// assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 0)) // un-send the rest 1 -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number) -// assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State()) - -// assert.NoError(t, g.SendGroup(Race_0.Name, 5, 2, 0)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number) -// assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 5).State()) -// } diff --git a/internal/model/game/group_test.go b/internal/model/game/group_test.go index af803bd..b07d69b 100644 --- a/internal/model/game/group_test.go +++ b/internal/model/game/group_test.go @@ -255,518 +255,3 @@ func TestShipGroupEqual(t *testing.T) { left.Load = right.Load / float64(right.Number) * float64(left.Number) assert.True(t, left.Equal(right)) } - -// func TestCreateShips(t *testing.T) { -// g := newGame() - -// assert.ErrorContains(t, -// g.CreateShips(Race_0_idx, "Unknown_Ship_Type", R0_Planet_0_num, 2), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.ErrorContains(t, -// g.CreateShips(Race_0_idx, Race_0_Gunship, R1_Planet_1_num, 2), -// e.GenericErrorText(e.ErrInputEntityNotOwned)) - -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 1) - -// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1)) -// assert.Len(t, slices.Collect(g.ListShipGroups(1)), 1) - -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2) - -// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1)) -// assert.Len(t, slices.Collect(g.ListShipGroups(1)), 2) -// } - -// func TestJoinEqualGroups(t *testing.T) { -// g := newGame() - -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2 -// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1)) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6)) // (2) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // (3) -// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1)) - -// g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 1.5) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 9)) // 4 -> 6 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) // 5 -> 7 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 4)) // (6) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 4)) // (7) - -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) - -// g.Race[Race_1_idx].SetTechLevel(game.TechShields, 2.0) -// assert.NoError(t, g.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3) - -// assert.NoError(t, g.JoinEqualGroups(Race_0.Name)) - -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) - -// shipTypeID := func(ri int, name string) uuid.UUID { -// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(v game.ShipType) bool { return v.Name == name }) -// if st < 0 { -// t.Fatalf("ShipType not found: %s", name) -// return uuid.Nil -// } -// return g.Race[ri].ShipTypes[st].ID -// } - -// for sg := range g.ListShipGroups(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) { -// g := newGame() -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 13)) // group #1 (0) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) // group #2 (1) - In_Space -// g.ShipGroups[1].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(g.ListShipGroups(Race_0_idx)), 2) -// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 1) - -// // group #1 -> group #3 (5 new, 8 left) -// assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 5)) // group #3 (2) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Equal(t, uint(8), g.ShipGroups[0].Number) -// assert.NotNil(t, g.ShipGroups[0].FleetID) -// assert.Equal(t, uint(5), g.ShipGroups[2].Number) -// assert.Equal(t, uint(3), g.ShipGroups[2].Index) -// assert.Nil(t, g.ShipGroups[2].FleetID) -// assert.Nil(t, g.ShipGroups[2].CargoType) - -// // group #1 -> group #4 (2 new, 6 left) -// g.ShipGroups[0].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[0].Load = 32.8 // 8 ships -// assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 2)) // group #4 (3) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(6), g.ShipGroups[0].Number) -// assert.NotNil(t, g.ShipGroups[0].FleetID) -// assert.Equal(t, uint(2), g.ShipGroups[3].Number) -// assert.Equal(t, uint(4), g.ShipGroups[3].Index) -// assert.Nil(t, g.ShipGroups[3].FleetID) -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 4, 0)) -// assert.NotNil(t, g.ShipGroups[3].FleetID) - -// assert.Equal(t, game.CargoColonist.Ref(), g.ShipGroups[0].CargoType) -// assert.Equal(t, 24.6, number.Fixed3(g.ShipGroups[0].Load)) -// assert.Equal(t, game.CargoColonist.Ref(), g.ShipGroups[3].CargoType) -// assert.Equal(t, 8.2, number.Fixed3(g.ShipGroups[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(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(6), g.ShipGroups[0].Number) -// assert.Nil(t, g.ShipGroups[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(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(2), g.ShipGroups[3].Number) -// assert.Nil(t, g.ShipGroups[3].FleetID) -// } - -// func TestGiveawayGroup(t *testing.T) { -// g := newGame() -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0) -// assert.NoError(t, g.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1) - -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 17)) // group #2 (2) - In_Space -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, "R0_Fleet", 2, 0)) -// assert.NotNil(t, g.ShipGroups[2].FleetID) -// g.ShipGroups[2].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 31.337, -// } -// g.ShipGroups[2].CargoType = game.CargoMaterial.Ref() -// g.ShipGroups[2].Load = 1.234 - -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2) -// assert.Len(t, slices.Collect(g.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(g.ListShipGroups(Race_0_idx)), 2) -// assert.Len(t, slices.Collect(g.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.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Name, g.Race[Race_0_idx].ShipTypes[sto].Name) -// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Drive, g.Race[Race_0_idx].ShipTypes[sto].Drive) -// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Weapons, g.Race[Race_0_idx].ShipTypes[sto].Weapons) -// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Shields, g.Race[Race_0_idx].ShipTypes[sto].Shields) -// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Cargo, g.Race[Race_0_idx].ShipTypes[sto].Cargo) -// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Armament, g.Race[Race_0_idx].ShipTypes[sto].Armament) -// assert.Equal(t, g.ShipGroups[2].State(), g.ShipGroups[3].State()) -// assert.Equal(t, g.ShipGroups[2].CargoType, g.ShipGroups[3].CargoType) -// assert.Equal(t, g.ShipGroups[2].Load, g.ShipGroups[3].Load) -// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechDrive), g.ShipGroups[3].TechLevel(game.TechDrive)) -// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechWeapons), g.ShipGroups[3].TechLevel(game.TechWeapons)) -// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechShields), g.ShipGroups[3].TechLevel(game.TechShields)) -// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechCargo), g.ShipGroups[3].TechLevel(game.TechCargo)) -// assert.Equal(t, g.ShipGroups[2].Destination, g.ShipGroups[3].Destination) -// assert.Equal(t, g.ShipGroups[2].StateInSpace, g.ShipGroups[3].StateInSpace) -// assert.Equal(t, g.ShipGroups[2].StateUpgrade, g.ShipGroups[3].StateUpgrade) -// assert.Equal(t, g.ShipGroups[3].OwnerID, g.Race[Race_1_idx].ID) -// assert.Equal(t, g.ShipGroups[3].TypeID, g.Race[Race_1_idx].ShipTypes[sti].ID) -// assert.Equal(t, g.ShipGroups[3].Number, uint(11)) -// assert.Nil(t, g.ShipGroups[3].FleetID) - -// assert.NoError(t, g.GiveawayGroup(Race_1.Name, Race_0.Name, 2, 11)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 1) -// } - -// func TestLoadCargo(t *testing.T) { -// g := newGame() - -// // 1: idx = 0 / Ready to load -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) - -// // 2: idx = 1 / Has no cargo bay -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) - -// // 3: idx = 2 / In_Space -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) -// g.ShipGroups[2].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 31.337, -// } - -// // 4: idx = 3 / loaded with COL -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[3].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[3].Load = 1.234 - -// // 5: idx = 4 / on foreign planet -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[4].Destination = R1_Planet_1_num - -// assert.Len(t, slices.Collect(g.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 -// // 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(g.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, g.Map.Planet[0].Material) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6) -// assert.Nil(t, g.ShipGroups[0].CargoType) -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) -// assert.Equal(t, uint(9), g.ShipGroups[0].Number) -// assert.Equal(t, 0.0, g.ShipGroups[0].Load) -// assert.Equal(t, uint(2), g.ShipGroups[5].Number) -// assert.Equal(t, 42.0, g.ShipGroups[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, g.Map.Planet[0].Material) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) -// assert.Nil(t, g.ShipGroups[0].CargoType) -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[6].CargoType) -// assert.Equal(t, uint(7), g.ShipGroups[0].Number) -// assert.Equal(t, 0.0, g.ShipGroups[0].Load) -// assert.Equal(t, uint(2), g.ShipGroups[6].Number) -// assert.Equal(t, 18.0, g.ShipGroups[6].Load) - -// // add cargo to planet -// g.Map.Planet[0].Material = 100 -// // loading all available cargo -// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) -// assert.Equal(t, 0.0, g.Map.Planet[0].Material) -// assert.Equal(t, 100.0, g.ShipGroups[0].Load) // free: 131.0 -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType) - -// // add cargo to planet -// g.Map.Planet[0].Material = 200 -// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 31)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) -// assert.Equal(t, 169.0, g.Map.Planet[0].Material) -// assert.Equal(t, 131.0, g.ShipGroups[0].Load) // free: 100.0 -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[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(g.ListShipGroups(Race_0_idx)), 7) -// assert.Equal(t, 153.0, g.Map.Planet[0].Material) -// assert.Equal(t, 147.0, g.ShipGroups[0].Load) // free: 0.0 -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[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(g.ListShipGroups(Race_0_idx)), 7) -// } - -// func TestUnloadCargo(t *testing.T) { -// g := newGame() - -// // 1: idx = 0 / empty -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) - -// // 2: idx = 1 / Has no cargo bay -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) - -// // 3: idx = 2 / In_Space -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) -// g.ShipGroups[2].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 31.337, -// } - -// // 4: idx = 3 / loaded with COL -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[3].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[3].Load = 1.234 - -// // 5: idx = 4 / on foreign planet / loaded with COL -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[4].Destination = R1_Planet_1_num -// g.ShipGroups[4].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[4].Load = 1.234 - -// // 6: idx = 5 / on foreign planet / loaded with MAT -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[5].Destination = R1_Planet_1_num -// g.ShipGroups[5].CargoType = game.CargoMaterial.Ref() -// g.ShipGroups[5].Load = 100.0 - -// assert.Len(t, slices.Collect(g.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)) -// g.ShipGroups[0].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[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(g.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(g.Map.Planet[1].Material)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) -// assert.Equal(t, uint(3), g.ShipGroups[6].Number) -// assert.Nil(t, g.ShipGroups[6].CargoType) -// assert.Equal(t, 0.0, g.ShipGroups[6].Load) -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) -// assert.Equal(t, uint(8), g.ShipGroups[5].Number) -// assert.Equal(t, 72.727, number.Fixed3(g.ShipGroups[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(g.Map.Planet[1].Material)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8) -// assert.Equal(t, uint(3), g.ShipGroups[7].Number) -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[7].CargoType) -// assert.Equal(t, 7.273, number.Fixed3(g.ShipGroups[7].Load)) -// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) -// assert.Equal(t, uint(5), g.ShipGroups[5].Number) -// assert.Equal(t, 45.455, number.Fixed3(g.ShipGroups[5].Load)) - -// // unload ALL -// assert.NoError(t, g.UnloadCargo(Race_0.Name, 1, 0, 0)) -// assert.Equal(t, 100.0, number.Fixed3(g.Map.Planet[0].Colonists)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8) -// assert.Equal(t, uint(10), g.ShipGroups[0].Number) -// assert.Nil(t, g.ShipGroups[0].CargoType) -// assert.Equal(t, 0.0, number.Fixed3(g.ShipGroups[0].Load)) -// } - -// func TestDisassembleGroup(t *testing.T) { -// g := newGame() - -// // 1: idx = 0 / empty -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) - -// // 2: idx = 1 / In_Space -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) -// g.ShipGroups[1].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 31.337, -// } - -// // 3: idx = 2 / loaded with COL -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) -// g.ShipGroups[2].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[2].Load = 80.0 - -// // 4: idx = 3 / on foreign planet / loaded with MAT -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[3].Destination = R1_Planet_1_num -// g.ShipGroups[3].CargoType = game.CargoMaterial.Ref() -// g.ShipGroups[3].Load = 100.0 - -// // 5: idx = 4 / on foreign planet / loaded with COL -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) -// g.ShipGroups[4].Destination = R1_Planet_1_num -// g.ShipGroups[4].CargoType = game.CargoColonist.Ref() -// g.ShipGroups[4].Load = 2.345 - -// assert.Len(t, slices.Collect(g.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 := g.ShipGroups[4].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) -// // groupLoadCOL := g.ShipGroups[4].Load -// planetMAT := g.Map.Planet[1].Material -// planetCOL := g.Map.Planet[1].Colonists - -// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 5, 0)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[1].Material) -// assert.Equal(t, planetCOL, g.Map.Planet[1].Colonists) - -// groupEmptyMass = g.ShipGroups[3].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) -// groupLoadMAT := g.ShipGroups[3].Load -// planetMAT = g.Map.Planet[1].Material -// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 4, 0)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Equal(t, planetMAT+groupEmptyMass+groupLoadMAT, g.Map.Planet[1].Material) - -// groupEmptyMass = g.ShipGroups[2].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) -// planetMAT = g.Map.Planet[0].Material -// planetCOL = g.Map.Planet[0].Colonists -// planetPOP := 90.0 -// g.Map.Planet[0].Population = planetPOP -// var shipsDisassembling uint = 3 -// groupEmptyMass = groupEmptyMass / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling) -// newGroupUnloadedCOL := g.ShipGroups[2].Load / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling) -// expectPOPIncrease := newGroupUnloadedCOL * 8 -// freePOPLeft := g.Map.Planet[0].Size - g.Map.Planet[0].Population -// expectAddedCOL := (expectPOPIncrease - freePOPLeft) / 8 -// expectAddedPOP := freePOPLeft -// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 3, shipsDisassembling)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) -// assert.Equal(t, planetCOL+expectAddedCOL, g.Map.Planet[0].Colonists) -// assert.Equal(t, planetPOP+expectAddedPOP, g.Map.Planet[0].Population) -// assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[0].Material) -// assert.Equal(t, uint(7), g.ShipGroups[2].Number) -// assert.Equal(t, 56.0, g.ShipGroups[2].Load) -// } diff --git a/internal/model/game/group_upgrade.go b/internal/model/game/group_upgrade.go index 83b7fb4..670920c 100644 --- a/internal/model/game/group_upgrade.go +++ b/internal/model/game/group_upgrade.go @@ -46,158 +46,6 @@ 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) upgradeGroupInternal(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { -// sgi := -1 -// for i, sg := range g.listIndexShipGroups(ri) { -// if sgi < 0 && sg.Index == groupIndex { -// sgi = i -// } -// } -// if sgi < 0 { -// return e.NewEntityNotExistsError("group #%d", groupIndex) -// } - -// var sti int -// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 { -// // hard to test, need manual game data invalidation -// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID) -// } -// st := g.Race[ri].ShipTypes[sti] - -// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination }) -// if pl < 0 { -// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination) -// } -// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID { -// return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", g.Map.Planet[pl].Number, groupIndex) -// } - -// if g.ShipGroups[sgi].State() != StateInOrbit && g.ShipGroups[sgi].State() != StateUpgrade { -// return e.NewShipsBusyError() -// } - -// upgradeValidTech := map[string]Tech{ -// TechDrive.String(): TechDrive, -// TechWeapons.String(): TechWeapons, -// TechShields.String(): TechShields, -// TechCargo.String(): TechCargo, -// TechAll.String(): TechAll, -// } - -// techRequest, ok := upgradeValidTech[techInput] -// if !ok { -// return e.NewTechUnknownError(techInput) -// } - -// var blockMasses map[Tech]float64 = map[Tech]float64{ -// TechDrive: st.DriveBlockMass(), -// TechWeapons: st.WeaponsBlockMass(), -// TechShields: st.ShieldsBlockMass(), -// TechCargo: st.CargoBlockMass(), -// } - -// switch { -// case techRequest != TechAll && blockMasses[techRequest] == 0: -// return e.NewUpgradeShipTechNotUsedError() -// case techRequest == TechAll && limitLevel != 0: -// return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel) -// } - -// targetLevel := make(map[Tech]float64) -// var sumLevels float64 -// for _, tech := range []Tech{TechDrive, TechWeapons, TechShields, TechCargo} { -// if techRequest == TechAll || tech == techRequest { -// if g.Race[ri].TechLevel(tech) < limitLevel { -// return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), g.Race[ri].TechLevel(tech), limitLevel) -// } -// targetLevel[tech] = FutureUpgradeLevel(g.Race[ri].TechLevel(tech), g.ShipGroups[sgi].TechLevel(tech), limitLevel) -// } else { -// targetLevel[tech] = CurrentUpgradingLevel(g.ShipGroups[sgi], tech) -// } -// sumLevels += targetLevel[tech] -// } - -// productionCapacity := PlanetProductionCapacity(g, g.Map.Planet[pl].Number) -// if g.ShipGroups[sgi].State() == StateUpgrade { -// // to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state -// productionCapacity -= g.ShipGroups[sgi].StateUpgrade.Cost() -// } -// uc := GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo]) -// costForShip := uc.UpgradeCost(1) -// if costForShip == 0 { -// return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel) -// } - -// shipsToUpgrade := g.ShipGroups[sgi].Number -// // НЕ БОЛЕЕ УКАЗАННОГО -// if limitShips > 0 && shipsToUpgrade > limitShips { -// shipsToUpgrade = limitShips -// } - -// maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity) - -// /* -// 1. считаем стоимость модернизации одного корабля -// 2. считаем сколько кораблей можно модернизировать -// 3. если не хватает даже на 1 корабль, ограничиваемся одним кораблём и пересчитываем коэффициент пропорционально массе блоков -// 4. иначе, считаем истинное количество кораблей с учётом ограничения maxShips -// */ -// blockMassSum := st.EmptyMass() - -// coef := productionCapacity / costForShip -// if maxUpgradableShips == 0 { -// if limitLevel > 0 { -// return e.NewUpgradeInsufficientResourcesError("ship cost=%.03f L=%.03f", costForShip, productionCapacity) -// } -// sumLevels = sumLevels * coef -// for tech := range targetLevel { -// if blockMasses[tech] > 0 { -// proportional := sumLevels * (blockMasses[tech] / blockMassSum) -// targetLevel[tech] = proportional -// } -// } -// maxUpgradableShips = 1 -// } else if maxUpgradableShips > shipsToUpgrade { -// maxUpgradableShips = shipsToUpgrade -// } - -// // sanity check -// uc = GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo]) -// costForGroup := uc.UpgradeCost(maxUpgradableShips) -// if costForGroup > productionCapacity { -// e.NewGameStateError("cost recalculation: coef=%f cost(%d)=%f L=%f", coef, maxUpgradableShips, costForGroup, productionCapacity) -// } - -// // break group if needed -// if maxUpgradableShips < g.ShipGroups[sgi].Number { -// if g.ShipGroups[sgi].State() == StateUpgrade { -// return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", g.ShipGroups[sgi].Number, maxUpgradableShips) -// } -// nsgi, err := g.breakGroupSafe(ri, groupIndex, maxUpgradableShips) -// if err != nil { -// return err -// } -// sgi = nsgi -// } - -// // finally, fill group upgrade prefs -// for tech := range targetLevel { -// if targetLevel[tech] > 0 { -// g.ShipGroups[sgi] = UpgradeGroupPreference(g.ShipGroups[sgi], st, tech, targetLevel[tech]) -// } -// } - -// return nil -// } - func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 { if sg.StateUpgrade == nil { return 0 diff --git a/internal/model/game/group_upgrade_test.go b/internal/model/game/group_upgrade_test.go index 3737cb6..ab45114 100644 --- a/internal/model/game/group_upgrade_test.go +++ b/internal/model/game/group_upgrade_test.go @@ -7,6 +7,19 @@ import ( "github.com/stretchr/testify/assert" ) +var ( + Cruiser = game.ShipType{ + ShipTypeReport: game.ShipTypeReport{ + Name: "Cruiser", + Drive: 15, + Armament: 1, + Weapons: 15, + Shields: 15, + Cargo: 0, + }, + } +) + 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)) @@ -108,60 +121,3 @@ func TestUpgradeGroupPreference(t *testing.T) { assert.Equal(t, 0., sg.StateUpgrade.TechCost(game.TechCargo)) assert.Equal(t, 900., sg.StateUpgrade.Cost()) } - -// func TestUpgradeGroup(t *testing.T) { -// g := newGame() -// // group #1 - in_orbit, free to upgrade -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10)) -// // group #2 - in_space -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) -// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} -// // group #3 - in_orbit, foreign planet -// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) -// g.ShipGroups[2].Destination = R1_Planet_1_num - -// assert.ErrorContains(t, -// g.UpgradeGroup("UnknownRace", 1, "DRIVE", 0, 0), -// e.GenericErrorText(e.ErrInputUnknownRace)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 555, "DRIVE", 0, 0), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 2, "DRIVE", 0, 0), -// e.GenericErrorText(e.ErrShipsBusy)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 3, "DRIVE", 0, 0), -// e.GenericErrorText(e.ErrInputEntityNotOwned)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "GUN", 0, 0), -// e.GenericErrorText(e.ErrInputTechUnknown)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "CARGO", 0, 0), -// e.GenericErrorText(e.ErrInputUpgradeShipTechNotUsed)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "ALL", 0, 2.0), -// e.GenericErrorText(e.ErrInputUpgradeParameterNotAllowed)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 2.0), -// e.GenericErrorText(e.ErrInputUpgradeTechLevelInsufficient)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 1.1), -// e.GenericErrorText(e.ErrInputUpgradeShipsAlreadyUpToDate)) - -// g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 10.0) -// assert.Equal(t, 10.0, g.Race[Race_0_idx].TechLevel(game.TechDrive)) -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 10.0), -// e.GenericErrorText(e.ErrUpgradeInsufficientResources)) - -// assert.NoError(t, g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 2, 1.2)) -// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) -// assert.Equal(t, uint(8), g.ShipGroups[0].Number) -// assert.Equal(t, uint(2), g.ShipGroups[3].Number) -// assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State()) -// assert.Equal(t, game.StateUpgrade, g.ShipGroups[3].State()) - -// assert.ErrorContains(t, -// g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3), -// e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed)) -// } diff --git a/internal/model/game/map.go b/internal/model/game/map.go index eac342b..867e842 100644 --- a/internal/model/game/map.go +++ b/internal/model/game/map.go @@ -5,7 +5,3 @@ type Map struct { Height uint32 `json:"height"` Planet []Planet `json:"planets"` } - -func Destination(x1, y1, x2, y2 float64) float64 { - return 0 -} diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index e910dd8..c29a6bb 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -2,10 +2,8 @@ package game import ( "math" - "slices" "github.com/google/uuid" - e "github.com/iliadenisov/galaxy/internal/error" ) type UnidentifiedPlanet struct { @@ -48,21 +46,6 @@ func (p Planet) ProductionCapacity() float64 { return p.Industry*0.75 + p.Population*0.25 } -// Свободный производственный потенциал (L) -// промышленность * 0.75 + население * 0.25 -// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей -func PlanetProductionCapacity(g *Game, planetNumber uint) float64 { - p, err := g.PlanetByNumber(planetNumber) - if err != nil { - panic(err) - } - var busyResources float64 - for sg := range g.ShipsInUpgrade(p.Number) { - busyResources += sg.StateUpgrade.Cost() - } - return PlanetProduction(p.Industry, p.Population) - busyResources -} - func PlanetProduction(industry, population float64) float64 { return industry*0.75 + population*0.25 } @@ -102,38 +85,3 @@ func UnloadColonists(p Planet, v float64) Planet { } return p } - -func (g Game) RenamePlanet(raceName string, planetNumber int, typeName string) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - return g.renamePlanetInternal(ri, planetNumber, typeName) -} - -func (g Game) renamePlanetInternal(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) - } - pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(number) }) - if pl < 0 { - return e.NewEntityNotExistsError("planet #%d", number) - } - if g.Map.Planet[pl].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet #%d", number) - } - g.Map.Planet[pl].Name = n - return nil -} - -func PlanetByNum(g *Game, number uint) (Planet, bool) { - pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number }) - if pi < 0 { - return Planet{}, false - } - return g.Map.Planet[pi], true -} diff --git a/internal/model/game/planet_test.go b/internal/model/game/planet_test.go index 91cc8a4..048bf8a 100644 --- a/internal/model/game/planet_test.go +++ b/internal/model/game/planet_test.go @@ -12,11 +12,3 @@ func TestPlanetProduction(t *testing.T) { assert.Equal(t, 750., game.PlanetProduction(1000., 0.)) assert.Equal(t, 250., game.PlanetProduction(0., 1000.)) } - -func TestPlanetProductionCapacity(t *testing.T) { - g := newGame() - assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) - assert.Equal(t, 100., game.PlanetProductionCapacity(g, R0_Planet_0_num)) - g.ShipGroups[0] = game.UpgradeGroupPreference(g.ShipGroups[0], Cruiser, game.TechDrive, 1.6) - assert.Equal(t, 53.125, game.PlanetProductionCapacity(g, R0_Planet_0_num)) -} diff --git a/internal/model/game/production.go b/internal/model/game/production.go index c45b508..8038cc0 100644 --- a/internal/model/game/production.go +++ b/internal/model/game/production.go @@ -1,10 +1,7 @@ package game import ( - "slices" - "github.com/google/uuid" - e "github.com/iliadenisov/galaxy/internal/error" ) type ProductionType string @@ -37,92 +34,3 @@ func (p ProductionType) AsType(subject uuid.UUID) Production { return Production{Type: p, SubjectID: nil} } } - -func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subject string) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - var prod ProductionType - switch ProductionType(prodType) { - case ProductionMaterial: - prod = ProductionMaterial - case ProductionCapital: - prod = ProductionCapital - case ResearchDrive: - prod = ResearchDrive - case ResearchWeapons: - prod = ResearchWeapons - case ResearchShields: - prod = ResearchShields - case ResearchCargo: - prod = ResearchCargo - case ResearchScience: - prod = ResearchScience - case ProductionShip: - prod = ProductionShip - default: - return e.NewProductionInvalidError(prodType) - } - return g.planetProductionInternal(ri, planetNumber, prod, subject) -} - -func (g Game) planetProductionInternal(ri int, number int, prod ProductionType, subj string) error { - if number < 0 { - return e.NewPlanetNumberError(number) - } - i := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(number) }) - if i < 0 { - return e.NewEntityNotExistsError("planet #%d", number) - } - if g.Map.Planet[i].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet #%d", number) - } - g.Map.Planet[i].Production.Progress = nil - var subjectID *uuid.UUID - if (prod == ResearchScience || prod == ProductionShip) && subj == "" { - return e.NewEntityTypeNameValidationError("%s=%q", prod, subj) - } - if prod == ResearchScience { - i := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == subj }) - if i < 0 { - return e.NewEntityNotExistsError("science %w", subj) - } - subjectID = &g.Race[ri].Sciences[i].ID - } - if prod == ProductionShip { - i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == subj }) - if i < 0 { - return e.NewEntityNotExistsError("ship type %w", subj) - } - if g.Map.Planet[i].Production.Type == ProductionShip && - g.Map.Planet[i].Production.SubjectID != nil && - *g.Map.Planet[i].Production.SubjectID == g.Race[ri].ShipTypes[i].ID { - // Planet already produces this ship type, keeping progress intact - return nil - } - subjectID = &g.Race[ri].ShipTypes[i].ID - var progress float64 = 0. - g.Map.Planet[i].Production.Progress = &progress - } - if g.Map.Planet[i].Production.Type == ProductionShip { - if g.Map.Planet[i].Production.SubjectID == nil { - return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", g.Map.Planet[i].Number) - } - s := *g.Map.Planet[i].Production.SubjectID - if g.Map.Planet[i].Production.Progress == nil { - return e.NewGameStateError("planet #%d produces ship but Progress is empty", g.Map.Planet[i].Number) - } - progress := *g.Map.Planet[i].Production.Progress - i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == s }) - if i < 0 { - return e.NewGameStateError("planet #%d produces ship but ShipType was not found for race %s", g.Map.Planet[i].Number, g.Race[ri].Name) - } - mat, _ := g.Race[ri].ShipTypes[i].ProductionCost() - extra := mat * progress - g.Map.Planet[i].Material += extra - } - g.Map.Planet[i].Production.Type = prod - g.Map.Planet[i].Production.SubjectID = subjectID - return nil -} diff --git a/internal/model/game/race.go b/internal/model/game/race.go index 4e5fca4..f5dd1e2 100644 --- a/internal/model/game/race.go +++ b/internal/model/game/race.go @@ -1,11 +1,6 @@ package game -import ( - // "slices" - - "github.com/google/uuid" - // e "github.com/iliadenisov/galaxy/internal/error" -) +import "github.com/google/uuid" type Race struct { ID uuid.UUID `json:"id"` @@ -38,11 +33,6 @@ func (r Race) TechLevel(t Tech) float64 { return r.Tech.Value(t) } -// TODO: remove func, move to Cache -func (r *Race) SetTechLevel(t Tech, v float64) { - r.Tech = r.Tech.Set(t, v) -} - func (r Race) FlightDistance() float64 { return r.TechLevel(TechDrive) * 40 } @@ -50,75 +40,3 @@ func (r Race) FlightDistance() float64 { 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) 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) 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 -// } diff --git a/internal/model/game/race_test.go b/internal/model/game/race_test.go index 5a2cdf6..2095df5 100644 --- a/internal/model/game/race_test.go +++ b/internal/model/game/race_test.go @@ -1 +1,35 @@ package game_test + +import ( + "testing" + + "github.com/iliadenisov/galaxy/internal/model/game" + "github.com/stretchr/testify/assert" +) + +var ( + ts = game.TechSet{ + game.TechDrive: 1.1, + game.TechWeapons: 1.2, + game.TechShields: 1.3, + game.TechCargo: 1.4, + } + r = game.Race{ + Tech: ts, + } +) + +func TestTechLevel(t *testing.T) { + assert.Equal(t, 1.1, r.TechLevel(game.TechDrive)) + assert.Equal(t, 1.2, r.TechLevel(game.TechWeapons)) + assert.Equal(t, 1.3, r.TechLevel(game.TechShields)) + assert.Equal(t, 1.4, r.TechLevel(game.TechCargo)) +} + +func TestFlightDistance(t *testing.T) { + assert.Equal(t, 44., r.FlightDistance()) +} + +func TestVisibilityDistance(t *testing.T) { + assert.Equal(t, 33., r.VisibilityDistance()) +} diff --git a/internal/model/game/route.go b/internal/model/game/route.go index ca53950..7b8f7ff 100644 --- a/internal/model/game/route.go +++ b/internal/model/game/route.go @@ -1,13 +1,5 @@ package game -import ( - "fmt" - "slices" - - e "github.com/iliadenisov/galaxy/internal/error" - "github.com/iliadenisov/galaxy/internal/util" -) - type RouteType string const ( @@ -18,7 +10,7 @@ const ( ) var ( - routeTypeSet map[string]RouteType = map[string]RouteType{ + RouteTypeSet map[string]RouteType = map[string]RouteType{ RouteMaterial.String(): RouteMaterial, RouteCapital.String(): RouteCapital, RouteColonist.String(): RouteColonist, @@ -33,84 +25,3 @@ func (rt RouteType) Ref() *RouteType { func (rt RouteType) String() string { return string(rt) } - -func (g *Game) SetRoute(raceName, loadType string, origin, destination uint) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - rt, ok := routeTypeSet[loadType] - if !ok { - return e.NewCargoTypeInvalidError(loadType) - } - return g.setRouteInternal(ri, rt, origin, destination) -} - -func (g *Game) RemoveRoute(raceName, loadType string, origin uint) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - rt, ok := routeTypeSet[loadType] - if !ok { - return e.NewCargoTypeInvalidError(loadType) - } - return g.removeRouteInternal(ri, rt, origin) -} - -func (g *Game) setRouteInternal(ri int, rt RouteType, origin, destination uint) error { - p1, ok := PlanetByNum(g, origin) - if !ok { - return e.NewEntityNotExistsError("origin planet #%d", origin) - } - if p1.Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet #%d", origin) - } - p2, ok := PlanetByNum(g, destination) - if !ok { - return e.NewEntityNotExistsError("destination planet #%d", destination) - } - 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) - } - - SetPlanetRoute(g, rt, origin, destination) - - return nil -} - -func (g *Game) removeRouteInternal(ri int, rt RouteType, origin uint) error { - p1, ok := PlanetByNum(g, origin) - if !ok { - return e.NewEntityNotExistsError("origin planet #%d", origin) - } - if p1.Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet #%d", origin) - } - - RemovePlanetRoute(g, rt, origin) - - return nil -} - -func SetPlanetRoute(g *Game, rt RouteType, origin, destination uint) { - pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == origin }) - if pi < 0 { - panic(fmt.Sprintf("SetPlanetRoute: origin planet #%d not found", origin)) - } - if g.Map.Planet[pi].Route == nil { - g.Map.Planet[pi].Route = make(map[RouteType]uint) - } - g.Map.Planet[pi].Route[rt] = destination -} - -func RemovePlanetRoute(g *Game, rt RouteType, origin uint) { - pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == origin }) - if pi < 0 { - panic(fmt.Sprintf("RemovePlanetRoute: origin planet #%d not found", origin)) - } - if g.Map.Planet[pi].Route != nil { - delete(g.Map.Planet[pi].Route, rt) - } -} diff --git a/internal/model/game/route_test.go b/internal/model/game/route_test.go deleted file mode 100644 index 9a2d8b0..0000000 --- a/internal/model/game/route_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package game_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) { - g := newGame() - - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty) - - assert.NoError(t, g.SetRoute(Race_0.Name, "COL", 0, 2)) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty) - - assert.NoError(t, g.SetRoute(Race_0.Name, "MAT", 0, 2)) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty) - - assert.NoError(t, g.SetRoute(Race_0.Name, "CAP", 0, 2)) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty) - - assert.NoError(t, g.SetRoute(Race_0.Name, "EMP", 0, 2)) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.Contains(t, g.MustPlanetByNumber(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) { - g := newGame() - - 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, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - assert.Contains(t, g.MustPlanetByNumber(2).Route, game.RouteEmpty) - - assert.NoError(t, g.RemoveRoute(Race_0.Name, "COL", 0)) - assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist) - assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital) - - assert.NoError(t, g.RemoveRoute(Race_0.Name, "EMP", 2)) - assert.NotContains(t, g.MustPlanetByNumber(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)) -} diff --git a/internal/model/game/science.go b/internal/model/game/science.go index 884ae92..4ebc63b 100644 --- a/internal/model/game/science.go +++ b/internal/model/game/science.go @@ -1,10 +1,7 @@ package game import ( - "slices" - "github.com/google/uuid" - e "github.com/iliadenisov/galaxy/internal/error" ) type Science struct { @@ -24,84 +21,3 @@ type ScienceReport struct { Shields float64 `json:"shields"` Cargo float64 `json:"cargo"` } - -func (g Game) Sciences(raceName string) ([]Science, error) { - ri, err := g.raceIndex(raceName) - if err != nil { - return nil, err - } - return g.sciencesInternal(ri), nil -} - -func (g Game) sciencesInternal(ri int) []Science { - return g.Race[ri].Sciences -} - -func (g Game) DeleteScience(raceName, typeName string) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - return g.deleteScienceInternal(ri, typeName) -} - -func (g Game) deleteScienceInternal(ri int, name string) error { - sc := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == name }) - if sc < 0 { - return e.NewEntityNotExistsError("science %w", name) - } - if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { - return p.Production.Type == ResearchScience && - p.Production.SubjectID != nil && - *p.Production.SubjectID == g.Race[ri].Sciences[sc].ID - }); pl >= 0 { - return e.NewDeleteSciencePlanetProductionError(g.Map.Planet[pl].Name) - } - g.Race[ri].Sciences = append(g.Race[ri].Sciences[:sc], g.Race[ri].Sciences[sc+1:]...) - return nil -} - -func (g Game) CreateScience(raceName, typeName string, d, w, s, c float64) error { - ri, err := g.raceIndex(raceName) - if err != nil { - return err - } - return g.createScienceInternal(ri, typeName, d, w, s, c) -} - -func (g Game) createScienceInternal(ri int, name string, d, w, s, c float64) error { - n, ok := validateTypeName(name) - if !ok { - return e.NewEntityTypeNameValidationError("%q", n) - } - if sc := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == n }); sc >= 0 { - return e.NewEntityTypeNameDuplicateError("science %w", g.Race[ri].Sciences[sc].Name) - } - if d < 0 { - return e.NewDriveValueError(d) - } - if w < 0 { - return e.NewWeaponsValueError(w) - } - if s < 0 { - return e.NewShieldsValueError(s) - } - if c < 0 { - return e.NewCargoValueError(c) - } - sum := d + w + s + c - if sum != 1 { - return e.NewScienceSumValuesError("D=%f W=%f S=%f C=%f sum=%f", d, w, s, c, sum) - } - g.Race[ri].Sciences = append(g.Race[ri].Sciences, Science{ - ID: uuid.New(), - ScienceReport: ScienceReport{ - Name: n, - Drive: d, - Weapons: w, - Shields: s, - Cargo: c, - }, - }) - return nil -} diff --git a/internal/model/game/ship.go b/internal/model/game/ship.go index bd9b76b..860c249 100644 --- a/internal/model/game/ship.go +++ b/internal/model/game/ship.go @@ -1,11 +1,7 @@ package game import ( - "fmt" - "slices" - "github.com/google/uuid" - e "github.com/iliadenisov/galaxy/internal/error" ) type ShipTypeReport struct { @@ -80,183 +76,3 @@ func (st ShipType) ProductionCost() (mat float64, pop float64) { pop = mat * 10 return } - -func (g Game) mustShipType(id uuid.UUID) *ShipType { - for ri := range g.Race { - if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == id }); st >= 0 { - return &g.Race[ri].ShipTypes[st] - } - } - panic(fmt.Sprintf("mustShipType: ShipType not found: %v", id)) -} - -func (g Game) ShipTypes(raceName string) ([]ShipType, error) { - ri, err := g.raceIndex(raceName) - if err != nil { - return nil, err - } - return g.shipTypesInternal(ri), nil -} - -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) 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) 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) 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 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:]...) - -// return nil -// } - -func checkShipTypeValues(d, w, s, c float64, a int) error { - if !checkShipTypeValueDWSC(d) { - return e.NewDriveValueError(d) - } - if !checkShipTypeValueDWSC(w) { - return e.NewWeaponsValueError(w) - } - if !checkShipTypeValueDWSC(s) { - return e.NewShieldsValueError(s) - } - if !checkShipTypeValueDWSC(c) { - return e.NewCargoValueError(s) - } - if a < 0 { - return e.NewShipTypeArmamentValueError(a) - } - if (w == 0 && a > 0) || (a == 0 && w > 0) { - return e.NewShipTypeArmamentAndWeaponsValueError("A=%d W=%.0f", a, w) - } - if d == 0 && w == 0 && s == 0 && c == 0 && a == 0 { - return e.NewShipTypeShipTypeZeroValuesError() - } - return nil -} - -func checkShipTypeValueDWSC(v float64) bool { - return v == 0 || v >= 1 -} - -func ShipClass(g *Game, ri int, classID uuid.UUID) (ShipType, bool) { - if len(g.Race) < ri+1 { - panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race))) - } - sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == classID }) - if sti < 0 { - return ShipType{}, false - } - return g.Race[ri].ShipTypes[sti], true -} - -func ShipClassIndex(g *Game, ri int, classID uuid.UUID) (int, bool) { - if len(g.Race) < ri+1 { - panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race))) - } - sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == classID }) - return sti, sti >= 0 -}