tests: produce on planets, unload on routes

This commit is contained in:
Ilia Denisov
2026-01-23 00:28:23 +02:00
parent 9825e05c0e
commit 812e0d4afd
15 changed files with 522 additions and 103 deletions
+65 -41
View File
@@ -41,7 +41,7 @@ func (c *Cache) SetRoute(ri int, rt game.RouteType, origin, destination uint) er
}
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)
return e.NewSendUnreachableDestinationError("range=%.03f max=%.03f", rangeToDestination, c.g.Race[ri].FlightDistance())
}
c.SetPlanetRoute(rt, origin, destination)
@@ -97,7 +97,7 @@ func (c *Cache) EnrouteGroups() {
if len(c.g.Map.Planet[pi].Route) == 0 {
continue
}
groups := slices.Collect(c.listRouteEligibleGroupIds(c.g.Map.Planet[pi].Number))
groups := slices.Collect(c.listRoutedSendGroupIds(c.g.Map.Planet[pi].Number))
if len(groups) == 0 {
continue
}
@@ -171,7 +171,7 @@ func (c *Cache) EnrouteGroups() {
}
}
func (c *Cache) listRouteEligibleGroupIds(pn uint) iter.Seq[int] {
func (c *Cache) listRoutedSendGroupIds(pn uint) iter.Seq[int] {
return func(yield func(int) bool) {
p := c.MustPlanet(pn)
for i := range c.ShipGroupsIndex() {
@@ -194,63 +194,83 @@ func (c *Cache) listRouteEligibleGroupIds(pn uint) iter.Seq[int] {
// Невозможно лишь выгрузить колонистов на чужой планете.
func (c *Cache) TurnUnloadEnroutedGroups() {
for pi := range c.g.Map.Planet {
p := &c.g.Map.Planet[pi]
colGroups := c.listUnloadEligibleShipGroupIds(p.Number, game.RouteColonist)
if p.Owner == uuid.Nil {
c.selectColUnloadGroup(colGroups)
} else {
for sgi := range colGroups {
sg := c.ShipGroup(sgi)
if sg.OwnerID != p.Owner {
continue
}
c.unloadCargoUnsafe(sgi, sg.Load)
}
}
for i := range c.g.Map.Planet {
p := &c.g.Map.Planet[i]
c.doUnload(c.unloadRoutedColonists(p.Number, c.listRoutedUnloadShipGroupIds(p.Number, game.RouteColonist)))
for _, rt := range []game.RouteType{game.RouteMaterial, game.RouteCapital} {
for sgi := range c.listUnloadEligibleShipGroupIds(p.Number, rt) {
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
}
c.doUnload(c.listRoutedUnloadShipGroupIds(p.Number, rt))
}
}
}
func (c *Cache) selectColUnloadGroup(seq iter.Seq[int]) {
func (c *Cache) doUnload(groups iter.Seq[int]) {
for sgi := range groups {
c.unsafeUnloadCargo(sgi, c.ShipGroup(sgi).Load)
}
}
func (c *Cache) unloadRoutedColonists(pn uint, groups iter.Seq[int]) iter.Seq[int] {
p := c.MustPlanet(pn)
gr := slices.Collect(groups)
if p.Owner == uuid.Nil {
return c.selectColUnloadGroup(gr)
}
return func(yield func(int) bool) {
for _, sgi := range gr {
sg := c.ShipGroup(sgi)
if p.Owner != sg.OwnerID {
continue
}
if !yield(sgi) {
return
}
}
}
}
func (c *Cache) selectColUnloadGroup(groups []int) (result iter.Seq[int]) {
groupByRace := make(map[int][]int)
loadByRace := make(map[int]float64)
for i := range seq {
for _, i := range groups {
sg := c.ShipGroup(i)
ri := c.RaceIndex(sg.OwnerID)
groupByRace[ri] = append(groupByRace[ri], i)
loadByRace[ri] += sg.Load
}
if len(loadByRace) < 2 {
for _, gr := range groupByRace {
for _, sgi := range gr {
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
}
}
// only one race has to unload cargo
result = slices.Values(groups)
return
}
// select winner to unload
// select winner to unload cargo
id := MaxOrRandomLoadId(loadByRace)
result = slices.Values(groupByRace[id])
raceIdx := slices.Collect(maps.Keys(loadByRace))
slices.SortFunc(raceIdx, func(ri1, ri2 int) int { return cmp.Compare(loadByRace[ri2], loadByRace[ri1]) })
if loadByRace[raceIdx[0]] == loadByRace[raceIdx[1]] {
// no single winner with highest load
raceIdx = slices.DeleteFunc(raceIdx, func(v int) bool { return loadByRace[v] < loadByRace[raceIdx[0]] })
rand.Shuffle(len(raceIdx), func(i, j int) { raceIdx[i], raceIdx[j] = raceIdx[j], raceIdx[i] })
// now raceIdx[0] has a random race index
}
for _, sgi := range groupByRace[raceIdx[0]] {
c.unloadCargoUnsafe(sgi, c.ShipGroup(sgi).Load)
}
return
}
func (c *Cache) listUnloadEligibleShipGroupIds(pn uint, routeType game.RouteType) iter.Seq[int] {
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)
for i := range c.g.Map.Planet {
for rt, dest := range c.g.Map.Planet[i].Route {
if dest != pn || rt != routeType {
@@ -258,12 +278,16 @@ func (c *Cache) listUnloadEligibleShipGroupIds(pn uint, routeType game.RouteType
}
for i := range c.ShipGroupsIndex() {
sg := c.ShipGroup(i)
if sg.FleetID != nil || sg.State() != game.StateInOrbit || sg.CargoType == nil {
if _, ok := yielded[i]; ok || sg.FleetID != nil || sg.CargoType == nil || sg.Load == 0. || sg.State() != game.StateInOrbit || sg.Destination != dest {
continue
}
if v, ok := game.RouteToCargo[rt]; !ok || v != *sg.CargoType {
continue
}
if !yield(i) {
return
}
yielded[i] = true
}
}
}