From 204ad0e1b283121a55759161d4b19f7c8d717ebc Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 17:49:23 +0200 Subject: [PATCH 01/24] catching every single pokemon nearby --- pokemongo_bot/cell_workers/catch_visible_pokemon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py index 1bfed225df..eafe866132 100644 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -17,6 +17,8 @@ def work(self): lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude']) ) user_web_catchable = 'web/catchable-{}.json'.format(self.bot.config.username) + + for pokemon in self.bot.cell['catchable_pokemons']: with open(user_web_catchable, 'w') as outfile: json.dump(pokemon, outfile) @@ -33,7 +35,7 @@ def work(self): } ) - return self.catch_pokemon(self.bot.cell['catchable_pokemons'].pop(0)) + self.catch_pokemon(self.bot.cell['catchable_pokemons'].pop(0)) if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0: # Sort all by distance from current pos- eventually this should From c9b25d6870e75521948ece78e54ec8c740aa2878 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 17:49:34 +0200 Subject: [PATCH 02/24] catch lured pokemon in all forts nearby --- .../cell_workers/catch_lured_pokemon.py | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/pokemongo_bot/cell_workers/catch_lured_pokemon.py b/pokemongo_bot/cell_workers/catch_lured_pokemon.py index 10a046dce9..14dd1dc568 100644 --- a/pokemongo_bot/cell_workers/catch_lured_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_lured_pokemon.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from pokemongo_bot.cell_workers.utils import fort_details -from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.constants import Constants +from pokemongo_bot.cell_workers.utils import fort_details, distance +from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker class CatchLuredPokemon(BaseTask): @@ -12,23 +13,37 @@ class CatchLuredPokemon(BaseTask): def work(self): lured_pokemon = self.get_lured_pokemon() if lured_pokemon: - self.catch_pokemon(lured_pokemon) + for pokemon in lured_pokemon: + self.catch_pokemon(pokemon) def get_lured_pokemon(self): + forts_in_range = [] + pokemon_to_catch = [] forts = self.bot.get_forts(order_by_distance=True) if len(forts) == 0: return False - fort = forts[0] - details = fort_details(self.bot, fort_id=fort['id'], - latitude=fort['latitude'], - longitude=fort['longitude']) - fort_name = details.get('name', 'Unknown') + for fort in forts: + distance_to_fort = distance( + self.bot.position[0], + self.bot.position[1], + fort['latitude'], + fort['longitude'] + ) + + encounter_id = fort.get('lure_info', {}).get('encounter_id', None) + if distance_to_fort < Constants.MAX_DISTANCE_FORT_IS_REACHABLE and encounter_id: + forts_in_range.append(fort) + - encounter_id = fort.get('lure_info', {}).get('encounter_id', None) + for fort in forts_in_range: + details = fort_details(self.bot, fort_id=fort['id'], + latitude=fort['latitude'], + longitude=fort['longitude']) + fort_name = details.get('name', 'Unknown') + encounter_id = fort['lure_info']['encounter_id'] - if encounter_id: result = { 'encounter_id': encounter_id, 'fort_id': fort['id'], @@ -36,15 +51,14 @@ def get_lured_pokemon(self): 'latitude': fort['latitude'], 'longitude': fort['longitude'] } + pokemon_to_catch.append(result) self.emit_event( 'lured_pokemon_found', formatted='Lured pokemon at fort {fort_name} ({fort_id})', data=result ) - return result - - return False + return pokemon_to_catch def catch_pokemon(self, pokemon): worker = PokemonCatchWorker(pokemon, self.bot) From 081d90de32de84810120a50b238b70093e51ef17 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 18:50:47 +0200 Subject: [PATCH 03/24] adding run_interval to some tasks to avoid running all the time and minimum tick time of 5 seconds Tasks inheriting from BaseTask should use `self._update_last_ran` and `_time_to_run` if they want to implement the time based running. The config to set a custom timer is named `run_interval`. --- pokemongo_bot/__init__.py | 14 +++- pokemongo_bot/base_task.py | 66 +++++++++++-------- .../cell_workers/catch_visible_pokemon.py | 20 +++++- .../cell_workers/collect_level_up_reward.py | 4 ++ pokemongo_bot/cell_workers/evolve_pokemon.py | 5 ++ pokemongo_bot/cell_workers/incubate_eggs.py | 4 ++ .../cell_workers/nickname_pokemon.py | 5 ++ .../cell_workers/pokemon_catch_worker.py | 2 +- pokemongo_bot/cell_workers/recycle_items.py | 4 ++ .../cell_workers/transfer_pokemon.py | 3 + 10 files changed, 93 insertions(+), 34 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index f60594b8d8..93645f6314 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -8,6 +8,7 @@ import random import re import sys +import struct import time from geopy.geocoders import GoogleV3 @@ -29,7 +30,9 @@ from worker_result import WorkerResult from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder from sys import platform as _platform -import struct + + + class PokemonGoBot(object): @property def position(self): @@ -391,11 +394,16 @@ def tick(self): # Check if session token has expired self.check_session(self.position[0:2]) + start_tick = time.time() for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return + end_tick = time.time() + if end_tick - start_tick < 5: + time.sleep(5 - (end_tick - start_tick)) + def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) @@ -540,14 +548,14 @@ def check_session(self, position): # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): - self.logger.info("Ticket expired value is not numeric", 'yellow') + self.logger.info("Ticket expired value is not numeric") return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: - self.logger.info("Session stale, re-logging in", 'yellow') + self.logger.info("Session stale, re-logging in") position = self.position self.api = ApiWrapper() self.position = position diff --git a/pokemongo_bot/base_task.py b/pokemongo_bot/base_task.py index 22bbedf4e8..d8214d971f 100644 --- a/pokemongo_bot/base_task.py +++ b/pokemongo_bot/base_task.py @@ -1,31 +1,43 @@ import logging +import time class BaseTask(object): - TASK_API_VERSION = 1 - - def __init__(self, bot, config): - self.bot = bot - self.config = config - self._validate_work_exists() - self.logger = logging.getLogger(type(self).__name__) - self.initialize() - - def _validate_work_exists(self): - method = getattr(self, 'work', None) - if not method or not callable(method): - raise NotImplementedError('Missing "work" method') - - def emit_event(self, event, sender=None, level='info', formatted='', data={}): - if not sender: - sender=self - self.bot.event_manager.emit( - event, - sender=sender, - level=level, - formatted=formatted, - data=data - ) - - def initialize(self): - pass + TASK_API_VERSION = 1 + + def __init__(self, bot, config): + self.bot = bot + self.config = config + self._validate_work_exists() + self.logger = logging.getLogger(type(self).__name__) + self.last_ran = time.time() + self.run_interval = config.get('run_interval', 10) + self.initialize() + + def _update_last_ran(self): + self.last_ran = time.time() + + def _time_to_run(self): + interval = time.time() - self.last_ran + if interval > self.run_interval: + return True + return False + + def _validate_work_exists(self): + method = getattr(self, 'work', None) + if not method or not callable(method): + raise NotImplementedError('Missing "work" method') + + def emit_event(self, event, sender=None, level='info', formatted='', data={}): + if not sender: + sender=self + self.bot.event_manager.emit( + event, + sender=sender, + level=level, + formatted=formatted, + data=data + ) + + def initialize(self): + pass diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py index eafe866132..880c23c90a 100644 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -24,7 +24,7 @@ def work(self): json.dump(pokemon, outfile) self.emit_event( 'catchable_pokemon', - level='debug', + level='info', data={ 'pokemon_id': pokemon['pokemon_id'], 'spawn_point_id': pokemon['spawn_point_id'], @@ -35,7 +35,7 @@ def work(self): } ) - self.catch_pokemon(self.bot.cell['catchable_pokemons'].pop(0)) + self.catch_pokemon(pokemon) if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0: # Sort all by distance from current pos- eventually this should @@ -43,7 +43,21 @@ def work(self): self.bot.cell['wild_pokemons'].sort( key= lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude'])) - return self.catch_pokemon(self.bot.cell['wild_pokemons'].pop(0)) + + for pokemon in self.bot.cell['wild_pokemons']: + self.emit_event( + 'catchable_pokemon', + level='info', + data={ + 'pokemon_id': pokemon['pokemon_data']['pokemon_id'], + 'spawn_point_id': pokemon['spawn_point_id'], + 'encounter_id': pokemon['encounter_id'], + 'latitude': pokemon['latitude'], + 'longitude': pokemon['longitude'], + 'expiration_timestamp_ms': pokemon['time_till_hidden_ms'], + } + ) + self.catch_pokemon(pokemon) def catch_pokemon(self, pokemon): worker = PokemonCatchWorker(pokemon, self.bot) diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index 950f450660..f4925f9b3b 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -12,6 +12,10 @@ def initialize(self): self.previous_level = 0 def work(self): + if not self._time_to_run(): + return False + self._update_last_ran() + self.current_level = self._get_current_level() # let's check level reward on bot initialization diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 74eb0abf79..8c3090006f 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -25,6 +25,8 @@ def work(self): if not self._should_run(): return + self._update_last_ran() + response_dict = self.api.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get( 'inventory_items', {}) @@ -42,6 +44,9 @@ def work(self): self._execute_pokemon_evolve(pokemon, candy_list, cache) def _should_run(self): + if not self._time_to_run(): + return False + if not self.evolve_all or self.evolve_all[0] == 'none': return False diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 5761090ea5..933e9d4018 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -21,6 +21,10 @@ def _process_config(self): self.longer_eggs_first = self.config.get("longer_eggs_first", True) def work(self): + if not self._time_to_run(): + return False + self._update_last_ran() + try: self._check_inventory() except: diff --git a/pokemongo_bot/cell_workers/nickname_pokemon.py b/pokemongo_bot/cell_workers/nickname_pokemon.py index cda206ad20..7fef6cfdb1 100644 --- a/pokemongo_bot/cell_workers/nickname_pokemon.py +++ b/pokemongo_bot/cell_workers/nickname_pokemon.py @@ -1,6 +1,7 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask + class NicknamePokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -10,6 +11,10 @@ def initialize(self): self.template = "" def work(self): + if not self._time_to_run(): + return False + self._update_last_ran() + try: inventory = reduce(dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], self.bot.get_inventory()) except KeyError: diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index d676e9f0e2..e17ccfe026 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -380,7 +380,7 @@ def work(self, response_dict=None): data={'pokemon': pokemon_name} ) break - time.sleep(5) + time.sleep(2) def count_pokemon_inventory(self): # don't use cached bot.get_inventory() here diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 2c969913b0..47e2f06233 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -18,6 +18,10 @@ def _validate_item_filter(self): raise ConfigException("item {} does not exist, spelling mistake? (check for valid item names in data/items.json)".format(config_item_name)) def work(self): + if not self._time_to_run(): + return False + self._update_last_ran() + self.bot.latest_inventory = None item_count_dict = self.bot.item_inventory_count('all') diff --git a/pokemongo_bot/cell_workers/transfer_pokemon.py b/pokemongo_bot/cell_workers/transfer_pokemon.py index c48e17b20d..90c8b9eb83 100644 --- a/pokemongo_bot/cell_workers/transfer_pokemon.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -8,6 +8,9 @@ class TransferPokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def work(self): + if not self._time_to_run(): + return False + self._update_last_ran() pokemon_groups = self._release_pokemon_get_groups() for pokemon_id in pokemon_groups: group = pokemon_groups[pokemon_id] From 4f3a7447510bab4f45d28ed05570e354aebc6047 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 19:31:54 +0200 Subject: [PATCH 04/24] added config to ignore item count for Spin and MoveToFort this works good with the `run_interval` configuration added to TransferPokemon and RecycleItem --- pokemongo_bot/cell_workers/catch_visible_pokemon.py | 1 - pokemongo_bot/cell_workers/move_to_fort.py | 9 +++++---- pokemongo_bot/cell_workers/spin_fort.py | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py index 880c23c90a..9af3a12c92 100644 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -34,7 +34,6 @@ def work(self): 'expiration_timestamp_ms': pokemon['expiration_timestamp_ms'], } ) - self.catch_pokemon(pokemon) if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0: diff --git a/pokemongo_bot/cell_workers/move_to_fort.py b/pokemongo_bot/cell_workers/move_to_fort.py index e4b4187d20..7dcd0977b1 100644 --- a/pokemongo_bot/cell_workers/move_to_fort.py +++ b/pokemongo_bot/cell_workers/move_to_fort.py @@ -13,17 +13,18 @@ class MoveToFort(BaseTask): def initialize(self): self.lure_distance = 0 - self.lure_attraction = True #self.config.get("lure_attraction", True) - self.lure_max_distance = 2000 #self.config.get("lure_max_distance", 2000) + self.lure_attraction = self.config.get("lure_attraction", True) + self.lure_max_distance = self.config.get("lure_max_distance", 2000) + self.ignore_item_count = self.config.get("ignore_item_count", False) def should_run(self): has_space_for_loot = self.bot.has_space_for_loot() if not has_space_for_loot: self.emit_event( 'inventory_full', - formatted="Not moving to any forts as there aren't enough space. You might want to change your config to recycle more items if this message appears consistently." + formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently." ) - return has_space_for_loot or self.bot.softban + return has_space_for_loot or self.ignore_item_count or self.bot.softban def is_attracted(self): return (self.lure_distance > 0) diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index e04a86dbc6..3805f7b6b4 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -15,14 +15,16 @@ class SpinFort(BaseTask): SUPPORTED_TASK_API_VERSION = 1 + def initialize(self): + self.ignore_item_count = self.config.get("ignore_item_count", False) + def should_run(self): if not self.bot.has_space_for_loot(): self.emit_event( 'inventory_full', - formatted="Not moving to any forts as there aren't enough space. You might want to change your config to recycle more items if this message appears consistently." + formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently." ) - return False - return True + return self.ignore_item_count or self.bot.has_space_for_loot() def work(self): fort = self.get_fort_in_range() From 235f6bfbbbff1577a323a553e6b913e3cd2116c2 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 20:00:06 +0200 Subject: [PATCH 05/24] spinning all pokestops in range --- pokemongo_bot/cell_workers/spin_fort.py | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 3805f7b6b4..533cf0a92a 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -27,11 +27,17 @@ def should_run(self): return self.ignore_item_count or self.bot.has_space_for_loot() def work(self): - fort = self.get_fort_in_range() - - if not self.should_run() or fort is None: + forts = self.get_fort_in_range() + if not self.should_run() or not forts: return WorkerResult.SUCCESS + for fort in forts: + self.spin_fort(fort) + sleep(1) + + return WorkerResult.SUCCESS + + def spin_fort(self, fort): lat = fort['latitude'] lng = fort['longitude'] @@ -141,21 +147,22 @@ def work(self): def get_fort_in_range(self): forts = self.bot.get_forts(order_by_distance=True) + forts = filter(lambda x: 'cooldown_complete_timestamp_ms' not in x, forts) + forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) - if len(forts) == 0: - return None + forts_in_range = [] - fort = forts[0] + for fort in forts: - distance_to_fort = distance( - self.bot.position[0], - self.bot.position[1], - fort['latitude'], - fort['longitude'] - ) + distance_to_fort = distance( + self.bot.position[0], + self.bot.position[1], + fort['latitude'], + fort['longitude'] + ) - if distance_to_fort <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: - return fort + if distance_to_fort <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: + forts_in_range.append(fort) - return None + return forts_in_range From 1fd60e71828e363a73af03295317ce696264e30d Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Sun, 7 Aug 2016 23:19:59 +0200 Subject: [PATCH 06/24] fixing loop in spin fort task --- pokemongo_bot/cell_workers/evolve_pokemon.py | 2 +- pokemongo_bot/cell_workers/spin_fort.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 8c3090006f..8ca8557fbb 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -64,7 +64,7 @@ def _should_run(self): if result is 1: # Request success self.emit_event( 'used_lucky_egg', - formmated='Used lucky egg ({amount_left} left).', + formatted='Used lucky egg ({amount_left} left).', data={ 'amount_left': lucky_egg_count - 1 } diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 533cf0a92a..46cd06218a 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -147,7 +147,10 @@ def spin_fort(self, fort): def get_fort_in_range(self): forts = self.bot.get_forts(order_by_distance=True) - forts = filter(lambda x: 'cooldown_complete_timestamp_ms' not in x, forts) + for fort in forts: + if 'cooldown_complete_timestamp_ms' in fort: + self.bot.fort_timeouts[fort["id"]] = fort['cooldown_complete_timestamp_ms'] + forts.remove(fort) forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) From 1cc011c9c903f2e4c7c453274fe2fcab6a312d13 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Mon, 8 Aug 2016 07:51:41 +0800 Subject: [PATCH 07/24] First basic features of the pokemon optimizer --- data/pokemon.json | 4380 ++++++++++++++++- pokemongo_bot/__init__.py | 2 +- pokemongo_bot/cell_workers/__init__.py | 1 + .../cell_workers/pokemon_optimizer.py | 320 ++ 4 files changed, 4701 insertions(+), 2 deletions(-) create mode 100644 pokemongo_bot/cell_workers/pokemon_optimizer.py diff --git a/data/pokemon.json b/data/pokemon.json index a227106841..705e9c1f75 100644 --- a/data/pokemon.json +++ b/data/pokemon.json @@ -1 +1,4379 @@ -[{"Number":"001","Name":"Bulbasaur","Classification":"Seed Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Tackle","Vine Whip"],"Weight":"6.9 kg","Height":"0.7 m","Next Evolution Requirements":{"Amount":25,"Name":"Bulbasaur candies"},"Next evolution(s)":[{"Number":"002","Name":"Ivysaur"},{"Number":"003","Name":"Venusaur"}]},{"Number":"002","Name":"Ivysaur","Classification":"Seed Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Razor Leaf","Vine Whip"],"Weight":"13.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"001","Name":"Bulbasaur"}],"Next Evolution Requirements":{"Amount":100,"Name":"Bulbasaur candies"},"Next evolution(s)":[{"Number":"003","Name":"Venusaur"}]},{"Number":"003","Name":"Venusaur","Classification":"Seed Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Razor Leaf","Vine Whip"],"Weight":"100.0 kg","Height":"2.0 m","Previous evolution(s)":[{"Number":"001","Name":"Bulbasaur"},{"Number":"002","Name":"Ivysaur"}]},{"Number":"004","Name":"Charmander","Classification":"Lizard Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Scratch"],"Weight":"8.5 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":25,"Name":"Charmander candies"},"Next evolution(s)":[{"Number":"005","Name":"Charmeleon"},{"Number":"006","Name":"Charizard"}]},{"Number":"005","Name":"Charmeleon","Classification":"Flame Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember",""],"Weight":"19.0 kg","Height":"1.1 m","Previous evolution(s)":[{"Number":"004","Name":"Charmander"}],"Next Evolution Requirements":{"Amount":100,"Name":"Charmander candies"},"Next evolution(s)":[{"Number":"006","Name":"Charizard"}]},{"Number":"006","Name":"Charizard","Classification":"Flame Pokemon","Type I":["Fire"],"Type II":["Flying"],"Weaknesses":["Water","Electric","Rock"],"Fast Attack(s)":["Ember","Wing Attack"],"Weight":"90.5 kg","Height":"1.7 m","Previous evolution(s)":[{"Number":"004","Name":"Charmander"},{"Number":"005","Name":"Charmeleon"}]},{"Number":"007","Name":"Squirtle","Classification":"Tiny Turtle Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Tackle","Bubble"],"Weight":"9.0 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":25,"Name":"Squirtle candies"},"Next evolution(s)":[{"Number":"008","Name":"Wartortle"},{"Number":"009","Name":"Blastoise"}]},{"Number":"008","Name":"Wartortle","Classification":"Turtle Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bite","Water Gun"],"Weight":"22.5 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"007","Name":"Squirtle"}],"Next Evolution Requirements":{"Amount":100,"Name":"Squirtle candies"},"Next evolution(s)":[{"Number":"009","Name":"Blastoise"}]},{"Number":"009","Name":"Blastoise","Classification":"Shellfish Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bite","Water Gun"],"Weight":"85.5 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"007","Name":"Squirtle"},{"Number":"008","Name":"Wartortle"}]},{"Number":"010","Name":"Caterpie","Classification":"Worm Pokemon","Type I":["Bug"],"Weaknesses":["Fire","Flying","Rock"],"Fast Attack(s)":["Bug Bite","Tackle"],"Weight":"2.9 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":12,"Name":"Caterpie candies"},"Next evolution(s)":[{"Number":"011","Name":"Metapod"},{"Number":"012","Name":"Butterfree"}]},{"Number":"011","Name":"Metapod","Classification":"Cocoon Pokemon","Type I":["Bug"],"Weaknesses":["Fire","Flying","Rock"],"Fast Attack(s)":["Bug Bite","Tackle"],"Weight":"9.9 kg","Height":"0.7 m","Previous evolution(s)":[{"Number":"010","Name":"Caterpie"}],"Next Evolution Requirements":{"Amount":50,"Name":"Caterpie candies"},"Next evolution(s)":[{"Number":"012","Name":"Butterfree"}]},{"Number":"012","Name":"Butterfree","Classification":"Butterfly Pokemon","Type I":["Bug"],"Type II":["Flying"],"Weaknesses":["Fire","Electric","Ice","Flying","Rock"],"Fast Attack(s)":["Bug Bite","Confusion"],"Weight":"32.0 kg","Height":"1.1 m","Previous evolution(s)":[{"Number":"010","Name":"Caterpie"},{"Number":"011","Name":"Metapod"}]},{"Number":"013","Name":"Weedle","Classification":"Hairy Pokemon","Type I":["Bug"],"Type II":["Poison"],"Weaknesses":["Fire","Flying","Psychic","Rock"],"Fast Attack(s)":["Bug Bite","Poison Sting"],"Weight":"3.2 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":12,"Name":"Weedle candies"},"Next evolution(s)":[{"Number":"014","Name":"Kakuna"},{"Number":"015","Name":"Beedrill"}]},{"Number":"014","Name":"Kakuna","Classification":"Cocoon Pokemon","Type I":["Bug"],"Type II":["Poison"],"Weaknesses":["Fire","Flying","Psychic","Rock"],"Fast Attack(s)":["Bug Bite","Posion Sting"],"Weight":"10.0 kg","Height":"0.6 m","Previous evolution(s)":[{"Number":"013","Name":"Weedle"}],"Next Evolution Requirements":{"Amount":50,"Name":"Weedle candies"},"Next evolution(s)":[{"Number":"015","Name":"Beedrill"}]},{"Number":"015","Name":"Beedrill","Classification":"Poison Bee Pokemon","Type I":["Bug"],"Type II":["Poison"],"Weaknesses":["Fire","Flying","Psychic","Rock"],"Fast Attack(s)":["Bug Bite","Poison Jab"],"Weight":"29.5 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"013","Name":"Weedle"},{"Number":"014","Name":"Kakuna"}]},{"Number":"016","Name":"Pidgey","Classification":"Tiny Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Quick Attack","Tackle"],"Special Attack(s)":["Aerial Ace","Air Cutter","Twister"],"Weight":"1.8 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":12,"Name":"Pidgey candies"},"Next evolution(s)":[{"Number":"017","Name":"Pidgeotto"},{"Number":"018","Name":"Pidgeot"}]},{"Number":"017","Name":"Pidgeotto","Classification":"Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Steel Wing","Wing Attack"],"Special Attack(s)":["Aerial Ace","Air Cutter","Twister"],"Weight":"30.0 kg","Height":"1.1 m","Previous evolution(s)":[{"Number":"016","Name":"Pidgey"}],"Next Evolution Requirements":{"Amount":50,"Name":"Pidgey candies"},"Next evolution(s)":[{"Number":"018","Name":"Pidgeot"}]},{"Number":"018","Name":"Pidgeot","Classification":"Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Steel Wing","Wing Attack"],"Special Attack(s)":["Hurricane"],"Weight":"39.5 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"016","Name":"Pidgey"},{"Number":"017","Name":"Pidgeotto"}]},{"Number":"019","Name":"Rattata","Classification":"Mouse Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Quick Attack","Tackle"],"Special Attack(s)":["Body Slam","Dig","Hyper Fang"],"Weight":"3.5 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":25,"Name":"Rattata candies"},"Next evolution(s)":[{"Number":"020","Name":"Raticate"}]},{"Number":"020","Name":"Raticate","Classification":"Mouse Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Bite","Quick Attack"],"Special Attack(s)":["Dig","Hyper Beam","Hyper Fang"],"Weight":"18.5 kg","Height":"0.7 m","Previous evolution(s)":[{"Number":"019","Name":"Rattata"}]},{"Number":"021","Name":"Spearow","Classification":"Tiny Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Peck","Quick Attack"],"Weight":"2.0 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":50,"Name":"Spearow candies"},"Next evolution(s)":[{"Number":"022","Name":"Fearow"}]},{"Number":"022","Name":"Fearow","Classification":"Beak Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Peck","Steel Wing"],"Weight":"38.0 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"021","Name":"Spearow"}]},{"Number":"023","Name":"Ekans","Classification":"Snake Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Acid","Poison Sting"],"Weight":"6.9 kg","Height":"2.0 m","Next Evolution Requirements":{"Amount":50,"Name":"Ekans candies"},"Next evolution(s)":[{"Number":"024","Name":"Arbok"}]},{"Number":"024","Name":"Arbok","Classification":"Cobra Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Acid","Bite"],"Weight":"65.0 kg","Height":"3.5 m","Previous evolution(s)":[{"Number":"023","Name":"Ekans"}]},{"Number":"025","Name":"Pikachu","Classification":"Mouse Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Quick Attack","Thunder Shock"],"Weight":"6.0 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Pikachu candies"},"Next evolution(s)":[{"Number":"026","Name":"Raichu"}]},{"Number":"026","Name":"Raichu","Classification":"Mouse Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Thunder Shock","Spark"],"Weight":"30.0 kg","Height":"0.8 m","Previous evolution(s)":[{"Number":"025","Name":"Pikachu"}]},{"Number":"027","Name":"Sandshrew","Classification":"Mouse Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Mud Shot","Scratch"],"Weight":"12.0 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":50,"Name":"Sandshrew candies"},"Next evolution(s)":[{"Number":"028","Name":"Sandslash"}]},{"Number":"028","Name":"Sandslash","Classification":"Mouse Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Metal Claw","Mud Shot"],"Weight":"29.5 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"027","Name":"Sandshrew"}]},{"Number":"029","Name":"Nidoran F","Classification":"Poison Pin Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Bite","Poison Sting"],"Weight":"7.0 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":25,"Name":"Nidoran F candies"},"Next evolution(s)":[{"Number":"030","Name":"Nidorina"},{"Number":"031","Name":"Nidoqueen"}]},{"Number":"030","Name":"Nidorina","Classification":"Poison Pin Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Bite","Poison Sting"],"Weight":"20.0 kg","Height":"0.8 m","Previous evolution(s)":[{"Number":"029","Name":"Nidoran F"}],"Next Evolution Requirements":{"Amount":100,"Name":"Nidoran F candies"},"Next evolution(s)":[{"Number":"031","Name":"Nidoqueen"}]},{"Number":"031","Name":"Nidoqueen","Classification":"Drill Pokemon","Type I":["Poison"],"Type II":["Ground"],"Weaknesses":["Water","Ice","Ground","Psychic"],"Fast Attack(s)":["Bite","Poison Jab"],"Weight":"60.0 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"029","Name":"Nidoran F"},{"Number":"030","Name":"Nidorina"}]},{"Number":"032","Name":"Nidoran M","Classification":"Poison Pin Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Peck","Poison Sting"],"Weight":"9.0 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":25,"Name":"Nidoran M candies"},"Next evolution(s)":[{"Number":"033","Name":"Nidorino"},{"Number":"034","Name":"Nidoking"}]},{"Number":"033","Name":"Nidorino","Classification":"Poison Pin Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Bite","Poison Jab"],"Weight":"19.5 kg","Height":"0.9 m","Previous evolution(s)":[{"Number":"032","Name":"Nidoran M"}],"Next Evolution Requirements":{"Amount":100,"Name":"Nidoran M candies"},"Next evolution(s)":[{"Number":"034","Name":"Nidoking"}]},{"Number":"034","Name":"Nidoking","Classification":"Drill Pokemon","Type I":["Poison"],"Type II":["Ground"],"Weaknesses":["Water","Ice","Ground","Psychic"],"Fast Attack(s)":["Fury Cutter","Poison Jab"],"Weight":"62.0 kg","Height":"1.4 m","Previous evolution(s)":[{"Number":"032","Name":"Nidoran M"},{"Number":"033","Name":"Nidorino"}]},{"Number":"035","Name":"Clefairy","Classification":"Fairy Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Pound","Zen Headbutt"],"Weight":"7.5 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":50,"Name":"Clefairy candies"},"Next evolution(s)":[{"Number":"036","Name":"Clefable"}]},{"Number":"036","Name":"Clefable","Classification":"Fairy Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Pound","Zen Headbutt"],"Weight":"40.0 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"035","Name":"Clefairy"}]},{"Number":"037","Name":"Vulpix","Classification":"Fox Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Quick Attack"],"Weight":"9.9 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":50,"Name":"Vulpix candies"},"Next evolution(s)":[{"Number":"038","Name":"Ninetales"}]},{"Number":"038","Name":"Ninetales","Classification":"Fox Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Quick Attack"],"Weight":"19.9 kg","Height":"1.1 m","Previous evolution(s)":[{"Number":"037","Name":"Vulpix"}]},{"Number":"039","Name":"Jigglypuff","Classification":"Balloon Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Feint Attack","Pound"],"Weight":"5.5 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":50,"Name":"Jigglypuff candies"},"Next evolution(s)":[{"Number":"039","Name":"Jigglypuff"}]},{"Number":"040","Name":"Wigglytuff","Classification":"Balloon Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Feint Attack","Pound"],"Weight":"12.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"040","Name":"Wigglytuff"}]},{"Number":"041","Name":"Zubat","Classification":"Bat Pokemon","Type I":["Poison"],"Type II":["Flying"],"Weaknesses":["Electric","Ice","Psychic","Rock"],"Fast Attack(s)":["Bite","Quick Attack"],"Weight":"7.5 kg","Height":"0.8 m","Next Evolution Requirements":{"Amount":50,"Name":"Zubat candies"},"Next evolution(s)":[{"Number":"042","Name":"Golbat"}]},{"Number":"042","Name":"Golbat","Classification":"Bat Pokemon","Type I":["Poison"],"Type II":["Flying"],"Weaknesses":["Electric","Ice","Psychic","Rock"],"Fast Attack(s)":["Bite","Wing Attack"],"Weight":"55.0 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"041","Name":"Zubat"}]},{"Number":"043","Name":"Oddish","Classification":"Weed Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid","Razor Leaf"],"Weight":"5.4 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":25,"Name":"Oddish candies"},"Next evolution(s)":[{"Number":"044","Name":"Gloom"},{"Number":"045","Name":"Vileplume"}]},{"Number":"044","Name":"Gloom","Classification":"Weed Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid","Razor Leaf"],"Weight":"8.6 kg","Height":"0.8 m","Previous evolution(s)":[{"Number":"043","Name":"Oddish"}],"Next Evolution Requirements":{"Amount":100,"Name":"Oddish candies"},"Next evolution(s)":[{"Number":"045","Name":"Vileplume"}]},{"Number":"045","Name":"Vileplume","Classification":"Flower Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid",""],"Weight":"18.6 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"043","Name":"Oddish"},{"Number":"044","Name":"Gloom"}]},{"Number":"046","Name":"Paras","Classification":"Mushroom Pokemon","Type I":["Bug"],"Type II":["Grass"],"Weaknesses":["Fire","Ice","Poison","Flying","Bug","Rock"],"Fast Attack(s)":["Bug Bite","Scratch"],"Weight":"5.4 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":50,"Name":"Paras candies"},"Next evolution(s)":[{"Number":"047","Name":"Parasect"}]},{"Number":"047","Name":"Parasect","Classification":"Mushroom Pokemon","Type I":["Bug"],"Type II":["Grass"],"Weaknesses":["Fire","Ice","Poison","Flying","Bug","Rock"],"Fast Attack(s)":["Bug Bite","Fury Cutter"],"Weight":"29.5 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"046","Name":"Paras"}]},{"Number":"048","Name":"Venonat","Classification":"Insect Pokemon","Type I":["Bug"],"Type II":["Poison"],"Weaknesses":["Fire","Flying","Psychic","Rock"],"Fast Attack(s)":["Bug Bite","Confusion"],"Weight":"30.0 kg","Height":"1.0 m","Next Evolution Requirements":{"Amount":50,"Name":"Venonat candies"},"Next evolution(s)":[{"Number":"049","Name":"Venomoth"}]},{"Number":"049","Name":"Venomoth","Classification":"Poison Moth Pokemon","Type I":["Bug"],"Type II":["Poison"],"Weaknesses":["Fire","Flying","Psychic","Rock"],"Fast Attack(s)":["Bug Bite","Confusion"],"Weight":"12.5 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"048","Name":"Venonat"}]},{"Number":"050","Name":"Diglett","Classification":"Mole Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Mud Shot","Scratch"],"Weight":"0.8 kg","Height":"0.2 m","Next Evolution Requirements":{"Amount":50,"Name":"Diglett candies"},"Next evolution(s)":[{"Number":"051","Name":"Dugtrio"}]},{"Number":"051","Name":"Dugtrio","Classification":"Mole Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Mud Shot","Sucker Punch"],"Weight":"33.3 kg","Height":"0.7 m","Previous evolution(s)":[{"Number":"050","Name":"Diglett"}]},{"Number":"052","Name":"Meowth","Classification":"Scratch Cat Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Bite","Scratch"],"Weight":"4.2 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Meowth candies"},"Next evolution(s)":[{"Number":"053","Name":"Persian"}]},{"Number":"053","Name":"Persian","Classification":"Classy Cat Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Feint Attack","Scratch"],"Weight":"32.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"052","Name":"Meowth"}]},{"Number":"054","Name":"Psyduck","Classification":"Duck Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Water Gun","Zen Headbutt"],"Weight":"19.6 kg","Height":"0.8 m","Next Evolution Requirements":{"Amount":50,"Name":"Psyduck candies"},"Next evolution(s)":[{"Number":"055","Name":"Golduck"}]},{"Number":"055","Name":"Golduck","Classification":"Duck Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Confusion","Zen Headbutt"],"Weight":"76.6 kg","Height":"1.7 m","Previous evolution(s)":[{"Number":"054","Name":"Psyduck"}]},{"Number":"056","Name":"Mankey","Classification":"Pig Monkey Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Karate Chop","Scratch"],"Weight":"28.0 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":50,"Name":"Mankey candies"},"Next evolution(s)":[{"Number":"057","Name":"Primeape"}]},{"Number":"057","Name":"Primeape","Classification":"Pig Monkey Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Karate Chop","Low Kick"],"Weight":"32.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"056","Name":"Mankey"}]},{"Number":"058","Name":"Growlithe","Classification":"Puppy Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Bite","Ember"],"Weight":"19.0 kg","Height":"0.7 m","Next Evolution Requirements":{"Amount":50,"Name":"Growlithe candies"},"Next evolution(s)":[{"Number":"059","Name":"Arcanine"}]},{"Number":"059","Name":"Arcanine","Classification":"Legendary Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Bite","Fire Fang"],"Weight":"155.0 kg","Height":"1.9 m","Previous evolution(s)":[{"Number":"058","Name":"Growlithe"}]},{"Number":"060","Name":"Poliwag","Classification":"Tadpole Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bubble","Mud Shot"],"Weight":"12.4 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":25,"Name":"Poliwag candies"},"Next evolution(s)":[{"Number":"061","Name":"Poliwhirl"},{"Number":"062","Name":"Poliwrath"}]},{"Number":"061","Name":"Poliwhirl","Classification":"Tadpole Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bubble","Mud Shot"],"Weight":"20.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"060","Name":"Poliwag"}],"Next Evolution Requirements":{"Amount":100,"Name":"Poliwag candies"},"Next evolution(s)":[{"Number":"062","Name":"Poliwrath"}]},{"Number":"062","Name":"Poliwrath","Classification":"Tadpole Pokemon","Type I":["Water"],"Type II":["Fighting"],"Weaknesses":["Electric","Grass","Flying","Psychic","Fairy"],"Fast Attack(s)":["Bubble","Mud Shot"],"Weight":"54.0 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"060","Name":"Poliwag"},{"Number":"061","Name":"Poliwhirl"}]},{"Number":"063","Name":"Abra","Classification":"Psi Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Zen Headbutt",""],"Weight":"19.5 kg","Height":"0.9 m","Next Evolution Requirements":{"Amount":25,"Name":"Abra candies"},"Next evolution(s)":[{"Number":"064","Name":"Kadabra"},{"Number":"065","Name":"Alakazam"}]},{"Number":"064","Name":"Kadabra","Classification":"Psi Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Psycho Cut"],"Weight":"56.5 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"063","Name":"Abra"}],"Next Evolution Requirements":{"Amount":100,"Name":"Abra candies"},"Next evolution(s)":[{"Number":"065","Name":"Alakazam"}]},{"Number":"065","Name":"Alakazam","Classification":"Psi Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Psycho Cut"],"Weight":"48.0 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"063","Name":"Abra"},{"Number":"064","Name":"Kadabra"}]},{"Number":"066","Name":"Machop","Classification":"Superpower Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Karate Chop","Low Kick"],"Weight":"19.5 kg","Height":"0.8 m","Next Evolution Requirements":{"Amount":25,"Name":"Machop candies"},"Next evolution(s)":[{"Number":"067","Name":"Machoke"},{"Number":"068","Name":"Machamp"}]},{"Number":"067","Name":"Machoke","Classification":"Superpower Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Karate Chop","Low Kick"],"Weight":"70.5 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"066","Name":"Machop"}],"Next Evolution Requirements":{"Amount":100,"Name":"Machop candies"},"Next evolution(s)":[{"Number":"068","Name":"Machamp"}]},{"Number":"068","Name":"Machamp","Classification":"Superpower Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Bullet Punch","Karate Chop"],"Weight":"130.0 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"066","Name":"Machop"},{"Number":"067","Name":"Machoke"}]},{"Number":"069","Name":"Bellsprout","Classification":"Flower Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid","Vine Whip"],"Weight":"4.0 kg","Height":"0.7 m","Next Evolution Requirements":{"Amount":25,"Name":"Bellsprout candies"},"Next evolution(s)":[{"Number":"070","Name":"Weepinbell"},{"Number":"071","Name":"Victreebel"}]},{"Number":"070","Name":"Weepinbell","Classification":"Flycatcher Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid","Razor Leaf"],"Weight":"6.4 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"069","Name":"Bellsprout"}],"Next Evolution Requirements":{"Amount":100,"Name":"Bellsprout candies"},"Next evolution(s)":[{"Number":"071","Name":"Victreebel"}]},{"Number":"071","Name":"Victreebel","Classification":"Flycatcher Pokemon","Type I":["Grass"],"Type II":["Poison"],"Weaknesses":["Fire","Ice","Flying","Psychic"],"Fast Attack(s)":["Acid","Razor Leaf"],"Weight":"15.5 kg","Height":"1.7 m","Previous evolution(s)":[{"Number":"069","Name":"Bellsprout"},{"Number":"070","Name":"Weepinbell"}]},{"Number":"072","Name":"Tentacool","Classification":"Jellyfish Pokemon","Type I":["Water"],"Type II":["Poison"],"Weaknesses":["Electric","Ground","Psychic"],"Fast Attack(s)":["Bubble","Poison Sting"],"Weight":"45.5 kg","Height":"0.9 m","Next Evolution Requirements":{"Amount":50,"Name":"Tentacool candies"},"Next evolution(s)":[{"Number":"073","Name":"Tentacruel"}]},{"Number":"073","Name":"Tentacruel","Classification":"Jellyfish Pokemon","Type I":["Water"],"Type II":["Poison"],"Weaknesses":["Electric","Ground","Psychic"],"Fast Attack(s)":["Acid","Poison Jab"],"Weight":"55.0 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"072","Name":"Tentacool"}]},{"Number":"074","Name":"Geodude","Classification":"Rock Pokemon","Type I":["Rock"],"Type II":["Ground"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Rock Throw","Tackle"],"Weight":"20.0 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":25,"Name":"Geodude candies"},"Next evolution(s)":[{"Number":"075","Name":"Graveler"},{"Number":"076","Name":"Golem"}]},{"Number":"075","Name":"Graveler","Classification":"Rock Pokemon","Type I":["Rock"],"Type II":["Ground"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Mud Shot","Rock Throw"],"Weight":"105.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"074","Name":"Geodude"}],"Next Evolution Requirements":{"Amount":100,"Name":"Geodude candies"},"Next evolution(s)":[{"Number":"076","Name":"Golem"}]},{"Number":"076","Name":"Golem","Classification":"Megaton Pokemon","Type I":["Rock"],"Type II":["Ground"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Mud Shot","Rock Throw"],"Weight":"300.0 kg","Height":"1.4 m","Previous evolution(s)":[{"Number":"074","Name":"Geodude"},{"Number":"075","Name":"Graveler"}]},{"Number":"077","Name":"Ponyta","Classification":"Fire Horse Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Tackle"],"Weight":"30.0 kg","Height":"1.0 m","Next Evolution Requirements":{"Amount":50,"Name":"Ponyta candies"},"Next evolution(s)":[{"Number":"078","Name":"Rapidash"}]},{"Number":"078","Name":"Rapidash","Classification":"Fire Horse Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Low Kick"],"Weight":"95.0 kg","Height":"1.7 m","Previous evolution(s)":[{"Number":"077","Name":"Ponyta"}]},{"Number":"079","Name":"Slowpoke","Classification":"Dopey Pokemon","Type I":["Water"],"Type II":["Psychic"],"Weaknesses":["Electric","Grass","Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Water Gun"],"Weight":"36.0 kg","Height":"1.2 m","Next Evolution Requirements":{"Amount":50,"Name":"Slowpoke candies"},"Next evolution(s)":[{"Number":"080","Name":"Slowbro"}]},{"Number":"080","Name":"Slowbro","Classification":"Hermit Crab Pokemon","Type I":["Water"],"Type II":["Psychic"],"Weaknesses":["Electric","Grass","Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Water Gun"],"Weight":"78.5 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"079","Name":"Slowpoke"}]},{"Number":"081","Name":"Magnemite","Classification":"Magnet Pokemon","Type I":["Electric"],"Type II":["Steel"],"Weaknesses":["Fire","Water","Ground"],"Fast Attack(s)":["Spark","Thunder Shock"],"Weight":"6.0 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":50,"Name":"Magnemite candies"},"Next evolution(s)":[{"Number":"082","Name":"Magneton"}]},{"Number":"082","Name":"Magneton","Classification":"Magnet Pokemon","Type I":["Electric"],"Type II":["Steel"],"Weaknesses":["Fire","Water","Ground"],"Fast Attack(s)":["Spark","Thunder Shock"],"Weight":"60.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"081","Name":"Magnemite"}]},{"Number":"083","Name":"Farfetch'd","Classification":"Wild Duck Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"15.0 kg","Height":"0.8 m"},{"Number":"084","Name":"Doduo","Classification":"Twin Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Peck","Quick Attack"],"Weight":"39.2 kg","Height":"1.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Doduo candies"},"Next evolution(s)":[{"Number":"085","Name":"Dodrio"}]},{"Number":"085","Name":"Dodrio","Classification":"Triple Bird Pokemon","Type I":["Normal"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Feint Attack","Steel Wing"],"Weight":"85.2 kg","Height":"1.8 m","Previous evolution(s)":[{"Number":"084","Name":"Doduo"}]},{"Number":"086","Name":"Seel","Classification":"Sea Lion Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Ice Shard","Water Gun"],"Weight":"90.0 kg","Height":"1.1 m","Next Evolution Requirements":{"Amount":50,"Name":"Seel candies"},"Next evolution(s)":[{"Number":"087","Name":"Dewgong"}]},{"Number":"087","Name":"Dewgong","Classification":"Sea Lion Pokemon","Type I":["Water"],"Type II":["Ice"],"Weaknesses":["Electric","Grass","Fighting","Rock"],"Fast Attack(s)":["Frost Breath","Ice Shard"],"Weight":"120.0 kg","Height":"1.7 m","Previous evolution(s)":[{"Number":"086","Name":"Seel"}]},{"Number":"088","Name":"Grimer","Classification":"Sludge Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Acid","Mud Slap"],"Weight":"30.0 kg","Height":"0.9 m","Next Evolution Requirements":{"Amount":50,"Name":"Grimer candies"},"Next evolution(s)":[{"Number":"089","Name":"Muk"}]},{"Number":"089","Name":"Muk","Classification":"Sludge Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Poison Jab",""],"Weight":"30.0 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"088","Name":"Grimer"}]},{"Number":"090","Name":"Shellder","Classification":"Bivalve Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Ice Shard","Tackle"],"Weight":"4.0 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":50,"Name":"Shellder candies"},"Next evolution(s)":[{"Number":"091","Name":"Cloyster"}]},{"Number":"091","Name":"Cloyster","Classification":"Bivalve Pokemon","Type I":["Water"],"Type II":["Ice"],"Weaknesses":["Electric","Grass","Fighting","Rock"],"Fast Attack(s)":["Frost Breath","Ice Shard"],"Weight":"132.5 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"090","Name":"Shellder"}]},{"Number":"092","Name":"Gastly","Classification":"Gas Pokemon","Type I":["Ghost"],"Type II":["Poison"],"Weaknesses":["Ground","Psychic","Ghost","Dark"],"Fast Attack(s)":["Lick","Sucker Punch"],"Weight":"0.1 kg","Height":"1.3 m","Next Evolution Requirements":{"Amount":25,"Name":"Gastly candies"},"Next evolution(s)":[{"Number":"093","Name":"Haunter"},{"Number":"094","Name":"Gengar"}]},{"Number":"093","Name":"Haunter","Classification":"Gas Pokemon","Type I":["Ghost"],"Type II":["Poison"],"Weaknesses":["Ground","Psychic","Ghost","Dark"],"Fast Attack(s)":["Lick","Shadow Claw"],"Weight":"0.1 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"092","Name":"Gastly"}],"Next Evolution Requirements":{"Amount":100,"Name":"Gastly candies"},"Next evolution(s)":[{"Number":"094","Name":"Gengar"}]},{"Number":"094","Name":"Gengar","Classification":"Shadow Pokemon","Type I":["Ghost"],"Type II":["Poison"],"Weaknesses":["Ground","Psychic","Ghost","Dark"],"Fast Attack(s)":["Shadow Claw","Sucker Punch"],"Weight":"40.5 kg","Height":"1.5 m","Previous evolution(s)":[{"Number":"092","Name":"Gastly"},{"Number":"093","Name":"Haunter"}]},{"Number":"095","Name":"Onix","Classification":"Rock Snake Pokemon","Type I":["Rock"],"Type II":["Ground"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Rock Throw","Tackle"],"Weight":"210.0 kg","Height":"8.8 m"},{"Number":"096","Name":"Drowzee","Classification":"Hypnosis Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Pound"],"Weight":"32.4 kg","Height":"1.0 m","Next Evolution Requirements":{"Amount":50,"Name":"Drowzee candies"},"Next evolution(s)":[{"Number":"097","Name":"Hypno"}]},{"Number":"097","Name":"Hypno","Classification":"Hypnosis Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Zen Headbutt"],"Weight":"75.6 kg","Height":"1.6 m","Previous evolution(s)":[{"Number":"096","Name":"Drowzee"}]},{"Number":"098","Name":"Krabby","Classification":"River Crab Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bubble","Mud Shot"],"Weight":"6.5 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Krabby candies"},"Next evolution(s)":[{"Number":"099","Name":"Kingler"}]},{"Number":"099","Name":"Kingler","Classification":"Pincer Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Metal Claw","Mud Shot"],"Weight":"60.0 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"098","Name":"Krabby"}]},{"Number":"100","Name":"Voltorb","Classification":"Ball Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Spark","Tackle"],"Weight":"10.4 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":50,"Name":"Voltorb candies"},"Next evolution(s)":[{"Number":"101","Name":"Electrode"}]},{"Number":"101","Name":"Electrode","Classification":"Ball Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Spark",""],"Weight":"66.6 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"100","Name":"Voltorb"}]},{"Number":"102","Name":"Exeggcute","Classification":"Egg Pokemon","Type I":["Grass"],"Type II":["Psychic"],"Weaknesses":["Fire","Ice","Poison","Flying","Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion",""],"Weight":"2.5 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Exeggcute candies"},"Next evolution(s)":[{"Number":"103","Name":"Exeggutor"}]},{"Number":"103","Name":"Exeggutor","Classification":"Coconut Pokemon","Type I":["Grass"],"Type II":["Psychic"],"Weaknesses":["Fire","Ice","Poison","Flying","Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Zen Headbutt"],"Weight":"120.0 kg","Height":"2.0 m","Previous evolution(s)":[{"Number":"102","Name":"Exeggcute"}]},{"Number":"104","Name":"Cubone","Classification":"Lonely Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Mud Slap","Rock Smash"],"Weight":"6.5 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Cubone candies"},"Next evolution(s)":[{"Number":"105","Name":"Marowak"}]},{"Number":"105","Name":"Marowak","Classification":"Bone Keeper Pokemon","Type I":["Ground"],"Weaknesses":["Water","Grass","Ice"],"Fast Attack(s)":["Mud Slap","Rock Smash"],"Weight":"45.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"104","Name":"Cubone"}]},{"Number":"106","Name":"Hitmonlee","Classification":"Kicking Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Low Kick","Rock Smash"],"Weight":"49.8 kg","Height":"1.5 m","Next evolution(s)":[{"Number":"107","Name":"Hitmonchan"}]},{"Number":"107","Name":"Hitmonchan","Classification":"Punching Pokemon","Type I":["Fighting"],"Weaknesses":["Flying","Psychic","Fairy"],"Fast Attack(s)":["Bullet Punch","Rock Smash"],"Weight":"50.2 kg","Height":"1.4 m","Previous evolution(s)":[{"Number":"106","Name":"Hitmonlee"}]},{"Number":"108","Name":"Lickitung","Classification":"Licking Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Lick","Zen Headbutt"],"Weight":"65.5 kg","Height":"1.2 m"},{"Number":"109","Name":"Koffing","Classification":"Poison Gas Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Acid","Tackle"],"Weight":"1.0 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":50,"Name":"Koffing candies"},"Next evolution(s)":[{"Number":"110","Name":"Weezing"}]},{"Number":"110","Name":"Weezing","Classification":"Poison Gas Pokemon","Type I":["Poison"],"Weaknesses":["Ground","Psychic"],"Fast Attack(s)":["Acid","Tackle"],"Weight":"9.5 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"109","Name":"Koffing"}]},{"Number":"111","Name":"Rhyhorn","Classification":"Spikes Pokemon","Type I":["Ground"],"Type II":["Rock"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Mud Slap","Rock Smash"],"Weight":"115.0 kg","Height":"1.0 m","Next Evolution Requirements":{"Amount":50,"Name":"Rhyhorn candies"},"Next evolution(s)":[{"Number":"112","Name":"Rhydon"}]},{"Number":"112","Name":"Rhydon","Classification":"Drill Pokemon","Type I":["Ground"],"Type II":["Rock"],"Weaknesses":["Water","Grass","Ice","Fighting","Ground","Steel"],"Fast Attack(s)":["Mud Slap","Rock Smash"],"Weight":"120.0 kg","Height":"1.9 m","Previous evolution(s)":[{"Number":"111","Name":"Rhyhorn"}]},{"Number":"113","Name":"Chansey","Classification":"Egg Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Pound","Zen Headbutt"],"Weight":"34.6 kg","Height":"1.1 m"},{"Number":"114","Name":"Tangela","Classification":"Vine Pokemon","Type I":["Grass"],"Weaknesses":["Fire","Ice","Poison","Flying","Bug"],"Fast Attack(s)":["Vine Whip",""],"Weight":"35.0 kg","Height":"1.0 m"},{"Number":"115","Name":"Kangaskhan","Classification":"Parent Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Low Kick",""],"Weight":"80.0 kg","Height":"2.2 m"},{"Number":"116","Name":"Horsea","Classification":"Dragon Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Bubble","Water Gun"],"Weight":"8.0 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Horsea candies"},"Next evolution(s)":[{"Number":"117","Name":"Seadra"}]},{"Number":"117","Name":"Seadra","Classification":"Dragon Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Dragon Breath","Water Gun"],"Weight":"25.0 kg","Height":"1.2 m","Previous evolution(s)":[{"Number":"116","Name":"Horsea"}]},{"Number":"118","Name":"Goldeen","Classification":"Goldfish Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Peck","Mud Shot"],"Weight":"15.0 kg","Height":"0.6 m","Next Evolution Requirements":{"Amount":50,"Name":"Goldeen candies"},"Next evolution(s)":[{"Number":"119","Name":"Seaking"}]},{"Number":"119","Name":"Seaking","Classification":"Goldfish Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Peck","Poison Jab"],"Weight":"39.0 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"118","Name":"Goldeen"}]},{"Number":"120","Name":"Staryu","Classification":"Starshape Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Quick Attack","Water Gun"],"Weight":"34.5 kg","Height":"0.8 m","Next Evolution Requirements":{"Amount":50,"Name":"Staryu candies"},"Next evolution(s)":[{"Number":"120","Name":"Staryu"}]},{"Number":"121","Name":"Starmie","Classification":"Mysterious Pokemon","Type I":["Water"],"Type II":["Psychic"],"Weaknesses":["Electric","Grass","Bug","Ghost","Dark"],"Fast Attack(s)":["Quick Attack","Water Gun"],"Weight":"80.0 kg","Height":"1.1 m","Previous evolution(s)":[{"Number":"121","Name":"Starmie"}]},{"Number":"122","Name":"Mr. Mime","Classification":"Barrier Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Confusion","Zen Headbutt"],"Weight":"54.5 kg","Height":"1.3 m"},{"Number":"123","Name":"Scyther","Classification":"Mantis Pokemon","Type I":["Bug"],"Type II":["Flying"],"Weaknesses":["Fire","Electric","Ice","Flying","Rock"],"Fast Attack(s)":["Fury Cutter","Steel Wing"],"Weight":"56.0 kg","Height":"1.5 m"},{"Number":"124","Name":"Jynx","Classification":"Humanshape Pokemon","Type I":["Ice"],"Type II":["Psychic"],"Weaknesses":["Fire","Bug","Rock","Ghost","Dark","Steel"],"Fast Attack(s)":["Frost Breath","Pound"],"Weight":"40.6 kg","Height":"1.4 m"},{"Number":"125","Name":"Electabuzz","Classification":"Electric Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Low Kick","Thunder Shock"],"Weight":"30.0 kg","Height":"1.1 m"},{"Number":"126","Name":"Magmar","Classification":"Spitfire Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember","Karate Chop"],"Weight":"44.5 kg","Height":"1.3 m"},{"Number":"127","Name":"Pinsir","Classification":"Stagbeetle Pokemon","Type I":["Bug"],"Weaknesses":["Fire","Flying","Rock"],"Fast Attack(s)":["Fury Cutter","Rock Smash"],"Weight":"55.0 kg","Height":"1.5 m"},{"Number":"128","Name":"Tauros","Classification":"Wild Bull Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Tackle","Zen Headbutt"],"Weight":"88.4 kg","Height":"1.4 m"},{"Number":"129","Name":"Magikarp","Classification":"Fish Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Splash",""],"Weight":"10.0 kg","Height":"0.9 m","Next Evolution Requirements":{"Amount":400,"Name":"Magikarp candies"},"Next evolution(s)":[{"Number":"130","Name":"Gyarados"}]},{"Number":"130","Name":"Gyarados","Classification":"Atrocious Pokemon","Type I":["Water"],"Type II":["Flying"],"Weaknesses":["Electric","Rock"],"Fast Attack(s)":["Bite","Dragon Breath"],"Weight":"235.0 kg","Height":"6.5 m","Previous evolution(s)":[{"Number":"129","Name":"Magikarp"}]},{"Number":"131","Name":"Lapras","Classification":"Transport Pokemon","Type I":["Water"],"Type II":["Ice"],"Weaknesses":["Electric","Grass","Fighting","Rock"],"Fast Attack(s)":["Frost Breath","Ice Shard"],"Weight":"220.0 kg","Height":"2.5 m"},{"Number":"132","Name":"Ditto","Classification":"Transform Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"4.0 kg","Height":"0.3 m"},{"Number":"133","Name":"Eevee","Classification":"Evolution Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Quick Attack","Tackle"],"Weight":"6.5 kg","Height":"0.3 m","Next Evolution Requirements":{"Amount":25,"Name":"Eevee candies"},"Next evolution(s)":[{"Number":"134","Name":"Vaporeon"},{"Number":"135","Name":"Jolteon"},{"Number":"136","Name":"Flareon"}]},{"Number":"134","Name":"Vaporeon","Classification":"Bubble Jet Pokemon","Type I":["Water"],"Weaknesses":["Electric","Grass"],"Fast Attack(s)":["Water Gun",""],"Weight":"29.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"133","Name":"Eevee"}]},{"Number":"135","Name":"Jolteon","Classification":"Lightning Pokemon","Type I":["Electric"],"Weaknesses":["Ground"],"Fast Attack(s)":["Thunder Shock",""],"Weight":"24.5 kg","Height":"0.8 m","Previous evolution(s)":[{"Number":"133","Name":"Eevee"}]},{"Number":"136","Name":"Flareon","Classification":"Flame Pokemon","Type I":["Fire"],"Weaknesses":["Water","Ground","Rock"],"Fast Attack(s)":["Ember",""],"Weight":"25.0 kg","Height":"0.9 m","Previous evolution(s)":[{"Number":"133","Name":"Eevee"}]},{"Number":"137","Name":"Porygon","Classification":"Virtual Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Quick Attack","Tackle"],"Weight":"36.5 kg","Height":"0.8 m"},{"Number":"138","Name":"Omanyte","Classification":"Spiral Pokemon","Type I":["Rock"],"Type II":["Water"],"Weaknesses":["Electric","Grass","Fighting","Ground"],"Fast Attack(s)":["Water Gun",""],"Weight":"7.5 kg","Height":"0.4 m","Next Evolution Requirements":{"Amount":50,"Name":"Omanyte candies"},"Next evolution(s)":[{"Number":"139","Name":"Omastar"}]},{"Number":"139","Name":"Omastar","Classification":"Spiral Pokemon","Type I":["Rock"],"Type II":["Water"],"Weaknesses":["Electric","Grass","Fighting","Ground"],"Fast Attack(s)":["Rock Throw","Water Gun"],"Weight":"35.0 kg","Height":"1.0 m","Previous evolution(s)":[{"Number":"138","Name":"Omanyte"}]},{"Number":"140","Name":"Kabuto","Classification":"Shellfish Pokemon","Type I":["Rock"],"Type II":["Water"],"Weaknesses":["Electric","Grass","Fighting","Ground"],"Fast Attack(s)":["Mud Shot","Scratch"],"Weight":"11.5 kg","Height":"0.5 m","Next Evolution Requirements":{"Amount":50,"Name":"Kabuto candies"},"Next evolution(s)":[{"Number":"141","Name":"Kabutops"}]},{"Number":"141","Name":"Kabutops","Classification":"Shellfish Pokemon","Type I":["Rock"],"Type II":["Water"],"Weaknesses":["Electric","Grass","Fighting","Ground"],"Fast Attack(s)":["Fury Cutter","Mud Shot"],"Weight":"40.5 kg","Height":"1.3 m","Previous evolution(s)":[{"Number":"140","Name":"Kabuto"}]},{"Number":"142","Name":"Aerodactyl","Classification":"Fossil Pokemon","Type I":["Rock"],"Type II":["Flying"],"Weaknesses":["Water","Electric","Ice","Rock","Steel"],"Fast Attack(s)":["Bite","Steel Wing"],"Weight":"59.0 kg","Height":"1.8 m"},{"Number":"143","Name":"Snorlax","Classification":"Sleeping Pokemon","Type I":["Normal"],"Weaknesses":["Fighting"],"Fast Attack(s)":["Lick","Zen Headbutt"],"Weight":"460.0 kg","Height":"2.1 m"},{"Number":"144","Name":"Articuno","Classification":"Freeze Pokemon","Type I":["Ice"],"Type II":["Flying"],"Weaknesses":["Fire","Electric","Rock","Steel"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"55.4 kg","Height":"1.7 m"},{"Number":"145","Name":"Zapdos","Classification":"Electric Pokemon","Type I":["Electric"],"Type II":["Flying"],"Weaknesses":["Ice","Rock"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"52.6 kg","Height":"1.6 m"},{"Number":"146","Name":"Moltres","Classification":"Flame Pokemon","Type I":["Fire"],"Type II":["Flying"],"Weaknesses":["Water","Electric","Rock"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"60.0 kg","Height":"2.0 m"},{"Number":"147","Name":"Dratini","Classification":"Dragon Pokemon","Type I":["Dragon"],"Weaknesses":["Ice","Dragon","Fairy"],"Fast Attack(s)":["Dragon Breath",""],"Weight":"3.3 kg","Height":"1.8 m","Next Evolution Requirements":{"Amount":25,"Name":"Dratini candies"}},{"Number":"148","Name":"Dragonair","Classification":"Dragon Pokemon","Type I":["Dragon"],"Weaknesses":["Ice","Dragon","Fairy"],"Fast Attack(s)":["Dragon Breath",""],"Weight":"16.5 kg","Height":"4.0 m","Next Evolution Requirements":{"Amount":100,"Name":"Dratini candies"},"Next evolution(s)":[{"Number":"149","Name":"Dragonite"}]},{"Number":"149","Name":"Dragonite","Classification":"Dragon Pokemon","Type I":["Dragon"],"Type II":["Flying"],"Weaknesses":["Ice","Rock","Dragon","Fairy"],"Fast Attack(s)":["Dragon Breath","Steel Wing"],"Weight":"210.0 kg","Height":"2.2 m","Previous evolution(s)":[{"Number":"148","Name":"Dragonair"}]},{"Number":"150","Name":"Mewtwo","Classification":"Genetic Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"122.0 kg","Height":"2.0 m"},{"Number":"151","Name":"Mew","Classification":"New Species Pokemon","Type I":["Psychic"],"Weaknesses":["Bug","Ghost","Dark"],"Fast Attack(s)":["Unknown"],"Special Attack(s)":["Unknown"],"Weight":"4.0 kg","Height":"0.4 m"}] \ No newline at end of file +[ + { + "Number": "001", + "Name": "Bulbasaur", + "Classification": "Seed Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Tackle", + "Vine Whip" + ], + "Weight": "6.9 kg", + "Height": "0.7 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Bulbasaur candies" + }, + "Next evolution(s)": [ + { + "Number": "002", + "Name": "Ivysaur" + }, + { + "Number": "003", + "Name": "Venusaur" + } + ] + }, + { + "Number": "002", + "Name": "Ivysaur", + "Classification": "Seed Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Razor Leaf", + "Vine Whip" + ], + "Weight": "13.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "001", + "Name": "Bulbasaur" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Bulbasaur candies" + }, + "Next evolution(s)": [ + { + "Number": "003", + "Name": "Venusaur" + } + ] + }, + { + "Number": "003", + "Name": "Venusaur", + "Classification": "Seed Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Razor Leaf", + "Vine Whip" + ], + "Weight": "100.0 kg", + "Height": "2.0 m", + "Previous evolution(s)": [ + { + "Number": "001", + "Name": "Bulbasaur" + }, + { + "Number": "002", + "Name": "Ivysaur" + } + ] + }, + { + "Number": "004", + "Name": "Charmander", + "Classification": "Lizard Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Scratch" + ], + "Weight": "8.5 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Charmander candies" + }, + "Next evolution(s)": [ + { + "Number": "005", + "Name": "Charmeleon" + }, + { + "Number": "006", + "Name": "Charizard" + } + ] + }, + { + "Number": "005", + "Name": "Charmeleon", + "Classification": "Flame Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "" + ], + "Weight": "19.0 kg", + "Height": "1.1 m", + "Previous evolution(s)": [ + { + "Number": "004", + "Name": "Charmander" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Charmander candies" + }, + "Next evolution(s)": [ + { + "Number": "006", + "Name": "Charizard" + } + ] + }, + { + "Number": "006", + "Name": "Charizard", + "Classification": "Flame Pokemon", + "Type I": [ + "Fire" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Water", + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Wing Attack" + ], + "Weight": "90.5 kg", + "Height": "1.7 m", + "Previous evolution(s)": [ + { + "Number": "004", + "Name": "Charmander" + }, + { + "Number": "005", + "Name": "Charmeleon" + } + ] + }, + { + "Number": "007", + "Name": "Squirtle", + "Classification": "Tiny Turtle Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Tackle", + "Bubble" + ], + "Weight": "9.0 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Squirtle candies" + }, + "Next evolution(s)": [ + { + "Number": "008", + "Name": "Wartortle" + }, + { + "Number": "009", + "Name": "Blastoise" + } + ] + }, + { + "Number": "008", + "Name": "Wartortle", + "Classification": "Turtle Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bite", + "Water Gun" + ], + "Weight": "22.5 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "007", + "Name": "Squirtle" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Squirtle candies" + }, + "Next evolution(s)": [ + { + "Number": "009", + "Name": "Blastoise" + } + ] + }, + { + "Number": "009", + "Name": "Blastoise", + "Classification": "Shellfish Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bite", + "Water Gun" + ], + "Weight": "85.5 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "007", + "Name": "Squirtle" + }, + { + "Number": "008", + "Name": "Wartortle" + } + ] + }, + { + "Number": "010", + "Name": "Caterpie", + "Classification": "Worm Pokemon", + "Type I": [ + "Bug" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Tackle" + ], + "Weight": "2.9 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 12, + "Name": "Caterpie candies" + }, + "Next evolution(s)": [ + { + "Number": "011", + "Name": "Metapod" + }, + { + "Number": "012", + "Name": "Butterfree" + } + ] + }, + { + "Number": "011", + "Name": "Metapod", + "Classification": "Cocoon Pokemon", + "Type I": [ + "Bug" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Tackle" + ], + "Weight": "9.9 kg", + "Height": "0.7 m", + "Previous evolution(s)": [ + { + "Number": "010", + "Name": "Caterpie" + } + ], + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Caterpie candies" + }, + "Next evolution(s)": [ + { + "Number": "012", + "Name": "Butterfree" + } + ] + }, + { + "Number": "012", + "Name": "Butterfree", + "Classification": "Butterfly Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Fire", + "Electric", + "Ice", + "Flying", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Confusion" + ], + "Weight": "32.0 kg", + "Height": "1.1 m", + "Previous evolution(s)": [ + { + "Number": "010", + "Name": "Caterpie" + }, + { + "Number": "011", + "Name": "Metapod" + } + ] + }, + { + "Number": "013", + "Name": "Weedle", + "Classification": "Hairy Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Poison Sting" + ], + "Weight": "3.2 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 12, + "Name": "Weedle candies" + }, + "Next evolution(s)": [ + { + "Number": "014", + "Name": "Kakuna" + }, + { + "Number": "015", + "Name": "Beedrill" + } + ] + }, + { + "Number": "014", + "Name": "Kakuna", + "Classification": "Cocoon Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Posion Sting" + ], + "Weight": "10.0 kg", + "Height": "0.6 m", + "Previous evolution(s)": [ + { + "Number": "013", + "Name": "Weedle" + } + ], + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Weedle candies" + }, + "Next evolution(s)": [ + { + "Number": "015", + "Name": "Beedrill" + } + ] + }, + { + "Number": "015", + "Name": "Beedrill", + "Classification": "Poison Bee Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Poison Jab" + ], + "Weight": "29.5 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "013", + "Name": "Weedle" + }, + { + "Number": "014", + "Name": "Kakuna" + } + ] + }, + { + "Number": "016", + "Name": "Pidgey", + "Classification": "Tiny Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Tackle" + ], + "Special Attack(s)": [ + "Aerial Ace", + "Air Cutter", + "Twister" + ], + "Weight": "1.8 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 12, + "Name": "Pidgey candies" + }, + "Next evolution(s)": [ + { + "Number": "017", + "Name": "Pidgeotto" + }, + { + "Number": "018", + "Name": "Pidgeot" + } + ] + }, + { + "Number": "017", + "Name": "Pidgeotto", + "Classification": "Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Steel Wing", + "Wing Attack" + ], + "Special Attack(s)": [ + "Aerial Ace", + "Air Cutter", + "Twister" + ], + "Weight": "30.0 kg", + "Height": "1.1 m", + "Previous evolution(s)": [ + { + "Number": "016", + "Name": "Pidgey" + } + ], + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Pidgey candies" + }, + "Next evolution(s)": [ + { + "Number": "018", + "Name": "Pidgeot" + } + ] + }, + { + "Number": "018", + "Name": "Pidgeot", + "Classification": "Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Steel Wing", + "Wing Attack" + ], + "Special Attack(s)": [ + "Hurricane" + ], + "Weight": "39.5 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "016", + "Name": "Pidgey" + }, + { + "Number": "017", + "Name": "Pidgeotto" + } + ] + }, + { + "Number": "019", + "Name": "Rattata", + "Classification": "Mouse Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Tackle" + ], + "Special Attack(s)": [ + "Body Slam", + "Dig", + "Hyper Fang" + ], + "Weight": "3.5 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Rattata candies" + }, + "Next evolution(s)": [ + { + "Number": "020", + "Name": "Raticate" + } + ] + }, + { + "Number": "020", + "Name": "Raticate", + "Classification": "Mouse Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Bite", + "Quick Attack" + ], + "Special Attack(s)": [ + "Dig", + "Hyper Beam", + "Hyper Fang" + ], + "Weight": "18.5 kg", + "Height": "0.7 m", + "Previous evolution(s)": [ + { + "Number": "019", + "Name": "Rattata" + } + ] + }, + { + "Number": "021", + "Name": "Spearow", + "Classification": "Tiny Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Peck", + "Quick Attack" + ], + "Weight": "2.0 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Spearow candies" + }, + "Next evolution(s)": [ + { + "Number": "022", + "Name": "Fearow" + } + ] + }, + { + "Number": "022", + "Name": "Fearow", + "Classification": "Beak Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Peck", + "Steel Wing" + ], + "Weight": "38.0 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "021", + "Name": "Spearow" + } + ] + }, + { + "Number": "023", + "Name": "Ekans", + "Classification": "Snake Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Poison Sting" + ], + "Weight": "6.9 kg", + "Height": "2.0 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Ekans candies" + }, + "Next evolution(s)": [ + { + "Number": "024", + "Name": "Arbok" + } + ] + }, + { + "Number": "024", + "Name": "Arbok", + "Classification": "Cobra Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Bite" + ], + "Weight": "65.0 kg", + "Height": "3.5 m", + "Previous evolution(s)": [ + { + "Number": "023", + "Name": "Ekans" + } + ] + }, + { + "Number": "025", + "Name": "Pikachu", + "Classification": "Mouse Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Thunder Shock" + ], + "Weight": "6.0 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Pikachu candies" + }, + "Next evolution(s)": [ + { + "Number": "026", + "Name": "Raichu" + } + ] + }, + { + "Number": "026", + "Name": "Raichu", + "Classification": "Mouse Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Thunder Shock", + "Spark" + ], + "Weight": "30.0 kg", + "Height": "0.8 m", + "Previous evolution(s)": [ + { + "Number": "025", + "Name": "Pikachu" + } + ] + }, + { + "Number": "027", + "Name": "Sandshrew", + "Classification": "Mouse Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Scratch" + ], + "Weight": "12.0 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Sandshrew candies" + }, + "Next evolution(s)": [ + { + "Number": "028", + "Name": "Sandslash" + } + ] + }, + { + "Number": "028", + "Name": "Sandslash", + "Classification": "Mouse Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Metal Claw", + "Mud Shot" + ], + "Weight": "29.5 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "027", + "Name": "Sandshrew" + } + ] + }, + { + "Number": "029", + "Name": "Nidoran F", + "Classification": "Poison Pin Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Bite", + "Poison Sting" + ], + "Weight": "7.0 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Nidoran F candies" + }, + "Next evolution(s)": [ + { + "Number": "030", + "Name": "Nidorina" + }, + { + "Number": "031", + "Name": "Nidoqueen" + } + ] + }, + { + "Number": "030", + "Name": "Nidorina", + "Classification": "Poison Pin Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Bite", + "Poison Sting" + ], + "Weight": "20.0 kg", + "Height": "0.8 m", + "Previous evolution(s)": [ + { + "Number": "029", + "Name": "Nidoran F" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Nidoran F candies" + }, + "Next evolution(s)": [ + { + "Number": "031", + "Name": "Nidoqueen" + } + ] + }, + { + "Number": "031", + "Name": "Nidoqueen", + "Classification": "Drill Pokemon", + "Type I": [ + "Poison" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Ice", + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Bite", + "Poison Jab" + ], + "Weight": "60.0 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "029", + "Name": "Nidoran F" + }, + { + "Number": "030", + "Name": "Nidorina" + } + ] + }, + { + "Number": "032", + "Name": "Nidoran M", + "Classification": "Poison Pin Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Peck", + "Poison Sting" + ], + "Weight": "9.0 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Nidoran M candies" + }, + "Next evolution(s)": [ + { + "Number": "033", + "Name": "Nidorino" + }, + { + "Number": "034", + "Name": "Nidoking" + } + ] + }, + { + "Number": "033", + "Name": "Nidorino", + "Classification": "Poison Pin Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Bite", + "Poison Jab" + ], + "Weight": "19.5 kg", + "Height": "0.9 m", + "Previous evolution(s)": [ + { + "Number": "032", + "Name": "Nidoran M" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Nidoran M candies" + }, + "Next evolution(s)": [ + { + "Number": "034", + "Name": "Nidoking" + } + ] + }, + { + "Number": "034", + "Name": "Nidoking", + "Classification": "Drill Pokemon", + "Type I": [ + "Poison" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Ice", + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Fury Cutter", + "Poison Jab" + ], + "Weight": "62.0 kg", + "Height": "1.4 m", + "Previous evolution(s)": [ + { + "Number": "032", + "Name": "Nidoran M" + }, + { + "Number": "033", + "Name": "Nidorino" + } + ] + }, + { + "Number": "035", + "Name": "Clefairy", + "Classification": "Fairy Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Pound", + "Zen Headbutt" + ], + "Weight": "7.5 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Clefairy candies" + }, + "Next evolution(s)": [ + { + "Number": "036", + "Name": "Clefable" + } + ] + }, + { + "Number": "036", + "Name": "Clefable", + "Classification": "Fairy Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Pound", + "Zen Headbutt" + ], + "Weight": "40.0 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "035", + "Name": "Clefairy" + } + ] + }, + { + "Number": "037", + "Name": "Vulpix", + "Classification": "Fox Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Quick Attack" + ], + "Weight": "9.9 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Vulpix candies" + }, + "Next evolution(s)": [ + { + "Number": "038", + "Name": "Ninetales" + } + ] + }, + { + "Number": "038", + "Name": "Ninetales", + "Classification": "Fox Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Quick Attack" + ], + "Weight": "19.9 kg", + "Height": "1.1 m", + "Previous evolution(s)": [ + { + "Number": "037", + "Name": "Vulpix" + } + ] + }, + { + "Number": "039", + "Name": "Jigglypuff", + "Classification": "Balloon Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Feint Attack", + "Pound" + ], + "Weight": "5.5 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Jigglypuff candies" + }, + "Next evolution(s)": [ + { + "Number": "039", + "Name": "Jigglypuff" + } + ] + }, + { + "Number": "040", + "Name": "Wigglytuff", + "Classification": "Balloon Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Feint Attack", + "Pound" + ], + "Weight": "12.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "039", + "Name": "Jigglypuff" + } + ] + }, + { + "Number": "041", + "Name": "Zubat", + "Classification": "Bat Pokemon", + "Type I": [ + "Poison" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Ice", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bite", + "Quick Attack" + ], + "Weight": "7.5 kg", + "Height": "0.8 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Zubat candies" + }, + "Next evolution(s)": [ + { + "Number": "042", + "Name": "Golbat" + } + ] + }, + { + "Number": "042", + "Name": "Golbat", + "Classification": "Bat Pokemon", + "Type I": [ + "Poison" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Ice", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bite", + "Wing Attack" + ], + "Weight": "55.0 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "041", + "Name": "Zubat" + } + ] + }, + { + "Number": "043", + "Name": "Oddish", + "Classification": "Weed Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Razor Leaf" + ], + "Weight": "5.4 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Oddish candies" + }, + "Next evolution(s)": [ + { + "Number": "044", + "Name": "Gloom" + }, + { + "Number": "045", + "Name": "Vileplume" + } + ] + }, + { + "Number": "044", + "Name": "Gloom", + "Classification": "Weed Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Razor Leaf" + ], + "Weight": "8.6 kg", + "Height": "0.8 m", + "Previous evolution(s)": [ + { + "Number": "043", + "Name": "Oddish" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Oddish candies" + }, + "Next evolution(s)": [ + { + "Number": "045", + "Name": "Vileplume" + } + ] + }, + { + "Number": "045", + "Name": "Vileplume", + "Classification": "Flower Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "" + ], + "Weight": "18.6 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "043", + "Name": "Oddish" + }, + { + "Number": "044", + "Name": "Gloom" + } + ] + }, + { + "Number": "046", + "Name": "Paras", + "Classification": "Mushroom Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Grass" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Scratch" + ], + "Weight": "5.4 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Paras candies" + }, + "Next evolution(s)": [ + { + "Number": "047", + "Name": "Parasect" + } + ] + }, + { + "Number": "047", + "Name": "Parasect", + "Classification": "Mushroom Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Grass" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Fury Cutter" + ], + "Weight": "29.5 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "046", + "Name": "Paras" + } + ] + }, + { + "Number": "048", + "Name": "Venonat", + "Classification": "Insect Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Confusion" + ], + "Weight": "30.0 kg", + "Height": "1.0 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Venonat candies" + }, + "Next evolution(s)": [ + { + "Number": "049", + "Name": "Venomoth" + } + ] + }, + { + "Number": "049", + "Name": "Venomoth", + "Classification": "Poison Moth Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "Fast Attack(s)": [ + "Bug Bite", + "Confusion" + ], + "Weight": "12.5 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "048", + "Name": "Venonat" + } + ] + }, + { + "Number": "050", + "Name": "Diglett", + "Classification": "Mole Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Scratch" + ], + "Weight": "0.8 kg", + "Height": "0.2 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Diglett candies" + }, + "Next evolution(s)": [ + { + "Number": "051", + "Name": "Dugtrio" + } + ] + }, + { + "Number": "051", + "Name": "Dugtrio", + "Classification": "Mole Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Sucker Punch" + ], + "Weight": "33.3 kg", + "Height": "0.7 m", + "Previous evolution(s)": [ + { + "Number": "050", + "Name": "Diglett" + } + ] + }, + { + "Number": "052", + "Name": "Meowth", + "Classification": "Scratch Cat Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Bite", + "Scratch" + ], + "Weight": "4.2 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Meowth candies" + }, + "Next evolution(s)": [ + { + "Number": "053", + "Name": "Persian" + } + ] + }, + { + "Number": "053", + "Name": "Persian", + "Classification": "Classy Cat Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Feint Attack", + "Scratch" + ], + "Weight": "32.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "052", + "Name": "Meowth" + } + ] + }, + { + "Number": "054", + "Name": "Psyduck", + "Classification": "Duck Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Water Gun", + "Zen Headbutt" + ], + "Weight": "19.6 kg", + "Height": "0.8 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Psyduck candies" + }, + "Next evolution(s)": [ + { + "Number": "055", + "Name": "Golduck" + } + ] + }, + { + "Number": "055", + "Name": "Golduck", + "Classification": "Duck Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Confusion", + "Zen Headbutt" + ], + "Weight": "76.6 kg", + "Height": "1.7 m", + "Previous evolution(s)": [ + { + "Number": "054", + "Name": "Psyduck" + } + ] + }, + { + "Number": "056", + "Name": "Mankey", + "Classification": "Pig Monkey Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Karate Chop", + "Scratch" + ], + "Weight": "28.0 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Mankey candies" + }, + "Next evolution(s)": [ + { + "Number": "057", + "Name": "Primeape" + } + ] + }, + { + "Number": "057", + "Name": "Primeape", + "Classification": "Pig Monkey Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Karate Chop", + "Low Kick" + ], + "Weight": "32.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "056", + "Name": "Mankey" + } + ] + }, + { + "Number": "058", + "Name": "Growlithe", + "Classification": "Puppy Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Bite", + "Ember" + ], + "Weight": "19.0 kg", + "Height": "0.7 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Growlithe candies" + }, + "Next evolution(s)": [ + { + "Number": "059", + "Name": "Arcanine" + } + ] + }, + { + "Number": "059", + "Name": "Arcanine", + "Classification": "Legendary Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Bite", + "Fire Fang" + ], + "Weight": "155.0 kg", + "Height": "1.9 m", + "Previous evolution(s)": [ + { + "Number": "058", + "Name": "Growlithe" + } + ] + }, + { + "Number": "060", + "Name": "Poliwag", + "Classification": "Tadpole Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bubble", + "Mud Shot" + ], + "Weight": "12.4 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Poliwag candies" + }, + "Next evolution(s)": [ + { + "Number": "061", + "Name": "Poliwhirl" + }, + { + "Number": "062", + "Name": "Poliwrath" + } + ] + }, + { + "Number": "061", + "Name": "Poliwhirl", + "Classification": "Tadpole Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bubble", + "Mud Shot" + ], + "Weight": "20.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "060", + "Name": "Poliwag" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Poliwag candies" + }, + "Next evolution(s)": [ + { + "Number": "062", + "Name": "Poliwrath" + } + ] + }, + { + "Number": "062", + "Name": "Poliwrath", + "Classification": "Tadpole Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Fighting" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Bubble", + "Mud Shot" + ], + "Weight": "54.0 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "060", + "Name": "Poliwag" + }, + { + "Number": "061", + "Name": "Poliwhirl" + } + ] + }, + { + "Number": "063", + "Name": "Abra", + "Classification": "Psi Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Zen Headbutt", + "" + ], + "Weight": "19.5 kg", + "Height": "0.9 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Abra candies" + }, + "Next evolution(s)": [ + { + "Number": "064", + "Name": "Kadabra" + }, + { + "Number": "065", + "Name": "Alakazam" + } + ] + }, + { + "Number": "064", + "Name": "Kadabra", + "Classification": "Psi Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Psycho Cut" + ], + "Weight": "56.5 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "063", + "Name": "Abra" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Abra candies" + }, + "Next evolution(s)": [ + { + "Number": "065", + "Name": "Alakazam" + } + ] + }, + { + "Number": "065", + "Name": "Alakazam", + "Classification": "Psi Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Psycho Cut" + ], + "Weight": "48.0 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "063", + "Name": "Abra" + }, + { + "Number": "064", + "Name": "Kadabra" + } + ] + }, + { + "Number": "066", + "Name": "Machop", + "Classification": "Superpower Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Karate Chop", + "Low Kick" + ], + "Weight": "19.5 kg", + "Height": "0.8 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Machop candies" + }, + "Next evolution(s)": [ + { + "Number": "067", + "Name": "Machoke" + }, + { + "Number": "068", + "Name": "Machamp" + } + ] + }, + { + "Number": "067", + "Name": "Machoke", + "Classification": "Superpower Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Karate Chop", + "Low Kick" + ], + "Weight": "70.5 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "066", + "Name": "Machop" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Machop candies" + }, + "Next evolution(s)": [ + { + "Number": "068", + "Name": "Machamp" + } + ] + }, + { + "Number": "068", + "Name": "Machamp", + "Classification": "Superpower Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Bullet Punch", + "Karate Chop" + ], + "Weight": "130.0 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "066", + "Name": "Machop" + }, + { + "Number": "067", + "Name": "Machoke" + } + ] + }, + { + "Number": "069", + "Name": "Bellsprout", + "Classification": "Flower Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Vine Whip" + ], + "Weight": "4.0 kg", + "Height": "0.7 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Bellsprout candies" + }, + "Next evolution(s)": [ + { + "Number": "070", + "Name": "Weepinbell" + }, + { + "Number": "071", + "Name": "Victreebel" + } + ] + }, + { + "Number": "070", + "Name": "Weepinbell", + "Classification": "Flycatcher Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Razor Leaf" + ], + "Weight": "6.4 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "069", + "Name": "Bellsprout" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Bellsprout candies" + }, + "Next evolution(s)": [ + { + "Number": "071", + "Name": "Victreebel" + } + ] + }, + { + "Number": "071", + "Name": "Victreebel", + "Classification": "Flycatcher Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Razor Leaf" + ], + "Weight": "15.5 kg", + "Height": "1.7 m", + "Previous evolution(s)": [ + { + "Number": "069", + "Name": "Bellsprout" + }, + { + "Number": "070", + "Name": "Weepinbell" + } + ] + }, + { + "Number": "072", + "Name": "Tentacool", + "Classification": "Jellyfish Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Electric", + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Bubble", + "Poison Sting" + ], + "Weight": "45.5 kg", + "Height": "0.9 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Tentacool candies" + }, + "Next evolution(s)": [ + { + "Number": "073", + "Name": "Tentacruel" + } + ] + }, + { + "Number": "073", + "Name": "Tentacruel", + "Classification": "Jellyfish Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Electric", + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Poison Jab" + ], + "Weight": "55.0 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "072", + "Name": "Tentacool" + } + ] + }, + { + "Number": "074", + "Name": "Geodude", + "Classification": "Rock Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Rock Throw", + "Tackle" + ], + "Weight": "20.0 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Geodude candies" + }, + "Next evolution(s)": [ + { + "Number": "075", + "Name": "Graveler" + }, + { + "Number": "076", + "Name": "Golem" + } + ] + }, + { + "Number": "075", + "Name": "Graveler", + "Classification": "Rock Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Rock Throw" + ], + "Weight": "105.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "074", + "Name": "Geodude" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Geodude candies" + }, + "Next evolution(s)": [ + { + "Number": "076", + "Name": "Golem" + } + ] + }, + { + "Number": "076", + "Name": "Golem", + "Classification": "Megaton Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Rock Throw" + ], + "Weight": "300.0 kg", + "Height": "1.4 m", + "Previous evolution(s)": [ + { + "Number": "074", + "Name": "Geodude" + }, + { + "Number": "075", + "Name": "Graveler" + } + ] + }, + { + "Number": "077", + "Name": "Ponyta", + "Classification": "Fire Horse Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Tackle" + ], + "Weight": "30.0 kg", + "Height": "1.0 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Ponyta candies" + }, + "Next evolution(s)": [ + { + "Number": "078", + "Name": "Rapidash" + } + ] + }, + { + "Number": "078", + "Name": "Rapidash", + "Classification": "Fire Horse Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Low Kick" + ], + "Weight": "95.0 kg", + "Height": "1.7 m", + "Previous evolution(s)": [ + { + "Number": "077", + "Name": "Ponyta" + } + ] + }, + { + "Number": "079", + "Name": "Slowpoke", + "Classification": "Dopey Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Water Gun" + ], + "Weight": "36.0 kg", + "Height": "1.2 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Slowpoke candies" + }, + "Next evolution(s)": [ + { + "Number": "080", + "Name": "Slowbro" + } + ] + }, + { + "Number": "080", + "Name": "Slowbro", + "Classification": "Hermit Crab Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Water Gun" + ], + "Weight": "78.5 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "079", + "Name": "Slowpoke" + } + ] + }, + { + "Number": "081", + "Name": "Magnemite", + "Classification": "Magnet Pokemon", + "Type I": [ + "Electric" + ], + "Type II": [ + "Steel" + ], + "Weaknesses": [ + "Fire", + "Water", + "Ground" + ], + "Fast Attack(s)": [ + "Spark", + "Thunder Shock" + ], + "Weight": "6.0 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Magnemite candies" + }, + "Next evolution(s)": [ + { + "Number": "082", + "Name": "Magneton" + } + ] + }, + { + "Number": "082", + "Name": "Magneton", + "Classification": "Magnet Pokemon", + "Type I": [ + "Electric" + ], + "Type II": [ + "Steel" + ], + "Weaknesses": [ + "Fire", + "Water", + "Ground" + ], + "Fast Attack(s)": [ + "Spark", + "Thunder Shock" + ], + "Weight": "60.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "081", + "Name": "Magnemite" + } + ] + }, + { + "Number": "083", + "Name": "Farfetch'd", + "Classification": "Wild Duck Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "15.0 kg", + "Height": "0.8 m" + }, + { + "Number": "084", + "Name": "Doduo", + "Classification": "Twin Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Peck", + "Quick Attack" + ], + "Weight": "39.2 kg", + "Height": "1.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Doduo candies" + }, + "Next evolution(s)": [ + { + "Number": "085", + "Name": "Dodrio" + } + ] + }, + { + "Number": "085", + "Name": "Dodrio", + "Classification": "Triple Bird Pokemon", + "Type I": [ + "Normal" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Feint Attack", + "Steel Wing" + ], + "Weight": "85.2 kg", + "Height": "1.8 m", + "Previous evolution(s)": [ + { + "Number": "084", + "Name": "Doduo" + } + ] + }, + { + "Number": "086", + "Name": "Seel", + "Classification": "Sea Lion Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Ice Shard", + "Water Gun" + ], + "Weight": "90.0 kg", + "Height": "1.1 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Seel candies" + }, + "Next evolution(s)": [ + { + "Number": "087", + "Name": "Dewgong" + } + ] + }, + { + "Number": "087", + "Name": "Dewgong", + "Classification": "Sea Lion Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Ice" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ], + "Fast Attack(s)": [ + "Frost Breath", + "Ice Shard" + ], + "Weight": "120.0 kg", + "Height": "1.7 m", + "Previous evolution(s)": [ + { + "Number": "086", + "Name": "Seel" + } + ] + }, + { + "Number": "088", + "Name": "Grimer", + "Classification": "Sludge Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Mud Slap" + ], + "Weight": "30.0 kg", + "Height": "0.9 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Grimer candies" + }, + "Next evolution(s)": [ + { + "Number": "089", + "Name": "Muk" + } + ] + }, + { + "Number": "089", + "Name": "Muk", + "Classification": "Sludge Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Poison Jab", + "" + ], + "Weight": "30.0 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "088", + "Name": "Grimer" + } + ] + }, + { + "Number": "090", + "Name": "Shellder", + "Classification": "Bivalve Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Ice Shard", + "Tackle" + ], + "Weight": "4.0 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Shellder candies" + }, + "Next evolution(s)": [ + { + "Number": "091", + "Name": "Cloyster" + } + ] + }, + { + "Number": "091", + "Name": "Cloyster", + "Classification": "Bivalve Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Ice" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ], + "Fast Attack(s)": [ + "Frost Breath", + "Ice Shard" + ], + "Weight": "132.5 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "090", + "Name": "Shellder" + } + ] + }, + { + "Number": "092", + "Name": "Gastly", + "Classification": "Gas Pokemon", + "Type I": [ + "Ghost" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Lick", + "Sucker Punch" + ], + "Weight": "0.1 kg", + "Height": "1.3 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Gastly candies" + }, + "Next evolution(s)": [ + { + "Number": "093", + "Name": "Haunter" + }, + { + "Number": "094", + "Name": "Gengar" + } + ] + }, + { + "Number": "093", + "Name": "Haunter", + "Classification": "Gas Pokemon", + "Type I": [ + "Ghost" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Lick", + "Shadow Claw" + ], + "Weight": "0.1 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "092", + "Name": "Gastly" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Gastly candies" + }, + "Next evolution(s)": [ + { + "Number": "094", + "Name": "Gengar" + } + ] + }, + { + "Number": "094", + "Name": "Gengar", + "Classification": "Shadow Pokemon", + "Type I": [ + "Ghost" + ], + "Type II": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Shadow Claw", + "Sucker Punch" + ], + "Weight": "40.5 kg", + "Height": "1.5 m", + "Previous evolution(s)": [ + { + "Number": "092", + "Name": "Gastly" + }, + { + "Number": "093", + "Name": "Haunter" + } + ] + }, + { + "Number": "095", + "Name": "Onix", + "Classification": "Rock Snake Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Rock Throw", + "Tackle" + ], + "Weight": "210.0 kg", + "Height": "8.8 m" + }, + { + "Number": "096", + "Name": "Drowzee", + "Classification": "Hypnosis Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Pound" + ], + "Weight": "32.4 kg", + "Height": "1.0 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Drowzee candies" + }, + "Next evolution(s)": [ + { + "Number": "097", + "Name": "Hypno" + } + ] + }, + { + "Number": "097", + "Name": "Hypno", + "Classification": "Hypnosis Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Zen Headbutt" + ], + "Weight": "75.6 kg", + "Height": "1.6 m", + "Previous evolution(s)": [ + { + "Number": "096", + "Name": "Drowzee" + } + ] + }, + { + "Number": "098", + "Name": "Krabby", + "Classification": "River Crab Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bubble", + "Mud Shot" + ], + "Weight": "6.5 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Krabby candies" + }, + "Next evolution(s)": [ + { + "Number": "099", + "Name": "Kingler" + } + ] + }, + { + "Number": "099", + "Name": "Kingler", + "Classification": "Pincer Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Metal Claw", + "Mud Shot" + ], + "Weight": "60.0 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "098", + "Name": "Krabby" + } + ] + }, + { + "Number": "100", + "Name": "Voltorb", + "Classification": "Ball Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Spark", + "Tackle" + ], + "Weight": "10.4 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Voltorb candies" + }, + "Next evolution(s)": [ + { + "Number": "101", + "Name": "Electrode" + } + ] + }, + { + "Number": "101", + "Name": "Electrode", + "Classification": "Ball Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Spark", + "" + ], + "Weight": "66.6 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "100", + "Name": "Voltorb" + } + ] + }, + { + "Number": "102", + "Name": "Exeggcute", + "Classification": "Egg Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "" + ], + "Weight": "2.5 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Exeggcute candies" + }, + "Next evolution(s)": [ + { + "Number": "103", + "Name": "Exeggutor" + } + ] + }, + { + "Number": "103", + "Name": "Exeggutor", + "Classification": "Coconut Pokemon", + "Type I": [ + "Grass" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Zen Headbutt" + ], + "Weight": "120.0 kg", + "Height": "2.0 m", + "Previous evolution(s)": [ + { + "Number": "102", + "Name": "Exeggcute" + } + ] + }, + { + "Number": "104", + "Name": "Cubone", + "Classification": "Lonely Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Mud Slap", + "Rock Smash" + ], + "Weight": "6.5 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Cubone candies" + }, + "Next evolution(s)": [ + { + "Number": "105", + "Name": "Marowak" + } + ] + }, + { + "Number": "105", + "Name": "Marowak", + "Classification": "Bone Keeper Pokemon", + "Type I": [ + "Ground" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "Fast Attack(s)": [ + "Mud Slap", + "Rock Smash" + ], + "Weight": "45.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "104", + "Name": "Cubone" + } + ] + }, + { + "Number": "106", + "Name": "Hitmonlee", + "Classification": "Kicking Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Low Kick", + "Rock Smash" + ], + "Weight": "49.8 kg", + "Height": "1.5 m", + "Next evolution(s)": [ + { + "Number": "107", + "Name": "Hitmonchan" + } + ] + }, + { + "Number": "107", + "Name": "Hitmonchan", + "Classification": "Punching Pokemon", + "Type I": [ + "Fighting" + ], + "Weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "Fast Attack(s)": [ + "Bullet Punch", + "Rock Smash" + ], + "Weight": "50.2 kg", + "Height": "1.4 m", + "Previous evolution(s)": [ + { + "Number": "106", + "Name": "Hitmonlee" + } + ] + }, + { + "Number": "108", + "Name": "Lickitung", + "Classification": "Licking Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Lick", + "Zen Headbutt" + ], + "Weight": "65.5 kg", + "Height": "1.2 m" + }, + { + "Number": "109", + "Name": "Koffing", + "Classification": "Poison Gas Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Tackle" + ], + "Weight": "1.0 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Koffing candies" + }, + "Next evolution(s)": [ + { + "Number": "110", + "Name": "Weezing" + } + ] + }, + { + "Number": "110", + "Name": "Weezing", + "Classification": "Poison Gas Pokemon", + "Type I": [ + "Poison" + ], + "Weaknesses": [ + "Ground", + "Psychic" + ], + "Fast Attack(s)": [ + "Acid", + "Tackle" + ], + "Weight": "9.5 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "109", + "Name": "Koffing" + } + ] + }, + { + "Number": "111", + "Name": "Rhyhorn", + "Classification": "Spikes Pokemon", + "Type I": [ + "Ground" + ], + "Type II": [ + "Rock" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Mud Slap", + "Rock Smash" + ], + "Weight": "115.0 kg", + "Height": "1.0 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Rhyhorn candies" + }, + "Next evolution(s)": [ + { + "Number": "112", + "Name": "Rhydon" + } + ] + }, + { + "Number": "112", + "Name": "Rhydon", + "Classification": "Drill Pokemon", + "Type I": [ + "Ground" + ], + "Type II": [ + "Rock" + ], + "Weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "Fast Attack(s)": [ + "Mud Slap", + "Rock Smash" + ], + "Weight": "120.0 kg", + "Height": "1.9 m", + "Previous evolution(s)": [ + { + "Number": "111", + "Name": "Rhyhorn" + } + ] + }, + { + "Number": "113", + "Name": "Chansey", + "Classification": "Egg Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Pound", + "Zen Headbutt" + ], + "Weight": "34.6 kg", + "Height": "1.1 m" + }, + { + "Number": "114", + "Name": "Tangela", + "Classification": "Vine Pokemon", + "Type I": [ + "Grass" + ], + "Weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug" + ], + "Fast Attack(s)": [ + "Vine Whip", + "" + ], + "Weight": "35.0 kg", + "Height": "1.0 m" + }, + { + "Number": "115", + "Name": "Kangaskhan", + "Classification": "Parent Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Low Kick", + "" + ], + "Weight": "80.0 kg", + "Height": "2.2 m" + }, + { + "Number": "116", + "Name": "Horsea", + "Classification": "Dragon Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Bubble", + "Water Gun" + ], + "Weight": "8.0 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Horsea candies" + }, + "Next evolution(s)": [ + { + "Number": "117", + "Name": "Seadra" + } + ] + }, + { + "Number": "117", + "Name": "Seadra", + "Classification": "Dragon Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Dragon Breath", + "Water Gun" + ], + "Weight": "25.0 kg", + "Height": "1.2 m", + "Previous evolution(s)": [ + { + "Number": "116", + "Name": "Horsea" + } + ] + }, + { + "Number": "118", + "Name": "Goldeen", + "Classification": "Goldfish Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Peck", + "Mud Shot" + ], + "Weight": "15.0 kg", + "Height": "0.6 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Goldeen candies" + }, + "Next evolution(s)": [ + { + "Number": "119", + "Name": "Seaking" + } + ] + }, + { + "Number": "119", + "Name": "Seaking", + "Classification": "Goldfish Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Peck", + "Poison Jab" + ], + "Weight": "39.0 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "118", + "Name": "Goldeen" + } + ] + }, + { + "Number": "120", + "Name": "Staryu", + "Classification": "Starshape Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Water Gun" + ], + "Weight": "34.5 kg", + "Height": "0.8 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Staryu candies" + }, + "Next evolution(s)": [ + { + "Number": "121", + "Name": "Starmie" + } + ] + }, + { + "Number": "121", + "Name": "Starmie", + "Classification": "Mysterious Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Water Gun" + ], + "Weight": "80.0 kg", + "Height": "1.1 m", + "Previous evolution(s)": [ + { + "Number": "120", + "Name": "Staryu" + } + ] + }, + { + "Number": "122", + "Name": "Mr. Mime", + "Classification": "Barrier Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Confusion", + "Zen Headbutt" + ], + "Weight": "54.5 kg", + "Height": "1.3 m" + }, + { + "Number": "123", + "Name": "Scyther", + "Classification": "Mantis Pokemon", + "Type I": [ + "Bug" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Fire", + "Electric", + "Ice", + "Flying", + "Rock" + ], + "Fast Attack(s)": [ + "Fury Cutter", + "Steel Wing" + ], + "Weight": "56.0 kg", + "Height": "1.5 m" + }, + { + "Number": "124", + "Name": "Jynx", + "Classification": "Humanshape Pokemon", + "Type I": [ + "Ice" + ], + "Type II": [ + "Psychic" + ], + "Weaknesses": [ + "Fire", + "Bug", + "Rock", + "Ghost", + "Dark", + "Steel" + ], + "Fast Attack(s)": [ + "Frost Breath", + "Pound" + ], + "Weight": "40.6 kg", + "Height": "1.4 m" + }, + { + "Number": "125", + "Name": "Electabuzz", + "Classification": "Electric Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Low Kick", + "Thunder Shock" + ], + "Weight": "30.0 kg", + "Height": "1.1 m" + }, + { + "Number": "126", + "Name": "Magmar", + "Classification": "Spitfire Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "Karate Chop" + ], + "Weight": "44.5 kg", + "Height": "1.3 m" + }, + { + "Number": "127", + "Name": "Pinsir", + "Classification": "Stagbeetle Pokemon", + "Type I": [ + "Bug" + ], + "Weaknesses": [ + "Fire", + "Flying", + "Rock" + ], + "Fast Attack(s)": [ + "Fury Cutter", + "Rock Smash" + ], + "Weight": "55.0 kg", + "Height": "1.5 m" + }, + { + "Number": "128", + "Name": "Tauros", + "Classification": "Wild Bull Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Tackle", + "Zen Headbutt" + ], + "Weight": "88.4 kg", + "Height": "1.4 m" + }, + { + "Number": "129", + "Name": "Magikarp", + "Classification": "Fish Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Splash", + "" + ], + "Weight": "10.0 kg", + "Height": "0.9 m", + "Next Evolution Requirements": { + "Amount": 400, + "Name": "Magikarp candies" + }, + "Next evolution(s)": [ + { + "Number": "130", + "Name": "Gyarados" + } + ] + }, + { + "Number": "130", + "Name": "Gyarados", + "Classification": "Atrocious Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Bite", + "Dragon Breath" + ], + "Weight": "235.0 kg", + "Height": "6.5 m", + "Previous evolution(s)": [ + { + "Number": "129", + "Name": "Magikarp" + } + ] + }, + { + "Number": "131", + "Name": "Lapras", + "Classification": "Transport Pokemon", + "Type I": [ + "Water" + ], + "Type II": [ + "Ice" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ], + "Fast Attack(s)": [ + "Frost Breath", + "Ice Shard" + ], + "Weight": "220.0 kg", + "Height": "2.5 m" + }, + { + "Number": "132", + "Name": "Ditto", + "Classification": "Transform Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "4.0 kg", + "Height": "0.3 m" + }, + { + "Number": "133", + "Name": "Eevee", + "Classification": "Evolution Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Tackle" + ], + "Weight": "6.5 kg", + "Height": "0.3 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Eevee candies" + }, + "Next evolution(s)": [ + { + "Number": "134", + "Name": "Vaporeon" + }, + { + "Number": "135", + "Name": "Jolteon" + }, + { + "Number": "136", + "Name": "Flareon" + } + ] + }, + { + "Number": "134", + "Name": "Vaporeon", + "Classification": "Bubble Jet Pokemon", + "Type I": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass" + ], + "Fast Attack(s)": [ + "Water Gun", + "" + ], + "Weight": "29.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "133", + "Name": "Eevee" + } + ] + }, + { + "Number": "135", + "Name": "Jolteon", + "Classification": "Lightning Pokemon", + "Type I": [ + "Electric" + ], + "Weaknesses": [ + "Ground" + ], + "Fast Attack(s)": [ + "Thunder Shock", + "" + ], + "Weight": "24.5 kg", + "Height": "0.8 m", + "Previous evolution(s)": [ + { + "Number": "133", + "Name": "Eevee" + } + ] + }, + { + "Number": "136", + "Name": "Flareon", + "Classification": "Flame Pokemon", + "Type I": [ + "Fire" + ], + "Weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "Fast Attack(s)": [ + "Ember", + "" + ], + "Weight": "25.0 kg", + "Height": "0.9 m", + "Previous evolution(s)": [ + { + "Number": "133", + "Name": "Eevee" + } + ] + }, + { + "Number": "137", + "Name": "Porygon", + "Classification": "Virtual Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Quick Attack", + "Tackle" + ], + "Weight": "36.5 kg", + "Height": "0.8 m" + }, + { + "Number": "138", + "Name": "Omanyte", + "Classification": "Spiral Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "Fast Attack(s)": [ + "Water Gun", + "" + ], + "Weight": "7.5 kg", + "Height": "0.4 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Omanyte candies" + }, + "Next evolution(s)": [ + { + "Number": "139", + "Name": "Omastar" + } + ] + }, + { + "Number": "139", + "Name": "Omastar", + "Classification": "Spiral Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "Fast Attack(s)": [ + "Rock Throw", + "Water Gun" + ], + "Weight": "35.0 kg", + "Height": "1.0 m", + "Previous evolution(s)": [ + { + "Number": "138", + "Name": "Omanyte" + } + ] + }, + { + "Number": "140", + "Name": "Kabuto", + "Classification": "Shellfish Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "Fast Attack(s)": [ + "Mud Shot", + "Scratch" + ], + "Weight": "11.5 kg", + "Height": "0.5 m", + "Next Evolution Requirements": { + "Amount": 50, + "Name": "Kabuto candies" + }, + "Next evolution(s)": [ + { + "Number": "141", + "Name": "Kabutops" + } + ] + }, + { + "Number": "141", + "Name": "Kabutops", + "Classification": "Shellfish Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Water" + ], + "Weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "Fast Attack(s)": [ + "Fury Cutter", + "Mud Shot" + ], + "Weight": "40.5 kg", + "Height": "1.3 m", + "Previous evolution(s)": [ + { + "Number": "140", + "Name": "Kabuto" + } + ] + }, + { + "Number": "142", + "Name": "Aerodactyl", + "Classification": "Fossil Pokemon", + "Type I": [ + "Rock" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Water", + "Electric", + "Ice", + "Rock", + "Steel" + ], + "Fast Attack(s)": [ + "Bite", + "Steel Wing" + ], + "Weight": "59.0 kg", + "Height": "1.8 m" + }, + { + "Number": "143", + "Name": "Snorlax", + "Classification": "Sleeping Pokemon", + "Type I": [ + "Normal" + ], + "Weaknesses": [ + "Fighting" + ], + "Fast Attack(s)": [ + "Lick", + "Zen Headbutt" + ], + "Weight": "460.0 kg", + "Height": "2.1 m" + }, + { + "Number": "144", + "Name": "Articuno", + "Classification": "Freeze Pokemon", + "Type I": [ + "Ice" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Fire", + "Electric", + "Rock", + "Steel" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "55.4 kg", + "Height": "1.7 m" + }, + { + "Number": "145", + "Name": "Zapdos", + "Classification": "Electric Pokemon", + "Type I": [ + "Electric" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Ice", + "Rock" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "52.6 kg", + "Height": "1.6 m" + }, + { + "Number": "146", + "Name": "Moltres", + "Classification": "Flame Pokemon", + "Type I": [ + "Fire" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Water", + "Electric", + "Rock" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "60.0 kg", + "Height": "2.0 m" + }, + { + "Number": "147", + "Name": "Dratini", + "Classification": "Dragon Pokemon", + "Type I": [ + "Dragon" + ], + "Weaknesses": [ + "Ice", + "Dragon", + "Fairy" + ], + "Fast Attack(s)": [ + "Dragon Breath", + "" + ], + "Weight": "3.3 kg", + "Height": "1.8 m", + "Next Evolution Requirements": { + "Amount": 25, + "Name": "Dratini candies" + }, + "Next evolution(s)": [ + { + "Number": "148", + "Name": "Dragonair" + }, + { + "Number": "149", + "Name": "Dragonite" + } + ] + }, + { + "Number": "148", + "Name": "Dragonair", + "Classification": "Dragon Pokemon", + "Type I": [ + "Dragon" + ], + "Weaknesses": [ + "Ice", + "Dragon", + "Fairy" + ], + "Fast Attack(s)": [ + "Dragon Breath", + "" + ], + "Weight": "16.5 kg", + "Height": "4.0 m", + "Previous evolution(s)": [ + { + "Number": "147", + "Name": "Dratini" + } + ], + "Next Evolution Requirements": { + "Amount": 100, + "Name": "Dratini candies" + }, + "Next evolution(s)": [ + { + "Number": "149", + "Name": "Dragonite" + } + ] + }, + { + "Number": "149", + "Name": "Dragonite", + "Classification": "Dragon Pokemon", + "Type I": [ + "Dragon" + ], + "Type II": [ + "Flying" + ], + "Weaknesses": [ + "Ice", + "Rock", + "Dragon", + "Fairy" + ], + "Fast Attack(s)": [ + "Dragon Breath", + "Steel Wing" + ], + "Weight": "210.0 kg", + "Height": "2.2 m", + "Previous evolution(s)": [ + { + "Number": "147", + "Name": "Dratini" + }, + { + "Number": "148", + "Name": "Dragonair" + } + ] + }, + { + "Number": "150", + "Name": "Mewtwo", + "Classification": "Genetic Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "122.0 kg", + "Height": "2.0 m" + }, + { + "Number": "151", + "Name": "Mew", + "Classification": "New Species Pokemon", + "Type I": [ + "Psychic" + ], + "Weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "Fast Attack(s)": [ + "Unknown" + ], + "Special Attack(s)": [ + "Unknown" + ], + "Weight": "4.0 kg", + "Height": "0.4 m" + } +] diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index d0f034f110..7b462cb229 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -265,7 +265,7 @@ def _register_events(self): ) self.event_manager.register_event( 'pokemon_evolved', - parameters=('pokemon', 'iv', 'cp') + parameters=('pokemon', 'iv', 'cp', 'xp') ) self.event_manager.register_event( 'pokemon_evolve_fail', diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 68d181947a..cb256dcbed 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -18,3 +18,4 @@ from follow_cluster import FollowCluster from sleep_schedule import SleepSchedule from update_title_stats import UpdateTitleStats +from pokemongo_bot.cell_workers.pokemon_optimizer import PokemonOptimizer diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py new file mode 100644 index 0000000000..5bbfd4306e --- /dev/null +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -0,0 +1,320 @@ +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.human_behaviour import sleep, action_delay + + +class PokemonOptimizer(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def initialize(self): + self.player_level_factor = None + self.pokemon_max_cp = None + self.family_data_by_pokemon_name = {} + + self.init_player_level_factor() + self.init_pokemon_max_cp() + self.init_family_data_by_pokemon_name() + + self.family_by_family_name = {} + self.candies_by_family_name = {} + self.pokemon_count = 0 + + self.dry_run = False + + def work(self): + family_changed = True + + while family_changed: + family_changed = False + + self.refresh_inventory() + + if self.get_pokemon_slot_left() > 5: + break + + for family_name, family in self.family_by_family_name.items(): + family_changed |= self.optimize_family(family_name, family) + + if family_changed: + self.bot.latest_inventory = None + + def get_pokemon_slot_left(self): + return self.bot._player["max_pokemon_storage"] - self.pokemon_count + + def optimize_family(self, family_name, family): + family_changed = False + + if family_name == "Eevee": + return False + + candies = self.candies_by_family_name.get(family_name, 0) + best_iv_pokemons = self.get_best_iv_in_family(family) + best_relative_cp_pokemons = self.get_best_relative_cp_in_family(family) + best_cp_pokemons = self.get_best_cp_in_family(family) + + best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) + + need_candy_for_best = sum(p["candy_count"] for p in best_pokemons) + need_candy_per_xp = self.family_data_by_pokemon_name[family_name][2] + + leftover_candy = candies - need_candy_for_best + + if (need_candy_per_xp > 0) and (leftover_candy >= need_candy_per_xp): + keep_for_evo = int(leftover_candy / need_candy_per_xp) + else: + keep_for_evo = 0 + + crap = family[:] + crap = [p for p in crap if p not in best_iv_pokemons] + crap = [p for p in crap if p not in best_relative_cp_pokemons] + crap = [p for p in crap if p not in best_cp_pokemons] + crap.sort(key=lambda p: p["iv"], reverse=True) + + transfer_pokemons = crap[keep_for_evo:] + evo_pokemons = crap[:keep_for_evo] + + for pokemon in transfer_pokemons: + self.transfer_pokemon(pokemon) + family_changed = True + + for pokemon in best_pokemons + evo_pokemons: + if pokemon["candy_count"] == 0: + continue + + if self.candies_by_family_name[pokemon["family_name"]] < pokemon["candy_count"]: + break + + if self.evolve_pokemon(pokemon): + family_changed = True + else: + break + + return family_changed + + def transfer_pokemon(self, pokemon): + if self.dry_run: + pass + else: + self.bot.api.release_pokemon(pokemon_id=pokemon["id"]) + + self.emit_event("pokemon_release", + formatted="Exchanged {pokemon} [IV {iv}] [CP {cp}]", + data={"pokemon": pokemon["name"], + "iv": pokemon["iv"], + "cp": pokemon["cp"]}) + + if not self.dry_run: + action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + + def evolve_pokemon(self, pokemon): + if self.dry_run: + response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} + else: + response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon["id"]) + + if not response_dict: + return False + + result = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("result", 0) + xp = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("experience_awarded", 0) + + if result == 1: + self.emit_event("pokemon_evolved", + formatted="Evolved {pokemon} [IV {iv}] [CP {cp}] [+{xp} xp]", + data={"pokemon": pokemon["name"], + "iv": pokemon["iv"], + "cp": pokemon["cp"], + "xp": xp}) + + if not self.dry_run: + self.candies_by_family_name[pokemon["family_name"]] -= pokemon["candy_count"] + sleep(20) + + return True + else: + return False + + def get_best_cp_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["cp"]) + return [p for p in family if p["cp"] == best_pokemon["cp"]] + + def get_best_relative_cp_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["relative_cp"]) + return [p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]] + + def get_best_iv_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["iv"]) + return [p for p in family if p["iv"] == best_pokemon["iv"]] + + def refresh_inventory(self): + self.family_by_family_name.clear() + self.candies_by_family_name.clear() + self.pokemon_count = 0 + + inventory = self.bot.get_inventory() + inventory_items = (inventory.get("responses", {}) + .get("GET_INVENTORY", {}) + .get("inventory_delta", {}) + .get("inventory_items", {})) + + for item in inventory_items: + pokemon_data = item.get("inventory_item_data", {}).get("pokemon_data", {}) + candy_data = item.get('inventory_item_data', {}).get('candy', {}) + + if pokemon_data: + self.pokemon_count += 1 + pokemon = self.get_pokemon_from_data(pokemon_data) + + if pokemon: + family_name = pokemon["family_name"] + self.family_by_family_name.setdefault(family_name, []).append(pokemon) + elif candy_data: + family_id = candy_data.get("family_id", 0) + count = candy_data.get("candy", 0) + + if (family_id > 0) and (count > 0): + family_name = self.bot.pokemon_list[family_id - 1].get("Name") + + if not family_name: + continue + + self.candies_by_family_name[family_name] = count + + def get_pokemon_from_data(self, pokemon_data): + pokemon_index = pokemon_data.get("pokemon_id", 0) - 1 + + if pokemon_index < 0: + return None + + pokemon_name = self.bot.pokemon_list[pokemon_index].get("Name") + + if not pokemon_name: + return None + + family_data = self.family_data_by_pokemon_name.get(pokemon_name) + + if not family_data: + return None + + family_name, family_rank, candy_count = family_data + + pokemon_cp = pokemon_data.get("cp", 0) + pokemon_max_cp = self.get_pokemon_max_cp(pokemon_name) + + if pokemon_max_cp > 0: + pokemon_relative_cp = float(pokemon_cp) / pokemon_max_cp + else: + pokemon_relative_cp = 0 + + return {"id": pokemon_data.get("id", 0), + "name": pokemon_name, + "family_name": family_name, + "family_rank": family_rank, + "candy_count": candy_count, + "cp": pokemon_cp, + "relative_cp": pokemon_relative_cp, + "iv": self.get_pokemon_iv(pokemon_data)} + + def get_pokemon_iv(self, pokemon_data): + iv = (pokemon_data.get("individual_attack", 0) + + pokemon_data.get("individual_stamina", 0) + + pokemon_data.get("individual_defense", 0)) + return round(iv / 45.0, 2) + + def get_pokemon_max_cp(self, pokemon_name): + return int(self.pokemon_max_cp.get(pokemon_name, 0)) + + def get_pokemon_max_cp_for_player(self, player_level, pokemon_name): + return int(self.player_level_factor[player_level] * self.pokemon_max_cp.get(pokemon_name, 0)) + + def combine_pokemon_lists(self, a, b): + seen = set() + return [p for p in a + b if not (p["id"] in seen or seen.add(p["id"]))] + + def init_player_level_factor(self): + self.player_level_factor = [ + 0, 0.094, 0.16639787, 0.21573247, 0.25572005, 0.29024988, + 0.3210876, 0.34921268, 0.37523559, 0.39956728, 0.4225, + 0.44310755, 0.46279839, 0.48168495, 0.49985844, 0.51739395, + 0.53435433, 0.55079269, 0.56675452, 0.58227891, 0.5974, + 0.61215729, 0.62656713, 0.64065295, 0.65443563, 0.667934, + 0.68116492, 0.69414365, 0.70688421, 0.71939909, 0.7317, + 0.73776948, 0.74378943, 0.74976104, 0.75568551, 0.76156384, + 0.76739717, 0.7731865, 0.77893275, 0.784637, 0.7903] + + def init_pokemon_max_cp(self): + self.pokemon_max_cp = { + "Abra": 600.44, "Aerodactyl": 2165.49, "Alakazam": 1813.82, + "Arbok": 1767.13, "Arcanine": 2983.9, "Articuno": 2978.16, + "Beedrill": 1439.96, "Bellsprout": 1117.43, "Blastoise": 2542.01, + "Bulbasaur": 1071.54, "Butterfree": 1454.94, "Caterpie": 443.52, + "Chansey": 675.12, "Charizard": 2602.2, "Charmander": 955.24, + "Charmeleon": 1557.48, "Clefable": 2397.71, "Clefairy": 1200.96, + "Cloyster": 2052.85, "Cubone": 1006.61, "Dewgong": 2145.77, + "Diglett": 456.76, "Ditto": 919.62, "Dodrio": 1836.37, + "Doduo": 855.41, "Dragonair": 1485.88, "Dragonite": 3500.06, + "Dratini": 983.47, "Drowzee": 1075.14, "Dugtrio": 1168.55, + "Eevee": 1077.2, "Ekans": 824.14, "Electabuzz": 2119.17, + "Electrode": 1646.14, "Exeggcute": 1099.81, "Exeggutor": 2955.18, + "Farfetch'd": 1263.89, "Fearow": 1746.35, "Flareon": 2643.43, + "Gastly": 804.41, "Gengar": 2078.23, "Geodude": 849.49, + "Gloom": 1689.46, "Golbat": 1921.35, "Goldeen": 965.14, + "Golduck": 2386.52, "Golem": 2303.17, "Graveler": 1433.63, + "Grimer": 1284.02, "Growlithe": 1335.03, "Gyarados": 2688.89, + "Haunter": 1380.21, "Hitmonchan": 1516.51, "Hitmonlee": 1492.94, + "Horsea": 764.67, "Hypno": 2184.16, "Ivysaur": 1632.19, + "Jigglypuff": 917.64, "Jolteon": 2140.27, "Jynx": 1716.73, + "Kabuto": 1104.72, "Kabutops": 2130.01, "Kadabra": 1131.96, + "Kakuna": 485.35, "Kangaskhan": 2043.4, "Kingler": 1823.15, + "Koffing": 1151.79, "Krabby": 792.21, "Lapras": 2980.73, + "Lickitung": 1626.82, "Machamp": 2594.17, "Machoke": 1760.71, + "Machop": 1089.59, "Magikarp": 262.7, "Magmar": 2265.3, + "Magnemite": 890.68, "Magneton": 1879.95, "Mankey": 878.67, + "Marowak": 1656.96, "Meowth": 756.32, "Metapod": 447.92, + "Mew": 3299.17, "Mewtwo": 4144.75, "Moltres": 3240.47, + "Mr. Mime": 1494.42, "Muk": 2602.9, "Nidoking": 2475.14, + "Nidoqueen": 2485.03, "Nidoran F": 876.01, "Nidoran M": 843.14, + "Nidorina": 1404.61, "Nidorino": 1372.5, "Ninetales": 2188.28, + "Oddish": 1148.28, "Omanyte": 1119.77, "Omastar": 2233.65, + "Onix": 857.2, "Paras": 916.6, "Parasect": 1747.07, + "Persian": 1631.84, "Pidgeotto": 1223.98, "Pidgey": 679.93, + "Pidgeot": 2091.39, "Pikachu": 887.69, "Pinsir": 2121.87, + "Poliwag": 795.96, "Poliwhirl": 1340.43, "Poliwrath": 2505.33, + "Ponyta": 1516.11, "Porygon": 1691.56, "Primeape": 1864.52, + "Psyduck": 1109.56, "Raichu": 2028.3, "Rapidash": 2199.34, + "Raticate": 1444.13, "Rattata": 581.65, "Rhydon": 2243.22, + "Rhyhorn": 1182.08, "Sandshrew": 798.76, "Sandslash": 1810.22, + "Scyther": 2073.96, "Seadra": 1713.22, "Seaking": 2043.92, + "Seel": 1107.03, "Shellder": 822.91, "Slowbro": 2597.19, + "Slowpoke": 1218.9, "Snorlax": 3112.85, "Spearow": 686.87, + "Squirtle": 1008.69, "Starmie": 2182.14, "Staryu": 937.89, + "Tangela": 1739.72, "Tauros": 1844.76, "Tentacool": 905.15, + "Tentacruel": 2220.32, "Vaporeon": 2816.25, "Venomoth": 1890.32, + "Venonat": 1029.39, "Venusaur": 2580.49, "Victreebel": 2530.52, + "Vileplume": 2492.66, "Voltorb": 839.73, "Vulpix": 831.41, + "Wartortle": 1582.79, "Weedle": 449.09, "Weepinbell": 1723.76, + "Weezing": 2250.15, "Wigglytuff": 2177.2, "Zapdos": 3114.38, + "Zubat": 642.51} + + def init_family_data_by_pokemon_name(self): + for pokemon in self.bot.pokemon_list: + pokemon_name = pokemon.get("Name") + + if not pokemon_name: + continue + + prev_evo = pokemon.get("Previous evolution(s)") + next_evo = pokemon.get("Next Evolution Requirements") + + if next_evo: + candy_count = next_evo.get("Amount", 0) + else: + candy_count = 0 + + if not prev_evo: + self.family_data_by_pokemon_name[pokemon_name] = (pokemon_name, 0, candy_count) + else: + family_rank = len(prev_evo) + family_name = prev_evo[0].get("Name") + + if family_name: + self.family_data_by_pokemon_name[pokemon_name] = (family_name, family_rank, candy_count) From a894a32e3d5fc920261d6c1137812b3837287b27 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Mon, 8 Aug 2016 08:02:21 +0800 Subject: [PATCH 08/24] For now, dry run only --- pokemongo_bot/cell_workers/pokemon_optimizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 5bbfd4306e..a26df2d83d 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -18,7 +18,7 @@ def initialize(self): self.candies_by_family_name = {} self.pokemon_count = 0 - self.dry_run = False + self.dry_run = True def work(self): family_changed = True @@ -34,6 +34,9 @@ def work(self): for family_name, family in self.family_by_family_name.items(): family_changed |= self.optimize_family(family_name, family) + if self.dry_run: + break + if family_changed: self.bot.latest_inventory = None From 7490bf84cd4328d2b2e29aac06d4d5ca7f720fcb Mon Sep 17 00:00:00 2001 From: villish Date: Sun, 7 Aug 2016 20:20:33 -0700 Subject: [PATCH 09/24] Add cygwin to supported platform and improved log readability (#2948) * Add cygwin to supported platform and improved log readability * fixed formatting --- pokemongo_bot/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index bb5578746f..3481a190f2 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -609,7 +609,7 @@ def get_encryption_lib(self): file_name = '' if _platform == "linux" or _platform == "linux2" or _platform == "darwin": file_name = 'encrypt.so' - elif _platform == "Windows" or _platform == "win32": + elif _platform == "Windows" or _platform == "win32" or _platform == "cygwin": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' @@ -621,7 +621,8 @@ def get_encryption_lib(self): if not os.path.isfile(full_path): self.logger.error(file_name + ' is not found! Please place it in the bots root directory.') - self.logger.info('Platform: '+ _platform + ' Bot root directory: '+ path) + self.logger.info('Platform: '+ _platform) + self.logger.info('Bot root directory: '+ path) sys.exit(1) else: self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' Bot root directory: ' + path) From 2a7a6cd5f6a96d38868a4494678836871d23f115 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Mon, 8 Aug 2016 23:41:14 +0800 Subject: [PATCH 10/24] - Add dry_run and use_lucky_egg in config - Evolve all pokemons together and only if enough for a full lucky egg (90). - Keep enough candies for consecutive evolutions of best pokemons - Only evolve the lowest rank of a family --- pokemongo_bot/cell_workers/__init__.py | 2 +- pokemongo_bot/cell_workers/evolve_pokemon.py | 3 +- .../cell_workers/pokemon_optimizer.py | 174 ++++++++++++------ 3 files changed, 123 insertions(+), 56 deletions(-) diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index cb256dcbed..d89eaf1a7d 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -18,4 +18,4 @@ from follow_cluster import FollowCluster from sleep_schedule import SleepSchedule from update_title_stats import UpdateTitleStats -from pokemongo_bot.cell_workers.pokemon_optimizer import PokemonOptimizer +from pokemon_optimizer import PokemonOptimizer diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index c3903a685d..45b1591dec 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -151,7 +151,8 @@ def _execute_pokemon_evolve(self, pokemon, candy_list, cache): data={ 'pokemon': pokemon_name, 'iv': pokemon_iv, - 'cp': pokemon_cp + 'cp': pokemon_cp, + 'xp': 0 } ) candy_list[pokemon["candies_family"]] -= pokemon["candies_amount"] diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index a26df2d83d..ad4f707591 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,5 +1,8 @@ +import logging + from pokemongo_bot.base_task import BaseTask from pokemongo_bot.human_behaviour import sleep, action_delay +from pokemongo_bot.item_list import Item class PokemonOptimizer(BaseTask): @@ -17,8 +20,13 @@ def initialize(self): self.family_by_family_name = {} self.candies_by_family_name = {} self.pokemon_count = 0 + self.lucky_egg_count = 0 - self.dry_run = True + self.dry_run = self.config.get("dry_run", True) + self.use_lucky_egg = self.config.get("use_lucky_egg", False) + + def get_pokemon_slot_left(self): + return self.bot._player["max_pokemon_storage"] - self.pokemon_count def work(self): family_changed = True @@ -31,8 +39,22 @@ def work(self): if self.get_pokemon_slot_left() > 5: break + transfer_all_pokemons = [] + evo_all_best_pokemons = [] + evo_all_crap_pokemons = [] + for family_name, family in self.family_by_family_name.items(): - family_changed |= self.optimize_family(family_name, family) + transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_family_optimized(family_name, family) + transfer_all_pokemons += transfer_pokemons + evo_all_best_pokemons += evo_best_pokemons + evo_all_crap_pokemons += evo_crap_pokemons + + evo_all_pokemons = evo_all_best_pokemons + evo_all_crap_pokemons + + if (not self.use_lucky_egg) or (self.lucky_egg_count == 0) or (len(evo_all_pokemons) < 90): + del evo_all_pokemons[:] + + family_changed = self.apply_optimization(transfer_all_pokemons, evo_all_pokemons) if self.dry_run: break @@ -40,14 +62,9 @@ def work(self): if family_changed: self.bot.latest_inventory = None - def get_pokemon_slot_left(self): - return self.bot._player["max_pokemon_storage"] - self.pokemon_count - - def optimize_family(self, family_name, family): - family_changed = False - + def get_family_optimized(self, family_name, family): if family_name == "Eevee": - return False + return ([], [], []) candies = self.candies_by_family_name.get(family_name, 0) best_iv_pokemons = self.get_best_iv_in_family(family) @@ -56,36 +73,63 @@ def optimize_family(self, family_name, family): best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) - need_candy_for_best = sum(p["candy_count"] for p in best_pokemons) - need_candy_per_xp = self.family_data_by_pokemon_name[family_name][2] + crap = family[:] + crap = [p for p in crap if p not in best_pokemons] + crap = [p for p in crap if p not in best_cp_pokemons] + crap.sort(key=lambda p: p["iv"], reverse=True) + + evo_best_pokemons = [] + + for pokemon in best_pokemons: + next_amount = pokemon["next_amount"] + + if next_amount == 0: + continue + + if next_amount <= candies: + evo_best_pokemons.append(pokemon) + candies -= next_amount - leftover_candy = candies - need_candy_for_best + next_name = pokemon["next_name"] - if (need_candy_per_xp > 0) and (leftover_candy >= need_candy_per_xp): - keep_for_evo = int(leftover_candy / need_candy_per_xp) + if next_name: + next_family_data = self.family_data_by_pokemon_name[next_name] + + if next_family_data["next_amount"] > 0: + # logging.info("Will try to multi evolve %s [IV %s] [CP %s]", pokemon["name"], pokemon["iv"], pokemon["cp"]) + + next_evo = dict(pokemon) + next_evo["name"] = next_name + next_evo.update(next_family_data) + + best_pokemons.append(next_evo) + else: + break + + junior_next_amount = self.family_data_by_pokemon_name[family_name]["next_amount"] + + # leftover_candy = candies + transfer + # transfer + keep_for_evo = len(crap) + # keep_for_evo = int(leftover_candy / junior_next_amount) + + if junior_next_amount > 0: + keep_for_evo = int((candies + len(crap)) / (junior_next_amount + 1)) else: keep_for_evo = 0 - crap = family[:] - crap = [p for p in crap if p not in best_iv_pokemons] - crap = [p for p in crap if p not in best_relative_cp_pokemons] - crap = [p for p in crap if p not in best_cp_pokemons] - crap.sort(key=lambda p: p["iv"], reverse=True) + evo_crap_pokemons = [p for p in crap if p["next_amount"] == junior_next_amount][:keep_for_evo] + transfer_pokemons = [p for p in crap if p not in evo_crap_pokemons] - transfer_pokemons = crap[keep_for_evo:] - evo_pokemons = crap[:keep_for_evo] + return (transfer_pokemons, evo_best_pokemons, evo_crap_pokemons) + + def apply_optimization(self, transfer_pokemons, evo_pokemons): + family_changed = False for pokemon in transfer_pokemons: self.transfer_pokemon(pokemon) family_changed = True - for pokemon in best_pokemons + evo_pokemons: - if pokemon["candy_count"] == 0: - continue - - if self.candies_by_family_name[pokemon["family_name"]] < pokemon["candy_count"]: - break - + for pokemon in evo_pokemons: if self.evolve_pokemon(pokemon): family_changed = True else: @@ -93,6 +137,18 @@ def optimize_family(self, family_name, family): return family_changed + def get_best_iv_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["iv"]) + return [p for p in family if p["iv"] == best_pokemon["iv"]] + + def get_best_relative_cp_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["relative_cp"]) + return [p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]] + + def get_best_cp_in_family(self, family): + best_pokemon = max(family, key=lambda p: p["cp"]) + return [p for p in family if p["cp"] == best_pokemon["cp"]] + def transfer_pokemon(self, pokemon): if self.dry_run: pass @@ -129,25 +185,12 @@ def evolve_pokemon(self, pokemon): "xp": xp}) if not self.dry_run: - self.candies_by_family_name[pokemon["family_name"]] -= pokemon["candy_count"] sleep(20) return True else: return False - def get_best_cp_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["cp"]) - return [p for p in family if p["cp"] == best_pokemon["cp"]] - - def get_best_relative_cp_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["relative_cp"]) - return [p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]] - - def get_best_iv_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["iv"]) - return [p for p in family if p["iv"] == best_pokemon["iv"]] - def refresh_inventory(self): self.family_by_family_name.clear() self.candies_by_family_name.clear() @@ -161,7 +204,8 @@ def refresh_inventory(self): for item in inventory_items: pokemon_data = item.get("inventory_item_data", {}).get("pokemon_data", {}) - candy_data = item.get('inventory_item_data', {}).get('candy', {}) + candy_data = item.get("inventory_item_data", {}).get("candy", {}) + item_data = item.get("inventory_item_data", {}).get("item", {}) if pokemon_data: self.pokemon_count += 1 @@ -181,6 +225,11 @@ def refresh_inventory(self): continue self.candies_by_family_name[family_name] = count + elif item_data: + item_id = item_data.get("item_id", False) + + if item_id == Item.ITEM_LUCKY_EGG: + self.lucky_egg_count = item_data.get("count", 0) def get_pokemon_from_data(self, pokemon_data): pokemon_index = pokemon_data.get("pokemon_id", 0) - 1 @@ -191,15 +240,15 @@ def get_pokemon_from_data(self, pokemon_data): pokemon_name = self.bot.pokemon_list[pokemon_index].get("Name") if not pokemon_name: + logging.error("Invalid pokemon.json data file. Missing 'Name' field for pokemon_index '%s'", pokemon_index) return None family_data = self.family_data_by_pokemon_name.get(pokemon_name) if not family_data: + logging.error("Invalid pokemon.json data file. Missing data for '%s'", pokemon_name) return None - family_name, family_rank, candy_count = family_data - pokemon_cp = pokemon_data.get("cp", 0) pokemon_max_cp = self.get_pokemon_max_cp(pokemon_name) @@ -210,9 +259,9 @@ def get_pokemon_from_data(self, pokemon_data): return {"id": pokemon_data.get("id", 0), "name": pokemon_name, - "family_name": family_name, - "family_rank": family_rank, - "candy_count": candy_count, + "family_name": family_data["family_name"], + "next_amount": family_data["next_amount"], + "next_name": family_data["next_name"], "cp": pokemon_cp, "relative_cp": pokemon_relative_cp, "iv": self.get_pokemon_iv(pokemon_data)} @@ -303,21 +352,38 @@ def init_family_data_by_pokemon_name(self): pokemon_name = pokemon.get("Name") if not pokemon_name: + logging.error("Invalid pokemon.json data file. Missing 'Name' field for '%s'", pokemon) continue prev_evo = pokemon.get("Previous evolution(s)") - next_evo = pokemon.get("Next Evolution Requirements") + next_evo_req = pokemon.get("Next Evolution Requirements") + next_evo = pokemon.get("Next evolution(s)") + + if next_evo and next_evo_req: + next_amount = next_evo_req.get("Amount") + next_name = next_evo[0].get("Name") + + if not next_amount: + logging.error("Invalid pokemon.json data file. Missing 'Amount' field for 'Next Evolution Requirements' of '%s'", pokemon_name) - if next_evo: - candy_count = next_evo.get("Amount", 0) + if not next_name: + logging.error("Invalid pokemon.json data file. Missing 'Name' field for 'Next Evolution Requirements' of '%s'", pokemon_name) + else: + next_name = next_name.replace("candies", "").strip() else: - candy_count = 0 + next_amount = 0 + next_name = None if not prev_evo: - self.family_data_by_pokemon_name[pokemon_name] = (pokemon_name, 0, candy_count) + self.family_data_by_pokemon_name[pokemon_name] = {"family_name": pokemon_name, + "next_amount": next_amount, + "next_name": next_name} else: - family_rank = len(prev_evo) family_name = prev_evo[0].get("Name") if family_name: - self.family_data_by_pokemon_name[pokemon_name] = (family_name, family_rank, candy_count) + self.family_data_by_pokemon_name[pokemon_name] = {"family_name": family_name, + "next_amount": next_amount, + "next_name": next_name} + else: + logging.error("Invalid pokemon.json data file. Missing 'Name' field for 'Previous evolution(s)' of '%s'", pokemon_name) From 4b0faaab8688abe5fb90912d55d54e75c69f1de5 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Tue, 9 Aug 2016 00:25:18 +0800 Subject: [PATCH 11/24] Add lucky egg support when enough pokemon to evolve --- .../cell_workers/pokemon_optimizer.py | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index ad4f707591..436ea0d226 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -23,7 +23,7 @@ def initialize(self): self.lucky_egg_count = 0 self.dry_run = self.config.get("dry_run", True) - self.use_lucky_egg = self.config.get("use_lucky_egg", False) + self.use_lucky_egg = self.config.get("use_lucky_egg", True) def get_pokemon_slot_left(self): return self.bot._player["max_pokemon_storage"] - self.pokemon_count @@ -51,9 +51,6 @@ def work(self): evo_all_pokemons = evo_all_best_pokemons + evo_all_crap_pokemons - if (not self.use_lucky_egg) or (self.lucky_egg_count == 0) or (len(evo_all_pokemons) < 90): - del evo_all_pokemons[:] - family_changed = self.apply_optimization(transfer_all_pokemons, evo_all_pokemons) if self.dry_run: @@ -129,6 +126,12 @@ def apply_optimization(self, transfer_pokemons, evo_pokemons): self.transfer_pokemon(pokemon) family_changed = True + if self.use_lucky_egg: + if (self.lucky_egg_count == 0) or (len(evo_pokemons) < 90): + return family_changed + + self.do_use_lucky_egg() + for pokemon in evo_pokemons: if self.evolve_pokemon(pokemon): family_changed = True @@ -164,6 +167,31 @@ def transfer_pokemon(self, pokemon): if not self.dry_run: action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + def do_use_lucky_egg(self): + if self.dry_run: + response_dict = {"responses": {"USE_ITEM_XP_BOOST": {"result": 1}}} + else: + response_dict = self.bot.use_lucky_egg() + + if not response_dict: + self.emit_event("lucky_egg_error", + level='error', + formatted="Failed to use lucky egg!") + return False + + result = response_dict.get("responses", {}).get("USE_ITEM_XP_BOOST", {}).get("result", 0) + + if result == 1: + self.emit_event("used_lucky_egg", + formatted="Used lucky egg ({amount_left} left).", + data={"amount_left": self.lucky_egg_count - 1}) + return True + else: + self.emit_event("lucky_egg_error", + level='error', + formatted="Failed to use lucky egg!") + return False + def evolve_pokemon(self, pokemon): if self.dry_run: response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} From a205de880ac3718d120532b3633febf5ab7e134e Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Mon, 8 Aug 2016 22:57:50 +0200 Subject: [PATCH 12/24] fixing returns --- pokemongo_bot/cell_workers/collect_level_up_reward.py | 6 +++--- pokemongo_bot/cell_workers/evolve_pokemon.py | 3 ++- pokemongo_bot/cell_workers/incubate_eggs.py | 3 ++- pokemongo_bot/cell_workers/nickname_pokemon.py | 3 ++- pokemongo_bot/cell_workers/recycle_items.py | 6 ++++-- pokemongo_bot/cell_workers/transfer_pokemon.py | 3 ++- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index f4925f9b3b..d2ce306c2e 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -1,5 +1,5 @@ from pokemongo_bot.base_task import BaseTask - +from pokemongo_bot.worker_result import WorkerResult class CollectLevelUpReward(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -13,9 +13,9 @@ def initialize(self): def work(self): if not self._time_to_run(): - return False + return WorkerResult.SUCCESS self._update_last_ran() - + self.current_level = self._get_current_level() # let's check level reward on bot initialization diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 8ca8557fbb..8efe15e5d4 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -1,6 +1,7 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.item_list import Item from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult class EvolvePokemon(BaseTask): @@ -23,7 +24,7 @@ def _validate_config(self): def work(self): if not self._should_run(): - return + return WorkerResult.SUCCESS self._update_last_ran() diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 933e9d4018..a42d3be272 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -1,5 +1,6 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult class IncubateEggs(BaseTask): @@ -22,7 +23,7 @@ def _process_config(self): def work(self): if not self._time_to_run(): - return False + return WorkerResult.SUCCESS self._update_last_ran() try: diff --git a/pokemongo_bot/cell_workers/nickname_pokemon.py b/pokemongo_bot/cell_workers/nickname_pokemon.py index 7fef6cfdb1..27588c712f 100644 --- a/pokemongo_bot/cell_workers/nickname_pokemon.py +++ b/pokemongo_bot/cell_workers/nickname_pokemon.py @@ -1,5 +1,6 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult class NicknamePokemon(BaseTask): @@ -12,7 +13,7 @@ def initialize(self): def work(self): if not self._time_to_run(): - return False + return WorkerResult.SUCCESS self._update_last_ran() try: diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 47e2f06233..0f98a71860 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -2,6 +2,8 @@ import os from pokemongo_bot.base_task import BaseTask from pokemongo_bot.tree_config_builder import ConfigException +from pokemongo_bot.worker_result import WorkerResult + class RecycleItems(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -19,9 +21,9 @@ def _validate_item_filter(self): def work(self): if not self._time_to_run(): - return False + return WorkerResult.SUCCESS self._update_last_ran() - + self.bot.latest_inventory = None item_count_dict = self.bot.item_inventory_count('all') diff --git a/pokemongo_bot/cell_workers/transfer_pokemon.py b/pokemongo_bot/cell_workers/transfer_pokemon.py index 90c8b9eb83..87471d2cf8 100644 --- a/pokemongo_bot/cell_workers/transfer_pokemon.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -2,6 +2,7 @@ from pokemongo_bot.human_behaviour import action_delay from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult class TransferPokemon(BaseTask): @@ -9,7 +10,7 @@ class TransferPokemon(BaseTask): def work(self): if not self._time_to_run(): - return False + return WorkerResult.SUCCESS self._update_last_ran() pokemon_groups = self._release_pokemon_get_groups() for pokemon_id in pokemon_groups: From 0514a94f3283f00482c5e16115c2ccd2d6770194 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Tue, 9 Aug 2016 21:56:30 +0800 Subject: [PATCH 13/24] - Support Eevee evolution scheme - Rename "use_lucky_egg" parameter in the more accurate "evolve_only_with_lucky_egg" --- .../cell_workers/pokemon_optimizer.py | 175 ++++++++++-------- 1 file changed, 99 insertions(+), 76 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 436ea0d226..1c20d1e103 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -3,17 +3,16 @@ 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 class PokemonOptimizer(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def initialize(self): - self.player_level_factor = None self.pokemon_max_cp = None self.family_data_by_pokemon_name = {} - self.init_player_level_factor() self.init_pokemon_max_cp() self.init_family_data_by_pokemon_name() @@ -23,58 +22,92 @@ def initialize(self): self.lucky_egg_count = 0 self.dry_run = self.config.get("dry_run", True) - self.use_lucky_egg = self.config.get("use_lucky_egg", True) + self.evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", True) def get_pokemon_slot_left(self): return self.bot._player["max_pokemon_storage"] - self.pokemon_count def work(self): - family_changed = True + self.refresh_inventory() - while family_changed: - family_changed = False + if self.get_pokemon_slot_left() > 5: + return WorkerResult.SUCCESS - self.refresh_inventory() - - if self.get_pokemon_slot_left() > 5: - break - - transfer_all_pokemons = [] - evo_all_best_pokemons = [] - evo_all_crap_pokemons = [] - - for family_name, family in self.family_by_family_name.items(): - transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_family_optimized(family_name, family) - transfer_all_pokemons += transfer_pokemons - evo_all_best_pokemons += evo_best_pokemons - evo_all_crap_pokemons += evo_crap_pokemons + transfer_all_pokemons = [] + evo_all_best_pokemons = [] + evo_all_crap_pokemons = [] - evo_all_pokemons = evo_all_best_pokemons + evo_all_crap_pokemons + for family_name, family in self.family_by_family_name.items(): + transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_family_optimized(family_name, family) + transfer_all_pokemons += transfer_pokemons + evo_all_best_pokemons += evo_best_pokemons + evo_all_crap_pokemons += evo_crap_pokemons - family_changed = self.apply_optimization(transfer_all_pokemons, evo_all_pokemons) + evo_all_pokemons = evo_all_best_pokemons + evo_all_crap_pokemons - if self.dry_run: - break + if self.apply_optimization(transfer_all_pokemons, evo_all_pokemons): + self.bot.latest_inventory = None + self.refresh_inventory() - if family_changed: - self.bot.latest_inventory = None + return WorkerResult.SUCCESS def get_family_optimized(self, family_name, family): if family_name == "Eevee": - return ([], [], []) + return self.get_multi_family_optimized(family_name, family, 3) - candies = self.candies_by_family_name.get(family_name, 0) best_iv_pokemons = self.get_best_iv_in_family(family) best_relative_cp_pokemons = self.get_best_relative_cp_in_family(family) best_cp_pokemons = self.get_best_cp_in_family(family) best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) + return self.get_crap_plan(family_name, family, best_pokemons, best_cp_pokemons) + + def get_multi_family_optimized(self, family_name, family, nb_branch): + # Transfer each group of senior independently + senior_family = [p for p in family if p["next_name"] is None] + other_family = [p for p in family if p["next_name"] is not None] + senior_names = set(s["name"] for s in senior_family) + senior_grouped_family = {name: [p for p in senior_family if p["name"] == name] for name in senior_names} + + transfer_senior_pokemons = [] + + for senior_name, senior_family in senior_grouped_family.items(): + transfer_senior_pokemons += self.get_family_optimized(senior_name, senior_family)[0] + + if len(senior_names) < nb_branch: + # We did not get every combination yet = All other Pokemons are potentially good to keep + best_pokemons = other_family + best_pokemons.sort(key=lambda p: p["iv"] * p["relative_cp"], reverse=True) + best_cp_pokemons = [] + else: + min_iv = min([max(f, key=lambda p: p["iv"]) for f in senior_grouped_family.values()], key=lambda p: p["iv"])["iv"] + min_relative_cp = min([max(f, key=lambda p: p["relative_cp"]) for f in senior_grouped_family.values()], key=lambda p: p["relative_cp"])["relative_cp"] + min_cp = min([max(f, key=lambda p: p["cp"]) for f in senior_grouped_family.values()], key=lambda p: p["cp"])["cp"] + + best_iv_pokemons = self.get_better_iv_in_family(other_family, min_iv) + best_relative_cp_pokemons = self.get_better_relative_cp_in_family(other_family, min_relative_cp) + best_cp_pokemons = self.get_better_cp_in_family(other_family, min_cp) + + best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) + + transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_crap_plan(family_name, other_family, best_pokemons, best_cp_pokemons) + transfer_pokemons += transfer_senior_pokemons + + return (transfer_pokemons, evo_best_pokemons, evo_crap_pokemons) + + def get_crap_plan(self, family_name, family, best_pokemons, best_cp_pokemons): + candies = self.candies_by_family_name.get(family_name, 0) + + # All the rest is crap, for now crap = family[:] crap = [p for p in crap if p not in best_pokemons] crap = [p for p in crap if p not in best_cp_pokemons] crap.sort(key=lambda p: p["iv"], reverse=True) + candies += len(crap) + + # Let's see if we can evolve our best pokemons evo_best_pokemons = [] for pokemon in best_pokemons: @@ -83,34 +116,34 @@ def get_family_optimized(self, family_name, family): if next_amount == 0: continue - if next_amount <= candies: - evo_best_pokemons.append(pokemon) - candies -= next_amount + candies -= next_amount - next_name = pokemon["next_name"] - - if next_name: - next_family_data = self.family_data_by_pokemon_name[next_name] + if next_amount > candies: + continue - if next_family_data["next_amount"] > 0: - # logging.info("Will try to multi evolve %s [IV %s] [CP %s]", pokemon["name"], pokemon["iv"], pokemon["cp"]) + evo_best_pokemons.append(pokemon) + next_name = pokemon["next_name"] - next_evo = dict(pokemon) - next_evo["name"] = next_name - next_evo.update(next_family_data) + if not next_name: + continue - best_pokemons.append(next_evo) - else: - break + next_evo = dict(pokemon) + next_evo["name"] = next_name + next_evo.update(self.family_data_by_pokemon_name[next_name]) + best_pokemons.append(next_evo) - junior_next_amount = self.family_data_by_pokemon_name[family_name]["next_amount"] + # Compute how many crap we should keep if we want to batch evolve them for xp + junior_next_amount = self.family_data_by_pokemon_name.get(family_name, {}).get("next_amount", 0) - # leftover_candy = candies + transfer # transfer + keep_for_evo = len(crap) - # keep_for_evo = int(leftover_candy / junior_next_amount) - - if junior_next_amount > 0: - keep_for_evo = int((candies + len(crap)) / (junior_next_amount + 1)) + # leftover_candies = candies - len(crap) + transfer * 1 + # keep_for_evo = leftover_candies / junior_next_amount + # + # keep_for_evo = (candies - len(crap) + transfer) / junior_next_amount + # keep_for_evo = (candies - keep_for_evo) / junior_next_amount + + if (candies > 0) and (junior_next_amount > 0): + keep_for_evo = int(candies / (junior_next_amount + 1)) else: keep_for_evo = 0 @@ -120,37 +153,38 @@ def get_family_optimized(self, family_name, family): return (transfer_pokemons, evo_best_pokemons, evo_crap_pokemons) def apply_optimization(self, transfer_pokemons, evo_pokemons): - family_changed = False - for pokemon in transfer_pokemons: self.transfer_pokemon(pokemon) - family_changed = True - if self.use_lucky_egg: + if self.evolve_only_with_lucky_egg: if (self.lucky_egg_count == 0) or (len(evo_pokemons) < 90): - return family_changed + return - self.do_use_lucky_egg() + self.use_lucky_egg() for pokemon in evo_pokemons: - if self.evolve_pokemon(pokemon): - family_changed = True - else: - break - - return family_changed + self.evolve_pokemon(pokemon) def get_best_iv_in_family(self, family): best_pokemon = max(family, key=lambda p: p["iv"]) - return [p for p in family if p["iv"] == best_pokemon["iv"]] + return sorted([p for p in family if p["iv"] == best_pokemon["iv"]], key=lambda p: p["relative_cp"], reverse=True) + + def get_better_iv_in_family(self, family, iv): + return sorted([p for p in family if p["iv"] >= iv], key=lambda p: (p["iv"], p["relative_cp"]), reverse=True) def get_best_relative_cp_in_family(self, family): best_pokemon = max(family, key=lambda p: p["relative_cp"]) - return [p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]] + return sorted([p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]], key=lambda p: p["iv"], reverse=True) + + def get_better_relative_cp_in_family(self, family, relative_cp): + return sorted([p for p in family if p["relative_cp"] >= relative_cp], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) def get_best_cp_in_family(self, family): best_pokemon = max(family, key=lambda p: p["cp"]) - return [p for p in family if p["cp"] == best_pokemon["cp"]] + return sorted([p for p in family if p["cp"] == best_pokemon["cp"]], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) + + def get_better_cp_in_family(self, family, cp): + return sorted([p for p in family if p["cp"] >= cp], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) def transfer_pokemon(self, pokemon): if self.dry_run: @@ -167,7 +201,7 @@ def transfer_pokemon(self, pokemon): if not self.dry_run: action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) - def do_use_lucky_egg(self): + def use_lucky_egg(self): if self.dry_run: response_dict = {"responses": {"USE_ITEM_XP_BOOST": {"result": 1}}} else: @@ -310,17 +344,6 @@ def combine_pokemon_lists(self, a, b): seen = set() return [p for p in a + b if not (p["id"] in seen or seen.add(p["id"]))] - def init_player_level_factor(self): - self.player_level_factor = [ - 0, 0.094, 0.16639787, 0.21573247, 0.25572005, 0.29024988, - 0.3210876, 0.34921268, 0.37523559, 0.39956728, 0.4225, - 0.44310755, 0.46279839, 0.48168495, 0.49985844, 0.51739395, - 0.53435433, 0.55079269, 0.56675452, 0.58227891, 0.5974, - 0.61215729, 0.62656713, 0.64065295, 0.65443563, 0.667934, - 0.68116492, 0.69414365, 0.70688421, 0.71939909, 0.7317, - 0.73776948, 0.74378943, 0.74976104, 0.75568551, 0.76156384, - 0.76739717, 0.7731865, 0.77893275, 0.784637, 0.7903] - def init_pokemon_max_cp(self): self.pokemon_max_cp = { "Abra": 600.44, "Aerodactyl": 2165.49, "Alakazam": 1813.82, From d34f74cf5898b4aed74f3d052274898379965518 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Tue, 9 Aug 2016 22:02:34 +0800 Subject: [PATCH 14/24] Revert "Merge remote-tracking branch 'origin/faeture/xp-improvements' into pokemon_optimizer" This reverts commit ff1f5e4bd3ec66b904625ec26b969f57ae6aaeb8, reversing changes made to e8fd90137e53409e87f8fdcf341916cf6d551481. --- pokemongo_bot/__init__.py | 19 ++---- pokemongo_bot/base_task.py | 66 ++++++++----------- .../cell_workers/catch_lured_pokemon.py | 40 ++++------- .../cell_workers/catch_visible_pokemon.py | 23 ++----- .../cell_workers/collect_level_up_reward.py | 6 +- pokemongo_bot/cell_workers/evolve_pokemon.py | 8 +-- pokemongo_bot/cell_workers/incubate_eggs.py | 5 -- pokemongo_bot/cell_workers/move_to_fort.py | 9 ++- .../cell_workers/nickname_pokemon.py | 6 -- pokemongo_bot/cell_workers/recycle_items.py | 6 -- pokemongo_bot/cell_workers/spin_fort.py | 48 +++++--------- .../cell_workers/transfer_pokemon.py | 4 -- 12 files changed, 73 insertions(+), 167 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 7f84ff127f..c15569119b 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -8,7 +8,6 @@ import random import re import sys -import struct import time import Queue import threading @@ -32,9 +31,7 @@ from worker_result import WorkerResult from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder from sys import platform as _platform - - - +import struct class PokemonGoBot(object): @property def position(self): @@ -444,16 +441,11 @@ def tick(self): # Check if session token has expired self.check_session(self.position[0:2]) - start_tick = time.time() for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return - end_tick = time.time() - if end_tick - start_tick < 5: - time.sleep(5 - (end_tick - start_tick)) - def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) @@ -598,7 +590,7 @@ def check_session(self, position): # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): - self.logger.info("Ticket expired value is not numeric") + self.logger.info("Ticket expired value is not numeric", 'yellow') return remaining_time = \ @@ -658,7 +650,7 @@ def login(self): def get_encryption_lib(self): if _platform == "linux" or _platform == "linux2" or _platform == "darwin": file_name = 'encrypt.so' - elif _platform == "Windows" or _platform == "win32" or _platform == "cygwin": + elif _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' @@ -672,9 +664,8 @@ def get_encryption_lib(self): full_path = path + '/'+ file_name if not os.path.isfile(full_path): - self.logger.error(file_name + ' is not found! Please place it in the bots root directory.') - self.logger.info('Platform: '+ _platform) - self.logger.info('Bot root directory: '+ path) + self.logger.error(file_name + ' is not found! Please place it in the bots root directory or set libencrypt_location in config.') + self.logger.info('Platform: '+ _platform + ' Encrypt.so directory: '+ path) sys.exit(1) else: self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' Encrypt.so directory: ' + path) diff --git a/pokemongo_bot/base_task.py b/pokemongo_bot/base_task.py index d8214d971f..22bbedf4e8 100644 --- a/pokemongo_bot/base_task.py +++ b/pokemongo_bot/base_task.py @@ -1,43 +1,31 @@ import logging -import time class BaseTask(object): - TASK_API_VERSION = 1 - - def __init__(self, bot, config): - self.bot = bot - self.config = config - self._validate_work_exists() - self.logger = logging.getLogger(type(self).__name__) - self.last_ran = time.time() - self.run_interval = config.get('run_interval', 10) - self.initialize() - - def _update_last_ran(self): - self.last_ran = time.time() - - def _time_to_run(self): - interval = time.time() - self.last_ran - if interval > self.run_interval: - return True - return False - - def _validate_work_exists(self): - method = getattr(self, 'work', None) - if not method or not callable(method): - raise NotImplementedError('Missing "work" method') - - def emit_event(self, event, sender=None, level='info', formatted='', data={}): - if not sender: - sender=self - self.bot.event_manager.emit( - event, - sender=sender, - level=level, - formatted=formatted, - data=data - ) - - def initialize(self): - pass + TASK_API_VERSION = 1 + + def __init__(self, bot, config): + self.bot = bot + self.config = config + self._validate_work_exists() + self.logger = logging.getLogger(type(self).__name__) + self.initialize() + + def _validate_work_exists(self): + method = getattr(self, 'work', None) + if not method or not callable(method): + raise NotImplementedError('Missing "work" method') + + def emit_event(self, event, sender=None, level='info', formatted='', data={}): + if not sender: + sender=self + self.bot.event_manager.emit( + event, + sender=sender, + level=level, + formatted=formatted, + data=data + ) + + def initialize(self): + pass diff --git a/pokemongo_bot/cell_workers/catch_lured_pokemon.py b/pokemongo_bot/cell_workers/catch_lured_pokemon.py index 14dd1dc568..10a046dce9 100644 --- a/pokemongo_bot/cell_workers/catch_lured_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_lured_pokemon.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.constants import Constants -from pokemongo_bot.cell_workers.utils import fort_details, distance +from pokemongo_bot.cell_workers.utils import fort_details from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker +from pokemongo_bot.base_task import BaseTask class CatchLuredPokemon(BaseTask): @@ -13,37 +12,23 @@ class CatchLuredPokemon(BaseTask): def work(self): lured_pokemon = self.get_lured_pokemon() if lured_pokemon: - for pokemon in lured_pokemon: - self.catch_pokemon(pokemon) + self.catch_pokemon(lured_pokemon) def get_lured_pokemon(self): - forts_in_range = [] - pokemon_to_catch = [] forts = self.bot.get_forts(order_by_distance=True) if len(forts) == 0: return False - for fort in forts: - distance_to_fort = distance( - self.bot.position[0], - self.bot.position[1], - fort['latitude'], - fort['longitude'] - ) - - encounter_id = fort.get('lure_info', {}).get('encounter_id', None) - if distance_to_fort < Constants.MAX_DISTANCE_FORT_IS_REACHABLE and encounter_id: - forts_in_range.append(fort) - + fort = forts[0] + details = fort_details(self.bot, fort_id=fort['id'], + latitude=fort['latitude'], + longitude=fort['longitude']) + fort_name = details.get('name', 'Unknown') - for fort in forts_in_range: - details = fort_details(self.bot, fort_id=fort['id'], - latitude=fort['latitude'], - longitude=fort['longitude']) - fort_name = details.get('name', 'Unknown') - encounter_id = fort['lure_info']['encounter_id'] + encounter_id = fort.get('lure_info', {}).get('encounter_id', None) + if encounter_id: result = { 'encounter_id': encounter_id, 'fort_id': fort['id'], @@ -51,14 +36,15 @@ def get_lured_pokemon(self): 'latitude': fort['latitude'], 'longitude': fort['longitude'] } - pokemon_to_catch.append(result) self.emit_event( 'lured_pokemon_found', formatted='Lured pokemon at fort {fort_name} ({fort_id})', data=result ) - return pokemon_to_catch + return result + + return False def catch_pokemon(self, pokemon): worker = PokemonCatchWorker(pokemon, self.bot) diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py index 9af3a12c92..1bfed225df 100644 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -17,14 +17,12 @@ def work(self): lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude']) ) user_web_catchable = 'web/catchable-{}.json'.format(self.bot.config.username) - - for pokemon in self.bot.cell['catchable_pokemons']: with open(user_web_catchable, 'w') as outfile: json.dump(pokemon, outfile) self.emit_event( 'catchable_pokemon', - level='info', + level='debug', data={ 'pokemon_id': pokemon['pokemon_id'], 'spawn_point_id': pokemon['spawn_point_id'], @@ -34,7 +32,8 @@ def work(self): 'expiration_timestamp_ms': pokemon['expiration_timestamp_ms'], } ) - self.catch_pokemon(pokemon) + + return self.catch_pokemon(self.bot.cell['catchable_pokemons'].pop(0)) if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0: # Sort all by distance from current pos- eventually this should @@ -42,21 +41,7 @@ def work(self): self.bot.cell['wild_pokemons'].sort( key= lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude'])) - - for pokemon in self.bot.cell['wild_pokemons']: - self.emit_event( - 'catchable_pokemon', - level='info', - data={ - 'pokemon_id': pokemon['pokemon_data']['pokemon_id'], - 'spawn_point_id': pokemon['spawn_point_id'], - 'encounter_id': pokemon['encounter_id'], - 'latitude': pokemon['latitude'], - 'longitude': pokemon['longitude'], - 'expiration_timestamp_ms': pokemon['time_till_hidden_ms'], - } - ) - self.catch_pokemon(pokemon) + return self.catch_pokemon(self.bot.cell['wild_pokemons'].pop(0)) def catch_pokemon(self, pokemon): worker = PokemonCatchWorker(pokemon, self.bot) diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index d2ce306c2e..950f450660 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -1,5 +1,5 @@ from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.worker_result import WorkerResult + class CollectLevelUpReward(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -12,10 +12,6 @@ def initialize(self): self.previous_level = 0 def work(self): - if not self._time_to_run(): - return WorkerResult.SUCCESS - self._update_last_ran() - self.current_level = self._get_current_level() # let's check level reward on bot initialization diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 6ae38501d8..45b1591dec 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -1,7 +1,6 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.item_list import Item from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.worker_result import WorkerResult class EvolvePokemon(BaseTask): @@ -24,9 +23,7 @@ def _validate_config(self): def work(self): if not self._should_run(): - return WorkerResult.SUCCESS - - self._update_last_ran() + return response_dict = self.api.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get( @@ -45,9 +42,6 @@ def work(self): self._execute_pokemon_evolve(pokemon, candy_list, cache) def _should_run(self): - if not self._time_to_run(): - return False - if not self.evolve_all or self.evolve_all[0] == 'none': return False diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index a42d3be272..5761090ea5 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -1,6 +1,5 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.worker_result import WorkerResult class IncubateEggs(BaseTask): @@ -22,10 +21,6 @@ def _process_config(self): self.longer_eggs_first = self.config.get("longer_eggs_first", True) def work(self): - if not self._time_to_run(): - return WorkerResult.SUCCESS - self._update_last_ran() - try: self._check_inventory() except: diff --git a/pokemongo_bot/cell_workers/move_to_fort.py b/pokemongo_bot/cell_workers/move_to_fort.py index 7dcd0977b1..e4b4187d20 100644 --- a/pokemongo_bot/cell_workers/move_to_fort.py +++ b/pokemongo_bot/cell_workers/move_to_fort.py @@ -13,18 +13,17 @@ class MoveToFort(BaseTask): def initialize(self): self.lure_distance = 0 - self.lure_attraction = self.config.get("lure_attraction", True) - self.lure_max_distance = self.config.get("lure_max_distance", 2000) - self.ignore_item_count = self.config.get("ignore_item_count", False) + self.lure_attraction = True #self.config.get("lure_attraction", True) + self.lure_max_distance = 2000 #self.config.get("lure_max_distance", 2000) def should_run(self): has_space_for_loot = self.bot.has_space_for_loot() if not has_space_for_loot: self.emit_event( 'inventory_full', - formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently." + formatted="Not moving to any forts as there aren't enough space. You might want to change your config to recycle more items if this message appears consistently." ) - return has_space_for_loot or self.ignore_item_count or self.bot.softban + return has_space_for_loot or self.bot.softban def is_attracted(self): return (self.lure_distance > 0) diff --git a/pokemongo_bot/cell_workers/nickname_pokemon.py b/pokemongo_bot/cell_workers/nickname_pokemon.py index 27588c712f..cda206ad20 100644 --- a/pokemongo_bot/cell_workers/nickname_pokemon.py +++ b/pokemongo_bot/cell_workers/nickname_pokemon.py @@ -1,7 +1,5 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.worker_result import WorkerResult - class NicknamePokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -12,10 +10,6 @@ def initialize(self): self.template = "" def work(self): - if not self._time_to_run(): - return WorkerResult.SUCCESS - self._update_last_ran() - try: inventory = reduce(dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], self.bot.get_inventory()) except KeyError: diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 0f98a71860..2c969913b0 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -2,8 +2,6 @@ import os from pokemongo_bot.base_task import BaseTask from pokemongo_bot.tree_config_builder import ConfigException -from pokemongo_bot.worker_result import WorkerResult - class RecycleItems(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -20,10 +18,6 @@ def _validate_item_filter(self): raise ConfigException("item {} does not exist, spelling mistake? (check for valid item names in data/items.json)".format(config_item_name)) def work(self): - if not self._time_to_run(): - return WorkerResult.SUCCESS - self._update_last_ran() - self.bot.latest_inventory = None item_count_dict = self.bot.item_inventory_count('all') diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 46cd06218a..e04a86dbc6 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -15,29 +15,21 @@ class SpinFort(BaseTask): SUPPORTED_TASK_API_VERSION = 1 - def initialize(self): - self.ignore_item_count = self.config.get("ignore_item_count", False) - def should_run(self): if not self.bot.has_space_for_loot(): self.emit_event( 'inventory_full', - formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently." + formatted="Not moving to any forts as there aren't enough space. You might want to change your config to recycle more items if this message appears consistently." ) - return self.ignore_item_count or self.bot.has_space_for_loot() + return False + return True def work(self): - forts = self.get_fort_in_range() - if not self.should_run() or not forts: - return WorkerResult.SUCCESS + fort = self.get_fort_in_range() - for fort in forts: - self.spin_fort(fort) - sleep(1) - - return WorkerResult.SUCCESS + if not self.should_run() or fort is None: + return WorkerResult.SUCCESS - def spin_fort(self, fort): lat = fort['latitude'] lng = fort['longitude'] @@ -147,25 +139,21 @@ def spin_fort(self, fort): def get_fort_in_range(self): forts = self.bot.get_forts(order_by_distance=True) - for fort in forts: - if 'cooldown_complete_timestamp_ms' in fort: - self.bot.fort_timeouts[fort["id"]] = fort['cooldown_complete_timestamp_ms'] - forts.remove(fort) - forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) - forts_in_range = [] + if len(forts) == 0: + return None - for fort in forts: + fort = forts[0] - distance_to_fort = distance( - self.bot.position[0], - self.bot.position[1], - fort['latitude'], - fort['longitude'] - ) + distance_to_fort = distance( + self.bot.position[0], + self.bot.position[1], + fort['latitude'], + fort['longitude'] + ) - if distance_to_fort <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: - forts_in_range.append(fort) + if distance_to_fort <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: + return fort - return forts_in_range + return None diff --git a/pokemongo_bot/cell_workers/transfer_pokemon.py b/pokemongo_bot/cell_workers/transfer_pokemon.py index 548df5058d..5c1d30fae7 100644 --- a/pokemongo_bot/cell_workers/transfer_pokemon.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -2,16 +2,12 @@ from pokemongo_bot.human_behaviour import action_delay from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.worker_result import WorkerResult class TransferPokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def work(self): - if not self._time_to_run(): - return WorkerResult.SUCCESS - self._update_last_ran() pokemon_groups = self._release_pokemon_get_groups() for pokemon_id in pokemon_groups: group = pokemon_groups[pokemon_id] From 4d9375f7896c7963c4daeade52a14c140b600aa4 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Wed, 10 Aug 2016 01:24:57 +0800 Subject: [PATCH 15/24] - Fix an issue in evolve_pokemon task - Use common inventory - Add configuration example --- .gitignore | 1 + configs/config.json.optimizer.example | 85 ++++ pokemongo_bot/cell_workers/evolve_pokemon.py | 6 +- .../cell_workers/pokemon_optimizer.py | 418 ++++++------------ 4 files changed, 233 insertions(+), 277 deletions(-) create mode 100644 configs/config.json.optimizer.example diff --git a/.gitignore b/.gitignore index 4721ce0253..d022a794b8 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ configs/* !configs/config.json.map.example !configs/path.example.json !config.json.cluster.example +!config.json.optimizer.example # Virtualenv folders bin/ diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example new file mode 100644 index 0000000000..113a19e452 --- /dev/null +++ b/configs/config.json.optimizer.example @@ -0,0 +1,85 @@ +{ + "auth_service": "google", + "username": "YOUR_USERNAME", + "password": "YOUR_PASSWORD", + "location": "SOME_LOCATION", + "gmapkey": "GOOGLE_MAPS_API_KEY", + "tasks": [ + { + "type": "HandleSoftBan" + }, + { + "type": "CollectLevelUpReward" + }, + { + "type": "IncubateEggs", + "config": { + "longer_eggs_first": true + } + }, + { + "type": "PokemonOptimizer", + "config": { + "dry_run": false, + "evolve_only_with_lucky_egg": false + } + }, + { + "type": "RecycleItems", + "config": { + "min_empty_space": 15, + "item_filter": { + "Pokeball": { "keep": 100 }, + "Potion": { "keep": 10 }, + "Super Potion": { "keep": 20 }, + "Hyper Potion": { "keep": 30 }, + "Revive": { "keep": 30 }, + "Razz Berry": { "keep": 100 } + } + } + }, + { + "type": "CatchVisiblePokemon" + }, + { + "type": "CatchLuredPokemon" + }, + { + "type": "SpinFort", + "config": { + "ignore_item_count": true + } + }, + { + "type": "MoveToFort", + "config": { + "lure_attraction": false, + "lure_max_distance": 2000, + "ignore_item_count": true + } + } + ], + "map_object_cache_time": 5, + "forts": { + "avoid_circles": true, + "max_circle_size": 50 + }, + "websocket_server": true, + "walk": 4.16, + "action_wait_min": 1, + "action_wait_max": 4, + "debug": false, + "test": false, + "health_record": false, + "location_cache": true, + "distance_unit": "km", + "reconnecting_timeout": 15, + "evolve_captured": false, + "catch_randomize_reticle_factor": 1.0, + "catch_randomize_spin_factor": 1.0, + "catch": { + "any": { + "always_catch": true + } + } +} diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 20b1e5311d..4f74906f1e 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -104,9 +104,9 @@ def _execute_pokemon_evolve(self, pokemon, cache): 'pokemon_evolved', formatted="Successfully evolved {pokemon} with CP {cp} and IV {iv}!", data={ - 'pokemon': pokemon_name, - 'iv': pokemon_iv, - 'cp': pokemon_cp, + 'pokemon': pokemon.name, + 'iv': pokemon.iv, + 'cp': pokemon.cp, 'xp': 0 } ) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 1c20d1e103..6637584af0 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,5 +1,6 @@ -import logging +import copy +from pokemongo_bot import inventory from pokemongo_bot.base_task import BaseTask from pokemongo_bot.human_behaviour import sleep, action_delay from pokemongo_bot.item_list import Item @@ -10,22 +11,16 @@ class PokemonOptimizer(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def initialize(self): - self.pokemon_max_cp = None - self.family_data_by_pokemon_name = {} + self.pokemon_max_cp = {} + self.family_by_family_id = {} self.init_pokemon_max_cp() - self.init_family_data_by_pokemon_name() - - self.family_by_family_name = {} - self.candies_by_family_name = {} - self.pokemon_count = 0 - self.lucky_egg_count = 0 self.dry_run = self.config.get("dry_run", True) self.evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", True) def get_pokemon_slot_left(self): - return self.bot._player["max_pokemon_storage"] - self.pokemon_count + return self.bot._player["max_pokemon_storage"] - len(inventory.pokemons()._data) def work(self): self.refresh_inventory() @@ -33,172 +28,174 @@ def work(self): if self.get_pokemon_slot_left() > 5: return WorkerResult.SUCCESS - transfer_all_pokemons = [] - evo_all_best_pokemons = [] - evo_all_crap_pokemons = [] + transfer_all = [] + evo_all_best = [] + evo_all_crap = [] - for family_name, family in self.family_by_family_name.items(): - transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_family_optimized(family_name, family) - transfer_all_pokemons += transfer_pokemons - evo_all_best_pokemons += evo_best_pokemons - evo_all_crap_pokemons += evo_crap_pokemons + for family_id, family in self.family_by_family_id.items(): + 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 - evo_all_pokemons = evo_all_best_pokemons + evo_all_crap_pokemons + evo_all = evo_all_best + evo_all_crap - if self.apply_optimization(transfer_all_pokemons, evo_all_pokemons): - self.bot.latest_inventory = None - self.refresh_inventory() + if self.apply_optimization(transfer_all, evo_all): + inventory.refresh_inventory() return WorkerResult.SUCCESS - def get_family_optimized(self, family_name, family): - if family_name == "Eevee": - return self.get_multi_family_optimized(family_name, family, 3) + def get_family_optimized(self, family_id, family): + if family_id == 133: # "Eevee" + return self.get_multi_family_optimized(family_id, family, 3) - best_iv_pokemons = self.get_best_iv_in_family(family) - best_relative_cp_pokemons = self.get_best_relative_cp_in_family(family) - best_cp_pokemons = self.get_best_cp_in_family(family) + best_iv = self.get_best_iv_in_family(family) + best_relative_cp = self.get_best_relative_cp_in_family(family) + best_cp = self.get_best_cp_in_family(family) - best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) + best = self.combine_pokemon_lists(best_iv, best_relative_cp) - return self.get_crap_plan(family_name, family, best_pokemons, best_cp_pokemons) + return self.get_evolution_plan(family_id, family, best, best_cp) - def get_multi_family_optimized(self, family_name, family, nb_branch): + def get_multi_family_optimized(self, family_id, family, nb_branch): # Transfer each group of senior independently - senior_family = [p for p in family if p["next_name"] is None] - other_family = [p for p in family if p["next_name"] is not None] - senior_names = set(s["name"] for s in senior_family) - senior_grouped_family = {name: [p for p in senior_family if p["name"] == name] for name in senior_names} + senior_family = [p for p in family if p.next_evolution_id is None] + other_family = [p for p in family if p.next_evolution_id is not None] + 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_pokemons = [] + transfer_senior = [] - for senior_name, senior_family in senior_grouped_family.items(): - transfer_senior_pokemons += self.get_family_optimized(senior_name, senior_family)[0] + for senior_pid, senior_family in senior_grouped_family.items(): + transfer_senior += self.get_family_optimized(senior_pid, senior_family)[0] - if len(senior_names) < nb_branch: + if len(senior_pids) < nb_branch: # We did not get every combination yet = All other Pokemons are potentially good to keep - best_pokemons = other_family - best_pokemons.sort(key=lambda p: p["iv"] * p["relative_cp"], reverse=True) - best_cp_pokemons = [] + best = other_family + best.sort(key=lambda p: p.iv * p.relative_cp, reverse=True) + best_cp = [] else: - min_iv = min([max(f, key=lambda p: p["iv"]) for f in senior_grouped_family.values()], key=lambda p: p["iv"])["iv"] - min_relative_cp = min([max(f, key=lambda p: p["relative_cp"]) for f in senior_grouped_family.values()], key=lambda p: p["relative_cp"])["relative_cp"] - min_cp = min([max(f, key=lambda p: p["cp"]) for f in senior_grouped_family.values()], key=lambda p: p["cp"])["cp"] + min_iv = min([max(f, key=lambda p: p.iv) for f in senior_grouped_family.values()], key=lambda p: p.iv).iv + min_relative_cp = min([max(f, key=lambda p: p.relative_cp) for f in senior_grouped_family.values()], key=lambda p: p.relative_cp).relative_cp + min_cp = min([max(f, key=lambda p: p.cp) for f in senior_grouped_family.values()], key=lambda p: p.cp).cp - best_iv_pokemons = self.get_better_iv_in_family(other_family, min_iv) - best_relative_cp_pokemons = self.get_better_relative_cp_in_family(other_family, min_relative_cp) - best_cp_pokemons = self.get_better_cp_in_family(other_family, min_cp) + best_iv = self.get_better_iv_in_family(other_family, min_iv) + best_relative_cp = self.get_better_relative_cp_in_family(other_family, min_relative_cp) + best_cp = self.get_better_cp_in_family(other_family, min_cp) - best_pokemons = self.combine_pokemon_lists(best_iv_pokemons, best_relative_cp_pokemons) + best = self.combine_pokemon_lists(best_iv, best_relative_cp) - transfer_pokemons, evo_best_pokemons, evo_crap_pokemons = self.get_crap_plan(family_name, other_family, best_pokemons, best_cp_pokemons) - transfer_pokemons += transfer_senior_pokemons + transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, best, best_cp) + transfer += transfer_senior - return (transfer_pokemons, evo_best_pokemons, evo_crap_pokemons) + return (transfer, evo_best, evo_crap) - def get_crap_plan(self, family_name, family, best_pokemons, best_cp_pokemons): - candies = self.candies_by_family_name.get(family_name, 0) + def get_evolution_plan(self, family_id, family, best, best_cp): + candies = inventory.candies().get(family_id).quantity # All the rest is crap, for now crap = family[:] - crap = [p for p in crap if p not in best_pokemons] - crap = [p for p in crap if p not in best_cp_pokemons] - crap.sort(key=lambda p: p["iv"], reverse=True) + crap = [p for p in crap if p not in best] + crap = [p for p in crap if p not in best_cp] + crap.sort(key=lambda p: p.iv, reverse=True) candies += len(crap) # Let's see if we can evolve our best pokemons - evo_best_pokemons = [] - - for pokemon in best_pokemons: - next_amount = pokemon["next_amount"] + evo_best = [] - if next_amount == 0: + for pokemon in best: + if not pokemon.has_next_evolution(): continue - candies -= next_amount + candies -= pokemon.evolution_cost - if next_amount > candies: + if candies < 0: continue - evo_best_pokemons.append(pokemon) - next_name = pokemon["next_name"] - - if not next_name: - continue + evo_best.append(pokemon) - next_evo = dict(pokemon) - next_evo["name"] = next_name - next_evo.update(self.family_data_by_pokemon_name[next_name]) - best_pokemons.append(next_evo) + # Not sure if the evo keep the same id + next_pid = pokemon.next_evolution_id + next_evo = copy.copy(pokemon) + next_evo.pokemon_id = next_pid + next_evo._static_data = inventory.pokemons().data_for(next_pid) + next_evo.name = inventory.pokemons().name_for(next_pid) + best.append(next_evo) # Compute how many crap we should keep if we want to batch evolve them for xp - junior_next_amount = self.family_data_by_pokemon_name.get(family_name, {}).get("next_amount", 0) + junior_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) # transfer + keep_for_evo = len(crap) # leftover_candies = candies - len(crap) + transfer * 1 - # keep_for_evo = leftover_candies / junior_next_amount + # keep_for_evo = leftover_candies / junior_evolution_cost # - # keep_for_evo = (candies - len(crap) + transfer) / junior_next_amount - # keep_for_evo = (candies - keep_for_evo) / junior_next_amount + # keep_for_evo = (candies - len(crap) + transfer) / junior_evolution_cost + # keep_for_evo = (candies - keep_for_evo) / junior_evolution_cost - if (candies > 0) and (junior_next_amount > 0): - keep_for_evo = int(candies / (junior_next_amount + 1)) + if (candies > 0) and junior_evolution_cost: + keep_for_evo = int(candies / (junior_evolution_cost + 1)) else: keep_for_evo = 0 - evo_crap_pokemons = [p for p in crap if p["next_amount"] == junior_next_amount][:keep_for_evo] - transfer_pokemons = [p for p in crap if p not in evo_crap_pokemons] + evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo] + transfer = [p for p in crap if p not in evo_crap] - return (transfer_pokemons, evo_best_pokemons, evo_crap_pokemons) + return (transfer, evo_best, evo_crap) - def apply_optimization(self, transfer_pokemons, evo_pokemons): - for pokemon in transfer_pokemons: + def apply_optimization(self, transfer, evo): + for pokemon in transfer: self.transfer_pokemon(pokemon) if self.evolve_only_with_lucky_egg: - if (self.lucky_egg_count == 0) or (len(evo_pokemons) < 90): + try: + lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG) + except: + lucky_egg_count = 0 + + if (lucky_egg_count == 0) or (len(evo) < 90): return self.use_lucky_egg() - for pokemon in evo_pokemons: + for pokemon in evo: self.evolve_pokemon(pokemon) def get_best_iv_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["iv"]) - return sorted([p for p in family if p["iv"] == best_pokemon["iv"]], key=lambda p: p["relative_cp"], reverse=True) + best = max(family, key=lambda p: p.iv) + return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.relative_cp, reverse=True) def get_better_iv_in_family(self, family, iv): - return sorted([p for p in family if p["iv"] >= iv], key=lambda p: (p["iv"], p["relative_cp"]), reverse=True) + return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.relative_cp), reverse=True) def get_best_relative_cp_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["relative_cp"]) - return sorted([p for p in family if p["relative_cp"] == best_pokemon["relative_cp"]], key=lambda p: p["iv"], reverse=True) + best = max(family, key=lambda p: p.relative_cp) + return sorted([p for p in family if p.relative_cp == best.relative_cp], key=lambda p: p.iv, reverse=True) def get_better_relative_cp_in_family(self, family, relative_cp): - return sorted([p for p in family if p["relative_cp"] >= relative_cp], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) + return sorted([p for p in family if p.relative_cp >= relative_cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) def get_best_cp_in_family(self, family): - best_pokemon = max(family, key=lambda p: p["cp"]) - return sorted([p for p in family if p["cp"] == best_pokemon["cp"]], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) + best = max(family, key=lambda p: p.cp) + return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) def get_better_cp_in_family(self, family, cp): - return sorted([p for p in family if p["cp"] >= cp], key=lambda p: (p["relative_cp"], p["iv"]), reverse=True) + return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) def transfer_pokemon(self, pokemon): if self.dry_run: pass else: - self.bot.api.release_pokemon(pokemon_id=pokemon["id"]) + self.bot.api.release_pokemon(pokemon_id=pokemon.id) self.emit_event("pokemon_release", formatted="Exchanged {pokemon} [IV {iv}] [CP {cp}]", - data={"pokemon": pokemon["name"], - "iv": pokemon["iv"], - "cp": pokemon["cp"]}) + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp}) if not self.dry_run: + inventory.candies().get(pokemon.pokemon_id).add(1) action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def use_lucky_egg(self): @@ -218,7 +215,7 @@ def use_lucky_egg(self): if result == 1: self.emit_event("used_lucky_egg", formatted="Used lucky egg ({amount_left} left).", - data={"amount_left": self.lucky_egg_count - 1}) + data={"amount_left": 0}) return True else: self.emit_event("lucky_egg_error", @@ -230,7 +227,7 @@ def evolve_pokemon(self, pokemon): if self.dry_run: response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} else: - response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon["id"]) + response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon.id) if not response_dict: return False @@ -241,12 +238,13 @@ def evolve_pokemon(self, pokemon): if result == 1: self.emit_event("pokemon_evolved", formatted="Evolved {pokemon} [IV {iv}] [CP {cp}] [+{xp} xp]", - data={"pokemon": pokemon["name"], - "iv": pokemon["iv"], - "cp": pokemon["cp"], + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp, "xp": xp}) if not self.dry_run: + inventory.candies().get(pokemon.pokemon_id).consume(pokemon.evolution_cost) sleep(20) return True @@ -254,187 +252,59 @@ def evolve_pokemon(self, pokemon): return False def refresh_inventory(self): - self.family_by_family_name.clear() - self.candies_by_family_name.clear() - self.pokemon_count = 0 - - inventory = self.bot.get_inventory() - inventory_items = (inventory.get("responses", {}) - .get("GET_INVENTORY", {}) - .get("inventory_delta", {}) - .get("inventory_items", {})) - - for item in inventory_items: - pokemon_data = item.get("inventory_item_data", {}).get("pokemon_data", {}) - candy_data = item.get("inventory_item_data", {}).get("candy", {}) - item_data = item.get("inventory_item_data", {}).get("item", {}) - - if pokemon_data: - self.pokemon_count += 1 - pokemon = self.get_pokemon_from_data(pokemon_data) - - if pokemon: - family_name = pokemon["family_name"] - self.family_by_family_name.setdefault(family_name, []).append(pokemon) - elif candy_data: - family_id = candy_data.get("family_id", 0) - count = candy_data.get("candy", 0) - - if (family_id > 0) and (count > 0): - family_name = self.bot.pokemon_list[family_id - 1].get("Name") - - if not family_name: - continue - - self.candies_by_family_name[family_name] = count - elif item_data: - item_id = item_data.get("item_id", False) - - if item_id == Item.ITEM_LUCKY_EGG: - self.lucky_egg_count = item_data.get("count", 0) - - def get_pokemon_from_data(self, pokemon_data): - pokemon_index = pokemon_data.get("pokemon_id", 0) - 1 + self.family_by_family_id.clear() - if pokemon_index < 0: - return None + for pokemon in inventory.pokemons().all(): + family_id = pokemon.first_evolution_id - pokemon_name = self.bot.pokemon_list[pokemon_index].get("Name") + max_cp = self.get_pokemon_max_cp(pokemon.name) - if not pokemon_name: - logging.error("Invalid pokemon.json data file. Missing 'Name' field for pokemon_index '%s'", pokemon_index) - return None - - family_data = self.family_data_by_pokemon_name.get(pokemon_name) - - if not family_data: - logging.error("Invalid pokemon.json data file. Missing data for '%s'", pokemon_name) - return None + if max_cp > 0: + relative_cp = float(pokemon.cp) / max_cp + else: + relative_cp = 0 - pokemon_cp = pokemon_data.get("cp", 0) - pokemon_max_cp = self.get_pokemon_max_cp(pokemon_name) + setattr(pokemon, "relative_cp", relative_cp) - if pokemon_max_cp > 0: - pokemon_relative_cp = float(pokemon_cp) / pokemon_max_cp - else: - pokemon_relative_cp = 0 - - return {"id": pokemon_data.get("id", 0), - "name": pokemon_name, - "family_name": family_data["family_name"], - "next_amount": family_data["next_amount"], - "next_name": family_data["next_name"], - "cp": pokemon_cp, - "relative_cp": pokemon_relative_cp, - "iv": self.get_pokemon_iv(pokemon_data)} - - def get_pokemon_iv(self, pokemon_data): - iv = (pokemon_data.get("individual_attack", 0) + - pokemon_data.get("individual_stamina", 0) + - pokemon_data.get("individual_defense", 0)) - return round(iv / 45.0, 2) + self.family_by_family_id.setdefault(family_id, []).append(pokemon) def get_pokemon_max_cp(self, pokemon_name): return int(self.pokemon_max_cp.get(pokemon_name, 0)) - def get_pokemon_max_cp_for_player(self, player_level, pokemon_name): - return int(self.player_level_factor[player_level] * self.pokemon_max_cp.get(pokemon_name, 0)) - def combine_pokemon_lists(self, a, b): seen = set() - return [p for p in a + b if not (p["id"] in seen or seen.add(p["id"]))] + return [p for p in a + b if not (p.id in seen or seen.add(p.id))] def init_pokemon_max_cp(self): self.pokemon_max_cp = { - "Abra": 600.44, "Aerodactyl": 2165.49, "Alakazam": 1813.82, - "Arbok": 1767.13, "Arcanine": 2983.9, "Articuno": 2978.16, - "Beedrill": 1439.96, "Bellsprout": 1117.43, "Blastoise": 2542.01, - "Bulbasaur": 1071.54, "Butterfree": 1454.94, "Caterpie": 443.52, - "Chansey": 675.12, "Charizard": 2602.2, "Charmander": 955.24, - "Charmeleon": 1557.48, "Clefable": 2397.71, "Clefairy": 1200.96, - "Cloyster": 2052.85, "Cubone": 1006.61, "Dewgong": 2145.77, - "Diglett": 456.76, "Ditto": 919.62, "Dodrio": 1836.37, - "Doduo": 855.41, "Dragonair": 1485.88, "Dragonite": 3500.06, - "Dratini": 983.47, "Drowzee": 1075.14, "Dugtrio": 1168.55, - "Eevee": 1077.2, "Ekans": 824.14, "Electabuzz": 2119.17, - "Electrode": 1646.14, "Exeggcute": 1099.81, "Exeggutor": 2955.18, - "Farfetch'd": 1263.89, "Fearow": 1746.35, "Flareon": 2643.43, - "Gastly": 804.41, "Gengar": 2078.23, "Geodude": 849.49, - "Gloom": 1689.46, "Golbat": 1921.35, "Goldeen": 965.14, - "Golduck": 2386.52, "Golem": 2303.17, "Graveler": 1433.63, - "Grimer": 1284.02, "Growlithe": 1335.03, "Gyarados": 2688.89, - "Haunter": 1380.21, "Hitmonchan": 1516.51, "Hitmonlee": 1492.94, - "Horsea": 764.67, "Hypno": 2184.16, "Ivysaur": 1632.19, - "Jigglypuff": 917.64, "Jolteon": 2140.27, "Jynx": 1716.73, - "Kabuto": 1104.72, "Kabutops": 2130.01, "Kadabra": 1131.96, - "Kakuna": 485.35, "Kangaskhan": 2043.4, "Kingler": 1823.15, - "Koffing": 1151.79, "Krabby": 792.21, "Lapras": 2980.73, - "Lickitung": 1626.82, "Machamp": 2594.17, "Machoke": 1760.71, - "Machop": 1089.59, "Magikarp": 262.7, "Magmar": 2265.3, - "Magnemite": 890.68, "Magneton": 1879.95, "Mankey": 878.67, - "Marowak": 1656.96, "Meowth": 756.32, "Metapod": 447.92, - "Mew": 3299.17, "Mewtwo": 4144.75, "Moltres": 3240.47, - "Mr. Mime": 1494.42, "Muk": 2602.9, "Nidoking": 2475.14, - "Nidoqueen": 2485.03, "Nidoran F": 876.01, "Nidoran M": 843.14, - "Nidorina": 1404.61, "Nidorino": 1372.5, "Ninetales": 2188.28, - "Oddish": 1148.28, "Omanyte": 1119.77, "Omastar": 2233.65, - "Onix": 857.2, "Paras": 916.6, "Parasect": 1747.07, - "Persian": 1631.84, "Pidgeotto": 1223.98, "Pidgey": 679.93, - "Pidgeot": 2091.39, "Pikachu": 887.69, "Pinsir": 2121.87, - "Poliwag": 795.96, "Poliwhirl": 1340.43, "Poliwrath": 2505.33, - "Ponyta": 1516.11, "Porygon": 1691.56, "Primeape": 1864.52, - "Psyduck": 1109.56, "Raichu": 2028.3, "Rapidash": 2199.34, - "Raticate": 1444.13, "Rattata": 581.65, "Rhydon": 2243.22, - "Rhyhorn": 1182.08, "Sandshrew": 798.76, "Sandslash": 1810.22, - "Scyther": 2073.96, "Seadra": 1713.22, "Seaking": 2043.92, - "Seel": 1107.03, "Shellder": 822.91, "Slowbro": 2597.19, - "Slowpoke": 1218.9, "Snorlax": 3112.85, "Spearow": 686.87, - "Squirtle": 1008.69, "Starmie": 2182.14, "Staryu": 937.89, - "Tangela": 1739.72, "Tauros": 1844.76, "Tentacool": 905.15, - "Tentacruel": 2220.32, "Vaporeon": 2816.25, "Venomoth": 1890.32, - "Venonat": 1029.39, "Venusaur": 2580.49, "Victreebel": 2530.52, - "Vileplume": 2492.66, "Voltorb": 839.73, "Vulpix": 831.41, - "Wartortle": 1582.79, "Weedle": 449.09, "Weepinbell": 1723.76, - "Weezing": 2250.15, "Wigglytuff": 2177.2, "Zapdos": 3114.38, - "Zubat": 642.51} - - def init_family_data_by_pokemon_name(self): - for pokemon in self.bot.pokemon_list: - pokemon_name = pokemon.get("Name") - - if not pokemon_name: - logging.error("Invalid pokemon.json data file. Missing 'Name' field for '%s'", pokemon) - continue - - prev_evo = pokemon.get("Previous evolution(s)") - next_evo_req = pokemon.get("Next Evolution Requirements") - next_evo = pokemon.get("Next evolution(s)") - - if next_evo and next_evo_req: - next_amount = next_evo_req.get("Amount") - next_name = next_evo[0].get("Name") - - if not next_amount: - logging.error("Invalid pokemon.json data file. Missing 'Amount' field for 'Next Evolution Requirements' of '%s'", pokemon_name) - - if not next_name: - logging.error("Invalid pokemon.json data file. Missing 'Name' field for 'Next Evolution Requirements' of '%s'", pokemon_name) - else: - next_name = next_name.replace("candies", "").strip() - else: - next_amount = 0 - next_name = None - - if not prev_evo: - self.family_data_by_pokemon_name[pokemon_name] = {"family_name": pokemon_name, - "next_amount": next_amount, - "next_name": next_name} - else: - family_name = prev_evo[0].get("Name") - - if family_name: - self.family_data_by_pokemon_name[pokemon_name] = {"family_name": family_name, - "next_amount": next_amount, - "next_name": next_name} - else: - logging.error("Invalid pokemon.json data file. Missing 'Name' field for 'Previous evolution(s)' of '%s'", pokemon_name) + "Bulbasaur": 1072, "Ivysaur": 1632, "Venusaur": 2580, "Charmander": 955, "Charmeleon": 1557, + "Charizard": 2602, "Squirtle": 1009, "Wartortle": 1583, "Blastoise": 2542, "Caterpie": 444, + "Metapod": 478, "Butterfree": 1455, "Weedle": 449, "Kakuna": 485, "Beedrill": 1440, + "Pidgey": 680, "Pidgeotto": 1224, "Pidgeot": 2091, "Rattata": 582, "Raticate": 1444, + "Spearow": 687, "Fearow": 1746, "Ekans": 824, "Arbok": 1767, "Pikachu": 888, + "Raichu": 2028, "Sandshrew": 799, "Sandslash": 1810, "Nidoran F": 876, "Nidorina": 1405, + "Nidoqueen": 2485, "Nidoran M": 843, "Nidorino": 1373, "Nidoking": 2475, "Clefairy": 1201, + "Clefable": 2398, "Vulpix": 831, "Ninetales": 2188, "Jigglypuff": 918, "Wigglytuff": 2177, + "Zubat": 643, "Golbat": 1921, "Oddish": 1148, "Gloom": 1689, "Vileplume": 2493, + "Paras": 917, "Parasect": 1747, "Venonat": 1029, "Venomoth": 1890, "Diglett": 457, + "Dugtrio": 1169, "Meowth": 756, "Persian": 1632, "Psyduck": 1110, "Golduck": 2387, + "Mankey": 879, "Primeape": 1865, "Growlithe": 1335, "Arcanine": 2984, "Poliwag": 796, + "Poliwhirl": 1340, "Poliwrath": 2505, "Abra": 600, "Kadabra": 1132, "Alakazam": 1814, + "Machop": 1090, "Machoke": 1761, "Machamp": 2594, "Bellsprout": 1117, "Weepinbell": 1724, + "Victreebel": 2531, "Tentacool": 905, "Tentacruel": 2220, "Geodude": 849, "Graveler": 1434, + "Golem": 2303, "Ponyta": 1516, "Rapidash": 2199, "Slowpoke": 1219, "Slowbro": 2597, + "Magnemite": 891, "Magneton": 1880, "Farfetch'd": 1264, "Doduo": 855, "Dodrio": 1836, + "Seel": 1107, "Dewgong": 2146, "Grimer": 1284, "Muk": 2603, "Shellder": 823, + "Cloyster": 2053, "Gastly": 804, "Haunter": 1380, "Gengar": 2078, "Onix": 857, + "Drowzee": 1075, "Hypno": 2184, "Krabby": 792, "Kingler": 1823, "Voltorb": 840, + "Electrode": 1646, "Exeggcute": 1100, "Exeggutor": 2955, "Cubone": 1007, "Marowak": 1657, + "Hitmonlee": 1493, "Hitmonchan": 1517, "Lickitung": 1627, "Koffing": 1152, "Weezing": 2250, + "Rhyhorn": 1182, "Rhydon": 2243, "Chansey": 675, "Tangela": 1740, "Kangaskhan": 2043, + "Horsea": 795, "Seadra": 1713, "Goldeen": 965, "Seaking": 2044, "Staryu": 938, + "Starmie": 2182, "Mr. Mime": 1494, "Scyther": 2074, "Jynx": 1717, "Electabuzz": 2119, + "Magmar": 2265, "Pinsir": 2122, "Tauros": 1845, "Magikarp": 263, "Gyarados": 2689, + "Lapras": 2981, "Ditto": 920, "Eevee": 1077, "Vaporeon": 2816, "Jolteon": 2140, + "Flareon": 2643, "Porygon": 1692, "Omanyte": 1120, "Omastar": 2234, "Kabuto": 1105, + "Kabutops": 2130, "Aerodactyl": 2165, "Snorlax": 3113, "Articuno": 2978, "Zapdos": 3114, + "Moltres": 3240, "Dratini": 983, "Dragonair": 1748, "Dragonite": 3500, "Mewtwo": 4145, + "Mew": 3299} From 86fe6bbae013bb9f5c961dae189d135eda9bd8bb Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Wed, 10 Aug 2016 01:31:57 +0800 Subject: [PATCH 16/24] Add missing inventory refresh at the end of the process --- pokemongo_bot/cell_workers/pokemon_optimizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 6637584af0..229cdfda10 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -40,8 +40,8 @@ def work(self): evo_all = evo_all_best + evo_all_crap - if self.apply_optimization(transfer_all, evo_all): - inventory.refresh_inventory() + self.apply_optimization(transfer_all, evo_all) + inventory.refresh_inventory() return WorkerResult.SUCCESS From 49f83e65435da52101440b56b4a7b90875d443b5 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Thu, 11 Aug 2016 00:05:37 +0800 Subject: [PATCH 17/24] Add missing inventory refresh after catching a pokemon --- .../cell_workers/pokemon_catch_worker.py | 3 +- .../cell_workers/pokemon_optimizer.py | 36 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index 345fcabc7e..b99f63c2ea 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -405,8 +405,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): ) # We could refresh here too, but adding 3 saves a inventory request - candy = inventory.candies().get(pokemon.num) - candy.add(3) + candy = inventory.candies(True).get(pokemon.num) self.emit_event( 'gained_candy', formatted='You now have {quantity} {type} candy!', diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 229cdfda10..5623ebdd5f 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -50,10 +50,10 @@ def get_family_optimized(self, family_id, family): return self.get_multi_family_optimized(family_id, family, 3) best_iv = self.get_best_iv_in_family(family) - best_relative_cp = self.get_best_relative_cp_in_family(family) + best_ncp = self.get_best_ncp_in_family(family) best_cp = self.get_best_cp_in_family(family) - best = self.combine_pokemon_lists(best_iv, best_relative_cp) + best = self.combine_pokemon_lists(best_iv, best_ncp) return self.get_evolution_plan(family_id, family, best, best_cp) @@ -72,18 +72,18 @@ def get_multi_family_optimized(self, family_id, family, nb_branch): if len(senior_pids) < nb_branch: # We did not get every combination yet = All other Pokemons are potentially good to keep best = other_family - best.sort(key=lambda p: p.iv * p.relative_cp, reverse=True) + best.sort(key=lambda p: p.iv * p.ncp, reverse=True) best_cp = [] else: min_iv = min([max(f, key=lambda p: p.iv) for f in senior_grouped_family.values()], key=lambda p: p.iv).iv - min_relative_cp = min([max(f, key=lambda p: p.relative_cp) for f in senior_grouped_family.values()], key=lambda p: p.relative_cp).relative_cp + min_ncp = min([max(f, key=lambda p: p.ncp) for f in senior_grouped_family.values()], key=lambda p: p.ncp).ncp min_cp = min([max(f, key=lambda p: p.cp) for f in senior_grouped_family.values()], key=lambda p: p.cp).cp best_iv = self.get_better_iv_in_family(other_family, min_iv) - best_relative_cp = self.get_better_relative_cp_in_family(other_family, min_relative_cp) + best_ncp = self.get_better_ncp_in_family(other_family, min_ncp) best_cp = self.get_better_cp_in_family(other_family, min_cp) - best = self.combine_pokemon_lists(best_iv, best_relative_cp) + best = self.combine_pokemon_lists(best_iv, best_ncp) transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, best, best_cp) transfer += transfer_senior @@ -163,24 +163,24 @@ def apply_optimization(self, transfer, evo): def get_best_iv_in_family(self, family): best = max(family, key=lambda p: p.iv) - return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.relative_cp, reverse=True) + return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.ncp, reverse=True) def get_better_iv_in_family(self, family, iv): - return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.relative_cp), reverse=True) + return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.ncp), reverse=True) - def get_best_relative_cp_in_family(self, family): - best = max(family, key=lambda p: p.relative_cp) - return sorted([p for p in family if p.relative_cp == best.relative_cp], key=lambda p: p.iv, reverse=True) + def get_best_ncp_in_family(self, family): + best = max(family, key=lambda p: p.ncp) + return sorted([p for p in family if p.ncp == best.ncp], key=lambda p: p.iv, reverse=True) - def get_better_relative_cp_in_family(self, family, relative_cp): - return sorted([p for p in family if p.relative_cp >= relative_cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) + def get_better_ncp_in_family(self, family, ncp): + return sorted([p for p in family if p.ncp >= ncp], key=lambda p: (p.ncp, p.iv), reverse=True) def get_best_cp_in_family(self, family): best = max(family, key=lambda p: p.cp) - return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) + return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.ncp, p.iv), reverse=True) def get_better_cp_in_family(self, family, cp): - return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.relative_cp, p.iv), reverse=True) + return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.ncp, p.iv), reverse=True) def transfer_pokemon(self, pokemon): if self.dry_run: @@ -260,11 +260,11 @@ def refresh_inventory(self): max_cp = self.get_pokemon_max_cp(pokemon.name) if max_cp > 0: - relative_cp = float(pokemon.cp) / max_cp + ncp = float(pokemon.cp) / max_cp else: - relative_cp = 0 + ncp = 0 - setattr(pokemon, "relative_cp", relative_cp) + setattr(pokemon, "ncp", ncp) self.family_by_family_id.setdefault(family_id, []).append(pokemon) From 15fb493c68318fdc6dc5f3fe6349488fcd5bd452 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Thu, 11 Aug 2016 00:45:18 +0800 Subject: [PATCH 18/24] Add parameters "transfer" and "evolve" to activate/deactivate corresponding action. If both false, this is equivalent to a dry_run. Add parameters "use_lucky_egg" to allow task to use a lucky egg before evolve. Add parameter "minimum_evolve_for_lucky_egg" to add a requirement on the number of evolution before using a lucky egg. --- configs/config.json.optimizer.example | 6 ++- .../cell_workers/pokemon_optimizer.py | 43 +++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index a7468d88c0..a642116346 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -20,8 +20,10 @@ { "type": "PokemonOptimizer", "config": { - "dry_run": false, - "evolve_only_with_lucky_egg": false + "transfer": true, + "evolve": true, + "use_lucky_egg": true, + "minimum_evolve_for_lucky_egg": 90 } }, { diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 5623ebdd5f..c5cfa7aaf5 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -16,18 +16,20 @@ def initialize(self): self.init_pokemon_max_cp() - self.dry_run = self.config.get("dry_run", True) - self.evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", True) + self.config_transfer = self.config.get("transfer", False) + self.config_evolve = self.config.get("evolve", False) + self.config_use_lucky_egg = self.config.get("use_lucky_egg", True) + self.config_minimum_evolve_for_lucky_egg = self.config.get("minimum_evolve_for_lucky_egg", 90) def get_pokemon_slot_left(self): return self.bot._player["max_pokemon_storage"] - len(inventory.pokemons()._data) def work(self): - self.refresh_inventory() - if self.get_pokemon_slot_left() > 5: return WorkerResult.SUCCESS + self.parse_inventory() + transfer_all = [] evo_all_best = [] evo_all_crap = [] @@ -147,13 +149,13 @@ def apply_optimization(self, transfer, evo): for pokemon in transfer: self.transfer_pokemon(pokemon) - if self.evolve_only_with_lucky_egg: + if self.config_evolve and self.config_use_lucky_egg: try: lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG) except: lucky_egg_count = 0 - if (lucky_egg_count == 0) or (len(evo) < 90): + if (lucky_egg_count == 0) or (len(evo) < self.config_minimum_evolve_for_lucky_egg): return self.use_lucky_egg() @@ -183,10 +185,10 @@ def get_better_cp_in_family(self, family, cp): return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.ncp, p.iv), reverse=True) def transfer_pokemon(self, pokemon): - if self.dry_run: - pass - else: + if self.config_transfer: self.bot.api.release_pokemon(pokemon_id=pokemon.id) + else: + pass self.emit_event("pokemon_release", formatted="Exchanged {pokemon} [IV {iv}] [CP {cp}]", @@ -194,15 +196,18 @@ def transfer_pokemon(self, pokemon): "iv": pokemon.iv, "cp": pokemon.cp}) - if not self.dry_run: + if self.config_transfer: inventory.candies().get(pokemon.pokemon_id).add(1) action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def use_lucky_egg(self): - if self.dry_run: - response_dict = {"responses": {"USE_ITEM_XP_BOOST": {"result": 1}}} - else: + lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG) + + if self.config_evolve and self.config_use_lucky_egg: response_dict = self.bot.use_lucky_egg() + lucky_egg_count -= 1 + else: + response_dict = {"responses": {"USE_ITEM_XP_BOOST": {"result": 1}}} if not response_dict: self.emit_event("lucky_egg_error", @@ -215,7 +220,7 @@ def use_lucky_egg(self): if result == 1: self.emit_event("used_lucky_egg", formatted="Used lucky egg ({amount_left} left).", - data={"amount_left": 0}) + data={"amount_left": lucky_egg_count}) return True else: self.emit_event("lucky_egg_error", @@ -224,10 +229,10 @@ def use_lucky_egg(self): return False def evolve_pokemon(self, pokemon): - if self.dry_run: - response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} - else: + if self.config_evolve: response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon.id) + else: + response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} if not response_dict: return False @@ -243,7 +248,7 @@ def evolve_pokemon(self, pokemon): "cp": pokemon.cp, "xp": xp}) - if not self.dry_run: + if self.config_evolve: inventory.candies().get(pokemon.pokemon_id).consume(pokemon.evolution_cost) sleep(20) @@ -251,7 +256,7 @@ def evolve_pokemon(self, pokemon): else: return False - def refresh_inventory(self): + def parse_inventory(self): self.family_by_family_id.clear() for pokemon in inventory.pokemons().all(): From f30cbf2c0aa5e577f61f2cc3f8ff26e786e29cb2 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Thu, 11 Aug 2016 07:40:40 +0800 Subject: [PATCH 19/24] Move some functions around --- .../cell_workers/pokemon_optimizer.py | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index c5cfa7aaf5..ff48624d13 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -47,6 +47,23 @@ def work(self): return WorkerResult.SUCCESS + def parse_inventory(self): + self.family_by_family_id.clear() + + for pokemon in inventory.pokemons().all(): + family_id = pokemon.first_evolution_id + + max_cp = self.get_pokemon_max_cp(pokemon.name) + + if max_cp > 0: + ncp = float(pokemon.cp) / max_cp + else: + ncp = 0 + + setattr(pokemon, "ncp", ncp) + + 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) @@ -92,6 +109,34 @@ def get_multi_family_optimized(self, family_id, family, nb_branch): return (transfer, evo_best, evo_crap) + def get_best_iv_in_family(self, family): + best = max(family, key=lambda p: p.iv) + return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.ncp, reverse=True) + + def get_better_iv_in_family(self, family, iv): + return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.ncp), reverse=True) + + def get_best_ncp_in_family(self, family): + best = max(family, key=lambda p: p.ncp) + return sorted([p for p in family if p.ncp == best.ncp], key=lambda p: p.iv, reverse=True) + + def get_better_ncp_in_family(self, family, ncp): + return sorted([p for p in family if p.ncp >= ncp], key=lambda p: (p.ncp, p.iv), reverse=True) + + def get_best_cp_in_family(self, family): + best = max(family, key=lambda p: p.cp) + return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.ncp, p.iv), reverse=True) + + def get_better_cp_in_family(self, family, cp): + return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.ncp, p.iv), reverse=True) + + def get_pokemon_max_cp(self, pokemon_name): + return int(self.pokemon_max_cp.get(pokemon_name, 0)) + + def combine_pokemon_lists(self, a, b): + seen = set() + return [p for p in a + b if not (p.id in seen or seen.add(p.id))] + def get_evolution_plan(self, family_id, family, best, best_cp): candies = inventory.candies().get(family_id).quantity @@ -163,27 +208,6 @@ def apply_optimization(self, transfer, evo): for pokemon in evo: self.evolve_pokemon(pokemon) - def get_best_iv_in_family(self, family): - best = max(family, key=lambda p: p.iv) - return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.ncp, reverse=True) - - def get_better_iv_in_family(self, family, iv): - return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.ncp), reverse=True) - - def get_best_ncp_in_family(self, family): - best = max(family, key=lambda p: p.ncp) - return sorted([p for p in family if p.ncp == best.ncp], key=lambda p: p.iv, reverse=True) - - def get_better_ncp_in_family(self, family, ncp): - return sorted([p for p in family if p.ncp >= ncp], key=lambda p: (p.ncp, p.iv), reverse=True) - - def get_best_cp_in_family(self, family): - best = max(family, key=lambda p: p.cp) - return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.ncp, p.iv), reverse=True) - - def get_better_cp_in_family(self, family, cp): - return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.ncp, p.iv), reverse=True) - def transfer_pokemon(self, pokemon): if self.config_transfer: self.bot.api.release_pokemon(pokemon_id=pokemon.id) @@ -256,30 +280,6 @@ def evolve_pokemon(self, pokemon): else: return False - def parse_inventory(self): - self.family_by_family_id.clear() - - for pokemon in inventory.pokemons().all(): - family_id = pokemon.first_evolution_id - - max_cp = self.get_pokemon_max_cp(pokemon.name) - - if max_cp > 0: - ncp = float(pokemon.cp) / max_cp - else: - ncp = 0 - - setattr(pokemon, "ncp", ncp) - - self.family_by_family_id.setdefault(family_id, []).append(pokemon) - - def get_pokemon_max_cp(self, pokemon_name): - return int(self.pokemon_max_cp.get(pokemon_name, 0)) - - def combine_pokemon_lists(self, a, b): - seen = set() - return [p for p in a + b if not (p.id in seen or seen.add(p.id))] - def init_pokemon_max_cp(self): self.pokemon_max_cp = { "Bulbasaur": 1072, "Ivysaur": 1632, "Venusaur": 2580, "Charmander": 955, "Charmeleon": 1557, From ae6c2789e040e770d6674a8fa84b43cc7a68a980 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Thu, 11 Aug 2016 08:16:11 +0800 Subject: [PATCH 20/24] Default lucky egg to false + had again parameter "evolve_only_with_lucky_egg" --- configs/config.json.optimizer.example | 1 + pokemongo_bot/cell_workers/pokemon_optimizer.py | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index a642116346..f13bdc54ef 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -23,6 +23,7 @@ "transfer": true, "evolve": true, "use_lucky_egg": true, + "evolve_only_with_lucky_egg": true, "minimum_evolve_for_lucky_egg": 90 } }, diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index ff48624d13..6f6ededa2e 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -18,7 +18,8 @@ def initialize(self): self.config_transfer = self.config.get("transfer", False) self.config_evolve = self.config.get("evolve", False) - self.config_use_lucky_egg = self.config.get("use_lucky_egg", 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) def get_pokemon_slot_left(self): @@ -200,10 +201,11 @@ def apply_optimization(self, transfer, evo): except: lucky_egg_count = 0 - if (lucky_egg_count == 0) or (len(evo) < self.config_minimum_evolve_for_lucky_egg): - return - - self.use_lucky_egg() + if lucky_egg_count == 0: + if self.config_evolve_only_with_lucky_egg: + return + elif len(evo) >= self.config_minimum_evolve_for_lucky_egg: + self.use_lucky_egg() for pokemon in evo: self.evolve_pokemon(pokemon) From 0530d80d9bca47cb46e2b6ba5790290f9ada92f5 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Fri, 12 Aug 2016 06:47:09 +0800 Subject: [PATCH 21/24] Fix qn issue with egg counting Add configuration parameter to allow customization of how pokemons are ranked in a family --- data/pokemon.json | 5 + .../cell_workers/pokemon_optimizer.py | 107 ++++++++++-------- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/data/pokemon.json b/data/pokemon.json index c80ac2aeba..7a2f0e12b0 100644 --- a/data/pokemon.json +++ b/data/pokemon.json @@ -4326,6 +4326,11 @@ ], "Weight": "49.8 kg", "Height": "1.5 m", + "Next Evolution Requirements": { + "Amount": 50, + "Family": 106, + "Name": "Hitmonlee candies" + }, "Next evolution(s)": [ { "Number": "107", diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 6f6ededa2e..b13e53fc31 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,4 +1,5 @@ import copy +import logging from pokemongo_bot import inventory from pokemongo_bot.base_task import BaseTask @@ -21,6 +22,9 @@ def initialize(self): 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_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): return self.bot._player["max_pokemon_storage"] - len(inventory.pokemons()._data) @@ -63,19 +67,27 @@ def parse_inventory(self): setattr(pokemon, "ncp", ncp) + # print pokemon.name, pokemon.max_cp, max_cp + 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) - best_iv = self.get_best_iv_in_family(family) - best_ncp = self.get_best_ncp_in_family(family) - best_cp = self.get_best_cp_in_family(family) + evolve_best = [] + keep_best = [] + + for criteria in self.config_keep: + if criteria.get("evolve", True): + evolve_best += self.get_top_rank(family, criteria) + else: + keep_best += self.get_top_rank(family, criteria) - best = self.combine_pokemon_lists(best_iv, best_ncp) + evolve_best = self.unique_pokemons(evolve_best) + keep_best = self.unique_pokemons(keep_best) - return self.get_evolution_plan(family_id, family, best, best_cp) + return self.get_evolution_plan(family_id, family, evolve_best, keep_best) def get_multi_family_optimized(self, family_id, family, nb_branch): # Transfer each group of senior independently @@ -91,68 +103,70 @@ def get_multi_family_optimized(self, family_id, family, nb_branch): if len(senior_pids) < nb_branch: # We did not get every combination yet = All other Pokemons are potentially good to keep - best = other_family - best.sort(key=lambda p: p.iv * p.ncp, reverse=True) - best_cp = [] + evolve_best = other_family + evolve_best.sort(key=lambda p: p.iv * p.ncp, reverse=True) + keep_best = [] else: - min_iv = min([max(f, key=lambda p: p.iv) for f in senior_grouped_family.values()], key=lambda p: p.iv).iv - min_ncp = min([max(f, key=lambda p: p.ncp) for f in senior_grouped_family.values()], key=lambda p: p.ncp).ncp - min_cp = min([max(f, key=lambda p: p.cp) for f in senior_grouped_family.values()], key=lambda p: p.cp).cp + evolve_best = [] + keep_best = [] - best_iv = self.get_better_iv_in_family(other_family, min_iv) - best_ncp = self.get_better_ncp_in_family(other_family, min_ncp) - best_cp = self.get_better_cp_in_family(other_family, min_cp) + for criteria in self.config_keep: + top = [] - best = self.combine_pokemon_lists(best_iv, best_ncp) + for f in senior_grouped_family.values(): + top += self.get_top_rank(f, criteria) - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, best, best_cp) - transfer += transfer_senior + worst = self.get_sorted_family(top, criteria)[-1] - return (transfer, evo_best, evo_crap) + if criteria.get("evolve", True): + evolve_best += self.get_better_rank(family, criteria, worst) + else: + keep_best += self.get_better_rank(family, criteria, worst) - def get_best_iv_in_family(self, family): - best = max(family, key=lambda p: p.iv) - return sorted([p for p in family if p.iv == best.iv], key=lambda p: p.ncp, reverse=True) + evolve_best = self.unique_pokemons(evolve_best) + keep_best = self.unique_pokemons(keep_best) - def get_better_iv_in_family(self, family, iv): - return sorted([p for p in family if p.iv >= iv], key=lambda p: (p.iv, p.ncp), reverse=True) + transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best) + transfer += transfer_senior + + return (transfer, evo_best, evo_crap) - def get_best_ncp_in_family(self, family): - best = max(family, key=lambda p: p.ncp) - return sorted([p for p in family if p.ncp == best.ncp], key=lambda p: p.iv, reverse=True) + 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)] - def get_better_ncp_in_family(self, family, ncp): - return sorted([p for p in family if p.ncp >= ncp], key=lambda p: (p.ncp, p.iv), reverse=True) + 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)] - def get_best_cp_in_family(self, family): - best = max(family, key=lambda p: p.cp) - return sorted([p for p in family if p.cp == best.cp], key=lambda p: (p.ncp, p.iv), reverse=True) + def get_sorted_family(self, family, criteria): + return sorted(family, key=lambda p: self.get_rank(p, criteria), reverse=True) - def get_better_cp_in_family(self, family, cp): - return sorted([p for p in family if p.cp >= cp], key=lambda p: (p.ncp, p.iv), reverse=True) + def get_rank(self, pokemon, criteria): + return tuple(getattr(pokemon, a, None) for a in criteria.get("sort")) def get_pokemon_max_cp(self, pokemon_name): return int(self.pokemon_max_cp.get(pokemon_name, 0)) - def combine_pokemon_lists(self, a, b): + def unique_pokemons(self, l): seen = set() - return [p for p in a + b if not (p.id in seen or seen.add(p.id))] + return [p for p in l if not (p.id in seen or seen.add(p.id))] - def get_evolution_plan(self, family_id, family, best, best_cp): + def get_evolution_plan(self, family_id, family, evolve_best, keep_best): candies = inventory.candies().get(family_id).quantity # All the rest is crap, for now crap = family[:] - crap = [p for p in crap if p not in best] - crap = [p for p in crap if p not in best_cp] + 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) candies += len(crap) # Let's see if we can evolve our best pokemons - evo_best = [] + can_evolve_best = [] - for pokemon in best: + for pokemon in evolve_best: if not pokemon.has_next_evolution(): continue @@ -161,7 +175,7 @@ def get_evolution_plan(self, family_id, family, best, best_cp): if candies < 0: continue - evo_best.append(pokemon) + can_evolve_best.append(pokemon) # Not sure if the evo keep the same id next_pid = pokemon.next_evolution_id @@ -169,7 +183,7 @@ def get_evolution_plan(self, family_id, family, best, best_cp): next_evo.pokemon_id = next_pid next_evo._static_data = inventory.pokemons().data_for(next_pid) next_evo.name = inventory.pokemons().name_for(next_pid) - best.append(next_evo) + evolve_best.append(next_evo) # 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) @@ -189,7 +203,7 @@ def get_evolution_plan(self, family_id, family, best, best_cp): evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo] transfer = [p for p in crap if p not in evo_crap] - return (transfer, evo_best, evo_crap) + return (transfer, can_evolve_best, evo_crap) def apply_optimization(self, transfer, evo): for pokemon in transfer: @@ -197,16 +211,19 @@ def apply_optimization(self, transfer, evo): if self.config_evolve and self.config_use_lucky_egg: try: - lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG) + lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable except: lucky_egg_count = 0 if lucky_egg_count == 0: if self.config_evolve_only_with_lucky_egg: + logging.info("Skipping evolution step. No lucky egg available") return elif len(evo) >= self.config_minimum_evolve_for_lucky_egg: self.use_lucky_egg() + logging.info("Evolving %s Pokemons", len(evo)) + for pokemon in evo: self.evolve_pokemon(pokemon) @@ -227,7 +244,7 @@ def transfer_pokemon(self, pokemon): action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def use_lucky_egg(self): - lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG) + lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable if self.config_evolve and self.config_use_lucky_egg: response_dict = self.bot.use_lucky_egg() From 37142216864abe26acfc488f552e65332f9766a1 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Fri, 12 Aug 2016 06:48:04 +0800 Subject: [PATCH 22/24] Update configuration example --- configs/config.json.optimizer.example | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index f13bdc54ef..f9138429a3 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -24,7 +24,24 @@ "evolve": true, "use_lucky_egg": true, "evolve_only_with_lucky_egg": true, - "minimum_evolve_for_lucky_egg": 90 + "minimum_evolve_for_lucky_egg": 90, + "keep": [ + { + "top": 1, + "evolve": true, + "sort": ["iv"] + }, + { + "top": 1, + "evolve": true, + "sort": ["ncp"] + }, + { + "top": 1, + "evolve": false, + "sort": ["cp"] + } + ] } }, { From 4b020243c643d6bdf77f7841aab2637ac990c9bd Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Fri, 12 Aug 2016 07:45:54 +0800 Subject: [PATCH 23/24] Upgrade to latest inventory --- configs/config.json.optimizer.example | 5 +- .../cell_workers/pokemon_optimizer.py | 71 +++---------------- 2 files changed, 13 insertions(+), 63 deletions(-) diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index f9138429a3..f63a2b7ae1 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -29,6 +29,8 @@ { "top": 1, "evolve": true, + "// Available sorting keys are:": true, + "// iv, cp, ncp, ivcp, max_cp, iv_attack, iv_defense, iv_stamina, hp_max, level": true, "sort": ["iv"] }, { @@ -94,9 +96,6 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "evolve_captured": false, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, "logging_color": true, "catch": { "any": { diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index b13e53fc31..97aea3459a 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -12,10 +12,8 @@ class PokemonOptimizer(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def initialize(self): - self.pokemon_max_cp = {} self.family_by_family_id = {} - - self.init_pokemon_max_cp() + self.logger = logging.getLogger(self.__class__.__name__) self.config_transfer = self.config.get("transfer", False) self.config_evolve = self.config.get("evolve", False) @@ -57,17 +55,7 @@ def parse_inventory(self): for pokemon in inventory.pokemons().all(): family_id = pokemon.first_evolution_id - - max_cp = self.get_pokemon_max_cp(pokemon.name) - - if max_cp > 0: - ncp = float(pokemon.cp) / max_cp - else: - ncp = 0 - - setattr(pokemon, "ncp", ncp) - - # print pokemon.name, pokemon.max_cp, max_cp + setattr(pokemon, "ncp", pokemon.cp_percent) self.family_by_family_id.setdefault(family_id, []).append(pokemon) @@ -91,8 +79,8 @@ def get_family_optimized(self, family_id, family): def get_multi_family_optimized(self, family_id, family, nb_branch): # Transfer each group of senior independently - senior_family = [p for p in family if p.next_evolution_id is None] - other_family = [p for p in family if p.next_evolution_id is not None] + senior_family = [p for p in family if not p.has_next_evolution()] + other_family = [p for p in family if p.has_next_evolution()] 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} @@ -210,19 +198,16 @@ def apply_optimization(self, transfer, evo): self.transfer_pokemon(pokemon) if self.config_evolve and self.config_use_lucky_egg: - try: - lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable - except: - lucky_egg_count = 0 + lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable - if lucky_egg_count == 0: + if lucky_egg.count == 0: if self.config_evolve_only_with_lucky_egg: - logging.info("Skipping evolution step. No lucky egg available") + self.logger.info("Skipping evolution step. No lucky egg available") return elif len(evo) >= self.config_minimum_evolve_for_lucky_egg: self.use_lucky_egg() - logging.info("Evolving %s Pokemons", len(evo)) + self.logger.info("Evolving %s Pokemons", len(evo)) for pokemon in evo: self.evolve_pokemon(pokemon) @@ -244,11 +229,11 @@ def transfer_pokemon(self, pokemon): action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def use_lucky_egg(self): - lucky_egg_count = inventory.items().count_for(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable + lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable if self.config_evolve and self.config_use_lucky_egg: response_dict = self.bot.use_lucky_egg() - lucky_egg_count -= 1 + lucky_egg.remove(1) else: response_dict = {"responses": {"USE_ITEM_XP_BOOST": {"result": 1}}} @@ -263,7 +248,7 @@ def use_lucky_egg(self): if result == 1: self.emit_event("used_lucky_egg", formatted="Used lucky egg ({amount_left} left).", - data={"amount_left": lucky_egg_count}) + data={"amount_left": lucky_egg.count}) return True else: self.emit_event("lucky_egg_error", @@ -298,37 +283,3 @@ def evolve_pokemon(self, pokemon): return True else: return False - - def init_pokemon_max_cp(self): - self.pokemon_max_cp = { - "Bulbasaur": 1072, "Ivysaur": 1632, "Venusaur": 2580, "Charmander": 955, "Charmeleon": 1557, - "Charizard": 2602, "Squirtle": 1009, "Wartortle": 1583, "Blastoise": 2542, "Caterpie": 444, - "Metapod": 478, "Butterfree": 1455, "Weedle": 449, "Kakuna": 485, "Beedrill": 1440, - "Pidgey": 680, "Pidgeotto": 1224, "Pidgeot": 2091, "Rattata": 582, "Raticate": 1444, - "Spearow": 687, "Fearow": 1746, "Ekans": 824, "Arbok": 1767, "Pikachu": 888, - "Raichu": 2028, "Sandshrew": 799, "Sandslash": 1810, "Nidoran F": 876, "Nidorina": 1405, - "Nidoqueen": 2485, "Nidoran M": 843, "Nidorino": 1373, "Nidoking": 2475, "Clefairy": 1201, - "Clefable": 2398, "Vulpix": 831, "Ninetales": 2188, "Jigglypuff": 918, "Wigglytuff": 2177, - "Zubat": 643, "Golbat": 1921, "Oddish": 1148, "Gloom": 1689, "Vileplume": 2493, - "Paras": 917, "Parasect": 1747, "Venonat": 1029, "Venomoth": 1890, "Diglett": 457, - "Dugtrio": 1169, "Meowth": 756, "Persian": 1632, "Psyduck": 1110, "Golduck": 2387, - "Mankey": 879, "Primeape": 1865, "Growlithe": 1335, "Arcanine": 2984, "Poliwag": 796, - "Poliwhirl": 1340, "Poliwrath": 2505, "Abra": 600, "Kadabra": 1132, "Alakazam": 1814, - "Machop": 1090, "Machoke": 1761, "Machamp": 2594, "Bellsprout": 1117, "Weepinbell": 1724, - "Victreebel": 2531, "Tentacool": 905, "Tentacruel": 2220, "Geodude": 849, "Graveler": 1434, - "Golem": 2303, "Ponyta": 1516, "Rapidash": 2199, "Slowpoke": 1219, "Slowbro": 2597, - "Magnemite": 891, "Magneton": 1880, "Farfetch'd": 1264, "Doduo": 855, "Dodrio": 1836, - "Seel": 1107, "Dewgong": 2146, "Grimer": 1284, "Muk": 2603, "Shellder": 823, - "Cloyster": 2053, "Gastly": 804, "Haunter": 1380, "Gengar": 2078, "Onix": 857, - "Drowzee": 1075, "Hypno": 2184, "Krabby": 792, "Kingler": 1823, "Voltorb": 840, - "Electrode": 1646, "Exeggcute": 1100, "Exeggutor": 2955, "Cubone": 1007, "Marowak": 1657, - "Hitmonlee": 1493, "Hitmonchan": 1517, "Lickitung": 1627, "Koffing": 1152, "Weezing": 2250, - "Rhyhorn": 1182, "Rhydon": 2243, "Chansey": 675, "Tangela": 1740, "Kangaskhan": 2043, - "Horsea": 795, "Seadra": 1713, "Goldeen": 965, "Seaking": 2044, "Staryu": 938, - "Starmie": 2182, "Mr. Mime": 1494, "Scyther": 2074, "Jynx": 1717, "Electabuzz": 2119, - "Magmar": 2265, "Pinsir": 2122, "Tauros": 1845, "Magikarp": 263, "Gyarados": 2689, - "Lapras": 2981, "Ditto": 920, "Eevee": 1077, "Vaporeon": 2816, "Jolteon": 2140, - "Flareon": 2643, "Porygon": 1692, "Omanyte": 1120, "Omastar": 2234, "Kabuto": 1105, - "Kabutops": 2130, "Aerodactyl": 2165, "Snorlax": 3113, "Articuno": 2978, "Zapdos": 3114, - "Moltres": 3240, "Dratini": 983, "Dragonair": 1748, "Dragonite": 3500, "Mewtwo": 4145, - "Mew": 3299} From cbb54d8376aa3ff8bbf833c19079b40d4d1ba0af Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Fri, 12 Aug 2016 14:54:48 +0800 Subject: [PATCH 24/24] Fix bug --- pokemongo_bot/cell_workers/pokemon_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 97aea3459a..ba7072eb2d 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -166,7 +166,7 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): can_evolve_best.append(pokemon) # Not sure if the evo keep the same id - next_pid = pokemon.next_evolution_id + next_pid = pokemon.next_evolution_ids[0] next_evo = copy.copy(pokemon) next_evo.pokemon_id = next_pid next_evo._static_data = inventory.pokemons().data_for(next_pid)