package controller import ( "cmp" "iter" "math" "slices" 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) } } // TODO: NOT IN THIS FUNC: remove routes if planet became uninhabited func (c *Cache) EnrouteGroups() { for pi := range c.g.Map.Planet { if len(c.g.Map.Planet[pi].Route) == 0 { continue } groups := slices.Collect(c.listRouteEligibleGroupIds(c.g.Map.Planet[pi].Number)) if len(groups) == 0 { continue } sortGroups := func(g []int) { // sort groups by largest CargoCapacity slices.SortFunc(g, func(l, r int) int { return cmp.Or(cmp.Compare(c.ShipGroup(r).CargoCapacity(c.ShipGroupShipClass(r)), c.ShipGroup(l).CargoCapacity(c.ShipGroupShipClass(l))), cmp.Compare(l, r)) }) } reorderGroups := func(g []int) []int { g = slices.DeleteFunc(g, func(i int) bool { return c.ShipGroup(i).State() != game.StateInOrbit }) sortGroups(g) return g } sortGroups(groups) p := c.MustPlanet(c.g.Map.Planet[pi].Number) // COL -> CAP -> MAT -> EMPTY for _, rt := range []game.RouteType{game.RouteColonist, game.RouteCapital, game.RouteMaterial, game.RouteEmpty} { dest, ok := c.g.Map.Planet[pi].Route[rt] if !ok { continue } var res *float64 var ct game.CargoType switch rt { case game.RouteColonist: res = &p.Colonists ct = game.CargoColonist case game.RouteCapital: res = &p.Capital ct = game.CargoCapital case game.RouteMaterial: res = &p.Material ct = game.CargoMaterial default: for _, sgi := range groups { c.LaunchShips(c.ShipGroup(sgi), dest) } groups = reorderGroups(groups) continue } for res != nil && *res > 0 && len(groups) > 0 { sgi := groups[0] sg := c.ShipGroup(sgi) st := c.ShipGroupShipClass(sgi) ships := sg.Number sgCapacity := sg.CargoCapacity(st) toLoad := *res if toLoad > sgCapacity { toLoad = sgCapacity } else if maxShips := uint(math.Ceil(toLoad / (sgCapacity / float64(ships)))); maxShips < ships { newGroupIdx := c.breakGroupUnsafe(c.RaceIndex(sg.OwnerID), sgi, maxShips) sg = c.ShipGroup(newGroupIdx) } // decrease planet resource *res = *res - toLoad // load group sg.Load += toLoad sg.CargoType = &ct c.LaunchShips(sg, dest) groups = reorderGroups(groups) } } } } func (c *Cache) listRouteEligibleGroupIds(pn uint) iter.Seq[int] { return func(yield func(int) bool) { p := c.MustPlanet(pn) for i := range c.ShipGroupsIndex() { sg := c.ShipGroup(i) st := c.ShipGroupShipClass(i) if sg.OwnerID != p.Owner || // Planet must be owned by ships owner sg.FleetID != nil || // Ships must not be part of a Fleet sg.State() != game.StateInOrbit || // Ships must be only In_Orbit state st.CargoBlockMass() == 0 || // Ship Class must have Cargo bays sg.Load != 0 || // Ships must not be loaded for enrouting sg.Destination != p.Number { continue } if !yield(i) { return } } } }