Repository: PokemonGoF/PokemonGo-Bot

Issue:
Error when starting due to PokemonOptimizer
```
2016-08-13 13:34:48,763 [PokemonGoBot] [INFO]
2016-08-13 13:34:49,647 [SleepSchedule] [INFO] [next_sleep] Next sleep at 2016-08-13 23:05:13.647168
2016-08-13 13:34:50,104 [PokemonGoBot] [INFO] [bot_start] Starting bot...
2016-08-13 13:34:50,344 [CollectLevelUpReward] [INFO] [level_up_reward] Received level up reward: []
2016-08-13 13:34:50,345 [IncubateEggs] [INFO] [next_egg_incubates] Next egg incubates in 9.36 km
2016-08-13 13:34:50,354 [PokemonOptimizer] [INFO] Pokemon Bag: 445/450
2016-08-13 13:34:50,806 [       cli] [INFO]
2016-08-13 13:34:50,806 [       cli] [INFO] Ran for 0:00:06
2016-08-13 13:34:50,806 [       cli] [INFO] Total XP Earned: 0  Average: 0.00/h
2016-08-13 13:34:50,806 [       cli] [INFO] Travelled 0.00km
2016-08-13 13:34:50,806 [       cli] [INFO] Visited 0 stops
2016-08-13 13:34:50,806 [       cli] [INFO] Encountered 0 pokemon, 0 caught, 0 released, 0 evolved, 0 never seen before
2016-08-13 13:34:50,807 [       cli] [INFO] Threw 0 pokeballs
2016-08-13 13:34:50,807 [       cli] [INFO] Earned 0 Stardust
2016-08-13 13:34:50,807 [       cli] [INFO]
2016-08-13 13:34:50,807 [       cli] [INFO] Highest CP Pokemon:
2016-08-13 13:34:50,807 [       cli] [INFO] Most Perfect Pokemon:
Traceback (most recent call last):
  File "pokecli.py", line 578, in <module>
    main()
  File "pokecli.py", line 103, in main
    bot.tick()
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/__init__.py", line 482, in tick
    if worker.work() == WorkerResult.RUNNING:
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/cell_workers/pokemon_optimizer.py", line 49, in work
    transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family)
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/cell_workers/pokemon_optimizer.py", line 75, in get_family_optimized
    return self.get_multi_family_optimized(family_id, family, 3)
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/cell_workers/pokemon_optimizer.py", line 101, in get_multi_family_optimized
    transfer_senior += self.get_family_optimized(senior_pid, senior_family)[0]
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/cell_workers/pokemon_optimizer.py", line 82, in get_family_optimized
    evolve_best += self.get_top_rank(family, criteria)
  File "/Users/scalp/pokemon/new-dev-pokemongo-bot/pokemongo_bot/cell_workers/pokemon_optimizer.py", line 135, in get_top_rank
    worst = sorted_family[criteria.get("top", 1) - 1]
IndexError: list index out of range
```

``` json
    {
        "type": "PokemonOptimizer",
        "config": {
                "// the 'transfer' parameter activate or deactivate the transfer of pokemons": {},
                "// at false, no pokemon is going to be transfered, ever": {},
                "// at false, you will still get the log information of what the optimizer": {},
                "// would have transfered if the parameter was true": {},
            "transfer": true,
                "// the 'evolve' parameter activate or deactivate the evolution of pokemons": {},
                "// at false, no pokemon is going to be evolved, ever": {},
                "// at false, you will still get the log information of what the": {},
                "// optimizer would have evolved if the parameter was true": {},
            "evolve": true,
                "// the 'use_candies_for_xp' parameter let you choose if you want the optimizer": {},
                "// to use your candies to evolve low quality pokemons in order to maximize your xp": {},
                "// at false, the optimizer will still use candies to evolve your best Pokemons": {},
            "use_candies_for_xp": false,
                "// the 'use_lucky_egg' parameter let you choose if you want the optimizer": {},
                "// to use a lucky egg right before evolving Pokemons. At false; the optimizer": {},
                "// is free to evolve Pokemons even if you do not have any lucky egg.": {},
            "use_lucky_egg": true,
                "// the 'evolve_only_with_lucky_egg' parameter let you choose if you want the optimizer": {},
                "// to only Evolve Pokemons when a lucky egg is available": {},
            "evolve_only_with_lucky_egg": true,
                "// the 'minimum_evolve_for_lucky_egg' parameter let you define the minimum": {},
                "// number of Pokemons that must evolve before using a lucky egg": {},
                "// If that number is not reached, and evolve_only_with_lucky_egg is true, evolution will be skipped": {},
                "// If that number is not reached, and evolve_only_with_lucky_egg is false,": {},
                "// evolution will be performed without using a lucky egg": {},
            "minimum_evolve_for_lucky_egg": 90,
                "// the 'keep' parameter let you define what pokemons you consider are the 'best'. These Pokemons": {},
                "// will be keep and evolved. Note that Pokemons are evaluated inside their whole family": {},
                "// Multiple way of ranking can be defined. Following configuration let you keep the best iv,": {},
                "// the best ncp and the best cp": {},
            "keep": [
                {
                        "// Following setting let you keep the best iv of the family": {},
                        "// the 'top' parameter allow you to define how many Pokemons you want to keep": {},
                        "// at the top of your ranking. If several Pokemons get the same score, they are": {},
                        "// considered equal. Thus, top=1 might result in keeping more than 1 Pokemon.": {},
                    "top": 1,
                        "// the 'evolve' parameter let you choose if you want to evolve the Pokemons you keep": {},
                    "evolve": true,
                        "// the 'sort' parameter define how you want to rank your pokemons": {},
                        "// Critera are sorted fro, the most important to the least important.": {},
                        "// Available criteria are:": {},
                        "// 'iv' = individual value": {},
                        "// 'ivcp' = iv weigted so that for equal iv, attack > defense > stamina": {},
                        "// 'cp' = combat power (can be increased with candies)": {},
                        "// 'cp_exact' = combar power (not rounded)": {},
                        "// 'ncp' (normalized cp) or 'cp_percent' = ratio cp / max_cp": {},
                        "// iv_attack = attach component of iv": {},
                        "// iv_defense = defense component of iv": {},
                        "// iv_stamina = stamina component of iv": {},
                        "// dps = raw dps based on the moves of the pokemon": {},
                        "// dps_attack = average dps when attacking": {},
                        "// dps_defense = average dps when defending": {},
                        "// Note that the more criteria you add to this list, the less likely Pokemons": {},
                        "// will be equals": {},
                    "sort": ["iv"]
                },
                {
                    "// Following setting let you keep keep the best normalized cp of the family": {},
                    "// That is the Pokemon with higher CP once fully evolved": {},
                    "top": 2,
                    "evolve": true,
                    "sort": ["ncp"]
                },
                {
                    "// Following setting let you keep keep the best cp of the family.": {},
                    "// But will not evolve it further (in favor of the best ncp)": {},
                    "top": 2,
                    "evolve": false,
                    "sort": ["cp"]
                }
            ]
        }
    },
```

Branch: dev
Git Commit: `d4200e9b34ef6ec9826eb81a6ceb420162b8c674`
Python Version: `2.7.12`



Discussion:

Resolving patch:
diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py
--- a/pokemongo_bot/cell_workers/pokemon_optimizer.py
+++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py
@@ -1,52 +1,68 @@
 import copy
-import logging
+import json
+import math
+import os
 
 from pokemongo_bot import inventory
+from pokemongo_bot.base_dir import _base_dir
 from pokemongo_bot.base_task import BaseTask
 from pokemongo_bot.human_behaviour import sleep, action_delay
 from pokemongo_bot.item_list import Item
 from pokemongo_bot.worker_result import WorkerResult
 
+SUCCESS = 1
+ERROR_INVALID_ITEM_TYPE = 2
+ERROR_XP_BOOST_ALREADY_ACTIVE = 3
+ERROR_NO_ITEMS_REMAINING = 4
+ERROR_LOCATION_UNSET = 5
+
 
 class PokemonOptimizer(BaseTask):
     SUPPORTED_TASK_API_VERSION = 1
 
     def initialize(self):
         self.family_by_family_id = {}
+        self.max_pokemon_storage = 0
         self.last_pokemon_count = 0
-        self.logger = logging.getLogger(self.__class__.__name__)
 
         self.config_transfer = self.config.get("transfer", False)
         self.config_evolve = self.config.get("evolve", False)
-        self.config_use_candies_for_xp = self.config.get("use_candies_for_xp", True)
-        self.config_use_lucky_egg = self.config.get("use_lucky_egg", False)
-        self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", True)
-        self.config_minimum_evolve_for_lucky_egg = self.config.get("minimum_evolve_for_lucky_egg", 90)
+        self.config_evolve_time = self.config.get("evolve_time", 20)
+        self.config_evolve_for_xp = self.config.get("evolve_for_xp", True)
+        self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", False)
+        self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 90)
+        self.config_may_use_lucky_egg = self.config.get("may_use_lucky_egg", False)
         self.config_keep = self.config.get("keep", [{"top": 1, "evolve": True, "sort": ["iv"]},
                                                     {"top": 1, "evolve": True, "sort": ["ncp"]},
                                                     {"top": 1, "evolve": False, "sort": ["cp"]}])
 
     def get_pokemon_slot_left(self):
         pokemon_count = len(inventory.pokemons()._data)
-        
+        self.max_pokemon_storage = self.bot.player_data["max_pokemon_storage"]
+
         if pokemon_count != self.last_pokemon_count:
             self.last_pokemon_count = pokemon_count
-            self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.bot._player["max_pokemon_storage"])
-        
-        return self.bot._player["max_pokemon_storage"] - pokemon_count
+            self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.max_pokemon_storage)
+
+        return self.max_pokemon_storage - pokemon_count
 
     def work(self):
-        if self.get_pokemon_slot_left() > 5:
+        if (not self.enabled) or (self.get_pokemon_slot_left() > 5):
             return WorkerResult.SUCCESS
 
-        self.parse_inventory()
+        self.open_inventory()
+        self.save_web_inventory()
 
         transfer_all = []
         evo_all_best = []
         evo_all_crap = []
 
         for family_id, family in self.family_by_family_id.items():
-            transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family)
+            if family_id == 133:  # "Eevee"
+                transfer, evo_best, evo_crap = self.get_multi_family_optimized(family_id, family, 3)
+            else:
+                transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family)
+
             transfer_all += transfer
             evo_all_best += evo_best
             evo_all_crap += evo_crap
@@ -54,14 +70,13 @@ def work(self):
         evo_all = evo_all_best + evo_all_crap
 
         self.apply_optimization(transfer_all, evo_all)
-        inventory.refresh_inventory()
 
         return WorkerResult.SUCCESS
 
-    def parse_inventory(self):
+    def open_inventory(self):
         self.family_by_family_id.clear()
 
-        for pokemon in inventory.pokemons().all():
+        for pokemon in inventory.pokemons(True).all():
             family_id = pokemon.first_evolution_id
             setattr(pokemon, "ncp", pokemon.cp_percent)
             setattr(pokemon, "dps", pokemon.moveset.dps)
@@ -70,10 +85,14 @@ def parse_inventory(self):
 
             self.family_by_family_id.setdefault(family_id, []).append(pokemon)
 
-    def get_family_optimized(self, family_id, family):
-        if family_id == 133:  # "Eevee"
-            return self.get_multi_family_optimized(family_id, family, 3)
+    def save_web_inventory(self):
+        inventory_items = self.bot.get_inventory()["responses"]["GET_INVENTORY"]["inventory_delta"]["inventory_items"]
+        web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username)
+
+        with open(web_inventory, "w") as outfile:
+            json.dump(inventory_items, outfile)
 
+    def get_family_optimized(self, family_id, family):
         evolve_best = []
         keep_best = []
 
@@ -95,16 +114,12 @@ def get_multi_family_optimized(self, family_id, family, nb_branch):
         senior_pids = set(p.pokemon_id for p in senior_family)
         senior_grouped_family = {pid: [p for p in senior_family if p.pokemon_id == pid] for pid in senior_pids}
 
-        transfer_senior = []
-
-        for senior_pid, senior_family in senior_grouped_family.items():
-            transfer_senior += self.get_family_optimized(senior_pid, senior_family)[0]
-
-        if len(senior_pids) < nb_branch:
+        if not self.config_evolve:
+            transfer, evo_best, evo_crap = self.get_family_optimized(family_id, other_family)
+        elif len(senior_pids) < nb_branch:
             # We did not get every combination yet = All other Pokemons are potentially good to keep
-            evolve_best = other_family
-            evolve_best.sort(key=lambda p: p.iv * p.ncp, reverse=True)
-            keep_best = []
+            transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, [], other_family, [])
+            evo_best.sort(key=lambda p: p.iv * p.ncp, reverse=True)
         else:
             evolve_best = []
             keep_best = []
@@ -124,16 +139,22 @@ def get_multi_family_optimized(self, family_id, family, nb_branch):
 
             evolve_best = self.unique_pokemons(evolve_best)
             keep_best = self.unique_pokemons(keep_best)
+            transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best)
 
-        transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best)
-        transfer += transfer_senior
+        for senior_pid, senior_family in senior_grouped_family.items():
+            transfer += self.get_family_optimized(senior_pid, senior_family)[0]
 
         return (transfer, evo_best, evo_crap)
 
     def get_top_rank(self, family, criteria):
         sorted_family = self.get_sorted_family(family, criteria)
-        worst = sorted_family[criteria.get("top", 1) - 1]
-        return [p for p in sorted_family if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)]
+        index = criteria.get("top", 1) - 1
+
+        if 0 <= index < len(sorted_family):
+            worst = sorted_family[index]
+            return [p for p in sorted_family if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)]
+        else:
+            return sorted_family
 
     def get_better_rank(self, family, criteria, worst):
         return [p for p in self.get_sorted_family(family, criteria) if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)]
@@ -158,7 +179,7 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best):
         crap = family[:]
         crap = [p for p in crap if p not in evolve_best]
         crap = [p for p in crap if p not in keep_best]
-        crap.sort(key=lambda p: p.iv, reverse=True)
+        crap.sort(key=lambda p: p.iv * p.ncp, reverse=True)
 
         candies += len(crap)
 
@@ -184,7 +205,7 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best):
             next_evo.name = inventory.pokemons().name_for(next_pid)
             evolve_best.append(next_evo)
 
-        if self.config_use_candies_for_xp:
+        if self.config_evolve_for_xp:
             # Compute how many crap we should keep if we want to batch evolve them for xp
             junior_evolution_cost = inventory.pokemons().evolution_cost_for(family_id)
 
@@ -201,6 +222,11 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best):
                 keep_for_evo = 0
 
             evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo]
+
+            # If not much to evolve, better keep the candies
+            if len(evo_crap) < math.ceil(self.max_pokemon_storage * 0.01):
+                evo_crap = []
+
             transfer = [p for p in crap if p not in evo_crap]
         else:
             evo_crap = []
@@ -209,25 +235,27 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best):
         return (transfer, can_evolve_best, evo_crap)
 
     def apply_optimization(self, transfer, evo):
+        self.logger.info("Transferring %s Pokemons", len(transfer))
+
         for pokemon in transfer:
             self.transfer_pokemon(pokemon)
 
         if len(evo) == 0:
             return
 
-        if self.config_evolve and self.config_use_lucky_egg and (not self.bot.config.test):
-            lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value)  # @UndefinedVariable
-
-            if self.config_evolve_only_with_lucky_egg and (lucky_egg.count == 0):
-                self.logger.info("Skipping evolution step. No lucky egg available")
+        if self.config_evolve and self.config_may_use_lucky_egg and (not self.bot.config.test):
+            if len(evo) >= self.config_evolve_count_for_lucky_egg:
+                lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value)  # @UndefinedVariable
+
+                if lucky_egg.count > 0:
+                    self.use_lucky_egg()
+                elif self.config_evolve_only_with_lucky_egg:
+                    self.logger.info("Skipping evolution step. No lucky egg available")
+                    return
+            elif self.config_evolve_only_with_lucky_egg:
+                self.logger.info("Skipping evolution step. Not enough Pokemons (%s) to evolve with lucky egg", len(evo))
                 return
 
-            if len(evo) < self.config_minimum_evolve_for_lucky_egg:
-                self.logger.info("Skipping evolution step. Not enough Pokemons (%s) to evolve", len(evo))
-                return
-
-            self.use_lucky_egg()
-
         self.logger.info("Evolving %s Pokemons", len(evo))
 
         for pokemon in evo:
@@ -276,13 +304,18 @@ def use_lucky_egg(self):
 
         result = response_dict.get("responses", {}).get("USE_ITEM_XP_BOOST", {}).get("result", 0)
 
-        if result == 1:
+        if result == SUCCESS:
             lucky_egg.remove(1)
 
             self.emit_event("used_lucky_egg",
                             formatted="Used lucky egg ({amount_left} left).",
                             data={"amount_left": lucky_egg.count})
             return True
+        elif result == ERROR_XP_BOOST_ALREADY_ACTIVE:
+            self.emit_event("used_lucky_egg",
+                            formatted="Lucky egg already active ({amount_left} left).",
+                            data={"amount_left": lucky_egg.count})
+            return True
         else:
             self.emit_event("lucky_egg_error",
                             level='error',
@@ -300,7 +333,7 @@ def evolve_pokemon(self, pokemon):
 
         result = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("result", 0)
 
-        if result != 1:
+        if result != SUCCESS:
             return False
 
         xp = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("experience_awarded", 0)
@@ -323,6 +356,6 @@ def evolve_pokemon(self, pokemon):
             new_pokemon = inventory.Pokemon(evolution)
             inventory.pokemons().add(new_pokemon)
 
-            sleep(20)
+            sleep(self.config_evolve_time)
 
         return True


Test patch: