private RouteScannerRoute UpdateRouteHash(RouteScannerRoute route) { // Generate a hash of the runs in the route. // A route that has the same runs but in a different order should result in the same hash. // Create the string var runs = new List <string>(); foreach (var run in route.Runs) { runs.Add($"{run.StartSystemName},{run.EndSystemName},{run.Comodity}"); } runs.Sort(); // MD5 it using (var md5 = System.Security.Cryptography.MD5.Create()) { byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(string.Join("|", runs)); byte[] hashBytes = md5.ComputeHash(inputBytes); var sb = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("X2")); } route.Hash = sb.ToString(); } return(route); }
private RouteScannerRoute UpdateRoute(RouteScannerRoute route) { UpdateRouteHash(route); int totalProfit = 0; int totalJumps = 0; float pps = 0.0f; float ppj = 0.0f; foreach (var run in route.Runs) { totalProfit += run.Profit; totalJumps += run.Jumps; } pps = totalProfit / route.Runs.Count; ppj = totalProfit / totalJumps; route.TotalProfit = totalProfit; route.TotalJumps = totalJumps; route.ProfitPerRun = pps; route.ProfitPerJump = ppj; UpdateRouteScore(route); return(route); }
private RouteScannerRoute UpdateRouteScore(RouteScannerRoute route) { route.ScoreTrail.Clear(); // Start the score with the profit per run float score = route.ProfitPerRun; route.ScoreTrail.Add($"Base score: {score}"); // Apply weight per run // Use an exponential type equasion. A little more is a little weight, a lot more is a LOT of weight. int runCountWeight = (route.Runs.Count * _options.ScoreWeightPerRun) * route.Runs.Count; score += runCountWeight; route.ScoreTrail.Add($"Route length score: {runCountWeight}"); // Apply weight per duplicate trades int duplicateTradeCount = 0; var seenTrades = new List <string>(); foreach (var run in route.Runs) { var buyTrade = $"b;{run.StartSystemName};{run.Comodity}"; var sellTrade = $"s;{run.EndSystemName};{run.Comodity}"; if (seenTrades.Contains(buyTrade)) { duplicateTradeCount++; } else { seenTrades.Add(buyTrade); } if (seenTrades.Contains(sellTrade)) { duplicateTradeCount++; } else { seenTrades.Add(sellTrade); } } int duplicateTradeWeight = duplicateTradeCount * _options.ScoreWeightPerDuplicateTrade; score += duplicateTradeWeight; route.ScoreTrail.Add($"Repeat trade score: {duplicateTradeWeight}"); route.Score = score; return(route); }
// Walk through runs to try and find profitable circular routes that start and end in the same system private void ScanForRoutes(TradeMapSystem startSystem, ICollection <RouteScannerRoute> routeCollection, RouteScannerOptions options) { _ct.ThrowIfCancellationRequested(); _log.Info($"Scanning for profitable routes starting from '{startSystem.Name}'"); var foundRoutes = new List <RouteScannerRoute>(); // Build a list of routes found Stack <RouteScannerRun> tradeRunStack = new Stack <RouteScannerRun>(); Stack <Stack <RouteScannerRun> > tradeRunQueueStack = new Stack <Stack <RouteScannerRun> >(); // Start off the run queue _log.Debug("Enqueuing start system's runs"); var newQueueStack = new Stack <RouteScannerRun>(); foreach (var run in startSystem.Runs) { _log.Debug($"Enqueuing run: {run}"); newQueueStack.Push(run); } tradeRunQueueStack.Push(newQueueStack); // Work the queues, while there are queues to work while (tradeRunQueueStack.Count > 0) { _ct.ThrowIfCancellationRequested(); _log.Debug($"tradeRunQueueStack: {tradeRunQueueStack.Count}"); // Get the queue at the top of the stack var tradeRunQueue = tradeRunQueueStack.Peek(); _log.Debug($"tradeRunQueue: {tradeRunQueue.Count}"); // If the queue is empty, pop it, and go back down a level if (tradeRunQueue.Count <= 0) { _log.Debug("tradeRunQueue empty for this step, going back a level"); tradeRunQueueStack.Pop(); if (tradeRunStack.Count > 0) { tradeRunStack.Pop(); continue; } else { // Hit the end, finished processing this start system return; } } else { // Have a queue to work on // Take it from the queue var thisRun = tradeRunQueue.Pop(); _log.Debug($"Checking run: {thisRun}"); // Don't double-back on runs. // If this run is already in the stack, don't scan it again. if (tradeRunStack.Contains(thisRun)) { _log.Debug("Already checked this run, skipping to avoid looped routes"); continue; } tradeRunStack.Push(thisRun); // If this trade run ends at the start system, it's good. // Save it as a route if (thisRun.EndSystem.Name == startSystem.Name) { _log.Debug("Route ends back at start, this is a complete route"); var newTradeRoute = new RouteScannerRoute(); foreach (var run in tradeRunStack.ToArray().Reverse()) { newTradeRoute.StartSystem = startSystem; newTradeRoute.Runs.Add(run); } UpdateRoute(newTradeRoute); if (newTradeRoute.Score < options.MinRouteScore) { _log.Debug($"Ignoring route, score {newTradeRoute.Score} min {options.MinRouteScore}"); } else if (routeCollection.FirstOrDefault(r => r.Hash == newTradeRoute.Hash) != null) { _log.Debug($"Ignoring route, similar route already found"); } else if (options.SingleRoutePerStartSystem) { // Check that a route doesn't already exist for this system var currentRouteForStartSystem = routeCollection.FirstOrDefault(r => r.StartSystem == startSystem); if (currentRouteForStartSystem != null) { if (currentRouteForStartSystem.Score >= newTradeRoute.Score) { _log.Debug("Ignoring route, single route mode active and new route scored less than existing route"); } else { _log.Debug("New route more profitable than existing route"); routeCollection.Remove(currentRouteForStartSystem); routeCollection.Add(newTradeRoute); } } else { routeCollection.Add(newTradeRoute); } } else { _log.Debug("Saving route"); routeCollection.Add(newTradeRoute); } // Don't try to walk further than this. //tradeRunQueueStack.Pop(); tradeRunStack.Pop(); continue; } // If we haven't hit the run limit, see if there's any runs to walk through from this system if (tradeRunStack.Count < options.RouteMaxStops) { _log.Debug($"{tradeRunStack.Count} is under the stop limit {options.RouteMaxStops}, enqueuing this stop's trade runs"); // Add a new trade run queue to the queue stack newQueueStack = new Stack <RouteScannerRun>(); foreach (var nextRun in thisRun.EndSystem.Runs) { if (!options.AllowSameStopBuySell && nextRun.Comodity == thisRun.Comodity) { // Don't buy the comodity that you just sold _log.Debug("Skipping this run, don't buy what was just sold"); continue; } _log.Debug($"Enqueuing run: {nextRun}"); newQueueStack.Push(nextRun); } if (newQueueStack.Count > 0) { tradeRunQueueStack.Push(newQueueStack); } else { _log.Debug("No more runs from this system"); tradeRunStack.Pop(); continue; } } else { _log.Debug($"Stop limit {options.RouteMaxStops} has been met, not looking any deeper"); tradeRunStack.Pop(); } } } // End of worker loop }