Wednesday, May 5, 2010

Implementing activated abilities: Divide and conquer

Last time, I showed the technical concept behind abilities (and spells) in Laterna. Today, I want to explain the PlayInformation interface from another perspective.

I hope I have given a good impression of why I use the approach I do: Abilities using a function to create PlayInformation objects which do the work. But why is PlayInformation so "big"? Why does it do all the work from paying costs over targeting to dispatching the effect?

The reason is a simple one: Because it's easier this way. Imagine a "complex" ability: "{X}: ~ Deals X damage to target creature." What this means is that the variable X is mentioned in both cost and effect, as most of the time, and this gives two possibilities:
  • Make Cost and Effect both interfaces and define some kind of communication, likely using another object between them, or
  • only use that object from "in between" and make the communication between cost and effect an implementation detail.
The process of playing a spell/ability is a complex one, and putting it into a single class enables more compact code to be written for individual abilities.

Now there's only the question from last time left: How can I still define cost and effect separately, giving me the ability to reuse them?

Delegation is a key concept in OO programming: If a class gets too big, try to find some separated piece of work that it does, and transfer it to a new class. Then, let the original class use the new one. An example in LaternaMagica is the CardCharacteristics class: Cards have characteristics that can modified by various effects. But instead of implementing the characteristics in the MagicObject class, every MagicObject object has a CardCharacteristics object. That does this work.

In that case, it's defined in the interface. With PlayAbility, it isn't. It's an implementation detail of AbstractPlayInformation, which implements PlayInformation. The AbstractPlayInformation class allows you to combine multiple PlayInformation objects into a single, big PlayInformation that carries out your instructions in order.
If you have a cantrip spell, just add in a DrawCardInformation at the end!
If you have a complicated activated ability, implement the effect, but use standard cost Informations and combine them with the AbstractPlayInformation.

Oh, and don't forget to add an information that removes the spell/ability from the stack...

No comments: