Showing posts with label Abilities. Show all posts
Showing posts with label Abilities. Show all posts

Wednesday, September 8, 2010

Static and triggered abilities

Laterna Magica already supports some core functionalities of Magic, but there's still much left. I don't talk about discard and such things; these are effects that just need to be coded, and then they will be there; just a new flavor of an existing thing. I also don't talk about very complicated effects. All cards currently supported in Laterna Magica use the original oracle wording (with the exception that "draw three cards" must be changed into "draw 3 cards"), which won't be possible for all abilities, but such abilities are still not what I'm talking about.

I'm thinking about really new features, like static and triggered abilities, the ability to target, or text changing effects.
Text changing effects are just very hard; they kind of break my concept of "one template, many instances" for abilities: The printed wording can be shared between cards, just its interpretation, the rules meaning, is card-specific.
Targets are currently not my goal; I don't really want to do much UI at the moment, and this in inevitable with giving the player yet another possibility to make a choice.

That neatly leaves behind the two thing I already stated in the title, and they have one thing in common which currently gives me a headache: Scope of influence.

The scope of an ability means in what places and at what times it works. For spells and activated abilities it's comparable to legality, but the big difference is that the user initiates those abilities; static and triggered abilities are around at all times.

There are two ways to handle the situation:
  • keep all abilities at all times and ask them if they should apply on every single occasion
  • let the abilities themselves handle it; registering and unregistering using some events
I don't know why, but my initial though was to use the second. Both have good and bad points of course: The first can result in a performance problem, the second might break in strange corner cases.
A more obvious, but still realistic example is controller changes: Imagine you get control of a Glorious Anthem, but it still pumps your opponent's creatures.
But even the first approach has similar issues, namely abilities that didn't exist at the beginning of the game, for example on tokens or from Auras. If you register all abilities at the beginning of the game, those would be ignored.

If you expected a definitive solution here, I'm sorry. I don't have one. There's no right way to do anything, just several ways with different consequences.

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...

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.