Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Architecture for achievements / badges

There's a lot of questions already about coding a badge system similar to SO, my question is different. Assume I have a web page system, badges / achievements, stored in the DB as a row with the achievement key (id), user id, and any other data.

My simple question is, where should I store the badge ID? I have one class per achievement with all the data and methods for testing if it has been earned. I imagine I may have dozens or hundreds at some point. I want the IDs to be used hard coded only once, and in one concise place, so I have no chance of accidentally changing them or mixing them up.

I could hard code them in the class, like

public int Key { get { return 15; } } // I'm calling it Key, not ID

but if I split my achievements among multiple files I don't want to have to run around looking for the highest Key when I add a new one and risk a mistake.

I could put them in some dictionary in another class...

public class AchievementSet
{
    private Dictionary<int, Achievement> _achievements;

    public AchievementSet()
    {
        _achievements = new Dictionary<int, Achievement>()
        {
            { 1, new SomethingAchievement() }
        };
    }
}

But now the class itself doesn't know its own key, and it needs to (or does it?) If I passed it into the constructor now I risk mismatching the numbers.

Any recommendations?

like image 314
Tesserex Avatar asked Oct 05 '22 15:10

Tesserex


2 Answers

In the context of Stack Overflow, I'd imagine each badge has properties such as: Id, Name, Class (Bronze, Silver or Gold) and Description etc.

You mention that you currently have a class for each badge/achievement, each with appropriate checks for conditions on which it would be awarded.

The reason I'm suggesting you move away from the model you're looking at now (one class per achievement) is because you're going to continue to face huge problems down the road when you're navigating through 200 different classes looking for that one ID you can't recall.

By storing your badges in the table, your data is all in one logical place and not scattered across your application.

In answer to the question: So do you disagree with the accepted answer to: stackoverflow.com/questions/3162446/

Not necessarily, and I like this idea more than my earlier proposal for a single class that would check all the badges based on their ID.

Despite its name, I believe RexM is not defining the CommenterBadge itself in that file and should have named it CommenterBadgeJob. (You'll notice it has none of the traits I've defined in my answer and inherits from BadgeJob). The obvious question is "How does each badge job know which BadgeId it corresponds to?"

I would have an additional unique field in my Badge called BadgeJob by which you could lookup a badge.

enum BadgeClass {Bronze, Silver, Gold}

//This class would be inherited from the database.
public class Badge
{
    public int Key {get;set;}
    public string Name {get;set;}
    public BadgeClass Class {get;set;}
    public string BadgeJob {get;set;}
    public string Description {get;set}
}

I would modify his code as follows:

public class CommenterBadgeJob : BadgeJob
{
    public Badge commenter_badge {get;set;}
    public CommenterBadgeJob() : base() 
    {
        //Lookup badge
        string badge_job_name = this.GetType().Name;
        commenter_badge  = db.Badges.Where(n=>n.BadgeJob == badge_job_name).Single();
    }

    protected override void AwardBadges()
    {
        //select all users who have more than x comments 
        //and dont have the commenter badge
        //add badges
    }

    //run every 10 minutes
    protected override TimeSpan Interval
    {
        get { return new TimeSpan(0,10,0); }
    }
}
like image 112
JoshVarty Avatar answered Oct 10 '22 03:10

JoshVarty


How about using an enum ?

  public enum BadgeTypes
  {
      GoodAnswer    = 1,
      Commenter     = 2,
      Teacher       = 3,
      //...
  }

Each BadgeJob could have a BadgeType property which would be used to populate the badge id when inserting an achievement during AwardBadges() (enum values can be persisted to integers).

I see no necessity for having one class per achievement. BadgeJob's contain all badge attribution logic and BadgeTypes suffice to represent the different badges.

like image 32
guillaume31 Avatar answered Oct 10 '22 03:10

guillaume31