private TradeMapSystemCollection GetSystemsInJumpRange(TradeMap map, TradeMapSystem startSystem, TradeMapSystemCollection systemCollection, int jumpRange) { // Traverse outwards from the start system using links. // Build a collection of systems within the specified jump radius from the start system TradeMapSystemCollection seenSystems = new TradeMapSystemCollection(); if (!systemCollection.Contains(startSystem)) { systemCollection.Add(startSystem); } Stack <TradeMapSystem> systemStack = new Stack <TradeMapSystem>(); systemStack.Push(startSystem); Stack <TradeMapSystem> nextSystemStack; int jumpCount = 1; while (jumpCount <= jumpRange && systemStack.Count > 0) { // Start the next stack nextSystemStack = new Stack <TradeMapSystem>(); // Add all of the linked systems while (systemStack.Count > 0) { var thisSystem = systemStack.Pop(); if (seenSystems.Contains(thisSystem)) { continue; } foreach (var link in thisSystem.Links) { if (link.System == null) { continue; } if (link.System == startSystem) { continue; // Ignore it if it's start system } // Good link nextSystemStack.Push(link.System); if (!systemCollection.Contains(link.System)) { systemCollection.Add(link.System); } seenSystems.Add(thisSystem); } } // Done with stack, ready the next stack systemStack = nextSystemStack; jumpCount++; } return(systemCollection); }
private void ScanForRuns(TradeMapSystem startSystem, TradeMapSystem destinationSystem, int jumpsAway, RouteScannerRunCollection runCollection, RouteScannerOptions options) { // Go through each comodity sold here, and compare against all //_log.Debug($"Scanning for profitable runs between '{startSystem.Name}' and '{destinationSystem.Name}'"); foreach (var startComodity in startSystem.Comodities) { foreach (var destComodity in destinationSystem.Comodities) { _ct.ThrowIfCancellationRequested(); if (startComodity.Name == destComodity.Name) { // Comodity matches // Compare prices var profit = destComodity.Price - startComodity.Price; if (profit >= options.MinProfitPerUnit) { // Profit is over threshold var newRun = new RouteScannerRun() { Comodity = startComodity.Name, StartSystem = startSystem, StartSystemName = startSystem.Name, EndSystem = destinationSystem, EndSystemName = destinationSystem.Name, Profit = profit, Jumps = jumpsAway }; _log.Debug($"{newRun}"); // Add runs to the all runs collection runCollection.Add(newRun); // Add run to that system startSystem.Runs.Add(newRun); } } } } }
// 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 }
public void MapBuilder_SimpleRead() { var sampleDataRoot = new DefNode(); var sampleDataSystem1 = new DefNode(); sampleDataRoot.ChildNodes.Add(sampleDataSystem1); sampleDataSystem1.Tokens.Add("system").Add("Tarazed"); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("belt").Add("1169") }); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("link").Add("Enif") }); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Clothing").Add("305") }); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Electronics").Add("761") }); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Equipment").Add("395") }); sampleDataSystem1.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Food").Add("238") }); var sampleDataSystem2 = new DefNode(); sampleDataRoot.ChildNodes.Add(sampleDataSystem2); sampleDataSystem2.Tokens.Add("system").Add("Enif"); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("belt").Add("1169") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("link").Add("Tarazed") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("link").Add("Sadalmelik") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Clothing").Add("361") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Electronics").Add("800") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Equipment").Add("485") }); sampleDataSystem2.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Food").Add("335") }); var sampleDataSystem3 = new DefNode(); sampleDataRoot.ChildNodes.Add(sampleDataSystem3); sampleDataSystem3.Tokens.Add("system").Add("Sadalmelik"); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("belt").Add("1169") }); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("link").Add("Enif") }); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Clothing").Add("361") }); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Electronics").Add("800") }); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Equipment").Add("485") }); sampleDataSystem3.ChildNodes.Add(new DefNode() { Tokens = new DefTokenCollection().Add("trade").Add("Food").Add("335") }); var resultMap = new TradeMapBuilder().Build(sampleDataRoot, CancellationToken.None); var expectedMap = new TradeMap(); var tradeSystem1 = new TradeMapSystem(); expectedMap.Systems.Add(tradeSystem1); tradeSystem1.Name = "Tarazed"; tradeSystem1.Comodities.Add(new TradeMapComodity { Name = "Clothing", Price = 305 }); tradeSystem1.Comodities.Add(new TradeMapComodity { Name = "Electronics", Price = 761 }); tradeSystem1.Comodities.Add(new TradeMapComodity { Name = "Equipment", Price = 395 }); tradeSystem1.Comodities.Add(new TradeMapComodity { Name = "Food", Price = 238 }); var tradeSystem2 = new TradeMapSystem(); expectedMap.Systems.Add(tradeSystem2); tradeSystem2.Name = "Enif"; tradeSystem2.Comodities.Add(new TradeMapComodity { Name = "Clothing", Price = 361 }); tradeSystem2.Comodities.Add(new TradeMapComodity { Name = "Electronics", Price = 800 }); tradeSystem2.Comodities.Add(new TradeMapComodity { Name = "Equipment", Price = 485 }); tradeSystem2.Comodities.Add(new TradeMapComodity { Name = "Food", Price = 335 }); var tradeSystem3 = new TradeMapSystem(); expectedMap.Systems.Add(tradeSystem3); tradeSystem3.Name = "Sadalmelik"; tradeSystem3.Comodities.Add(new TradeMapComodity { Name = "Clothing", Price = 361 }); tradeSystem3.Comodities.Add(new TradeMapComodity { Name = "Electronics", Price = 800 }); tradeSystem3.Comodities.Add(new TradeMapComodity { Name = "Equipment", Price = 485 }); tradeSystem3.Comodities.Add(new TradeMapComodity { Name = "Food", Price = 335 }); tradeSystem1.Links.Add(new TradeMapSystemLink() { Name = "Enif", System = tradeSystem2 }); tradeSystem2.Links.Add(new TradeMapSystemLink() { Name = "Tarazed", System = tradeSystem1 }); tradeSystem2.Links.Add(new TradeMapSystemLink() { Name = "Sadalmelik", System = tradeSystem3 }); tradeSystem3.Links.Add(new TradeMapSystemLink() { Name = "Enif", System = tradeSystem2 }); // TODO assert }
public TradeMap CreateMap(DefNode rootNode, CancellationToken ct) { var map = new TradeMap(); //foreach (var topNode in rootNode.ChildNodes) for (int iNode = 0; iNode < rootNode.ChildNodes.Count; iNode++) { ProgressEvents.DoEvent(this, new ProgressEventArgs(iNode, rootNode.ChildNodes.Count, ProgressEventStatus.Working, $"Reading node {iNode}/{rootNode.ChildNodes.Count}")); var topNode = rootNode.ChildNodes[iNode]; // Only care about "system" nodes if (topNode.Tokens.Count >= 2 && topNode.Tokens[0] == NODE_NAME_SYSTEM) { // Is a system node var newSystem = new TradeMapSystem(); newSystem.Name = topNode.Tokens[1].Trim(); // TODO handle blank system name foreach (var subNode in topNode.ChildNodes) { if (subNode.Tokens.Count >= 2 && subNode.Tokens[0] == NODE_NAME_LINK) { // TODO handle blank system name in link newSystem.Links.Add(new TradeMapSystemLink() { Name = subNode.Tokens[1].Trim() }); } if (subNode.Tokens.Count >= 3 && subNode.Tokens[0] == NODE_NAME_TRADE) { int tradePrice; if (!int.TryParse(subNode.Tokens[2].Trim(), out tradePrice)) { // TODO Handle bad number on trade price } else { // TODO handle empty comodity name newSystem.Comodities.Add(new TradeMapComodity() { Name = subNode.Tokens[1].Trim(), Price = tradePrice }); } } if (subNode.Tokens.Count >= 2 && subNode.Tokens[0] == NODE_NAME_OBJECT) { newSystem.NamedObjects.Add(subNode.Tokens[1].Trim()); } } newSystem.Name = newSystem.Name.Trim(); map.Systems.Add(newSystem); } else if (topNode.Tokens.Count >= 2 && topNode.Tokens[0] == NODE_NAME_PLANET) { // Is a planet // I was really annoyed when I found out that a bunch of systems in the map // have trade & comoddity information, but no planets to trade on. // I had to change the way I did things, shat me to tears :( var newPlanet = new TradeMapPlanet(); newPlanet.Name = topNode.Tokens[1].Trim(); foreach (var subNode in topNode.ChildNodes) { if (subNode.Tokens.Count >= 2 && subNode.Tokens[0] == NODE_NAME_SPACEPORT) { newPlanet.HasSpaceport = true; } } // Only save planets that have spaceports if (newPlanet.HasSpaceport) { map.Planets.Add(newPlanet); } } else { // skipping node I don't care about, like "galaxy" } } return(map); }