Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 2 functions that check the statistics of an achievement #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions AUTHORS
Expand Up @@ -14,3 +14,5 @@ Maintainers

Patches and Suggestions
```````````````````````

- Nathan Zilora <Zwork101>
56 changes: 52 additions & 4 deletions pychievements/trackers.py
Expand Up @@ -12,6 +12,16 @@ class NotRegistered(Exception):
pass


def median(lst):
n = len(lst)
if n < 1:
return None
if n % 2 == 1:
return sorted(lst)[n // 2]
else:
return sum(sorted(lst)[n // 2-1:n // 2 + 1])/ 2.0


class AchievementTracker(object):
"""
AchievementTracker tracks achievements and current levels for ``tracked_id`` using a configured
Expand Down Expand Up @@ -77,7 +87,7 @@ def is_registered(self, achievement):
"""
return achievement in self._registry

def achievements(self, category=None, keywords=[]):
def achievements(self, category=None, keywords=None):
"""
Returns all registered achievements.

Expand All @@ -88,9 +98,10 @@ def achievements(self, category=None, keywords=[]):

keywords
Filters returned achievements by keywords. Returned achievements will match all
given keywords
given keywords. This will be [] if not provided.
"""
achievements = []
keywords = keywords if keywords is not None else []
for achievement in self._registry:
if category is None or achievement.category == category:
if not keywords or all([_ in achievement.keywords for _ in keywords]):
Expand All @@ -117,9 +128,10 @@ class or a string of the name of an achievement class that has been registered w
return self._backend.achievement_for_id(tracked_id, a[0])
raise NotRegistered('The achievement %s is not registered with this tracker' % achievement)

def achievements_for_id(self, tracked_id, category=None, keywords=[]):
def achievements_for_id(self, tracked_id, category=None, keywords=None):
""" Returns all of the achievements for tracked_id that match the given category and
keywords """
keywords. keywords will be [] if not provided."""
keywords = keywords if keywords is not None else []
return self._backend.achievements_for_id(tracked_id, self.achievements(category, keywords))

def _check_signals(self, tracked_id, achievement, old_level, old_achieved):
Expand Down Expand Up @@ -216,3 +228,39 @@ def get_tracked_ids(self):
def remove_id(self, tracked_id):
""" Remove all tracked information for tracked_id """
self._backend.remove_id(tracked_id)

def compare_stats(self, tracked_ids, achievement):
"""
Fetches an achievement's statistics for each ID provided, and compares them.

This will return a dictionary that looks like the following:
{
"mean": Number with the average current level. This will be rounded into a goal index.
"median": The median of all the current levels.
"max": The highest level among the IDs.
"min": The lowest level among the IDs.
}
"""
stats = {
"mean": 0,
"median": [],
"max": [],
"min": []
}

for tid in tracked_ids:
personal_achievement = self._backend.achievement_for_id(tid, achievement)
stats["mean"] += personal_achievement.current[0]
for stat in ["median", "max", "min"]:
stats[stat].append(personal_achievement.current[0])

stats["mean"] /= len(tracked_ids)
stats["median"] = median(stats["median"])
stats["max"] = max(stats["max"])
stats["min"] = min(stats["min"])

return stats

def compare_global_stats(self, achievement):
""" Shorthand for tracker.compare_stats(tracker.get_tracked_ids(), ``achievement``) """
return self.compare_stats(self.get_tracked_ids(), achievement)
34 changes: 33 additions & 1 deletion tests/__init__.py
Expand Up @@ -30,6 +30,24 @@ def setUp(self):
self.tracker = AchievementTracker()
self.tracker.register(ACHIEVEMENTS)

self.fixed_tracker = AchievementTracker()

class FixedAchievement(Achievement):
name = "Fixed"
category = "test"
keywords = "unittest"
goals = (
{'level': 10, 'name': 'Level 1', 'icon': icons.star, 'description': 'Level One'},
{'level': 20, 'name': 'Level 2', 'icon': icons.star, 'description': 'Level Two'},
{'level': 30, 'name': 'Level 3', 'icon': icons.star, 'description': 'Level Three'},
)

self.fixed_achievement = FixedAchievement
self.fixed_tracker.register(FixedAchievement)
people = {"Greg": 10, "Cindy": 20, "Megan": 30}
for name, value in people.items():
self.fixed_tracker.increment(name, FixedAchievement, value)

def test_bad_backend(self):
self.assertRaises(ValueError, self.tracker.set_backend, AchievementTracker)

Expand Down Expand Up @@ -107,6 +125,20 @@ def test_remove_id(self):
print(self.tracker.get_tracked_ids())
self.assertEqual(len(self.tracker.get_tracked_ids()), len(TRACKED_IDS)-1)

def test_compare_stats(self):
results = self.fixed_tracker.compare_stats(["Greg", "Megan"], self.fixed_achievement)
self.assertEqual(results["mean"], 20)
self.assertEqual(results["median"], 20)
self.assertEqual(results["max"], 30)
self.assertEqual(results["min"], 10)

def test_compare_global_stats(self):
results = self.fixed_tracker.compare_global_stats(self.fixed_achievement)
self.assertEqual(results["mean"], 20)
self.assertEqual(results["median"], 20)
self.assertEqual(results["max"], 30)
self.assertEqual(results["min"], 10)


class AchievementBackenedTests(unittest.TestCase):
# only tests things that haven't been hit in TrackerTests
Expand All @@ -128,7 +160,7 @@ def setUp(self):
self.tracker.register(ACHIEVEMENTS)

def tearDown(self):
os.remove(self.dbfile.name)
os.remove(self.dbfile.name) # Fails on windows. This should be looked into later.

def test_increment(self):
tid = random.choice(TRACKED_IDS)
Expand Down