Thursday 12 February 2015

Fixed it

I fixed my circular definitions problem with a little trick. Part of the solution comes from the way I was storing the values in the first place. I had dealt earlier with the twin problems of:
  1. Sequencing modifiers to values
  2. Not having advance knowledge of what modifiers would be applied
Generally, if you have a value, such as a number of 2001-esque monoliths you generate per second, and you have different modifiers to that value, you don't want them to apply in a random order. If you look at the code for many idle games you'll see this was handled by putting all of the possible modifiers into a function. So you'd have a function that looks like this:

function monoliths_per_second () { 
     return (1 + game.has_upgrade('Early Hominid Exploration')) 
            * (1 + game.has_upgrade('HAL'))
}

I don't like this solution because it means that every time you add a new upgrade that affects monoliths per second you have to go change that code. In particular, when I was making my dungeon delving game this was never going to be a workable solution because I didn't know what would be upgrading any given stat.

On the other hand if you simply store the monoliths_per_second as a property of some object, then add one to it upon getting 'Early Hominid Exporation' and multiply it by two when you get 'HAL' then you end up with a different value depending on what order you bought those upgrades in.

Instead, what I'd done is taken every value that I mean to be able to modify from outside and defined it using a function I've written that accepts three variables: an object to put the property on, a name for the property, and an initial value, which is optional. The function added two properties to the object that was passed to it. One was named whatever name was passed to the function, the other was named name + '_handler'. The '_handler' part had three methods of it's own, one to add an 'atom', one to remove one and one to recalculate the value - the last one being called every time one of the first two is. Adding an atom required passing two things: one was a function, the other was an integer to order that function among any other atoms.

So first I'd run H.add_value(objectname, 'monoliths_per_second', 1) and then the upgrade named 'Early Hominid Exploration' would call

objectname.monoliths_per_second_handler.add_atom (function (x) {return x+1}, 400) 

and 'HAL' would call

objectname.monoliths_per_second_handler.add_atom(function (x) {return x*2}, 700)

The objectname.monoliths_per_second property, on the other hand, is a data accessor that gets the stored result of the last computation.

The problem was that if I declared HAL and gave it an "atoms" property which contained an array of objects like {target: objectname.monoliths_per_second_handler, func: function (x) {return x*2}, order: 700} that would be applied when HAL was purchased, I needed objectname to exist at the time HAL was declared because otherwise the declaration would be trying to access a property of an undefined variable.

All I did to fix my problems was that instead of adding 'monoliths_per_second_handler' to the object with monoliths_per_second, I added it to another object that holds all the handlers. That object gets created right away so HAL will see it at declaration time - even if monoliths_per_second is undefined, that's okay. Javascript interprets objectname.monoliths_per_second as a pointer to the property, not as the literal value, so as long as its defined by runtime it will find the right thing.

I've probably put way too much work into this underlying structure, because so far there isn't really a single value that is modified by more than one upgrade. Presumably they will come if I keep working on it, though.

Addendum: That didn't work because I was wrong about how javascript decides whether to pass a reference or a value. However, all the work gave me an easy way to describe what value I wanted to increase in two strings, so now it is really fixed.

No comments:

Post a Comment