I try to discuss both Magic and computer programming in my blog but sometimes I need to address just the boring, technical side of programming. I’ll try to make it as “user friendly” as possible but feel free to skip this if it gets too technical for you.
Cardfactory.java is one of the key objects in MTG Forge, it makes cards. CardFactory is a huge class weighing in at 423 kb and 14,000 lines (with many blank lines for readability). It is the largest class in MTG Forge and is 10 times bigger than the user interface Gui_Display2.
CardFactory’s intention is just to make cards. The longest method in CardFactory is getCard(String cardName, String owner) which returns a new Card object. This one method is 14,000 lines long, insane I know, but I have never had any trouble with it. Each card is subdivided with brackets, limiting the scope of all variables, much like a method of a class. Look at the code at the end of this article for an example of Wrath of God.
I needed some way of returning a variety of different card objects. I could have cloned objects or used another mechanism. In the new version of MTG Forge, each object is in its own separate class. So the file Wrath_of_God.java holds all of the code for Wrath of God.
I have tried to use many good software practices and design patterns. MTG Forge’s user interface just observes. SpellAbility’s resolve method is abstract, much like the command pattern. All of the mouse input uses the state pattern. The state pattern is crucial and without it I wouldn’t know how to handle the variety of user input that Magic requires. MTG Forge would not exist without the state pattern.
The two most common classes are Card and SpellAbility. The Card class is used like a physical card and exists in your hand, play, graveyard, or library. SpellAbility handles the effect that spells and abilities do when they resolve. SpellAbility has an abstract resolve method where the functionality of a card is programmed. For example the resolve method for Wrath of God would destroy everything. A Card object can hold one or more SpellAbility objects.
A Card object representing Elvish Piper which has an activated ability, will have two SpellAbilities. One will be the normal summon creature spell that puts it into play and the other SpellAbility will represent the activated ability. A Card object representing Wrath of God will only hold one SpellAbility object. Combining the classes for both spells and abilities into one class was a pivotal moment during the development of MTG Forge.
For more information about programming cards for MTG Forge, download the file 10-18-mtgforge-source.zip from
sourceforge.net/projects/mtgforge and read compile.htm. To see how Magic cards can be encoded as XML files see
sourceforge.net/projects/firemoxGod or Damnation
//code snippet from CardFactory.java.getCard(String cardName, String owner)
//mana cost and card text is read from the file “cards.txt”
//a Card object name “card” is created earlier in the method
if(cardName.equals("Wrath of God") cardName.equals("Damnation"))
{
//the Spell class extends SpellAbility and ensures that sorceries
//and instants can only be played at the appropriate times
SpellAbility spell = new Spell(card)
{
public void resolve()
{
CardList all = new CardList();
//AllZone is a global, static class that holds all of the zones
//for each player: play, graveyard, hand, library
all.addAll(AllZone.Human_Play.getCards());
all.addAll(AllZone.Computer_Play.getCards());
for(int i = 0; i < all.size(); i++)
{
Card c = all.get(i);
if(c.isCreature())
AllZone.GameAction.destroyNoRegeneration(c);
//GameAction is a global, static class that does common game activities
//like drawing a card, shuffling library, destroying a creature, etc...
}
}//resolve()
//the computer will only play this card if canPlayAI() returns true
public boolean canPlayAI()
{
CardList human = new CardList(AllZone.Human_Play.getCards());
CardList computer = new CardList(AllZone.Computer_Play.getCards());
human = human.getType("Creature");
computer = computer.getType("Creature");
//the computer will at least destroy 2 more human creatures
return computer.size() < human.size()-1 AllZone.Computer_Life.getLife() < 7;
}//canPlayAI()
};//SpellAbility
card.addSpellAbility(spell);
return card;
}//if Wrath of God or Damnation