Tuesday, February 19, 2008

Programming Magic Cards 5

I wasn’t sure how popular topics concerning computer programming would be but the Python column I wrote seemed to generate many comments. Some of my columns, like this one, will predominately feature computer programming. I also realize that I don’t want to completely exclude my non-programmer readers, so even though you may not understand everything that I am talking about, hopefully you’ll understand most of it.

Today I’m going to talk about how to actually program a Magic card. Translating the rules of a card into a programming language is probably the hardest part of actually programming Magic. Obviously Magic cards have a large variety of specific rules and actions. This variety makes Magic fun to play but especially difficult to program. Let’s look at a very simple card, Lava Spike.

Lava Spike
R
Sorcery - Arcane
Lava Spike deals 3 damage to target player.

This card is very straightforward but it is still a good example of a “typical” card. I’m going to show you 3 different ways to program this card. The first way is currently how MTG Forge encodes Lava Spike.

if(cardName.equals("Lava Spike"))
{
final SpellAbility spell = new Spell(card)
{
public void resolve()
{
AllZone.GameAction.getPlayerLife(getTargetPlayer()).subtractLife(3);
}//resolve()
};//SpellAbility

spell.setChooseTargetAI(CardFactoryUtil.AI_targetHuman());
card.addSpellAbility(spell);

spell.setBeforePayMana(CardFactoryUtil.input_targetPlayer(spell));

MTG Forge translates the card into Java. The resulting code is reasonably easy to read, but you would certainly have to understand Java and MTG Forge in order to fully understand the code. This way of encoding a card is a little long. The planeswalkers, with their 3 abilities, each take over 200 lines of code.

I have recently become interested in the computer language Python and I have started working on the next version of MTG Forge using it. This is how Lava Spike looks in Python.

from Card import Card

class Lava_Spike(Card):
def __init__(self):
Card.__init__(self)

def create(self, factory):
self.name = "Lava Spike"
self.type = ["Sorcery"]
self.subtype = ["Arcane"]

spell = factory.getSpellOrAbility(self, "Spell")
spell.manaCost = "R"
spell.text = "Lava Spike deals 3 damage to target player."

spell.target = factory.getTarget(spell, "Player")
spell.resolveList.append(factory.getEffect(spell, "Change Life - target player", -3))

self.spellAbility.append(spell)

This code is approximately the same length but most cards will be shorter in Python than Java. Java is nice but it seems very inflexible sometimes. Most programmers know of the factory pattern, but in many languages it is hard to use without numerous interfaces. With Python the factory pattern is very easy to program and to use.

In the example above, the factory is used to get 3 different objects. One, the spell object. Two, the object that lets the user select the target player. Three, the effect the card will have when it resolves. The factory pattern is also useful since I can change what objects are returned without changing the code for Lava Spike. The factory pattern also promotes code refactoring and reuse because the factory can change its internal structure without changing its external functionality.

Finally I am going to show you the shortest version of Lava Spike.

Lava Spike
R
Sorcery - Arcane
Lava Spike deals 3 damage to target player.
spell-resolve: Change Life - target player : -3
spell-target: Player

Obviously this version is the easiest to read and almost looks like the plaintext version of the card present at the beginning of this column. From this abbreviated template I should be able to generate the previous Python code. The text “Change Life - target player” is exactly the same string given to the factory method getEffect. The text “Player” is also the same string passed to the factory method getTarget. By using the factory pattern, I can use plaintext as my programming language which is easier to read than even XML. Also notice that the above card is not encoded in Python but in a domain specific language.

If this idea works, users could actually add new cards themselves, which is phenomenal. Other Magic projects have tried to let users add new cards, like Magma, but it was still difficult. Hypothetically users could add new cards and then have their work uploaded to a central server.

But before any gets too excited this is still theoretical. I should be able to do everything that I have discussed above but there is still a lot that needs to be done before I have 50 cards working. The user interface (GUI) has yet to be written. Hopefully that shouldn’t be too hard since Python has some nice graphic game libraries (PyGame and PGU). I plan to model the GUI for MTG Forge 2.0 on Shandalar but I’m hoping to add a few improvements like fancy arrows to show card targets. I also want users to be able to upload and download decks. This should create a lot of interest and forum activity. You could also compare deck builds for sealed and draft.

In conclusion, a famous programmer once said, “Write programs to write programs when you can” and with a bit of luck I can do that. The difficult part will be to break down a card’s effect into smaller pieces. Some cards like Colfenor’s Plans are very unique and may have to be implemented separated. I am eager to try out my ideas and to report on my progress. Feel free to post any comments. And as always, thanks for reading :)

4 comments:

Nanocore said...

OK, now I am excited!

Unknown said...

I am a programmer, and I'd love to be able to read your post, but the thin column format of the site doesn't do it justice....

Yeah, I'm excited too. What would it take for you to get a basic framework together, and posted so that maybe some others can help with the GUI?

Forge said...

I might try out a different format for my blog, the narrow columns make reading any code nearly impossible.

>What would it take for you to get a basic framework together, and posted so that maybe some others can help with the GUI?

I'm not really sure. I'm working on the new GUI now, but I prefer programming cards. I'm trying to build a decent framework so that other people can contribute but currently it is just easier for me to do everything. After I get something working, I'll post it and if anyone wants to make any modifications they can.

Clamatius said...

The way you're implementing this isn't quite right, if I'm understanding what you're doing.

Lava Spike does 3 damage to a player - it doesn't make them lose 3 life (typically the preserve of black spells).

Normally doesn't make any difference but when damage prevention etc. is around, it would make it easier to delineate life loss from damage.