Saturday, May 1, 2010

Implementing activated abilities: Getting the effect to the game

There are two paragraphs in my previous post that I want to further examine:
  • ActivatedAbility has a method getPlayInformation() which takes an ActivateAction and returns a PlayInformation.
  • The PlayInformation is the real worker of abilities and implements modes, targeting, costs, effects etc. It depends on the game, so an activated ability can only create the PlayInformation if a game is provided.
First, let me show you the ActivatedAbility and PlayInformation interfaces, so you can see what I'm talking about:

public interface ActivatedAbility extends NonStaticAbility {
    public boolean isLegal(ActivateAction a);
  
    public PlayInformation getPlayInformation(ActivateAction a);
   
    //inherited from NonStaticAbility
    //public boolean isManaAbility();
}

public interface PlayInformation extends GameContent {
    /**
     * Returns the action that is used in playing.
     */
    public PlayAction getAction();
  
    /**
     * Returns the object that was played using this play information.
     */
    public MagicObject getObject();
  
    public void makeChoices();
  
    public void chooseTargets();
  
    public void distributeEffect();
  
    /**
     * This action is only defined for activated abilities and spells, but not for triggered abilities.
     */
    public GameAction getCost();
  
    public GameAction getEffect();
   
    //inherited from GameContent
    //public Game getGame();
}


As you can see, the Activated ability interface is pretty small; its job is primarily to create PlayInformation objects. PlayInformation does the whole work.

This means especially two things:
  • Although it would seem intuitive, to make a new activated ability, you don't implement ActivatedAbility but PlayInformation. This may be confusing for the first time, but you can get used to it. But:
  • You still have to implement the getPlayInformation() method to return your new PlayInformation. And this is what really bugged me. An ActivatedAbility class would usually exactly follow a template, and only replace the PlayInformation.
So I thought about what I could do to save me from creating all these redundant classes. The first step was to extract the job "create a PlayInformation for an ActivateAction" out of the ActivatedAbility class. As luck would have it, the Google Collect API, which I was already using, has the right interface for that: Function (simplified):

public interface Function<F, T> {
    T apply(F from);
}


Which led me to the ActivatedAbilityImpl class:

public class ActivatedAbilityImpl extends NonStaticAbilityImpl implements ActivatedAbility {
    private static final long                                           serialVersionUID = -8987886570576715112L;
   
    private Function<? super ActivateAction, ? extends PlayInformation> object;
    private Predicate<? super ActivateAction>                           legal;
   
    public ActivatedAbilityImpl(boolean manaAbility, Function<? super ActivateAction, ? extends PlayInformation> object, Predicate<? super ActivateAction> legal, String text) {
        super(manaAbility, text);
        this.object = object;
        this.legal = legal;
    }
   
    /**
     * Returns if casting the spell using the specified CastAction is legal.
     */
    @Override
    public boolean isLegal(ActivateAction a) {
        return legal.apply(a);
    }
   
   
    @Override
    public PlayInformation getPlayInformation(ActivateAction a) {
        return object.apply(a);
    }
}


(Predicate is also from Google Collect and represents a true/false decision about an object)

But that step doesn't really solve the problem. Now I don't have to implement ActivatedAbility, but instead Function. The real solution only came when diving deep into Java's pool of features: Reflection. Reflection means to analyze a program's parts, like, classes, methods, constructors and so on while it's running. So my idea was to make a Function which creates a new object of any class you want:

class B {
    public B(A a) {}
}

Function<A, B> f = new MyFunction<A, B>(A.class, B.class);
B b = f.apply(new A());


...and creating a new class has transformed into creating an object! However, there is still a problem: what if your PlayInformation needs configuration? Your PlayInformation might Implement the ability "<mana cost>: Tap target permanent." How do you set the mana cost? The ActivateAction, which is the function's input, can't provide it. What you need is a per-function configuration in addition to the per-apply parameter:

class B {
    public B(C c, A a) {}
}

C config = new C();
Function<A, B> f = new MyFunction<A, B, C>(C.class, config, A.class, B.class);
B b = f.apply(new A());
//b is equal to
//new B(config, new A());


And that's exactly how I ended up, and I'm quite satisfied with my work. You can look at the source code at FactoryFunction.java.

And the last note for today: PlayInformation has many things to do, among them providing the ability's (or spell's, PlayInformation is used for both) cost and effect. By Implementing both in the same class, you take yourself the opportunity to reuse costs in other abilities. Next time, I'll show you how to mix-and-match these.

No comments: