Search code examples
javaandroideclipsealgorithmachievements

What are some methods of dynamic validation for a generic class?


So basically the title sounds way fancier than the actual question.

I am writing an application in which I would like to implement an achievement system. If I have all of my achievements created as instances of a generic class, how do I write a method to validate those achievements (i.e. determine if the user has met or exceeded goals) when the type of feat accomplished may vary, the number of conditions to be met may vary and the type of validation may vary?

For example:

Achievement - 10,000 points!
Type - point total
Value (X) - 10,000
Conditions - 1
Validation - greater than X

vs.

Achievement - Ultra-frag
Type - kills
Value (X) - 10
Type - time
Value (Y) - 10 seconds
Conditions - 2
Validation - at least X in less than Y

I am trying to avoid hardcoding a validation function for every achievement since they are mainly generic and the only difference is how they are validated.

Like the achievement class looks something like

public class Achievement 
{
    boolean locked;
    String name;
    String desc;

    public Achievement(string n, string d)
    {
        //ctor code
    }
}

I am trying to think of a way to do this without function pointers and I am failing miserably. Also, can you even use function pointers in java? Im new to the language :(


Solution

  • I think the right track would be to have a validate method for each Achievement:

    public abstract class Achievement {
        //...name, etc
        public boolean isSatisfied(Data byPlayerData);
    }
    

    But that doesn't stop you from providing a few concrete implementations and configuring them with parameters.

    public class ZombieKillingAchievement extends Achievement {
         private final int numKills;
         public ZombieKillingAchievement(String name, int numKills) {
             this.numKills = numKills;
         }
         public boolean isSatisfied(Data userData) {
             return userData.getZombieKills() >= numKills;
         }
    }
    
    //...
    registerAchievement(new ZombieKillingAchievement("Zombie Master", 100));
    registerAchievement(new ZombieKillingAchievement("Zombie Tamer", 50));
    
    //... on user data change
    for ( Achievement a : registeredAchievements ) {
        if ( a.isSatisfied() ) {
            //show congratulatory message
        }
    }
    

    EDIT Another option to prevent inheritance (and use composition) is to use is something similar to the strategy pattern.

    public class Achievement {
        //...name, etc
        private final AchievementValidator validator;
    }
    
    public interface AchievementValidator {
        public boolean isSatisfied(Data userData);
    }
    

    When used with anonymous inner classes it's pretty much using function pointers

    registerAchievement(new Achievement("Zombie Killer", new AchievementValidator() {
        public boolean isSatisfied(Data data) { return data.getZombieKills() >= 50; }
    });