Monday, July 2, 2007

Programming Magic Cards 1

Many people are interested in how I program cards in MTG Forge. Obviously some of the cards are simple and some are complicated. Wrath of God is very simple while Kiki-Jiki, Mirror Breaker is very complicated. The article “MTG Forge Overview” showed you Serra Angel which is a typical creature. Today I’m going to show you how I programmed tap abilities with and without mana costs as well as how to draw a card and gain life.

The code showed below is for “Super Card.” It is an artifact that lets you tap to draw a card or tap and pay 1 to gain a life. Super Card has 3 SpellAbilities added to the card: 1. so it comes into play as a permanent, 2. gain 1 life, and 3. draw a card. As you may or not remember, the SpellAbility class is used for all spells and abilities, hence its name. The life gaining effects costs 1 mana of any color while the draw card ability only taps the card. The resolve method of SpellAbility does the main function of any card or ability. The card’s controller is the one who gains life. If your opponent steals it during the game the effect will still work correctly and you will still own the card, but your opponent would control it. If it is put into a graveyard, it will go into its owner’s graveyard.

The setDescription method of SpellAbility sets the text that is show text box of the card on screen. The setStackDescription, also a part of SpellAbility, is the text that the stack displays. These two abilities require a different argument for the setBeforePayMana function. The setBeforePayMana function optionally lets a user do some action before choosing any targets and before paying for mana, it is the first thing that is checked when a card is clicked on. The setBeforePayMana function makes the user pay for the mana cost when gaining life, and just taps the card when drawing another card. You can put anything into the setBeforePayMana method and I added “tap abilities” late in development. setBeforePayMana accepts Input objects. Input objects process all user input through the mouse and allows the user choose targets. This way of doing abilities and tap abilities is not every elegant but it works.

AllZone is a global (static) object that can be accessed from anywhere in the program and GameAction does some logical actions like drawing a card, gaining life, discarding, etc... When this card is clicked on the user is given the choice of playing either of the two abilities.

Card card = new Card();
card.setName("Super Card");
card.addSpellAbility(new Spell_Permanent(card));

final SpellAbility a = new Ability_Tap(card, "1")
public void resolve()
setStackDescription(card.getController() +" gains 1 life.");
a.setDescription("1, tap: You gain 1 life.");
a.setBeforePayMana(new Input_PayManaCost(a));

final SpellAbility b = new Ability_Tap(card, "0")
public void resolve()
setStackDescription(card.getController() +" draws a card");
b.setDescription("tap: You draw a card.");
b.setBeforePayMana(new Input_NoCost_TapAbility((Ability_Tap) b));

1 comment:

Zeigfreid said...


The way I am going handle spells in my attempt will be with a grammar! I know the magic game quite well (I used to work at a game shop and once upon a time I dreamed of being a judge), and I am pretty good at parsing it's grammar. So I am going to have two classes, one called MagicRules and one called MagicGrammar that will interact like this.

Say you get a card that reads, "Return any number of swamps you control to their owners hands. Target player discards one card for each swamp returned in this way."

my grammar will parse this into two orders, one for each "." character. It will go through looking for the words "target" or "choose" and ask you to make some decisions. It will call the method, passing in the string that comes after "target" in the card text ("player") which will make sure that what you choose is a valid target. Once you have made all choices and have valid targets, the grammar will call MagicRules.setCosts and then MagicRules.payCosts and finally it will try to play the card.

This is will do by reading each order from the earlier parsing, looking at the first word of each sentence: "return"; and then passing "any number" "swamps you control" owner's hand" as parameters to MagicRules.return(num, what, where). In the second order the game will settle on the word "discards" and pass in the target you chose earlier, "swamps you control", and the number of swamps returned with the previous method.

This is the idea, anyway: it is very complicated and will require lots of if statements and hours of work. I will, however, gain one great advantage from this implementation! Cards will be stored in their entirety as text in a .txt file that is read from. Woo!