/// <summary> /// Initializes this manager. /// </summary> private void Init() { // --> Init shared component or ensure consistency Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // --> Store information about the station to which a cache storage location belongs _cacheStations = new VolatileIDDictionary <Waypoint, OutputStation>( Instance.SharedControlElements.StoragePartitioner.CachePartitions.SelectMany(kvp => kvp.Value.Select(wp => new VolatileKeyValuePair <Waypoint, OutputStation>(wp, kvp.Key))).ToList()); // --> Init scoring List <Func <double> > scorers = new List <Func <double> >(); // First select cache or not depending on decision scorers.Add(() => { // Check whether the current location belongs to the current station's cache and the pod does make the cut according to the cache score if (_currentCacheable && _currentStorageLocation.InfoTagCache == ZoneType.Cache && _cacheStations[_currentStorageLocation] == _currentStation) { // It fits, reward it return(-1); } // Check whether the pod shouldn't be cached and the storage location does not belong to a cache else if (!_currentCacheable && _currentStorageLocation.InfoTagCache != ZoneType.Cache) { // It fits, reward it return(-1); } // Check whether the storage location belongs to a foreign station's cache else if (_currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Heavily penalize using foreign cache storage locations return(1); } else { // It does not fit, penalize it return(0); } }); // Then select the nearest one scorers.Add(() => { switch (_config.PodDisposeRule) { case CacheStorageLocationSelectionRule.Euclid: return(Distances.CalculateEuclid(_currentPodLocation, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.Manhattan: return(Distances.CalculateManhattan(_currentPodLocation, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.ShortestPath: return(Distances.CalculateShortestPathPodSafe(_currentPodLocation, _currentStorageLocation, Instance)); case CacheStorageLocationSelectionRule.ShortestTime: return(Distances.CalculateShortestTimePathPodSafe(_currentPodLocation, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }); // Instantiate best candidate assessment _bestCandidateSelector = new BestCandidateSelector(false, scorers.ToArray()); }
private double Score(QueueOrderSelectionInboundMatches config, bool queueAssignment) { // Init int podCount = _inboundPodsByDistance[_currentStation].Count; int podNumber = 0; double score = 0; // Get demands given by the current order List <IGrouping <ItemDescription, ExtractRequest> > demands = Instance.ResourceManager.GetExtractRequestsOfOrder(_currentOrder).GroupBy(r => r.Item).ToList(); foreach (var itemRequests in demands) { _orderItemDemand[itemRequests.Key] = itemRequests.Count(); } // Check all inbound pods foreach (var pod in _inboundPodsByDistance[_currentStation]) { // Get distance to pod double distance; if (pod.Bot != null && pod.Bot.CurrentWaypoint != null) { // Use the path distance (this should always be possible) distance = Distances.CalculateShortestPathPodSafe(pod.Bot.CurrentWaypoint, _currentStation.Waypoint, Instance); } else { // Use manhattan distance as a fallback distance = Distances.CalculateManhattan(pod, _currentStation, Instance.WrongTierPenaltyDistance); } // Check all demands for the order for availability in the pod foreach (var item in demands.Select(d => d.Key)) { // If there is no demand left, skip this item if (_orderItemDemand[item] <= 0) { continue; } // Get available count int availableCount = Math.Min(_orderItemDemand[item], pod.CountAvailable(item)); // Update demand for item with available count _orderItemDemand[item] -= availableCount; // Update score by taking distance to pod into account score += availableCount * distance > config.DistanceForWeighting ? // Pod is too far away, do not weight it's resulting score 1 : // Pod is sufficiently near, weight it's score by it's position in queue (podCount - podNumber); } // Track pod number podNumber++; } // Return the score return(score); }
/// <summary> /// Estimates the time for traveling using the manhattan metric. /// </summary> /// <param name="from">The from part of the trip.</param> /// <param name="to">The to part of the trip.</param> /// <param name="instance">The instance.</param> /// <returns>The time needed to for conducting the trip according to the manhattan metric.</returns> public double EstimateShortestPathManhattan(Circle from, Circle to, Instance instance) { // Init, if not done yet if (_timeGraph == null) { _timeGraph = new TimeGraph(_instance); } return // Assume quarter rotation for manhattan metric ((Math.PI / 2) / TimeGraph.PI2 * _timeGraph.TurnSpeed + // Use full manhattan distance to estimate travel time Distances.CalculateManhattan(from, to, instance.WrongTierPenaltyDistance) / _timeGraph.Speed); }
/// <summary> /// Decides the waypoint to use when storing a pod. This call is measured by the timing done. /// </summary> /// <param name="pod">The pod to store.</param> /// <returns>The waypoint to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { double minDistance = double.PositiveInfinity; Waypoint bestStorageLocation = null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Calculate the distance double distance; switch (_config.PodDisposeRule) { case StationBasedPodStorageLocationDisposeRule.Euclid: distance = (_config.OutputStationMode ? Instance.OutputStations.Cast <Circle>() : Instance.InputStations.Cast <Circle>()) .Min(station => Distances.CalculateEuclid(station, storageLocation, Instance.WrongTierPenaltyDistance)); break; case StationBasedPodStorageLocationDisposeRule.Manhattan: distance = (_config.OutputStationMode ? Instance.OutputStations.Cast <Circle>() : Instance.InputStations.Cast <Circle>()) .Min(station => Distances.CalculateManhattan(station, storageLocation, Instance.WrongTierPenaltyDistance)); break; case StationBasedPodStorageLocationDisposeRule.ShortestPath: distance = (_config.OutputStationMode ? Instance.OutputStations.Select(s => s.Waypoint) : Instance.InputStations.Select(s => s.Waypoint)) .Min(station => Distances.CalculateShortestPathPodSafe(storageLocation, station, Instance)); break; case StationBasedPodStorageLocationDisposeRule.ShortestTime: distance = (_config.OutputStationMode ? Instance.OutputStations.Select(s => s.Waypoint) : Instance.InputStations.Select(s => s.Waypoint)) .Min(station => Distances.CalculateShortestTimePathPodSafe(storageLocation, station, Instance)); break; default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } // Update minimum if (distance < minDistance) { minDistance = distance; bestStorageLocation = storageLocation; } } // Check success if (bestStorageLocation == null) { throw new InvalidOperationException("There was no suitable storage location for the pod: " + pod.ToString()); } // Return it return(bestStorageLocation); }
/// <summary> /// Prepares another assessment run. /// </summary> private void PrepareAssessment() { if (_orderItemDemand == null) { _orderItemDemand = new VolatileIDDictionary <ItemDescription, int>(Instance.ItemDescriptions.Select(i => new VolatileKeyValuePair <ItemDescription, int>(i, 0)).ToList()); } Instance.OutputStations.ForEach(station => _inboundPodsByDistance[station] = station.InboundPods.OrderBy(p => { if (p.Bot != null && p.Bot.CurrentWaypoint != null) { // Use the path distance (this should always be possible) return(Distances.CalculateShortestPathPodSafe(p.Bot.CurrentWaypoint, station.Waypoint, Instance)); } else { // Use manhattan distance as a fallback return(Distances.CalculateManhattan(p, station, Instance.WrongTierPenaltyDistance)); } }).ToList()); Instance.OutputStations.ForEach(station => _nearestInboundPod[station] = _inboundPodsByDistance[station].FirstOrDefault());; }
/// <summary> /// Returns a suitable storage location for the given pod. /// </summary> /// <param name="pod">The pod to fetch a storage location for.</param> /// <returns>The storage location to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { double minDistance = double.PositiveInfinity; Waypoint bestStorageLocation = null; Waypoint podLocation = // Get current waypoint of pod, if we want to estimate a path _config.PodDisposeRule == NearestPodStorageLocationDisposeRule.ShortestPath || _config.PodDisposeRule == NearestPodStorageLocationDisposeRule.ShortestTime ? Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y) : null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Calculate the distance double distance; switch (_config.PodDisposeRule) { case NearestPodStorageLocationDisposeRule.Euclid: distance = Distances.CalculateEuclid(pod, storageLocation, Instance.WrongTierPenaltyDistance); break; case NearestPodStorageLocationDisposeRule.Manhattan: distance = Distances.CalculateManhattan(pod, storageLocation, Instance.WrongTierPenaltyDistance); break; case NearestPodStorageLocationDisposeRule.ShortestPath: distance = Distances.CalculateShortestPathPodSafe(podLocation, storageLocation, Instance); break; case NearestPodStorageLocationDisposeRule.ShortestTime: distance = Distances.CalculateShortestTimePathPodSafe(podLocation, storageLocation, Instance); break; default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } // Update minimum if (distance < minDistance) { minDistance = distance; bestStorageLocation = storageLocation; } } // Check success if (bestStorageLocation == null) { throw new InvalidOperationException("There was no suitable storage location for the pod: " + pod.ToString()); } // Return it return(bestStorageLocation); }
/// <summary> /// Prepares some meta information. /// </summary> private void PrepareAssessment() { if (_config.FastLane) { foreach (var station in Instance.OutputStations.Where(s => IsAssignable(s))) { _nearestInboundPod[station] = station.InboundPods.ArgMin(p => { if (p.Bot != null && p.Bot.CurrentWaypoint != null) { // Use the path distance (this should always be possible) return(Distances.CalculateShortestPathPodSafe(p.Bot.CurrentWaypoint, station.Waypoint, Instance)); } else { // Use manhattan distance as a fallback return(Distances.CalculateManhattan(p, station, Instance.WrongTierPenaltyDistance)); } }); } } }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).ToArray()) { // Check all pods for the maximum number of picks that can be done with them List <Pod> bestPods = new List <Pod>(); int bestPodPicks = -1; foreach (var pod in Instance.Pods.Where(p => !p.InUse)) { // Calculate picks that can potentially be done with the pod int picks = order.Positions.Sum(pos => Math.Min(pod.CountAvailable(pos.Key), pos.Value)); // Check whether we found even more possible picks with this pod if (bestPodPicks < picks) { bestPods.Clear(); bestPods.Add(pod); bestPodPicks = picks; } else { // Check whether the current pod belongs into the winner group if (bestPodPicks == picks) { bestPods.Add(pod); } } } // Choose station nearest to one of the best pods OutputStation chosenStation = null; double shortestDistance = double.PositiveInfinity; foreach (var station in Instance.OutputStations.Where(s => s.Active && s.FitsForReservation(order))) { foreach (var pod in bestPods) { double distance; switch (_config.DistanceRule) { case NearBestPodOrderBatchingDistanceRule.Euclid: distance = Distances.CalculateEuclid(pod, station, Instance.WrongTierPenaltyDistance); break; case NearBestPodOrderBatchingDistanceRule.Manhattan: distance = Distances.CalculateManhattan(pod, station, Instance.WrongTierPenaltyDistance); break; case NearBestPodOrderBatchingDistanceRule.ShortestPath: distance = Distances.CalculateShortestPathPodSafe(pod.Waypoint, station.Waypoint, Instance); break; default: throw new ArgumentException("Unknown distance rule: " + _config.DistanceRule); } if (distance < shortestDistance) { shortestDistance = distance; chosenStation = station; } } } // If we found a station, assign the bundle to it if (chosenStation != null) { AllocateOrder(order, chosenStation); } } }
/// <summary> /// Reallocates the pods to different classes. /// </summary> private void ReallocatePods() { // Sort pods by their current average frequency _podsOrdered = Instance.Pods .OrderBy(p => p.ItemDescriptionsContained.Any() ? p.ItemDescriptionsContained.Average(item => item.OrderCount) : 0) .ThenBy(p => Instance.OutputStations.Min(o => { double value = 0; switch (_storageLocationClassRule) { case TurnoverPodStorageLocationClassRule.OutputStationDistanceEuclidean: value = Distances.CalculateEuclid(p, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceManhattan: value = Distances.CalculateManhattan(p, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestPath: value = Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(p.Tier, p.X, p.Y), o.Waypoint, Instance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestTime: value = Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(p.Tier, p.X, p.Y), o.Waypoint, Instance); break; default: throw new ArgumentException("Unknown storage location rule: " + _storageLocationClassRule); } return(value); })) .ToList(); // Determine the shares of the capacity of the storage location classes double overallCapacity = _classStorageLocations.Sum(l => l.Count); Dictionary <int, double> classStorageCapacityShares = new Dictionary <int, double>(); for (int i = 0; i < _classCount; i++) { classStorageCapacityShares[i] = _classStorageLocations[i].Count / overallCapacity; } // Group pods to classes _podClasses = new Dictionary <Pod, int>(); _classStoragePods = Enumerable.Range(0, _classCount).Select(i => new List <Pod>()).ToArray(); int currentClass = 0; int podCount = 0; double aggregatedRelativeCapacity = classStorageCapacityShares[0]; foreach (var pod in _podsOrdered) { podCount++; // See whether the pod still fits in the current storage location area if ((double)podCount / _podsOrdered.Count > aggregatedRelativeCapacity) { // Update virtual capacity currentClass++; if (currentClass < _classCount) { aggregatedRelativeCapacity += classStorageCapacityShares[currentClass]; } } // Assign the pod to the class _podClasses[pod] = currentClass; _classStoragePods[currentClass].Add(pod); // Mark the pods pod.InfoTagPodStorageType = (_classCount - currentClass - 1.0) / (_classCount - 1.0); pod.InfoTagPodStorageInfo = "Class" + currentClass; } // Log this reallocation Instance.LogInfo("Reallocated pods - classes: " + string.Join(";", Enumerable.Range(0, _classCount).Select(c => c + ":" + _classStoragePods[c].Count))); // Mark this re-allocation _lastReallocation = Instance.Controller.CurrentTime; }
/// <summary> /// Initializes this manager. /// </summary> private void Init() { // Remember initialization _initialized = true; // Fetch additional settings if (Instance.Controller.PodStorageManager is TurnoverPodStorageManager) { _storageLocationClassRule = (Instance.ControllerConfig.PodStorageConfig as TurnoverPodStorageConfiguration).StorageLocationClassRule; } // Initialize ordered storage location list _storageLocationsOrdered = Instance.Waypoints .Where(wp => wp.PodStorageLocation) .OrderBy(wp => Instance.OutputStations.Min(o => { double value = 0; switch (_storageLocationClassRule) { case TurnoverPodStorageLocationClassRule.OutputStationDistanceEuclidean: value = Distances.CalculateEuclid(wp, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceManhattan: value = Distances.CalculateManhattan(wp, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestPath: value = Distances.CalculateShortestPathPodSafe(wp, o.Waypoint, Instance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestTime: value = Distances.CalculateShortestTimePathPodSafe(wp, o.Waypoint, Instance); break; default: throw new ArgumentException("Unknown storage location rule: " + _storageLocationClassRule); } return(value); })) .ToList(); // Allocate storage locations to classes _classStorageLocations = new List <Waypoint> [_classCount]; List <Waypoint> tempPodsOrdered = _storageLocationsOrdered.ToList(); for (int i = 0; i < _classCount; i++) { if (i < _classCount - 1) { _classStorageLocations[i] = tempPodsOrdered.Take((int)(_classBorders[i] * _storageLocationsOrdered.Count)).ToList(); tempPodsOrdered.RemoveRange(0, _classStorageLocations[i].Count); } else { _classStorageLocations[i] = tempPodsOrdered; } } // Log this reallocation Instance.LogInfo("Allocated storage locations - classes: " + string.Join(";", Enumerable.Range(0, _classCount).Select(c => c + ":" + _classStorageLocations[c].Count))); // Reallocate items and pods for the first time ReallocatePods(); // Reallocate items and pods for the first time ReallocateItems(); }
private void Init() { // --> Init shared component or ensure consistency Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // --> Init meta info _cacheTargetCounts = new VolatileIDDictionary <OutputStation, int>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, int>(s, (int)(_config.TargetFillCache * Instance.SharedControlElements.StoragePartitioner.CachePartitions[s].Count))).ToList()); _cachePodCounts = new VolatileIDDictionary <OutputStation, int>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, int>(s, 0)).ToList()); _storageLocationStations = new VolatileIDDictionary <Waypoint, OutputStation>( Instance.Waypoints.Select(w => new VolatileKeyValuePair <Waypoint, OutputStation>(w, null)).ToList()); foreach (var station in Instance.OutputStations) { foreach (var wp in Instance.SharedControlElements.StoragePartitioner.CachePartitions[station]) { _storageLocationStations[wp] = station; } } _cacheableInfoPerStation = new VolatileIDDictionary <OutputStation, bool>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, bool>(s, false)).ToList()); // --> Init move scoring List <Func <double> > scorers = new List <Func <double> >(); // First try to keep the move on the same tier, if desired (this heavily influences the possible moves) if (_config.PreferSameTierMoves) { scorers.Add(() => { return(_currentPod.Tier == _currentBot.Tier && _currentStorageLocation.Tier == _currentBot.Tier ? 1 : 0); }); } // Then decide about move type scorers.Add(() => { if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Drop-off -> Normal return(2); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Drop-off -> Cache return(2); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Cache -> Normal return(1); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.None && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Normal -> Cache return(1); } else { throw new InvalidOperationException("Forbidden move: " + _currentPod.Waypoint.InfoTagCache + " -> " + _currentStorageLocation.InfoTagCache); } }); // Then prefer moves keeping cache at targeted level scorers.Add(() => { // Move types should now be separated, i.e. if there is a drop-off clearing move there is no 'normal' move, respectively, if there is no drop-off clearing move there are only 'normal' moves if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Cache -> Normal return // Current cache level minus ((double)_cachePodCounts[_storageLocationStations[_currentPod.Waypoint]] / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_storageLocationStations[_currentPod.Waypoint]].Count - // targeted cache level _config.TargetFillCache); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.None && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Normal -> Cache return // Targeted cache level minus (_config.TargetFillCache - // current cache level (double)_cachePodCounts[_storageLocationStations[_currentStorageLocation]] / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_storageLocationStations[_currentStorageLocation]].Count); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Drop-off -> Normal return (_cacheableAtAll ? 0 : 1); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Drop-off -> Cache return (_cacheableInfoPerStation[_storageLocationStations[_currentStorageLocation]] ? 1 : 0); } else { throw new InvalidOperationException("Forbidden move: " + _currentPod.Waypoint.InfoTagCache + " -> " + _currentStorageLocation.InfoTagCache); } }); // Then prefer moves that make the greatest contribution in keeping the cache hot (depending on settings) scorers.Add(() => { if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache) // Cache clearing move { return(1 - Instance.ElementMetaInfoTracker.GetPodCombinedScore(_currentPod, _config.WeightSpeed, _config.WeightUtility)); } else if (_currentStorageLocation.InfoTagCache == ZoneType.Cache) // Cache filling move { return(Instance.ElementMetaInfoTracker.GetPodCombinedScore(_currentPod, _config.WeightSpeed, _config.WeightUtility)); } else { return(0); // Drop-off clearing -> highest priority } }); // Then prefer the shortest moves scorers.Add(() => { switch (_config.PodDisposeRule) { case CacheStorageLocationSelectionRule.Euclid: return(-Distances.CalculateEuclid(_currentPod.Waypoint, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.Manhattan: return(-Distances.CalculateManhattan(_currentPod.Waypoint, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.ShortestPath: return(-Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); case CacheStorageLocationSelectionRule.ShortestTime: return(-Distances.CalculateShortestTimePathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }); // Then use randomness scorers.Add(() => { return(Instance.Randomizer.NextDouble()); }); // Instantiate best candidate assessment _bestCandidateSelector = new BestCandidateSelector(true, scorers.ToArray()); }
/// <summary> /// Chooses the storage location to use for the given pod. /// </summary> /// <param name="pod">The pod to store.</param> /// <returns>The storage location to use for the pod.</returns> private Waypoint ChooseStorageLocation(Pod pod) { // Get the storage class the pod should end up in int desiredStorageClass = _classManager.DetermineStorageClass(pod); // Try to allocate the pod to its storage class - if not possible try neighboring classes int currentClassTriedLow = desiredStorageClass; int currentClassTriedHigh = desiredStorageClass; Waypoint chosenStorageLocation = null; while (true) { // Try the less frequent class first if (currentClassTriedLow < _classManager.ClassCount) { chosenStorageLocation = _classManager.GetClassStorageLocations(currentClassTriedLow) .Where(wp => !Instance.ResourceManager.IsStorageLocationClaimed(wp)) // Only use not occupied ones .OrderBy(wp => { switch (_config.PodDisposeRule) { case TurnoverPodStorageLocationDisposeRule.NearestEuclid: return(Distances.CalculateEuclid(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestManhattan: return(Distances.CalculateManhattan(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestPath: return(Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestTime: return(Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.OStationNearestEuclid: return(Instance.OutputStations.Min(s => Distances.CalculateEuclid(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestManhattan: return(Instance.OutputStations.Min(s => Distances.CalculateManhattan(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestPath: return(Instance.OutputStations.Min(s => Distances.CalculateShortestPathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestTime: return(Instance.OutputStations.Min(s => Distances.CalculateShortestTimePathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.Random: return(wp.Instance.Randomizer.NextDouble()); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }) // Order the remaining ones by the given rule .FirstOrDefault(); // Use the first one } // Check whether we found a suitable pod of this class if (chosenStorageLocation != null) { break; } // Try the higher frequent class next if (currentClassTriedHigh >= 0 && currentClassTriedHigh != currentClassTriedLow) { chosenStorageLocation = _classManager.GetClassStorageLocations(currentClassTriedHigh) .Where(wp => !Instance.ResourceManager.IsStorageLocationClaimed(wp)) // Only use not occupied ones .OrderBy(wp => { switch (_config.PodDisposeRule) { case TurnoverPodStorageLocationDisposeRule.NearestEuclid: return(Distances.CalculateEuclid(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestManhattan: return(Distances.CalculateManhattan(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestPath: return(Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestTime: return(Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.OStationNearestEuclid: return(Instance.OutputStations.Min(s => Distances.CalculateEuclid(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestManhattan: return(Instance.OutputStations.Min(s => Distances.CalculateManhattan(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestPath: return(Instance.OutputStations.Min(s => Distances.CalculateShortestPathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestTime: return(Instance.OutputStations.Min(s => Distances.CalculateShortestTimePathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.Random: return(wp.Instance.Randomizer.NextDouble()); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }) // Order the remaining ones by the given rule .FirstOrDefault(); // Use the first one } // Check whether we found a suitable pod of this class if (chosenStorageLocation != null) { break; } // Update the class indeces to check next currentClassTriedLow++; currentClassTriedHigh--; // Check index correctness if (currentClassTriedHigh < 0 && currentClassTriedLow >= _classManager.ClassCount) { throw new InvalidOperationException("There was no storage location available!"); } } // Return the chosen one return(chosenStorageLocation); }