diff --git a/internal/controller/cache.go b/internal/controller/cache.go index 95cc363..2a1a066 100644 --- a/internal/controller/cache.go +++ b/internal/controller/cache.go @@ -43,7 +43,7 @@ func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType { func (c *Cache) RaceIndex(ID uuid.UUID) int { if c.cacheRaceIndexByID == nil { c.cacheRaceIndexByID = make(map[uuid.UUID]int) - for i := range c.g.Race { + for i := range c.listRaceIdx() { c.cacheRaceIndexByID[c.g.Race[i].ID] = i } } diff --git a/internal/controller/command.go b/internal/controller/command.go new file mode 100644 index 0000000..e3ffdc4 --- /dev/null +++ b/internal/controller/command.go @@ -0,0 +1,262 @@ +package controller + +import ( + "strings" + + e "github.com/iliadenisov/galaxy/internal/error" + "github.com/iliadenisov/galaxy/internal/model/game" +) + +func (c Controller) QuitGame(actor string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + c.Cache.g.Race[ri].TTL = 3 + return nil +} + +func (c Controller) GiveVotes(actor, acceptor string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + rec, err := c.Cache.validRace(acceptor) + if err != nil { + return err + } + c.Cache.g.Race[ri].VoteFor = c.Cache.g.Race[rec].ID + return nil +} + +// FIXME: remove, not a command +func (c Controller) Relation(actor, acceptor string) (game.Relation, error) { + r1, err := c.Cache.validActor(actor) + if err != nil { + return game.Relation(""), err + } + r2, err := c.Cache.validRace(acceptor) + if err != nil { + return game.Relation(""), err + } + return c.Cache.Relation(r1, r2), nil +} + +func (c Controller) UpdateRelation(actor, acceptor string, rel game.Relation) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + var other int + if actor == acceptor { + other = ri + } else if other, err = c.Cache.validRace(acceptor); err != nil { + return err + } + if err != nil { + return err + } + return c.Cache.UpdateRelation(ri, other, rel) +} + +func (c *Controller) JoinShipGroupToFleet(actor, fleetName string, group, count uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.JoinShipGroupToFleet(ri, fleetName, group, count) +} + +func (c *Controller) JoinFleets(actor, fleetSourceName, fleetTargetName string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.JoinFleets(ri, fleetSourceName, fleetTargetName) +} + +func (c *Controller) SendFleet(actor, fleetName string, planetNumber uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + fi, ok := c.Cache.fleetIndex(ri, fleetName) + if !ok { + return e.NewEntityNotExistsError("fleet %q", fleetName) + } + return c.Cache.SendFleet(ri, fi, planetNumber) +} + +func (c *Controller) RenamePlanet(actor string, planetNumber int, typeName string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.RenamePlanet(ri, planetNumber, typeName) +} + +func (c *Controller) PlanetProduction(actor string, planetNumber int, prodType, subject string) error { + ri, err := c.Cache.validActor(actor) + 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 *Controller) SetRoute(actor, loadType string, origin, destination uint) error { + ri, err := c.Cache.validActor(actor) + 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 *Controller) RemoveRoute(actor, loadType string, origin uint) error { + ri, err := c.Cache.validActor(actor) + 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 *Controller) CreateScience(actor, typeName string, drive, weapons, shields, cargo float64) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.CreateScience(ri, typeName, drive, weapons, shields, cargo) +} + +func (c *Controller) DeleteScience(actor, typeName string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.DeleteScience(ri, typeName) +} + +func (c *Controller) CreateShipType(actor, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.CreateShipType(ri, typeName, drive, ammo, weapons, shileds, cargo) +} + +func (c *Controller) MergeShipType(actor, name, targetName string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.MergeShipType(ri, name, targetName) +} + +func (c *Controller) DeleteShipType(actor, typeName string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.DeleteShipType(ri, typeName) +} + +func (c *Controller) SendGroup(actor string, groupIndex, planetNumber, quantity uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.SendGroup(ri, groupIndex, planetNumber, quantity) +} + +func (c *Controller) UpgradeGroup(actor string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.UpgradeGroup(ri, groupIndex, techInput, limitShips, limitLevel) +} + +func (c *Controller) JoinEqualGroups(actor string) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + c.Cache.JoinEqualGroups(ri) + return nil +} + +func (c *Controller) BreakGroup(actor string, groupIndex, quantity uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.BreakGroup(ri, groupIndex, quantity) +} + +func (c *Controller) DisassembleGroup(actor string, groupIndex, quantity uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.DisassembleGroup(ri, groupIndex, quantity) +} + +func (c *Controller) LoadCargo(actor string, groupIndex uint, cargoType string, ships uint, quantity float64) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + ct, ok := game.CargoTypeSet[cargoType] + if !ok { + return e.NewCargoTypeInvalidError(cargoType) + } + return c.Cache.LoadCargo(ri, groupIndex, ct, ships, quantity) +} + +func (c *Controller) UnloadCargo(actor string, groupIndex uint, ships uint, quantity float64) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + return c.Cache.UnloadCargo(ri, groupIndex, ships, quantity) +} + +func (c *Controller) TransferGroup(actor, acceptor string, groupIndex, quantity uint) error { + ri, err := c.Cache.validActor(actor) + if err != nil { + return err + } + riAccept, err := c.Cache.validRace(acceptor) + if err != nil { + return err + } + return c.Cache.TransferGroup(ri, riAccept, groupIndex, quantity) +} diff --git a/internal/controller/controller_export_test.go b/internal/controller/controller_export_test.go index 4ffc8a2..383cdb6 100644 --- a/internal/controller/controller_export_test.go +++ b/internal/controller/controller_export_test.go @@ -18,7 +18,7 @@ func (c *Cache) AddRace(n string) (int, uuid.UUID) { Relations: make([]game.RaceRelation, len(c.g.Race)), } c.g.Race = append(c.g.Race, *r) - for i := range c.g.Race { + for i := range c.listRaceIdx() { if c.g.Race[i].ID != id { c.g.Race[i].Relations = append(c.g.Race[i].Relations, game.RaceRelation{RaceID: id, Relation: game.RelationPeace}) continue diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index ae58dd0..f5b7937 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -16,6 +16,7 @@ var ( ID: Race_0_ID, VoteFor: Race_0_ID, Name: "Race_0", + TTL: 10, Tech: map[game.Tech]game.Float{ game.TechDrive: 1.1, game.TechWeapons: 1.2, @@ -28,6 +29,7 @@ var ( ID: Race_1_ID, VoteFor: Race_1_ID, Name: "Race_1", + TTL: 10, Tech: map[game.Tech]game.Float{ game.TechDrive: 2.1, game.TechWeapons: 2.2, diff --git a/internal/controller/fleet.go b/internal/controller/fleet.go index 75f249c..fda1a75 100644 --- a/internal/controller/fleet.go +++ b/internal/controller/fleet.go @@ -100,14 +100,6 @@ func (c *Cache) FleetSpeedAndMass(fi int) (float64, float64) { return speed, mass } -func (c *Controller) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.JoinShipGroupToFleet(ri, fleetName, group, count) -} - func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quantity uint) (err error) { c.validateRaceIndex(ri) name, ok := util.ValidateTypeName(fleetName) @@ -119,8 +111,8 @@ func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quant return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).State() != game.StateInOrbit { - return e.NewShipsBusyError() + if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit { + return e.NewShipsBusyError("state: %s", state) } if c.ShipGroup(sgi).Number < quantity { @@ -178,14 +170,6 @@ func (c *Cache) JoinShipGroupToFleet(ri int, fleetName string, groupIndex, quant return nil } -func (c *Controller) JoinFleets(raceName, fleetSourceName, fleetTargetName string) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.JoinFleets(ri, fleetSourceName, fleetTargetName) -} - func (c *Cache) JoinFleets(ri int, fleetSourceName, fleetTargetName string) (err error) { fiSource, ok := c.fleetIndex(ri, fleetSourceName) if !ok { @@ -242,19 +226,21 @@ 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) } } - c.g.Fleets = append(c.g.Fleets[:fi], c.g.Fleets[fi+1:]...) - c.cacheFleetIndexByID = nil + c.unsafeDeleteFleet(fi) return nil } +func (c *Cache) unsafeDeleteFleet(fi int) { + c.validateFleetIndex(fi) + c.g.Fleets = append(c.g.Fleets[:fi], c.g.Fleets[fi+1:]...) + c.invalidateFleetCache() +} + // Internal funcs func (c *Cache) FleetIndex(ID uuid.UUID) (int, bool) { 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 - } + c.cacheFleetIndex() } if v, ok := c.cacheFleetIndexByID[ID]; ok { return v, true @@ -263,6 +249,17 @@ func (c *Cache) FleetIndex(ID uuid.UUID) (int, bool) { } } +func (c *Cache) cacheFleetIndex() { + if c.cacheFleetIndexByID != nil { + clear(c.cacheFleetIndexByID) + } else { + c.cacheFleetIndexByID = make(map[uuid.UUID]int) + } + for i := range c.g.Fleets { + c.cacheFleetIndexByID[c.g.Fleets[i].ID] = i + } +} + func (c *Cache) MustFleetIndex(ID uuid.UUID) int { if v, ok := c.FleetIndex(ID); ok { return v diff --git a/internal/controller/fleet_send.go b/internal/controller/fleet_send.go index 3238024..aa4ecb0 100644 --- a/internal/controller/fleet_send.go +++ b/internal/controller/fleet_send.go @@ -6,25 +6,13 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -func (c *Controller) SendFleet(raceName, fleetName string, planetNumber uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - fi, ok := c.Cache.fleetIndex(ri, fleetName) - if !ok { - return e.NewEntityNotExistsError("fleet %q", fleetName) - } - return c.Cache.SendFleet(ri, fi, planetNumber) -} - func (c *Cache) SendFleet(ri, fi int, planetNumber uint) error { c.validateRaceIndex(ri) c.validateFleetIndex(fi) fleetState := c.FleetState(c.g.Fleets[fi].ID) sourcePlanet, ok := fleetState.AtPlanet() if !ok || game.StateInOrbit != fleetState.State && game.StateLaunched != fleetState.State { - return e.NewShipsBusyError() + return e.NewShipsBusyError("state: %s", fleetState.State) } p1, ok := c.Planet(sourcePlanet) diff --git a/internal/controller/generate_game.go b/internal/controller/generate_game.go index e933c61..aed531f 100644 --- a/internal/controller/generate_game.go +++ b/internal/controller/generate_game.go @@ -118,6 +118,10 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) { gameMap.Planet[i].Number, gameMap.Planet[j].Number = gameMap.Planet[j].Number, gameMap.Planet[i].Number }) + for i := range gameMap.Planet { + g.Votes = g.Votes.Add(gameMap.Planet[i].Votes()) + } + g.Map = *gameMap return g, nil diff --git a/internal/controller/generate_turn.go b/internal/controller/generate_turn.go index 0109847..0aad426 100644 --- a/internal/controller/generate_turn.go +++ b/internal/controller/generate_turn.go @@ -13,6 +13,9 @@ func MakeTurn(c *Controller, r Repo) error { // Next turn c.Cache.g.Turn += 1 + // 00. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода + c.Cache.TurnWipeExtinctRaces() + // 01. Корабли, где это возможно, объединяются в группы. c.Cache.TurnMergeEqualShipGroups() @@ -118,6 +121,13 @@ func MakeTurn(c *Controller, r Repo) error { } } + for i := range c.Cache.g.Race { + if c.Cache.g.Race[i].Extinct { + continue + } + c.Cache.g.Race[i].TTL -= 1 + } + // [ ] monitor memory consumption at this point? return nil } diff --git a/internal/controller/planet.go b/internal/controller/planet.go index 59aefd5..a083a58 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -4,7 +4,6 @@ import ( "fmt" "iter" "slices" - "strings" "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" @@ -12,14 +11,6 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -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 := util.ValidateTypeName(name) if !ok { @@ -39,35 +30,6 @@ func (c *Cache) RenamePlanet(ri int, number int, name string) error { 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 { diff --git a/internal/controller/race.go b/internal/controller/race.go index 371fb11..cfc34fd 100644 --- a/internal/controller/race.go +++ b/internal/controller/race.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "iter" "slices" e "github.com/iliadenisov/galaxy/internal/error" @@ -9,40 +10,11 @@ import ( "github.com/iliadenisov/galaxy/internal/model/game" ) -func (c Controller) Relation(from, to string) (game.Relation, error) { - r1, err := c.Cache.raceIndex(from) - if err != nil { - return game.Relation(""), err - } - r2, err := c.Cache.raceIndex(to) - if err != nil { - return game.Relation(""), err - } - return c.Cache.Relation(r1, r2), nil -} - -func (c Controller) UpdateRelation(race, opponent string, rel game.Relation) error { - ri, err := c.Cache.raceIndex(race) - if err != nil { - return err - } - var other int - if race == opponent { - other = ri - } else if other, err = c.Cache.raceIndex(opponent); err != nil { - return err - } - if err != nil { - return err - } - return c.Cache.UpdateRelation(ri, other, rel) -} - func (c *Cache) Relation(r1, r2 int) game.Relation { if c.cacheRelation == nil { c.cacheRelation = make(map[int]map[int]game.Relation) - for r1 := range c.g.Race { - for r2 := range c.g.Race { + for r1 := range c.listRaceActingIdx() { + for r2 := range c.listRaceActingIdx() { if r1 == r2 { continue } @@ -78,19 +50,6 @@ func (c *Cache) updateRelationCache(r1, r2 int, rel game.Relation) { c.cacheRelation[r1][r2] = rel } -func (c *Cache) GiveVotes(race, recipient string) error { - ri, err := c.raceIndex(race) - if err != nil { - return err - } - rec, err := c.raceIndex(recipient) - if err != nil { - return err - } - c.g.Race[ri].VoteFor = c.g.Race[rec].ID - return nil -} - func (c *Cache) Voted(ri int) int { c.validateRaceIndex(ri) return c.RaceIndex(c.g.Race[ri].VoteFor) @@ -123,6 +82,26 @@ func (c *Cache) validateRaceIndex(i int) { } } +func (c *Cache) validActor(name string) (int, error) { + i, err := c.validRace(name) + if err != nil { + return -1, err + } + c.g.Race[i].TTL = 10 + return i, nil +} + +func (c *Cache) validRace(name string) (int, error) { + i, err := c.raceIndex(name) + if err != nil { + return -1, err + } + if c.g.Race[i].Extinct { + return -1, e.NewRaceExinctError(name) + } + return i, nil +} + func (c *Cache) raceIndex(name string) (int, error) { i := slices.IndexFunc(c.g.Race, func(r game.Race) bool { return r.Name == name }) if i < 0 { @@ -135,3 +114,56 @@ 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) } + +func (c *Cache) TurnWipeExtinctRaces() { + for i := range c.listRaceActingIdx() { + if c.g.Race[i].TTL == 0 { + c.wipeRace(i) + } + } +} + +func (c *Cache) wipeRace(ri int) { + c.validateRaceIndex(ri) + r := &c.g.Race[ri] + c.g.ShipGroups = slices.DeleteFunc(c.g.ShipGroups, func(v game.ShipGroup) bool { return v.OwnerID == r.ID }) + c.g.Fleets = slices.DeleteFunc(c.g.Fleets, func(v game.Fleet) bool { return v.OwnerID == r.ID }) + clear(r.ShipTypes) + clear(r.Sciences) + for i := range c.g.Map.Planet { + p := &c.g.Map.Planet[i] + if p.Owner != nil && *p.Owner != r.ID { + continue + } + p.Wipe() + } + r.Votes = 0 + r.VoteFor = r.ID + r.Extinct = true + r.TTL = 0 + c.invalidateFleetCache() + c.invalidateShipGroupCache() +} + +func (c *Cache) listRaceActingIdx() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range c.listRaceIdx() { + if c.g.Race[i].Extinct { + continue + } + if !yield(i) { + return + } + } + } +} + +func (c *Cache) listRaceIdx() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range c.g.Race { + if !yield(i) { + return + } + } + } +} diff --git a/internal/controller/race_test.go b/internal/controller/race_test.go index a1d7814..402b38d 100644 --- a/internal/controller/race_test.go +++ b/internal/controller/race_test.go @@ -8,15 +8,15 @@ import ( ) func TestGiveVotes(t *testing.T) { - c, _ := newCache() + c, g := newCache() assert.Equal(t, c.Voted(Race_0_idx), Race_0_idx) assert.Equal(t, c.Voted(Race_1_idx), Race_1_idx) - assert.NoError(t, c.GiveVotes(Race_0.Name, Race_1.Name)) + assert.NoError(t, g.GiveVotes(Race_0.Name, Race_1.Name)) assert.Equal(t, Race_1_idx, c.Voted(Race_0_idx)) assert.Equal(t, Race_1_idx, c.Voted(Race_1_idx)) - assert.ErrorContains(t, c.GiveVotes("UnknownRace", Race_1.Name), e.GenericErrorText(e.ErrInputUnknownRace)) - assert.ErrorContains(t, c.GiveVotes(Race_0.Name, "UnknownRace"), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, g.GiveVotes("UnknownRace", Race_1.Name), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, g.GiveVotes(Race_0.Name, "UnknownRace"), e.GenericErrorText(e.ErrInputUnknownRace)) } diff --git a/internal/controller/report.go b/internal/controller/report.go index 233c159..7cb00dd 100644 --- a/internal/controller/report.go +++ b/internal/controller/report.go @@ -15,7 +15,7 @@ import ( func (c *Cache) Report(t uint, battles []*mr.BattleReport, bombings []*mr.Bombing) iter.Seq[*mr.Report] { report := c.InitReport(t) return func(yield func(*mr.Report) bool) { - for i := range c.g.Race { + for i := range c.listRaceActingIdx() { c.ReportRace(i, report, battles, bombings) if !yield(report) { break @@ -55,12 +55,13 @@ func (c *Cache) InitReport(t uint) *mr.Report { planets[ri] = planets[ri] + 1 } - for ri := range c.g.Race { + for ri := range c.listRaceIdx() { r := &c.g.Race[ri] rr := &report.Player[ri] rr.ID = r.ID rr.Name = r.Name + rr.Extinct = r.Extinct rr.Drive = mr.F(r.TechLevel(game.TechDrive)) rr.Weapons = mr.F(r.TechLevel(game.TechWeapons)) rr.Shields = mr.F(r.TechLevel(game.TechShields)) diff --git a/internal/controller/route.go b/internal/controller/route.go index fa2efc5..47fd613 100644 --- a/internal/controller/route.go +++ b/internal/controller/route.go @@ -13,18 +13,6 @@ import ( "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) @@ -48,18 +36,6 @@ func (c *Cache) SetRoute(ri int, rt game.RouteType, origin, destination uint) er 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) @@ -269,23 +245,6 @@ func (c *Cache) selectColUnloadGroup(groups []int) (result iter.Seq[int]) { return } -func MaxOrRandomLoadId(IDtoLoad map[int]float64) int { - if len(IDtoLoad) < 2 { - panic("IDtoLoad must contain at least 2 keys") - } - IDs := slices.Collect(maps.Keys(IDtoLoad)) - slices.SortFunc(IDs, func(id1, id2 int) int { return cmp.Compare(IDtoLoad[id2], IDtoLoad[id1]) }) - - // no single winner with highest load - if IDtoLoad[IDs[0]] == IDtoLoad[IDs[1]] { - // remove IDs which load less than maximum - IDs = slices.DeleteFunc(IDs, func(v int) bool { return IDtoLoad[v] < IDtoLoad[IDs[0]] }) - // IDs[0] will have random index - rand.Shuffle(len(IDs), func(i, j int) { IDs[i], IDs[j] = IDs[j], IDs[i] }) - } - return IDs[0] -} - func (c *Cache) listRoutedUnloadShipGroupIds(pn uint, routeType game.RouteType) iter.Seq[int] { return func(yield func(int) bool) { yielded := make(map[int]bool) @@ -311,3 +270,20 @@ func (c *Cache) listRoutedUnloadShipGroupIds(pn uint, routeType game.RouteType) } } } + +func MaxOrRandomLoadId(IDtoLoad map[int]float64) int { + if len(IDtoLoad) < 2 { + panic("IDtoLoad must contain at least 2 keys") + } + IDs := slices.Collect(maps.Keys(IDtoLoad)) + slices.SortFunc(IDs, func(id1, id2 int) int { return cmp.Compare(IDtoLoad[id2], IDtoLoad[id1]) }) + + // no single winner with highest load + if IDtoLoad[IDs[0]] == IDtoLoad[IDs[1]] { + // remove IDs which load less than maximum + IDs = slices.DeleteFunc(IDs, func(v int) bool { return IDtoLoad[v] < IDtoLoad[IDs[0]] }) + // IDs[0] will have random index + rand.Shuffle(len(IDs), func(i, j int) { IDs[i], IDs[j] = IDs[j], IDs[i] }) + } + return IDs[0] +} diff --git a/internal/controller/route_test.go b/internal/controller/route_test.go index cf9f3c2..37aaa34 100644 --- a/internal/controller/route_test.go +++ b/internal/controller/route_test.go @@ -115,7 +115,7 @@ func TestListRoutedSendGroupIds(t *testing.T) { // Foreign group -> idx 1 assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) - assert.NoError(t, g.GiveawayGroup(Race_0.Name, Race_1.Name, 5, 0)) + assert.NoError(t, g.TransferGroup(Race_0.Name, Race_1.Name, 5, 0)) // 5: idx = 4 / Part of the Fleet assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) diff --git a/internal/controller/science.go b/internal/controller/science.go index 7f6bb55..21dce81 100644 --- a/internal/controller/science.go +++ b/internal/controller/science.go @@ -10,14 +10,6 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -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 := util.ValidateTypeName(name) @@ -55,14 +47,6 @@ func (c *Cache) CreateScience(ri int, name string, drive, weapons, shileds, carg 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 }) diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index 2a8d1ed..155d1f3 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -11,14 +11,6 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -func (c *Controller) CreateShipType(raceName, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.CreateShipType(ri, typeName, drive, ammo, weapons, shileds, cargo) -} - func (c *Cache) 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 { @@ -45,14 +37,6 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, return nil } -func (c *Controller) MergeShipType(race, name, targetName string) error { - ri, err := c.Cache.raceIndex(race) - if err != nil { - return err - } - return c.Cache.MergeShipType(ri, name, targetName) -} - func (c *Cache) MergeShipType(ri int, name, targetName string) error { c.validateRaceIndex(ri) st, sti, ok := c.ShipClass(ri, name) @@ -101,14 +85,6 @@ func (c *Cache) MergeShipType(ri int, name, targetName string) error { return nil } -func (c *Controller) DeleteShipType(raceName, typeName string) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.DeleteShipType(ri, typeName) -} - func (c *Cache) DeleteShipType(ri int, name string) error { c.validateRaceIndex(ri) st, i, ok := c.ShipClass(ri, name) @@ -136,15 +112,6 @@ func (c *Cache) DeleteShipType(ri int, name string) error { return nil } -// ShipTypes used for tests only -func (c *Controller) ShipTypes(race string) ([]*game.ShipType, error) { - ri, err := c.Cache.raceIndex(race) - if err != nil { - return nil, err - } - return c.Cache.ShipTypes(ri), nil -} - // ShipTypes used for tests only func (c *Cache) ShipTypes(ri int) []*game.ShipType { c.validateRaceIndex(ri) diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index 4ee4d36..cece2e8 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -118,29 +118,39 @@ func (c *Cache) DeleteShipGroup(i int) { } func (c *Cache) DeleteKilledShipGroups() { + keepFleet := make(map[uuid.UUID]bool, len(c.g.Fleets)) for i := len(c.g.ShipGroups) - 1; i >= 0; i-- { + if c.g.ShipGroups[i].FleetID != nil { + id := *c.g.ShipGroups[i].FleetID + keepFleet[id] = keepFleet[id] || c.g.ShipGroups[i].Number > 0 + } if c.g.ShipGroups[i].Number == 0 { c.unsafeDeleteShipGroup(i) } } - // TODO: delete empty fleets -} - -func (c *Controller) JoinEqualGroups(raceName string) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err + for id, keep := range keepFleet { + if keep { + continue + } + c.unsafeDeleteFleet(c.MustFleetIndex(id)) } - c.Cache.JoinEqualGroups(ri) - return nil } func (c *Cache) TurnMergeEqualShipGroups() { - for i := range c.g.Race { + for i := range c.listRaceActingIdx() { + c.transferPendingGroups(i) c.JoinEqualGroups(i) } } +func (c *Cache) transferPendingGroups(ri int) { + for sg := range c.listShipGroups(ri) { + if sg.State() == game.StateTransfer { + sg.StateTransfer = false + } + } +} + func (c *Cache) JoinEqualGroups(ri int) { c.validateRaceIndex(ri) raceGroups := make([]game.ShipGroup, 0) @@ -183,30 +193,14 @@ func (c *Cache) JoinEqualGroups(ri int) { } } -func (c *Controller) BreakGroup(raceName string, groupIndex, quantity uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.BreakGroup(ri, groupIndex, quantity) -} - -func (c *Controller) DisassembleGroup(raceName string, groupIndex, quantity uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.DisassembleGroup(ri, groupIndex, quantity) -} - func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error { sgi, ok := c.raceShipGroupIndex(ri, groupIndex) if !ok { return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).State() != game.StateInOrbit { - return e.NewShipsBusyError() + if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit { + return e.NewShipsBusyError("state: %s", state) } if c.ShipGroup(sgi).Number < quantity { @@ -254,18 +248,6 @@ func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error { return nil } -func (c *Controller) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - ct, ok := game.CargoTypeSet[cargoType] - if !ok { - return e.NewCargoTypeInvalidError(cargoType) - } - return c.Cache.LoadCargo(ri, groupIndex, ct, ships, quantity) -} - // Корабль может нести только один тип груза одновременно. // Возможные типы груза - это колонисты, сырье и промышленность. // Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется. @@ -277,8 +259,8 @@ func (c *Cache) LoadCargo(ri int, groupIndex uint, ct game.CargoType, ships uint if !ok { return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).State() != game.StateInOrbit { - return e.NewShipsBusyError() + if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit { + return e.NewShipsBusyError("state: %s", state) } p, ok := c.Planet(c.ShipGroup(sgi).Destination) if !ok { @@ -337,14 +319,6 @@ func (c *Cache) LoadCargo(ri int, groupIndex uint, ct game.CargoType, ships uint return nil } -func (c *Controller) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.UnloadCargo(ri, groupIndex, ships, quantity) -} - // Промышленность и Сырье могут быть выгружены на любой планете. // Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты. func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float64) error { @@ -356,8 +330,8 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6 if !ok { return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).State() != game.StateInOrbit { - return e.NewShipsBusyError() + if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit { + return e.NewShipsBusyError("state: %s", state) } st := c.ShipGroupShipClass(sgi) if st.Cargo < 1 { @@ -429,19 +403,7 @@ func (c *Cache) unsafeUnloadCargo(sgi int, q float64) { p.UnpackCapital() } -func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - riAccept, err := c.Cache.raceIndex(raceAcceptor) - if err != nil { - return err - } - return c.Cache.GiveawayGroup(ri, riAccept, groupIndex, quantity) -} - -func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err error) { +func (c *Cache) TransferGroup(ri, riAccept int, groupIndex, quantity uint) (err error) { if ri == riAccept { return e.NewSameRaceError(c.g.Race[riAccept].Name) } @@ -449,8 +411,13 @@ func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err if !ok { return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).Number < quantity { - return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity) + sg := c.ShipGroup(sgi) + state := sg.State() + if state == game.StateTransfer { + return e.NewShipsBusyError("state: %s", state) + } + if sg.Number < quantity { + return e.NewBeakGroupNumberNotEnoughError("%d<%d", sg.Number, quantity) } st := c.ShipGroupShipClass(sgi) @@ -474,18 +441,22 @@ func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err stAcc = len(c.g.Race[riAccept].ShipTypes) - 1 } - 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) + newGroup := *(sg) + newGroup.TypeID = c.g.Race[riAccept].ShipTypes[stAcc].ID + newGroup.Tech = maps.Clone(sg.Tech) + if state == game.StateLaunched { + newGroup.StateTransfer = true + } - if quantity == 0 || quantity == c.ShipGroup(sgi).Number { + if quantity == 0 || quantity == sg.Number { c.unsafeDeleteShipGroup(sgi) } else { - c.ShipGroup(sgi).Number -= quantity + newGroup.Number = quantity + sg.Number -= quantity } + c.appendShipGroup(riAccept, &newGroup) + return nil } @@ -502,7 +473,7 @@ func (c *Cache) BreakGroup(ri int, groupIndex, quantity uint) error { return e.NewEntityNotExistsError("group #%d", groupIndex) } - if c.ShipGroup(sgi).State() != game.StateInOrbit { + if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit { return e.NewShipsBusyError() } diff --git a/internal/controller/ship_group_send.go b/internal/controller/ship_group_send.go index 3386579..2e6f327 100644 --- a/internal/controller/ship_group_send.go +++ b/internal/controller/ship_group_send.go @@ -6,14 +6,6 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -func (c *Controller) SendGroup(raceName string, groupIndex, planetNumber, quantity uint) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.SendGroup(ri, groupIndex, planetNumber, quantity) -} - func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error { c.validateRaceIndex(ri) @@ -29,7 +21,7 @@ func (c *Cache) SendGroup(ri int, groupIndex, planetNumber, quantity uint) error sourcePlanet, ok := c.ShipGroup(sgi).AtPlanet() if !ok { - return e.NewShipsBusyError() + return e.NewShipsBusyError("state: %s", c.ShipGroup(sgi).State()) } if c.ShipGroup(sgi).Number < quantity { diff --git a/internal/controller/ship_group_test.go b/internal/controller/ship_group_test.go index 837a24e..c71ed9f 100644 --- a/internal/controller/ship_group_test.go +++ b/internal/controller/ship_group_test.go @@ -1,6 +1,7 @@ package controller_test import ( + "fmt" "slices" "testing" @@ -159,7 +160,7 @@ func TestBreakGroup(t *testing.T) { assert.Nil(t, c.ShipGroup(3).FleetID) } -func TestGiveawayGroup(t *testing.T) { +func TestTransferGroup(t *testing.T) { c, g := newCache() assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0) assert.NoError(t, c.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1) @@ -178,25 +179,25 @@ func TestGiveawayGroup(t *testing.T) { assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1) assert.ErrorContains(t, - g.GiveawayGroup("UnknownRace", Race_1.Name, 2, 0), + g.TransferGroup("UnknownRace", Race_1.Name, 2, 0), e.GenericErrorText(e.ErrInputUnknownRace)) assert.ErrorContains(t, - g.GiveawayGroup(Race_0.Name, "UnknownRace", 2, 0), + g.TransferGroup(Race_0.Name, "UnknownRace", 2, 0), e.GenericErrorText(e.ErrInputUnknownRace)) assert.ErrorContains(t, - g.GiveawayGroup(Race_0.Name, Race_0.Name, 2, 0), + g.TransferGroup(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), + g.TransferGroup(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), + g.TransferGroup(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), + g.TransferGroup(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.NoError(t, g.TransferGroup(Race_0.Name, Race_1.Name, 2, 11)) // group #2 (3) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 2) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 2) @@ -216,14 +217,31 @@ func TestGiveawayGroup(t *testing.T) { assert.Equal(t, c.ShipGroup(2).Destination, c.ShipGroup(3).Destination) assert.Equal(t, c.ShipGroup(2).StateInSpace, c.ShipGroup(3).StateInSpace) assert.Equal(t, c.ShipGroup(2).StateUpgrade, c.ShipGroup(3).StateUpgrade) + assert.Equal(t, c.ShipGroup(2).StateTransfer, c.ShipGroup(3).StateTransfer) assert.Equal(t, c.ShipGroup(3).OwnerID, Race_1_ID) assert.Equal(t, c.ShipGroup(3).TypeID, c.MustShipClass(Race_1_idx, Race_0_Gunship).ID) assert.Equal(t, c.ShipGroup(3).Number, uint(11)) assert.Nil(t, c.ShipGroup(3).FleetID) - assert.NoError(t, g.GiveawayGroup(Race_1.Name, Race_0.Name, 2, 11)) + assert.NoError(t, g.TransferGroup(Race_1.Name, Race_0.Name, 2, 11)) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1) + + assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) + assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 4) + assert.Equal(t, game.StateInOrbit, c.ShipGroup(4).State()) + assert.NoError(t, g.SendGroup(Race_0.Name, c.ShipGroup(4).Index, R0_Planet_2_num, 0)) + assert.Equal(t, game.StateLaunched, c.ShipGroup(4).State()) + assert.Equal(t, c.ShipGroup(4).OwnerID, Race_0_ID) + + assert.NoError(t, g.TransferGroup(Race_0.Name, Race_1.Name, c.ShipGroup(4).Index, 0)) + assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3) + assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 2) + assert.Equal(t, game.StateTransfer, c.ShipGroup(4).State()) + assert.Equal(t, c.ShipGroup(4).OwnerID, Race_1_ID) + assert.ErrorContains(t, + g.TransferGroup(Race_1.Name, Race_0.Name, c.ShipGroup(4).Index, 0), + e.GenericErrorText(e.ErrShipsBusy)) } func TestLoadCargo(t *testing.T) { @@ -540,3 +558,7 @@ func TestShipGroupDestroyItem(t *testing.T) { assert.Equal(t, float64(c.ShipGroup(0).Number)*10, c.ShipGroup(0).Load.F()) } } + +func TestState(t *testing.T) { + assert.Equal(t, "In_Orbit", fmt.Sprintf("%s", game.StateInOrbit)) +} diff --git a/internal/controller/ship_group_upgrade.go b/internal/controller/ship_group_upgrade.go index 1fd6fc8..fd753ea 100644 --- a/internal/controller/ship_group_upgrade.go +++ b/internal/controller/ship_group_upgrade.go @@ -9,14 +9,6 @@ import ( "github.com/iliadenisov/galaxy/internal/model/game" ) -func (c *Controller) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { - ri, err := c.Cache.raceIndex(raceName) - if err != nil { - return err - } - return c.Cache.UpgradeGroup(ri, groupIndex, techInput, limitShips, limitLevel) -} - func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { c.validateRaceIndex(ri) sgi, ok := c.raceShipGroupIndex(ri, groupIndex) @@ -26,8 +18,8 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi st := c.ShipGroupShipClass(sgi) sg := c.ShipGroup(sgi) - if s := sg.State(); s != game.StateInOrbit { // && s != game.StateUpgrade - return e.NewShipsBusyError() + if state := sg.State(); state != game.StateInOrbit { + return e.NewShipsBusyError("state: %s", state) } p := c.MustPlanet(sg.Destination) diff --git a/internal/error/generic.go b/internal/error/generic.go index 10057ec..17fd8a1 100644 --- a/internal/error/generic.go +++ b/internal/error/generic.go @@ -27,6 +27,7 @@ const ( ErrUpgradeInsufficientResources = 5011 ErrSendShipHasNoDrives = 5012 ErrSendUnreachableDestination = 5013 + ErrRaceExinct = 5014 ) const ( @@ -141,7 +142,7 @@ func GenericErrorText(code int) string { case ErrBeakGroupNumberNotEnough: return "Not enough ships in the group to make a separate group" case ErrShipsBusy: - return "Ships currently not in orbit or free to use" + return "Ship(s) are'n free to use" case ErrShipsNotOnSamePlanet: return "Ships not on the same planet" case ErrGiveawayGroupShipsTypeNotEqual: @@ -166,6 +167,8 @@ func GenericErrorText(code int) string { return "One or more ships are not equipped with hyperdrive and cannot be moved" case ErrSendUnreachableDestination: return "Destination planet is too far for current Drive level" + case ErrRaceExinct: + return "Race is extinct" default: return fmt.Sprintf("Undescribed error with code %d", code) } diff --git a/internal/error/state.go b/internal/error/state.go index b5778ff..dba44d8 100644 --- a/internal/error/state.go +++ b/internal/error/state.go @@ -1,5 +1,9 @@ package error +func NewRaceExinctError(arg ...any) error { + return newGenericError(ErrRaceExinct, arg...) +} + func NewGameNotInitializedError(arg ...any) error { return newGenericError(ErrGameNotInitialized, arg...) } diff --git a/internal/game/cmd_ship_type_test.go b/internal/game/cmd_ship_type_test.go index 524eb31..4b304a2 100644 --- a/internal/game/cmd_ship_type_test.go +++ b/internal/game/cmd_ship_type_test.go @@ -22,8 +22,7 @@ func TestCreateShipType(t *testing.T) { assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) err = game.CreateShipType(p, race, " "+typeName+" ", 1, 0, 0, 0, 0) assert.NoError(t, err) - st, err := ctrl().ShipTypes(race) - assert.NoError(t, err) + st := ctrl().Cache.ShipTypes(1) assert.Len(t, st, 1) assert.Equal(t, st[0].Name, typeName) assert.Equal(t, st[0].Drive.F(), 1.) @@ -36,8 +35,7 @@ func TestCreateShipType(t *testing.T) { assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) err = game.DeleteShipType(p, race, typeName) assert.NoError(t, err) - st, err = ctrl().ShipTypes(race) - assert.NoError(t, err) + st = ctrl().Cache.ShipTypes(1) assert.Len(t, st, 0) }) } @@ -96,7 +94,7 @@ func TestCreateShipTypeValidation(t *testing.T) { func TestMergeShipType(t *testing.T) { race := "race_01" - c(t, func(p func(*controller.Param), ctl func() *controller.Controller) { + c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) { err := game.CreateShipType(p, race, "Drone", 1, 0, 0, 0, 0) assert.NoError(t, err) err = game.CreateShipType(p, race, "Spy", 1, 0, 0, 0, 0) @@ -109,8 +107,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 := ctl().ShipTypes(race) - assert.NoError(t, err) + st := ctrl().Cache.ShipTypes(1) assert.Len(t, st, 2) err = game.MergeShipType(p, race, "Drone", "Cruiser") assert.ErrorContains(t, err, e.GenericErrorText(e.ErrMergeShipTypeNotEqual)) diff --git a/internal/model/game/group.go b/internal/model/game/group.go index ffa5f45..ba94ab3 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -38,7 +38,7 @@ const ( StateLaunched ShipGroupState = "Launched" StateInSpace ShipGroupState = "In_Space" StateUpgrade ShipGroupState = "Upgrade" - StateTransfer ShipGroupState = "Transfer_Status" + StateTransfer ShipGroupState = "Transfer" ) func (sgs ShipGroupState) String() string { @@ -104,17 +104,18 @@ func (t Tech) String() string { } type ShipGroup struct { - Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered) - OwnerID uuid.UUID `json:"ownerId"` // Race link - TypeID uuid.UUID `json:"typeId"` // ShipType link - FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link - Number uint `json:"number"` // Number (quantity) ships of specific ShipType - CargoType *CargoType `json:"loadType,omitempty"` // - Load Float `json:"load"` // Cargo loaded - "Масса груза" - Tech TechSet `json:"tech"` // - Destination uint `json:"destination"` // TODO: TEST: Destination, Origin, Range - StateInSpace *InSpace `json:"stateInSpace,omitempty"` // - StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"` // + Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered) + OwnerID uuid.UUID `json:"ownerId"` // Race link + TypeID uuid.UUID `json:"typeId"` // ShipType link + FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link + Number uint `json:"number"` // Number (quantity) ships of specific ShipType + CargoType *CargoType `json:"loadType,omitempty"` // + Load Float `json:"load"` // Cargo loaded - "Масса груза" + Tech TechSet `json:"tech"` // + Destination uint `json:"destination"` // TODO: TEST: Destination, Origin, Range + StateInSpace *InSpace `json:"inSpace,omitempty"` // + StateUpgrade *InUpgrade `json:"upgrade,omitempty"` // + StateTransfer bool `json:"transfer,omitempty"` // } func (sg ShipGroup) TechLevel(t Tech) Float { @@ -134,6 +135,9 @@ func (sg ShipGroup) State() ShipGroupState { if sg.StateInSpace.Range > 0 { return StateInSpace } + if sg.StateTransfer { + return StateTransfer + } return StateLaunched case sg.StateUpgrade != nil && sg.StateInSpace == nil: return StateUpgrade diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index 3b5a6c2..ba04b78 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -38,9 +38,16 @@ func (p *Planet) Free() { p.Owner = nil p.Production = ProductionNone.AsType(uuid.Nil) p.Colonists = 0. + p.Population = 0. clear(p.Route) } +func (p *Planet) Wipe() { + p.Free() + p.Industry = 0 + p.Capital = 0 +} + func (p Planet) Owned() bool { return p.Owner != nil && *p.Owner != uuid.Nil } diff --git a/internal/model/game/race.go b/internal/model/game/race.go index 231433a..bef879a 100644 --- a/internal/model/game/race.go +++ b/internal/model/game/race.go @@ -5,6 +5,7 @@ import "github.com/google/uuid" type Race struct { ID uuid.UUID `json:"id"` Name string `json:"name"` + TTL uint `json:"ttl"` Extinct bool `json:"extinct"` Votes Float `json:"votes"` VoteFor uuid.UUID `json:"voteFor"` diff --git a/internal/model/report/report.go b/internal/model/report/report.go index 13a5686..a8acb65 100644 --- a/internal/model/report/report.go +++ b/internal/model/report/report.go @@ -63,6 +63,7 @@ type Player struct { Planets uint16 `json:"planets"` Relation string `json:"relation"` Votes Float `json:"votes"` + Extinct bool `json:"extinct"` } func (r Report) MarshalBinary() (data []byte, err error) {