views:

53

answers:

2

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 :(

+2  A: 

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; }
});
Mark Peters
Yea this is the only solution I could come up with, but I was trying to find a way that didnt require abstraction of the base class :( if all else fails I will fall back to this.*edit* err not abstraction but yea..
FlyingStreudel
@FlyingStreudel: Why are you trying to avoid this?
Mark Peters
Doing this as much for fun as I am an excersise in programming technique. I want to see if theres a way to achieve the same functionality without having to subtype
FlyingStreudel
OK see my edit.
Mark Peters
Essentially I dont want to have to limit the types of input. So if I ever wanted to add a brand-new never before thought of achievement type, I could do it without having to add a new subtype of achievement.
FlyingStreudel
@FlyingStreudel: You probably want some rudimentary language/interpreter then.
Mark Peters
I like the last option you posted, but my issue is this. Lets say I was really lame and wanted to read in achievements from a database stored separate from the application. How would I pass in the anonymous validation methods?
FlyingStreudel
Like I am looking to create achievements at run-time
FlyingStreudel
@FlyingStreudel: Yes, I think you would want some kind of domain scripting language then or use an existing one. But just because it has to happen at runtime doesn't preclude the above ideas. Classes can be loaded at runtime, and classes can be retrieved from a database. Though you might be up against Android's security manager at that point; I don't know.
Mark Peters
Ah I meant lets say I have a database with tbl_achievement. Columns are name, desc and validation_fn. If I get a string from validation_fn that looks like 'return data.getZombieKills() >= 50;' is there any way for java to 'execute string as code' without 3rd party libraries?
FlyingStreudel
@FlyingStreudel: Not unlimitedly, but you could set one up with a scripting API: http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/. Apparently stock JRE 6 comes with Mozilla Rhino.
Mark Peters
Awesome, thanks sir
FlyingStreudel
+1  A: 

Function pointers in Java (and in general in any Object Oriented Language) are usually replaced by Polymorphic objects.

About not hardcoding the validation, I don't think it's worth to write a rule-engine for that, the effort would be much higher. What you can do is to unify the achievements of the same type as objects of the same class with a field having different thresholds values.

fortran