Skip to main content

The Modifier Pattern

Designing my own fantasy CCG - Collectible Card Game - was something that had been on my mind for a while. I would find myself regularly going back to that idea, wondering about both game mechanics and implementation details. Personally I must say it is a complete delight for me to engage in game development. The kind of genuine joy it brings me is indescribable.

And so there I was, last December, at the airport of Madrid, patiently waiting for my late fligth to Lisbon, when suddenly this idea came back to me. Alone in the terminal, I started sketching the founding mechanics of my game as well as prototyping its basic entities. When I left Barajas I already had a very basic engine working.

Sadly I couldn't keep up with it in the following weeks. But recently I've been able to take that project back, and so the ArcanaEngine™ was born. Having its first version almost complete and being already working on a game implementation using this engine, it is my pleasure to share with my dear readers an illusively simple design pattern that alone made my engine extremely resilient and powerful.

This design pattern, which I call the Modifier Pattern, comes in handy when you have to model entities with highly mutable attributes, of which you need to have some form of traceability. Let's pick an example that will make this pattern extremely easy to understand.

Say you are modeling a Creature entity which has two attributes Attack and Defense. One immediate implementation you could come up with could be something like this:

function Creature (conf) {
    this.attack = conf.attack || 0;
    this.defense = conf.defense || 1;
}

As it is, the Creature constructor will setup the values for Attack and Defense with whichever values were passed on the conf object. Alternatively, if no values are passed, it will set itself as a 0/1 creature. So far this could work just fine. But if you have ever played this kind of game, you know that sometimes creatures get enchanted for extra attack points, they will do battle and the damage they take will be deducted on their defense. Moreover, some effects are temporary and will be removed when some triggered events happen.

With this implementation you can write new values constantly and still be able to manage all these changes, but you will lose traceability on which effects are applied at a certain moment in time.

This is the time when the Modifier Pattern comes to your help. A Modifier will be a simple entity holding information about the issuer of the modifier, its target entity and the effect to be applied.

function Modifier (conf) {
    this.type = conf.type || 'timeless';
    this.action = conf.action || {};
    this.source = conf.source;
    this.target = conf.target;
}

For Arcana I am also interested in knowing the nature of the modifier, and so I also have a type attribute. But what is important to retain is that a modifier will be like a little card saying, Entity A applied effect X on B.

Now, this calls for a slight modification on how our Creature entity is modeled.

function Creature () {
    this.modifiers = [];
}

"Wait, what?.."

As you can see, we no longer need the Attack and Defense attributes. They will be represented in the form of modifiers. We just need to make sure modifiers with the initial values are added upon initialization.

var attack = new Modifier({
    type: 'timeless',
    action: {'attack': initialAttack},
    source: board,
    target: creature
});
board.registerModifier(attack);
var defense = new Modifier({
    type: 'timeless',
    action: {'defense': initialDefense},
    source: board,
    target: creature
});
board.registerModifier(defense);

I guess you are wondering what the board might be. The Board is the central entity through which all state transitions and actions are taken. It is also responsible to dispatch modifiers around game entities. If you have been meddling around with Redux you are familiar with these strict state policies.

What we are missing now is just a couple of accessors (getters in Java jargon) that will let us read the state of those two attributes.

Creature.prototype.getAttack = function () {
    var attack = 0;
    for (var m = 0; m < this.modifiers.length; m++) {
        var mod =  this.modifiers[m];
        if (mod.action.attack) {
            attack += mod.action.attack;
        }
    }
    return attack;
};

Creature.prototype.getDefense = function () {
    var defense = 0;
    for (var m = 0; m < this.modifiers.length; m++) {
        var mod =  this.modifiers[m];
        if (mod.action.defense) {
            defense += mod.action.defense;
        }
    }
    return defense;
};

Conclusion

In this case, I am using modifiers holding numerical data that I chose to resolve cumulatively. For other attributes, stored in the form of booleans or strings, it is more adequate to resolve them in the form of a stack - assuming the value of the last attached modifier. Numerical attributes can also be resolved in this fashion.

This technique is very simple and yet quite powerful. The traceability it provides is of extreme value for a game like Arcana, but it will prove worthwhile for any situation where you need to model entities with mutable state that you need to backtrack.

Good night and good coding.

Comments

Comments powered by Disqus