views:

193

answers:

2

I'm building a flash game website using Google App Engine. I'm going to put achievements on it, and am scratching my head on how exactly to store this data. I have a (non-google) user model. I need to know how to store each kind of achievement (name, description, picture), as well as link them with users who earn them. Also, I need to keep track of when they earned it, as well as their current progress for those who don't have one yet.

If anyone wants to give suggestions about detection or other achievement related task feel free.

EDIT:

Non-Google User Model:

class BeardUser(db.Model):
    email = db.StringProperty()
    username = db.StringProperty()
    password = db.StringProperty()
    settings = db.ReferenceProperty(UserSettings)
    is_admin = db.BooleanProperty()
    user_role = db.StringProperty(default="user")
    datetime = db.DateTimeProperty(auto_now_add=True)
+2  A: 

Unless your users will be adding achievements dynamically (as in something like Kongregate where users can upload their own games, etc), your achievement list will be static. That means you can store the (name, description, picture) list in your main python file, or as an achievements module or something. You can reference the achievements in your dynamic data by name or id as specified in this list.

To indicate whether a given user has gotten an achievement, a simple way is to add a ListProperty to your player model that will hold the achievements that the player has gotten. By default this will be indexed so you can query for which players have gotten which achievements, etc.

If you also want to store what date/time the user has gotten the achievement, we're getting into an area where the built-in properties are less ideal. You could add another ListProperty with datetime properties corresponding to each achievement, but that's awkward: what we'd really like is a tuple or dict so that we can store the achievement id/name together with the time it was achieved, and make it easy to add additional properties related to each achievement in the future.

My usual approach for cases like this is to dump my ideal data structure into a BlobProperty, but that has some disadvantages and you may prefer a different approach.

Another approach is to have an achievement model separate from your user model, and a ListProperty full of referenceProperties to all the achievements that user has gotten. this has the advantage of letting you index everything very nicely but will be an additional API CPU cost at runtime especially if you have a lot of achievements to look up, and operations like deleting all a user's achievements will be very expensive compared to the above.

Brandon Thomson
Would a custom data model be a good idea?
Josh Patton
+2  A: 

Building on Brandon's answer here.

If you can, it would be MUCH faster to define all the achievements in the Python files, so they are in-memory and require no database fetching. This is also simpler to implement.

class StaticAchievement(object):
    """
    An achievement defined in a Python file.
    """
    by_name = {}
    by_index = []
    def __init__(self, name, description="", picture=None):        
        if picture is None: picture = "static/default_achievement.png"
        StaticAchievement.by_name[name] = self
        StaticAchievement.by_index.append(self)
        self.index = len(StaticAchievement.by_index)

# This automatically adds an entry to the StaticAchievement.by_name dict.
# It also adds an entry to to the StaticAchievement.by_index list.
StaticAchievement(
  name="tied your shoe", 
  description="You successfully tied your shoes!", 
  picture="static/shoes.png"
)

Then all you have to do is keep the ids for each player's achievements in a db.StringListProperty. When you have the player's object loaded, rendering the achievements requires no additional db lookups - you already have the ids, now you just have to look them up in StaticAchievement.all. This is simple to implement, and allows you to easily query which users have a given achievement, etc. Nothing else is required.

If you want additional data associated with the user's possession of an achievement (e.g. the date at which it was acquired) then you have a choice of approaches:

1: Store it in another ListProperty of the same length.

This retains the implementation simplicity and the indexability of the properties. However, this sort of solution is not to everyone's taste. If you need to make the use of this data less messy, just write a method on the player object like this:

    def achievement_tuples(self):
        r = []
        for i in range(0, len(self.achievements)):
            r.append( (self.achievements[i], self.achievement_dates[i]) )
        return r

You could handle progress by maintaining a parallel ListProperty of integers, and incrementing those integers when the user makes progress.

As long as you can understand how the data is represented, you can easily hide that representation behind whatever methods you want - allowing you to have both the interface you want and the performance and indexing characteristics you want. But if you don't really need the indexing and don't like the lists, see option #2.

2: Store the additional data in a BlobProperty.

This requires you to serialize and deserialize the data, and you give up the nice querying of the listproperties, but maybe you will be happier if you hate the concept of the parallel lists. This is what people tend to do when they really want the Python way of doing things, as distinct from the App Engine way.

3: Store the additional data in the db

(e.g. a PlayersAchievement object containing both a StringProperty of the Achievement id, and a DateProperty, and a UserProperty; and on the player an achivements listproperty full of references to PlayersAchievement objects)

Since we expect players to potentially get a large number of achievements, the overhead for this will get bad fast. Getting around it will get very complex very quickly: memcache, storing intermediate data like blobs of prerendered HTML or serialized lists of tuples, setting up tasks, etc. This is also the kind of stuff you will have to do if you want the achievement definitions themselves to be modifiable/stored in the DB.

Sasha