Instead of introducing arbitrary, fixed-length delays between PokeStop visits

Patrick Branch [2016-07-27 15:45:43]
Instead of introducing arbitrary, fixed-length delays between PokeStop visits
and before catching Pokemon, I changed the code to make the length of delay
before interacting with a map object a function of that object's distance.
I also changed the movement algorithm so that the bot does not quit after
just one round of PokeStop visits, but continues to explore until none of
the PokeStops visited in the current round of visits have neighboring PokeStops
which are not in the current round of PokeStops and were visited a long enough time
ago to permit visiting them again.  Overall, the bot has about the same rate of success
for farming items from PokeStops, but does so more quickly.  Due to this and the
increased uptime, these changes result in higher XP/hour and pokemon/hour rates.
Filename
PokemonGo/RocketAPI/Client.cs
PokemonGo/RocketAPI/Window/LocationManager.cs
PokemonGo/RocketAPI/Window/MainForm.cs
PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
diff --git a/PokemonGo/RocketAPI/Client.cs b/PokemonGo/RocketAPI/Client.cs
index 26804a3..097f82d 100644
--- a/PokemonGo/RocketAPI/Client.cs
+++ b/PokemonGo/RocketAPI/Client.cs
@@ -398,6 +398,16 @@ namespace PokemonGo.RocketAPI
                         releasePokemonRequest);
         }

+        public double getCurrentLat()
+        {
+            return this._currentLat;
+        }
+
+        public double getCurrentLong()
+        {
+            return this._currentLng;
+        }
+
         public async Task<PlayerUpdateResponse> UpdatePlayerLocation(double lat, double lng)
         {
             SetCoordinates(lat, lng);
diff --git a/PokemonGo/RocketAPI/Window/LocationManager.cs b/PokemonGo/RocketAPI/Window/LocationManager.cs
new file mode 100644
index 0000000..065febc
--- /dev/null
+++ b/PokemonGo/RocketAPI/Window/LocationManager.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI.Window
+{
+    public class LocationManager
+    {
+        private Client client;
+        private double kilometersPerMillisecond;
+
+        public LocationManager(Client client, double speed)
+        {
+            this.client = client;
+            this.kilometersPerMillisecond = speed / 3600000;
+        }
+
+        public double getDistance(double lat, double lng)
+        {
+            Coordinate currentLoc = new Coordinate(client.getCurrentLat(), client.getCurrentLong());
+            return currentLoc.distanceFrom(new Coordinate(lat, lng));
+        }
+
+        public async Task update(double lat, double lng)
+        {
+            double waitTime = getDistance(lat, lng)/this.kilometersPerMillisecond;
+            await Task.Delay((int)Math.Ceiling(waitTime));
+            await client.UpdatePlayerLocation(lat, lng);
+        }
+
+        public struct Coordinate
+        {
+
+            public Coordinate(double lat, double lng)
+            {
+                this.latitude = lat;
+                this.longitude = lng;
+            }
+            public double latitude;
+            public double longitude;
+
+            //returns distance in kilometers
+            public double distanceFrom(Coordinate c2)
+            {
+                double R = 6371;
+                Func<double, double> toRad = x => x * (Math.PI / 180);
+                double dLat = toRad(c2.latitude - this.latitude);
+                double dLong = toRad(c2.longitude - c2.longitude);
+                double lat1 = toRad(this.latitude);
+                double lat2 = toRad(c2.latitude);
+                double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
+                    Math.Sin(dLong / 2) * Math.Sin(dLong / 2) * Math.Cos(lat1) * Math.Cos(lat2);
+                double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
+                return R * c;
+            }
+        }
+    }
+
+}
diff --git a/PokemonGo/RocketAPI/Window/MainForm.cs b/PokemonGo/RocketAPI/Window/MainForm.cs
index 9d2d616..0831393 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.cs
@@ -40,9 +40,10 @@ namespace PokemonGo.RocketAPI.Window
         private static int TotalPokemon = 0;
         private static DateTime TimeStarted = DateTime.Now;
         public static DateTime InitSessionDateTime = DateTime.Now;
+        private static double Speed = 60; //in km/h

         Client client;
-
+        LocationManager locationManager;
         public static double GetRuntime()
         {
             return ((DateTime.Now - TimeStarted).TotalSeconds) / 3600;
@@ -114,6 +115,16 @@ namespace PokemonGo.RocketAPI.Window
             statusLabel.Text = text;
         }

+        private async Task EvolvePokemons(Client client)
+        {
+            var inventory = await client.GetInventory();
+            var pokemons =
+                inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.Pokemon)
+                    .Where(p => p != null && p?.PokemonId > 0);
+
+            await EvolveAllGivenPokemons(client, pokemons);
+        }
+
         private async Task EvolveAllGivenPokemons(Client client, IEnumerable<PokemonData> pokemonToEvolve)
         {
             foreach (var pokemon in pokemonToEvolve)
@@ -168,6 +179,7 @@ namespace PokemonGo.RocketAPI.Window
         private async void Execute()
         {
             client = new Client(ClientSettings);
+            this.locationManager = new LocationManager(client, Speed);
             try
             {
                 switch (ClientSettings.AuthType)
@@ -298,13 +310,11 @@ namespace PokemonGo.RocketAPI.Window
             }
             return "Error";
         }
-
         private async Task ExecuteCatchAllNearbyPokemons(Client client)
         {
             var mapObjects = await client.GetMapObjects();

             var pokemons = mapObjects.MapCells.SelectMany(i => i.CatchablePokemons);
-
             var inventory2 = await client.GetInventory();
             var pokemons2 = inventory2.InventoryDelta.InventoryItems
                 .Select(i => i.InventoryItemData?.Pokemon)
@@ -313,7 +323,7 @@ namespace PokemonGo.RocketAPI.Window

             foreach (var pokemon in pokemons)
             {
-                var update = await client.UpdatePlayerLocation(pokemon.Latitude, pokemon.Longitude);
+                await locationManager.update(pokemon.Latitude, pokemon.Longitude);
                 var encounterPokemonResponse = await client.EncounterPokemon(pokemon.EncounterId, pokemon.SpawnpointId);
                 var pokemonCP = encounterPokemonResponse?.WildPokemon?.PokemonData?.Cp;
                 var pokemonIV = Math.Round(Perfect(encounterPokemonResponse?.WildPokemon?.PokemonData));
@@ -365,15 +375,20 @@ namespace PokemonGo.RocketAPI.Window
             }
         }

-        private async Task ExecuteFarmingPokestopsAndPokemons(Client client)
+        private async Task ExecuteFarmingPokestopsAndPokemons(Client client, IEnumerable<FortData> pokeStops = null)
         {
             var mapObjects = await client.GetMapObjects();
-
-            var pokeStops = mapObjects.MapCells.SelectMany(i => i.Forts).Where(i => i.Type == FortType.Checkpoint && i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime());
-
+            if (pokeStops == null)
+            {
+                pokeStops = mapObjects.MapCells.SelectMany(i => i.Forts).Where(i => i.Type == FortType.Checkpoint && i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime());
+            }
+            HashSet<FortData> pokeStopSet = new HashSet<FortData>(pokeStops);
+            IEnumerable<FortData> nextPokeStopList = null;
+            ColoredConsoleWrite(Color.Cyan, $"Visiting {pokeStops.Count()} PokeStops");
             foreach (var pokeStop in pokeStops)
             {
-                var update = await client.UpdatePlayerLocation(pokeStop.Latitude, pokeStop.Longitude);
+                double pokeStopDistance = locationManager.getDistance(pokeStop.Latitude, pokeStop.Longitude);
+                await locationManager.update(pokeStop.Latitude, pokeStop.Longitude);
                 var fortInfo = await client.GetFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude);
                 var fortSearch = await client.SearchFort(pokeStop.Id, pokeStop.Latitude, pokeStop.Longitude);

@@ -393,9 +408,29 @@ namespace PokemonGo.RocketAPI.Window

                 if (fortSearch.ExperienceAwarded != 0)
                     TotalExperience += (fortSearch.ExperienceAwarded);
-                await Task.Delay(15000);
+                var pokeStopMapObjects = await client.GetMapObjects();
+
+                /* Gets all pokeStops near this pokeStop which are not in the set of pokeStops being currently
+                 * traversed and which are ready to be farmed again.  */
+                var pokeStopsNearPokeStop = pokeStopMapObjects.MapCells.SelectMany(i => i.Forts).Where(i =>
+                    i.Type == FortType.Checkpoint
+                    && i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()
+                    && !pokeStopSet.Contains(i)
+                    );
+
+                /* We choose the longest list of farmable PokeStops to traverse next, though we could use a different
+                 * criterion, such as the number of PokeStops with lures in the list.*/
+                if (pokeStopsNearPokeStop.Count() > (nextPokeStopList == null ? 0 : nextPokeStopList.Count()))
+                {
+                    nextPokeStopList = pokeStopsNearPokeStop;
+                }
                 await ExecuteCatchAllNearbyPokemons(client);
             }
+            if (nextPokeStopList != null)
+            {
+                client.RecycleItems(client);
+                await ExecuteFarmingPokestopsAndPokemons(client, nextPokeStopList);
+            }
         }

         private string GetFriendlyItemsString(IEnumerable<FortSearchResponse.Types.ItemAward> items)
diff --git a/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj b/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
index d83e624..2375f1d 100644
--- a/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
+++ b/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
@@ -62,6 +62,7 @@
     <Compile Include="CueTextBox.cs">
       <SubType>Component</SubType>
     </Compile>
+    <Compile Include="LocationManager.cs" />
     <Compile Include="MainForm.cs">
       <SubType>Form</SubType>
     </Compile>
You may download the files in Public Git.