1
\$\begingroup\$

I understand that for more complex achievements, specific code can't be avoided, but for simple ones like (player clicked 100 times) or (player earned 30 bajillion cookies) or (player played for 1 hour) it should be as simple as abstracting the achievements to something like a list of properties/values and having some event handler.

My problem is that since I'm using Javascript and there's no really clean way to do pointers, how am I supposed to create an achievement class that can be iterated through procedurally?

Ideally I'd like something like this:

Achievement class:

function Achievement(name, property, value, text, points){
  this.name = name;
  this.property = property;
  this.value = value;
  this.text = text;
}

And then I'd add an achievement like so:

achievements.push(new Achievement("Clicktastic", &numClicks, 1000, "You clicked 1000 times!");

And have some kind of function bound to a Window.setInterval that was constantly looping through all achievements and for these simple types doing something like:

for (var i = 0; i < achievements.length; i++){
  if (*achievements[i].property >= achievements[i].value){
    //deal with earning achievement
  }
}

I'm just using some C style pointer/dereferencing notation here.

Basic idea is that I'm not sure how to create a generic achievement that can arbitrarily care about specific properties without pointers.

\$\endgroup\$
  • 1
    \$\begingroup\$ Possible duplicate of Best way to implement achievement system? \$\endgroup\$ – DMGregory Mar 25 '17 at 0:10
  • \$\begingroup\$ I read that post thoroughly before posting this. Unfortunately I never saw a way past this pointer issue there \$\endgroup\$ – David Bandel Mar 25 '17 at 0:27
  • \$\begingroup\$ basically it's all well and good to store hashes of the property names. but I don't see a way to programatically map that back to the variable that property refers to \$\endgroup\$ – David Bandel Mar 25 '17 at 0:30
  • \$\begingroup\$ And I just don't like the way that solution has an id for each property. If you changed the order of those properties anywhere, you'd have to reflect that in a lot of code elsewhere. Unfortunately, not my kind of solution, at least aesthetically \$\endgroup\$ – David Bandel Mar 25 '17 at 0:51
0
\$\begingroup\$

Javascript supports "pointers" in form of object references. All variables which contain objects actually contain pointers to objects. Further, you can access fields of objects using the object[fieldname] syntax. Assuming you have an object clickManager and want to monitor its variable numClicks, this would look like this:

achievements.push(new Achievement("Clicktastic", clickManager, "numClicks", 1000, "You clicked 1000 times!");

The code to check the variable monitored by an achievement would then look like this:

var a = achievents[i];
if (a.monitoredObject[a.monitoredVariableName] > a.value) {

If you want to access a global variable, you can access it over the global object "window".

But as you said, this isn't really a very beautiful solution. First, it's a bit cumbersome. Second, it's not very flexible, because all it can do is monitor numerical variables. What if you have an achievement with a more complex condition? And third, it ignores one of JavaScript's most powerful features: Closures!

Javascript allows you to define inline-functions which can access variables in the current scope. These inline-functions can be passed around as variables and called from somewhere else (just like function-pointers in C). To make use of this, your achievement class would look like this:

function Achievement(name, text, checkFunction){
  this.name = name;
  this.text = text;
  this.checkFunction = checkFunction;
}

An example of creating an achievement:

function initClickHandling() {

     var numClicks = 0;

     achievements.push(new Achievement(
           "Clicktastic", 
           "You clicked 1000 times!", 
           function() {
               return numClicks > 1000;
           })
     );


     window.onclick = function() {
          numClicks++;
     }
}

This unnamed function "captures" the local variable numClicks. When that function is executed, it will use the then-current value of numClicks. This even works when the function-call which declared that variable has already returned.

Also notice the second function, the click handler for window. It captures the same variable and manipulates it. When it does that, the check function will notice.

Your achievement management code would then look like this (using Array.prototype.forEach instead of a for-loop. Another example of using ad-hoc unnamed functions):

 achievements.forEach(function(achievement) {
      if (achievement.checkFunction()) {
            // award the achievement
      }
 });
\$\endgroup\$
  • \$\begingroup\$ This is very nice. I like it. It's a little different from the way I wanted to do it but it solves my problem so I will implement it your way \$\endgroup\$ – David Bandel Mar 25 '17 at 0:55
  • \$\begingroup\$ @DavidBandel Closures are really the soul of JavaScript. The language sucks a whole lot less when you understood and mastered this feature. \$\endgroup\$ – Philipp Mar 25 '17 at 0:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.