Skip to content

Commit

Permalink
UpdateTitleStats -> UpdateLiveStats, new stat, refactoring (#3467)
Browse files Browse the repository at this point in the history
* Renamed UpdateTitleStats to UpdateLiveStats

* Cleaned worker documentation
* Added documentation for terminal_log and terminal_title
* Fixed #3312 (comment)
* Made some refactoring
* Added captures_per_hour stat that shows estimated pokemon captures per hour
* Added a captures_per_hour method in metrics.py
* Added unit tests for features added in #3312
* Added unit tests for captures_per_hour

* Avoid useless overhead when no output configured

* Added default config values in documentation

* Fixed issue with title updating on Windows

* See #3472
  • Loading branch information
Genesis authored and elicwhite committed Aug 11, 2016
1 parent 04d4c26 commit 8674d0d
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 82 deletions.
2 changes: 1 addition & 1 deletion pokemongo_bot/cell_workers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
from collect_level_up_reward import CollectLevelUpReward
from follow_cluster import FollowCluster
from sleep_schedule import SleepSchedule
from update_title_stats import UpdateTitleStats
from update_live_stats import UpdateLiveStats
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,33 @@
from pokemongo_bot.worker_result import WorkerResult
from pokemongo_bot.tree_config_builder import ConfigException

class UpdateTitleStats(BaseTask):

class UpdateLiveStats(BaseTask):
"""
Periodically updates the terminal title to display stats about the bot.
Periodically displays stats about the bot in the terminal and/or in its title.
Fetching some stats requires making API calls. If you're concerned about the amount of calls
your bot is making, don't enable this worker.
Example config :
{
"type": "UpdateTitleStats",
"config": {
"min_interval": 10,
"stats": ["login", "uptime", "km_walked", "level_stats", "xp_earned", "xp_per_hour"],
}
}
You can set a logging on terminal mode like this:
Example logging on console (and disabling title change):
{
"type": "UpdateTitleStats",
"type": "UpdateLiveStats",
"config": {
"min_interval": 10,
"stats": ["login", "uptime", "km_walked", "level_stats", "xp_earned", "xp_per_hour"],
"terminal_log": true,
"terminal_title": false
}
}
min_interval : The minimum interval at which the stats are displayed,
in seconds (defaults to 120 seconds).
The update interval cannot be accurate as workers run synchronously.
stats : An array of stats to display and their display order (implicitly),
see available stats below (defaults to []).
terminal_log : Logs the stats into the terminal (defaults to false).
terminal_title : Displays the stats into the terminal title (defaults to true).
Available stats :
- login : The account login (from the credentials).
- username : The trainer name (asked at first in-game connection).
Expand All @@ -48,6 +47,7 @@ class UpdateTitleStats(BaseTask):
- stops_visited : The number of visited stops.
- pokemon_encountered : The number of encountered pokemon.
- pokemon_caught : The number of caught pokemon.
- captures_per_hour : The estimated number of pokemon captured per hour.
- pokemon_released : The number of released pokemon.
- pokemon_evolved : The number of evolved pokemon.
- pokemon_unseen : The number of pokemon never seen before.
Expand All @@ -56,16 +56,9 @@ class UpdateTitleStats(BaseTask):
- stardust_earned : The number of earned stardust since the bot started.
- highest_cp_pokemon : The caught pokemon with the highest CP since the bot started.
- most_perfect_pokemon : The most perfect caught pokemon since the bot started.
min_interval : The minimum interval at which the title is updated,
in seconds (defaults to 10 seconds).
The update interval cannot be accurate as workers run synchronously.
stats : An array of stats to display and their display order (implicitly),
see available stats above.
"""
SUPPORTED_TASK_API_VERSION = 1


def __init__(self, bot, config):
"""
Initializes the worker.
Expand All @@ -74,62 +67,78 @@ def __init__(self, bot, config):
:param config: The task configuration.
:type config: dict
"""
super(UpdateTitleStats, self).__init__(bot, config)
super(UpdateLiveStats, self).__init__(bot, config)

self.next_update = None

self.min_interval = int(self.config.get('min_interval', 120))
self.displayed_stats = self.config.get('stats', [])
self.terminal_log = self.config.get('terminal_log', False)
self.terminal_title = self.config.get('terminal_title', True)
self.terminal_log = bool(self.config.get('terminal_log', False))
self.terminal_title = bool(self.config.get('terminal_title', True))

self.bot.event_manager.register_event('update_title', parameters=('title',))
self.bot.event_manager.register_event('log_stats',parameters=('title',))
self.bot.event_manager.register_event('log_stats', parameters=('stats',))

def initialize(self):
pass

def work(self):
"""
Updates the title if necessary.
Displays the stats if necessary.
:return: Always returns WorkerResult.SUCCESS.
:rtype: WorkerResult
"""
if not self._should_display():
return WorkerResult.SUCCESS
title = self._get_stats_title(self._get_player_stats())
# If title is empty, it couldn't be generated.
if not title:
line = self._get_stats_line(self._get_player_stats())
# If line is empty, it couldn't be generated.
if not line:
return WorkerResult.SUCCESS

if self.terminal_title:
self._update_title(title, _platform)
self._update_title(line, _platform)

if self.terminal_log:
self._log_on_terminal(title)
self._log_on_terminal(line)
return WorkerResult.SUCCESS

def _should_display(self):
"""
Returns a value indicating whether the title should be updated.
:return: True if the title should be updated; otherwise, False.
Returns a value indicating whether the stats should be displayed.
:return: True if the stats should be displayed; otherwise, False.
:rtype: bool
"""
if not self.terminal_title and not self.terminal_log:
return False
return self.next_update is None or datetime.now() >= self.next_update

def _log_on_terminal(self, title):
def _compute_next_update(self):
"""
Computes the next update datetime based on the minimum update interval.
:return: Nothing.
:rtype: None
"""
self.next_update = datetime.now() + timedelta(seconds=self.min_interval)

def _log_on_terminal(self, stats):
"""
Logs the stats into the terminal using an event.
:param stats: The stats to display.
:type stats: string
:return: Nothing.
:rtype: None
"""
self.emit_event(
'log_stats',
formatted="{title}",
formatted="{stats}",
data={
'title': title
'stats': stats
}
)
self.next_update = datetime.now() + timedelta(seconds=self.min_interval)
self._compute_next_update()

def _update_title(self, title, platform):
"""
Updates the window title using different methods, according to the given platform
Updates the window title using different methods, according to the given platform.
:param title: The new window title.
:type title: string
:param platform: The platform string.
Expand All @@ -139,29 +148,19 @@ def _update_title(self, title, platform):
:raise: RuntimeError: When the given platform isn't supported.
"""

self.emit_event(
'update_title',
formatted="{title}",
data={
'title': title
}
)

if platform == "linux" or platform == "linux2" or platform == "cygwin":
stdout.write("\x1b]2;{}\x07".format(title))
stdout.flush()
elif platform == "darwin":
stdout.write("\033]0;{}\007".format(title))
stdout.flush()
elif platform == "win32":
ctypes.windll.kernel32.SetConsoleTitleA(title)
ctypes.windll.kernel32.SetConsoleTitleA(title.encode())
else:
raise RuntimeError("unsupported platform '{}'".format(platform))
self._compute_next_update()

self.next_update = datetime.now() + timedelta(seconds=self.min_interval)


def _get_stats_title(self, player_stats):
def _get_stats_line(self, player_stats):
"""
Generates a stats string with the given player stats according to the configuration.
:return: A string containing human-readable stats, ready to be displayed.
Expand Down Expand Up @@ -194,6 +193,7 @@ def _get_stats_title(self, player_stats):
stops_visited = metrics.visits['latest'] - metrics.visits['start']
pokemon_encountered = metrics.num_encounters()
pokemon_caught = metrics.num_captures()
captures_per_hour = int(metrics.captures_per_hour())
pokemon_released = metrics.releases
pokemon_evolved = metrics.num_evolutions()
pokemon_unseen = metrics.num_new_mons()
Expand Down Expand Up @@ -223,6 +223,7 @@ def _get_stats_title(self, player_stats):
'stops_visited': 'Visited {:,} stops'.format(stops_visited),
'pokemon_encountered': 'Encountered {:,} pokemon'.format(pokemon_encountered),
'pokemon_caught': 'Caught {:,} pokemon'.format(pokemon_caught),
'captures_per_hour': '{:,} pokemon/h'.format(captures_per_hour),
'pokemon_released': 'Released {:,} pokemon'.format(pokemon_released),
'pokemon_evolved': 'Evolved {:,} pokemon'.format(pokemon_evolved),
'pokemon_unseen': 'Encountered {} new pokemon'.format(pokemon_unseen),
Expand Down Expand Up @@ -251,9 +252,9 @@ def get_stat(stat):
return available_stats[stat]

# Map stats the user wants to see to available stats and join them with pipes.
title = ' | '.join(map(get_stat, self.displayed_stats))
line = ' | '.join(map(get_stat, self.displayed_stats))

return title
return line

def _get_player_stats(self):
"""
Expand Down
8 changes: 8 additions & 0 deletions pokemongo_bot/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ def num_throws(self):
def num_captures(self):
return self.captures['latest'] - self.captures['start']

def captures_per_hour(self):
"""
Returns an estimated number of pokemon caught per hour.
:return: An estimated number of pokemon caught per hour.
:rtype: float
"""
return self.num_captures() / (time.time() - self.start_time) * 3600

def num_visits(self):
return self.visits['latest'] - self.visits['start']

Expand Down
Loading

0 comments on commit 8674d0d

Please sign in to comment.