示例#1
0
        /// <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());
        }
示例#2
0
        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);
        }
示例#3
0
 /// <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);
 }
示例#4
0
        /// <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);
        }
示例#5
0
 /// <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);
        }
示例#7
0
 /// <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);
                }
            }
        }
示例#9
0
        /// <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;
        }
示例#10
0
        /// <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();
        }
示例#11
0
        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);
        }