/// <summary> /// Initializes this controller. /// </summary> private void Initialize() { // Set some values for statistics _statPodMatchingScoreIndex = _config.LateBeforeMatch ? 1 : 0; // --> Setup normal scorers List <Func <double> > normalScorers = new List <Func <double> >(); // Select late orders first if (_config.LateBeforeMatch) { normalScorers.Add(() => { return(_currentOrder.DueTime > Instance.Controller.CurrentTime ? 1 : 0); }); } // Select best by match with inbound pods normalScorers.Add(() => { return(_currentOrder.Positions.Sum(line => Math.Min(_currentStation.InboundPods.Sum(pod => pod.CountAvailable(line.Key)), line.Value))); }); // If we run into ties use the oldest order normalScorers.Add(() => { switch (_config.TieBreaker) { case Shared.OrderSelectionTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case Shared.OrderSelectionTieBreaker.EarliestDueTime: return(-_currentOrder.DueTime); case Shared.OrderSelectionTieBreaker.FCFS: return(-_currentOrder.TimeStamp); default: throw new ArgumentException("Unknown tie breaker: " + _config.FastLaneTieBreaker); } }); // --> Setup fast lane scorers List <Func <double> > fastLaneScorers = new List <Func <double> >(); // If we run into ties use the oldest order fastLaneScorers.Add(() => { switch (_config.FastLaneTieBreaker) { case Shared.FastLaneTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case Shared.FastLaneTieBreaker.EarliestDueTime: return(-_currentOrder.DueTime); case Shared.FastLaneTieBreaker.FCFS: return(-_currentOrder.TimeStamp); default: throw new ArgumentException("Unknown tie breaker: " + _config.FastLaneTieBreaker); } }); // Init selectors _bestCandidateSelectNormal = new BestCandidateSelector(true, normalScorers.ToArray()); _bestCandidateSelectFastLane = new BestCandidateSelector(true, fastLaneScorers.ToArray()); if (_config.FastLane) { _nearestInboundPod = new VolatileIDDictionary <OutputStation, Pod>(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, Pod>(s, null)).ToList()); } }
/// <summary> /// Inits the controller. /// </summary> private void Init() { // Init queue assignment assessment if (_bestCandidateSelectorQueue == null) { List <Func <double> > scorers = new List <Func <double> >(); if (_config.QueueOrderSelectionRule1 != null) { scorers.Add(GenerateScorer(_config.QueueOrderSelectionRule1, false)); } if (_config.QueueOrderSelectionRule2 != null) { scorers.Add(GenerateScorer(_config.QueueOrderSelectionRule2, false)); } if (_config.QueueOrderSelectionRule3 != null) { scorers.Add(GenerateScorer(_config.QueueOrderSelectionRule3, false)); } _bestCandidateSelectorQueue = new BestCandidateSelector(true, scorers.ToArray()); } // Init fast lane assignment assessment if (_bestCandidateSelectorFastLane == null) { List <Func <double> > scorers = new List <Func <double> >(); if (_config.FastLaneOrderSelectionRule1 != null) { scorers.Add(GenerateScorer(_config.FastLaneOrderSelectionRule1, true)); } if (_config.FastLaneOrderSelectionRule2 != null) { scorers.Add(GenerateScorer(_config.FastLaneOrderSelectionRule2, true)); } if (_config.FastLaneOrderSelectionRule3 != null) { scorers.Add(GenerateScorer(_config.FastLaneOrderSelectionRule3, true)); } _bestCandidateSelectorFastLane = new BestCandidateSelector(true, scorers.ToArray()); } // Init station assignment assessment if (_bestCandidateSelectorStation == null) { List <Func <double> > scorers = new List <Func <double> >(); if (_config.StationOrderSelectionRule1 != null) { scorers.Add(GenerateScorer(_config.StationOrderSelectionRule1, true)); } if (_config.StationOrderSelectionRule2 != null) { scorers.Add(GenerateScorer(_config.StationOrderSelectionRule2, true)); } if (_config.StationOrderSelectionRule3 != null) { scorers.Add(GenerateScorer(_config.StationOrderSelectionRule3, true)); } _bestCandidateSelectorStation = new BestCandidateSelector(true, scorers.ToArray()); } }
/// <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()); }
/// <summary> /// Initializes this controller. /// </summary> private void Initialize() { // Set some values for statistics _statLinesInCommonScoreIndex = 0; // Setup scorers _bestCandidateSelectNormal = new BestCandidateSelector(true, // First select order with most lines in common () => { return(_currentOrder.Positions.Sum(line => _currentStation.AssignedOrders.Sum(o => o.GetDemandCount(line.Key) > 0 ? 1 : 0))); }, // If we run into ties use the oldest order () => { switch (_config.TieBreaker) { case Shared.OrderSelectionTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case Shared.OrderSelectionTieBreaker.EarliestDueTime: return(-_currentOrder.DueTime); case Shared.OrderSelectionTieBreaker.FCFS: return(-_currentOrder.TimeStamp); default: throw new ArgumentException("Unknown tie breaker: " + _config.FastLaneTieBreaker); } }); _bestCandidateSelectFastLane = new BestCandidateSelector(true, // If we run into ties, break them () => { switch (_config.FastLaneTieBreaker) { case Shared.FastLaneTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case Shared.FastLaneTieBreaker.EarliestDueTime: return(-_currentOrder.DueTime); case Shared.FastLaneTieBreaker.FCFS: return(-_currentOrder.TimeStamp); default: throw new ArgumentException("Unknown tie breaker: " + _config.FastLaneTieBreaker); } }); if (_config.FastLane) { _nearestInboundPod = new VolatileIDDictionary <OutputStation, Pod>(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, Pod>(s, null)).ToList()); } }
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> /// Creates all zones or ensures their consistency between different managers using those. /// </summary> /// <param name="config">The configuration to use for creating the zones.</param> internal void CreateOrEnsureZones(CacheConfiguration config) { // Check whether this was already done if (_config != null) { // --> Ensure compatibility if (!_config.Match(config)) { throw new ArgumentException("Incompatible cache configurations: " + _config.ToString() + " vs. " + config.ToString()); } } else { // Init _config = config; List <ZoneType> zoneTypes = new List <ZoneType>() { ZoneType.Cache, ZoneType.Dropoff }; UnzonedStorageLocations = Instance.Waypoints.Where(w => w.PodStorageLocation).ToHashSet(); CachePartitions = new VolatileIDDictionary <OutputStation, HashSet <Waypoint> >(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, HashSet <Waypoint> >(s, new HashSet <Waypoint>())).ToList()); int cacheWPCountPerStation = (int)Math.Ceiling(UnzonedStorageLocations.Count * _config.CacheFraction / Instance.OutputStations.Count); DropoffPartitions = new VolatileIDDictionary <OutputStation, HashSet <Waypoint> >(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, HashSet <Waypoint> >(s, new HashSet <Waypoint>())).ToList()); int dropoffWPCountPerStation = _config.DropoffCount; // Init selector BestCandidateSelector scorer = new BestCandidateSelector(false, // First adhere to preference between zone types () => { switch (_config.ZonePriority) { case ZonePriority.CacheFirst: return(_currentZoneType == ZoneType.Cache ? 0 : 1); case ZonePriority.DropoffFirst: return(_currentZoneType == ZoneType.Dropoff ? 0 : 1); case ZonePriority.CacheDropoffEqual: return(0); default: throw new ArgumentException("Unknown priority: " + _config.ZonePriority); } }, // Then assess the main distance metric () => { switch (_currentZoneType) { case ZoneType.Cache: return(Distances.CalculateShortestTimePathPodSafe(_currentStorageLocation, _currentStation.Waypoint, Instance)); case ZoneType.Dropoff: return(Distances.CalculateShortestTimePathPodSafe(_currentStation.Waypoint, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown zone type for partitioning: " + _currentZoneType.ToString()); } }, // Break ties based on the value for the other zone () => { switch (_currentZoneType) { case ZoneType.Cache: return(-Distances.CalculateShortestTimePathPodSafe(_currentStation.Waypoint, _currentStorageLocation, Instance)); case ZoneType.Dropoff: return(-Distances.CalculateShortestTimePathPodSafe(_currentStorageLocation, _currentStation.Waypoint, Instance)); default: throw new ArgumentException("Unknown zone type for partitioning: " + _currentZoneType.ToString()); } }); // --> Create partitions // Assign storage locations to different zones while ( // Check for any remaining assignments CachePartitions.Values.Any(p => p.Count < cacheWPCountPerStation) || DropoffPartitions.Values.Any(p => p.Count < dropoffWPCountPerStation)) { // Search for next assignment scorer.Recycle(); OutputStation bestStation = null; Waypoint bestStorageLocation = null; ZoneType bestZoneType = ZoneType.None; // Check all unzoned storage locations foreach (var storageLocation in UnzonedStorageLocations) { _currentStorageLocation = storageLocation; // Check all stations foreach (var station in Instance.OutputStations) { _currentStation = station; // Check all types foreach (var zoneType in zoneTypes) { _currentZoneType = zoneType; // Skip invalid assignments if (zoneType == ZoneType.Cache && CachePartitions[station].Count >= cacheWPCountPerStation) { continue; } if (zoneType == ZoneType.Dropoff && DropoffPartitions[station].Count >= dropoffWPCountPerStation) { continue; } // Determine score and update assignment if (scorer.Reassess()) { bestStation = _currentStation; bestStorageLocation = _currentStorageLocation; bestZoneType = _currentZoneType; } } } } // Sanity check if (bestStation == null) { throw new InvalidOperationException("Ran out of available assignments while partitioning the caches - partitions so far: " + "Cache: " + string.Join(",", CachePartitions.Select(p => p.Key.ToString() + "(" + p.Value.Count + ")")) + "Dropoff: " + string.Join(",", DropoffPartitions.Select(p => p.Key.ToString() + "(" + p.Value.Count + ")"))); } // Set assignment switch (bestZoneType) { case ZoneType.Cache: CachePartitions[bestStation].Add(bestStorageLocation); bestStorageLocation.InfoTagCache = ZoneType.Cache; break; case ZoneType.Dropoff: DropoffPartitions[bestStation].Add(bestStorageLocation); bestStorageLocation.InfoTagCache = ZoneType.Dropoff; break; default: throw new InvalidOperationException("Invalid zone determined: " + bestZoneType); } UnzonedStorageLocations.Remove(bestStorageLocation); } } }
/// <summary> /// Initializes this controller. /// </summary> private void Initialize() { // Setup scorers switch (_config.OrderSelectionRule) { case DefaultOrderSelection.Random: _bestCandidateSelectOrder = new BestCandidateSelector(false, () => { return(Instance.Randomizer.NextDouble()); }); break; case DefaultOrderSelection.FCFS: _bestCandidateSelectOrder = new BestCandidateSelector(false, () => { return(_currentOrder.TimeStamp); }); break; case DefaultOrderSelection.DueTime: _bestCandidateSelectOrder = new BestCandidateSelector(false, () => { return(_currentOrder.DueTime); }); break; case DefaultOrderSelection.FrequencyAge: _bestCandidateSelectOrder = new BestCandidateSelector(false, () => { double orderBirth = 1 - ((_currentOrder.TimeStamp - _oldestOrderTimestamp) / (_newestOrderTimestamp - _oldestOrderTimestamp)); double frequency = _currentOrder.Positions.Average(p => Instance.FrequencyTracker.GetMeasuredFrequency(p.Key)); return(-(orderBirth * frequency)); }); break; default: throw new ArgumentException("Unknown selection rule: " + _config.OrderSelectionRule); } switch (_config.StationSelectionRule) { case DefaultOutputStationSelection.Random: _bestCandidateSelectStation = new BestCandidateSelector(false, () => { return(Instance.Randomizer.NextDouble()); }); break; case DefaultOutputStationSelection.LeastBusy: _bestCandidateSelectStation = new BestCandidateSelector(false, () => { return((_currentStation.CapacityInUse + _currentStation.CapacityReserved) / (double)_currentStation.Capacity); }); break; case DefaultOutputStationSelection.MostBusy: _bestCandidateSelectStation = new BestCandidateSelector(false, () => { return(-((_currentStation.CapacityInUse + _currentStation.CapacityReserved) / (double)_currentStation.Capacity)); }); break; default: throw new ArgumentException("Unknown selection rule: " + _config.OrderSelectionRule); } // Setup fast lane helpers _bestCandidateSelectFastLane = new BestCandidateSelector(true, // If we run into ties use the oldest order () => { switch (_config.FastLaneTieBreaker) { case Shared.FastLaneTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case Shared.FastLaneTieBreaker.EarliestDueTime: return(-_currentOrder.DueTime); case Shared.FastLaneTieBreaker.FCFS: return(-_currentOrder.TimeStamp); default: throw new ArgumentException("Unknown tie breaker: " + _config.FastLaneTieBreaker); } }); if (_config.FastLane) { _nearestInboundPod = new VolatileIDDictionary <OutputStation, Pod>(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, Pod>(s, null)).ToList()); } }
/// <summary> /// Decides the next repositioning move to do for the given robot. /// </summary> /// <param name="robot">The robot that is asking to conduct such a move.</param> /// <returns>A repositioning move or <code>null</code> if no such move was available.</returns> protected override RepositioningMove GetRepositioningMove(Bot robot) { // Prepare hot zones if (_chacheStorageLocations == null) { // Ensure valid zones Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // Store zones for fast access _chacheStorageLocations = Instance.SharedControlElements.StoragePartitioner.CachePartitions; foreach (var station in _chacheStorageLocations.Keys) { foreach (var storageLocation in _chacheStorageLocations[station]) { _stationsOfStorageLocations[storageLocation] = station; } } _regularStorageLocations = Instance.Waypoints.Except(_chacheStorageLocations.SelectMany(c => c.Value)).ToHashSet(); } // Init if (_bestCandidateSelectorClear == null) { _bestCandidateSelectorClear = new BestCandidateSelector(false, // First try to keep the move on the same tier as the robot () => { return(_currentPod.Tier == robot.Tier && _currentStorageLocation.Tier == robot.Tier ? 0 : 1); }, // Then try to find a pod useless for the station () => { // Check the number of potential picks possible with the pod (given by station orders' demand) int potentialPicks = Instance.ResourceManager.GetExtractRequestsOfStation(_currentStation) .Concat(Instance.ResourceManager.GetQueuedExtractRequestsOfStation(_currentStation)) .GroupBy(r => r.Item).Sum(g => Math.Min(_currentPod.CountContained(g.Key), g.Count())); // Use negative potential picks to mark useless pod return(potentialPicks); }, // Then try to find a pod useless overall () => { // Check the number of potential picks possible with the pod (given by all orders' demand) int potentialPicks = _currentPod.ItemDescriptionsContained.Sum(i => Math.Min(_currentPod.CountContained(i), Instance.ResourceManager.GetDemandAssigned(i) + Instance.ResourceManager.GetDemandQueued(i) + (_config.UselessConsiderBacklog ? Instance.ResourceManager.GetDemandBacklog(i) : 0))); // Use negative potential picks to mark useless pod return(potentialPicks); }, // Then try to use an empty pod () => { return(_currentPod.CapacityInUse); }, // Then try to get a destination location most near to the input-stations (if pod is considered empty) or the shortest move distance (if pod still has sufficient content) () => { return((_currentPod.CapacityInUse / _currentPod.Capacity < _config.PodEmptyThreshold) ? _currentStorageLocation.ShortestPodPathDistanceToNextInputStation : Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); }, // Then try to make a move with the pod most near to an output-station () => { return(_currentPod.Waypoint.ShortestPodPathDistanceToNextOutputStation); }); } if (_bestCandidateSelectorFill == null) { _bestCandidateSelectorFill = new BestCandidateSelector(false, // First try to keep the move on the same tier as the robot () => { return(_currentPod.Tier == robot.Tier && _currentStorageLocation.Tier == robot.Tier ? 0 : 1); }, // Then try to find a pod useful for the station () => { // Check the number of potential picks possible with the pod (given by station orders' demand) int potentialPicks = Instance.ResourceManager.GetExtractRequestsOfStation(_currentStation) .Concat(Instance.ResourceManager.GetQueuedExtractRequestsOfStation(_currentStation)) .GroupBy(r => r.Item).Sum(g => Math.Min(_currentPod.CountContained(g.Key), g.Count())); // Use negative potential picks to mark useless pod return(-potentialPicks); }, // Then try to find a pod useful overall () => { // Check the number of potential picks possible with the pod (given by all orders' demand) int potentialPicks = _currentPod.ItemDescriptionsContained.Sum(i => Math.Min(_currentPod.CountContained(i), Instance.ResourceManager.GetDemandAssigned(i) + Instance.ResourceManager.GetDemandQueued(i) + (_config.UselessConsiderBacklog ? Instance.ResourceManager.GetDemandBacklog(i) : 0))); // Use negative potential picks to mark useless pod return(-potentialPicks); }, // Then try to use a full pod () => { return(-_currentPod.CapacityInUse); }, // Then try to do a short move () => { return(Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); }); } // Init Pod bestPod = null; Waypoint bestStorageLocation = null; // Check whether any cache has too many pods if (Instance.OutputStations.Any(s => NeedsClearingRepositioning(s))) { // Clear potential old results _bestCandidateSelectorClear.Recycle(); // Check all stations foreach (var station in Instance.OutputStations.Where(s => NeedsClearingRepositioning(s))) { // Update current candidate to assess _currentStation = station; // Check occupied storage locations of cache of station foreach (var from in _chacheStorageLocations[station].Where(w => w.Pod != null && !Instance.ResourceManager.IsPodClaimed(w.Pod))) { // Check unoccupied storage locations not in a cache foreach (var to in Instance.ResourceManager.UnusedPodStorageLocations.Where(w => _regularStorageLocations.Contains(w))) { // Update current candidate to assess _currentPod = from.Pod; _currentStorageLocation = to; // Check whether the current combination is better if (_bestCandidateSelectorClear.Reassess()) { // Update best candidate bestPod = _currentPod; bestStorageLocation = _currentStorageLocation; } } } } } // Check whether any cache has too few pods if (Instance.OutputStations.Any(s => NeedsFillingRepositioning(s))) { // Clear potential old results _bestCandidateSelectorFill.Recycle(); // Check all stations foreach (var station in Instance.OutputStations.Where(s => NeedsFillingRepositioning(s))) { // Update current candidate to assess _currentStation = station; // Check unused pods foreach (var pod in Instance.ResourceManager.UnusedPods.Where(p => _regularStorageLocations.Contains(p.Waypoint))) { // Check unoccupied storage locations of cache of station foreach (var to in _chacheStorageLocations[station].Where(w => !Instance.ResourceManager.IsStorageLocationClaimed(w))) { // Update current candidate to assess _currentPod = pod; _currentStorageLocation = to; // Check whether the current combination is better if (_bestCandidateSelectorFill.Reassess()) { // Update best candidate bestPod = _currentPod; bestStorageLocation = _currentStorageLocation; } } } } } // Check whether a move was obtained if (bestPod != null) { // Return the move return(new RepositioningMove() { Pod = bestPod, StorageLocation = bestStorageLocation }); } else { // No move available - block calls for a while GlobalTimeout = Instance.Controller.CurrentTime + _config.GlobalTimeout; return(null); } }