Thursday, February 12, 2009

Mana Flare and cards that change the rules

Cards like Mana Flare and other cards that change the “core rules” of Magic are some of the hardest cards to program. I’m going to briefly talk about how to program these cards.

Let’s say that there is a method called “getMana(Card c) : String” that takes a card as an argument and returns a string representing the mana which that card generates. If getMana() was passed a Forest, “G” would be returned. (For this example we are presuming that a card can only generate one type of mana like basic lands not Birds of Paradise) The method getMana() is relatively simple so far.

One of the ways to program Mana Flare is to have getMana() check to see is Mana Flare on the board. While this approach isn’t elegant, it gets the job done.
String getMana(Card c)
{
String s = c.getMana()

if Mana Flare is in play
s = s + s

return s
}
This approach is cumbersome if there are many cards that modify getMana(), because getMana() could become very complicated. Even though I use this approach in MTG Forge I dislike it because it means that the code for Mana Flare isn’t in only one place, it would be best if all the code relating to a card is together.

The second way I know of implementing Mana Flare is by “stacking methods” or adding “modifiers”. Let me show a brief example.

abstract class ChangeGetMana
{
getMana(String mana) : String
}

class Mana
{
addModifier(ChangeGetMana c)
getMana(Card c) : String
}
I’ve created a “blank” class called ChangeGetMana which has one method, this class can be “added” to Mana and will be called in the getMana() method.

String getMana(Card c)
{
String s = c.getMana()

loop through all ChangeGetMana that were added
s = changeGetMana(s);

return s
}
In essence you are adding code that will be executed in the getMana() method. This approach is good because it automatically handles multiple Mana Flares in play.

Some languages like Python let you actually change another object’s method, so object A can change object’s B method. Python also lets you “stack” methods easier than languages like Java. Python is more flexible than languages based on C, like Java, because all types are checked at runtime versus compile time.

I’m sure there are better ways to implement Mana Flare but these are the only two that I know of. Feel free to share your ideas.

7 comments:

rising fruition said...

Ideally, you could set up a trigger that happens when a land is tapped for mana, and execute the ability "make another mana of that type". Sounds easy, unless you're a programmer, in which case you cringe and shudder. Well, I do at least.

I'm currently implementing a Universal Design Pattern, a la Steve Yegge: http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html. Also called Prototype Pattern, among other things. I'm hoping that it will help me solve some of the complexities of programming Magic.

Forge said...

Thanks for the info, I'll check it out. There is an old programmer saying, "Anything can be solved by adding another layer of indirection" and most of the time that is true. Also thank goodness for software patterns, I haven't gotten a chance to use them all, but the ones I use (Command, State, Observer, Singleton, Factory) are invaluable.

And in case someone doesn't know about software patterns, checkout the handy-dandy wikipedia

Forge said...

As a side note, I love software patterns but I find it hard to fully understand them. When do I apply it? How to I use it? What does the code look like in Java?

I still consider myself an amateur programmer and I keep reviewing the "book of four" as well as other patterns that I see on the web.

MageKing17 said...

What you say about Python is absolutely true. Incantus uses stacked functions for all of its zany rules-modifying effects (even basic ones, like "This creature can't block" are handled with stacked functions). This allows the core rules code to be pretty simple, while the cards can modify just about any part of the rules they want (I say just about any part because, as I discovered when I wanted to do Brothers Yamazaki and Mirror Gallery, small functions are easier to replace/override than big functions).

Our two basic "stacked function" functions are replace() and override(). Replacements are, obviously, for replacement effects, such as damage prevention and cards like Boon Reflection. Overrides are actually a lot more simple (replacement effects are hard), and not even explicitly named in the Magic comprehensive rules. Whenever you see an effect like "This creature can't block," or "Players can't gain life," or "You may play land cards from your graveyard," these are all implemented (in Incantus) with overrides. Overrides act similar to replacements (they replace an existing function with their own code), but instead of having the player affected by them choose from a list, they all apply all of the time. In order to prevent us from going insane, they have a variety of combiners they can use... logical_and, which takes the result of each function and ANDs them together to see what the function actually returns; logical_or, which I suppose you can guess for yourself; do_all, which executes each function and ignores what they want to return; and most_recent, which is for when the last effect played decides what occurs (I can't think of an example of most_recent, actually. I don't think I've ever used it). There is a fifth type of override, called a global_override, which is sort of like most_recent, except it takes priority over the four "normal" overrides... this is usually for a game-wide change instead of an object-specific change (I.E. "Players can't play lands", or "Players can't gain life" as opposed to "This creature can't block").

Now, this is how you use stacked functions... how they actually work is well beyond my level of programming skill. Incantus coded them well before I started working on the project, and they're one of the most important and innovative aspects of the system. I occasionally look at the code, but usually only when I want to feel awed and/or inadequate, because I really can't wrap my brain around exactly how they work (I could probably do it... with a handful of graph paper to make charts on).

Incantus said...

rising fruition - when I read that article, it reminded me a lot of what I implemented in Incantus. So it does seem to help manage the complexity.

Silly Freak said...

note: mana flare does not exactly what you just showed: you doubled the mana, but mana flare just adds one mana the land could produce. say you have an Azorius Chancery (T: Add WU to your mana pool.). you could either get WWU or WUU, not WWUU.

also, mana flare does not change the ammount the land produced. it adds extra mana to your mana pool. it's a triggered, not a static, rules-changing ability.
this is important when many of these effects happen.

say there are several effects:
"Whenever a land is tapped for mana, it produces an additional G."
"Whenever a land is tapped for mana, add R to your mana pool."
"Whenever a land would produce mana, it produces twice as much of each type of mana instead."

okay, the effects are pretty complicated, but what I want to say: When you tap a plains, you should get WWRGG, WWRRGG or WWRG. in the case of mana flare, you should try a triggered ability if possible.

MageKing17 said...

It's actually a triggered mana ability, meaning it doesn't use the stack. Incantus (the program) can't yet do this, but that's mainly just because we haven't implemented it yet.