Friday, February 29, 2008

Wooh Hoo

(I probably didn't spell that correctly.) Tuesday, Feb 26 is the first time my blog got more 100 hits on the same day. Spread the news, Forge ;)

Programming ManaCost

Paying for a spell or ability is a fundamental part of Magic although not very flashy. No one plays Magic, just so they can tap land. The fun part is _after_ you tap your land. Anyways, I’m going to talk about how MTG Forge implements this action.

The ManaCost class has the following methods:

setManaCost(cost)
addMana(color):
toString() : returns string
isPaid() : returns boolean
isNeeded(color) : return Boolean

The basic example of how to use ManaCost is shown below.

m = ManaCost()
m.setManaCost(“1G”)
m.addMana(“G”)
m.addMana(“U”)
print m.isPaid() #prints true

Notice that ManaCost has to check if all of the colored mana costs are paid before paying the colorless cost. If this is done incorrectly, the green mana will be applied to the colorless cost and isPaid() will return false.

ManaCost is a “low level” class that is to be used by the user interface (UI) component. The UI requires a method like isNeeded() so if you click on a Forest and a Mountain is needed, it will not tap the Forest. (My explanations for some of these things sound really
stupid written out.) The UI also needs to display what mana is still needed, so it uses toString().

ManaCost is used to play Instants, Sorceries, and Abilities. The UI component will keep track of all the land that was tapped in case the user decides to cancel or doesn’t have enough mana. After the cost is paid, the UI will add the spell/ability to the stack. ManaCost does not handle 0 cost spells like Ornithopter, so the UI must handle that situation by directly adding the spell/ability to the stack. (Imagine if Magic used a queue instead of a Stack, it would be pretty weird. All the trading card games I know of like Yu-Gi-Oh and Marvel/DC VS use a stack.)

ManaCost does not handle X spells like Fireball. A 2nd class, ManaCost_X, handles X spells. ManaCost_X is very similar to ManaCost but toString(), isPaid(), and isNeeded() all work slightly different. ManaCost_X.isNeeded() always returns true, since you can always add more mana to an X spell. Spells and abilities with double X costs are not currently handled.

Hopefully this will put things more into perspective. Let’s say that playing a game of Magic is level 1. And the rules that allow the game to happen are level 2. So Magic Online (MODO) and MTG Forge have to enforce the rules so they are level 3. The rules, level 2, don’t have to think about invalid user actions, and just state that “no illegal player actions can take place.” MTG Forge and MODO have to add more “rules” to Magic in order to make everything work nicely. A tournament judge would also be level 3 because they enforce the rules. (Imagine the computer penalizing you if you for illegal game state if you didn’t draw a card, that wouldn’t be fun at all but you would learn the rules pretty quickly.)

The code below is in Python. I’m not sure if showing you code like this is interesting or not. Feel free to use it if you want to. I also have ManaCost written in Java if anyone really wants it. In MTG Forge’s source code the Java class is called ManaCost and it uses ManaPool. I have extensively tested this code because finding errors later on is really annoying and hard to correct.

#lets player pay mana costs for a card
#UI component will use this class
#handles mana costs like 2WW, 14GU, R, BBB
#does not handle X spells
#does not handle 0 cost cards like Ornithopter
class ManaCost:
def __init__(self):
self.reset()

def reset(self):
self.manaPool = {"C":0,#colorless
"W":0,
"U":0,
"B":0,
"R":0,
"G":0}
self.manaCost = ""

def setManaCost(self, manaCost):
self.reset()
self.manaCost = manaCost

#check for space, the character "C" or "X", 0 length cost, or cost that is "0"
if manaCost.find(" ") is not -1 or \
manaCost.find("C") is not -1 or \
manaCost.find("X") is not -1 or \
len(manaCost) is 0 or \
manaCost is "0":
raise RuntimeError("ManaCost : setManaCost() error, invalid mana cost - " +str(manaCost))

#convert mana cost like 2WW into mana pool
colorLess = "0"
for z in manaCost:
#is color?
if z in self.manaPool.keys():
self.manaPool[z] += 1
else:
colorLess += str(z)
#this handles double digit colorless costs like 11, or 12GG
self.manaPool["C"] += int(colorLess)

#color is string of length 1 like "1", "G", "U", or "B"
#colorless mana can ONLY be "1", NOT "2", "3", etc...
def isNeeded(self, color):
if color not in ("1", "W", "G", "U", "B", "R"):
raise RuntimeError("ManaCost : isNeeded() error, invalid color - " +color)

return 0 < self.manaPool.get(color) or 0 < self.manaPool["C"]

def isPaid(self):
for z in self.manaPool.keys():
if 0 < self.manaPool[z]:
return False

return True

def __str__(self):
if self.isPaid():
raise RuntimeError("ManaCost : str error, mana cost is paid, this method shouldn't be called")

out = ""
key = self.manaPool.keys()
#remove colorless key
key.remove("C")

for color in key:
#repeats color, makes W into WWW
out += color * self.manaPool[color]

#prefix colorless mana cost, so WW becomes 2WW
check = self.manaPool["C"]
if check is not 0:
out = str(check) + out

return out

#pays color toward the mana cost
#color is string of length 1 like "1", "G", "U", or "B"
#colorless mana can ONLY be "1", NOT "2", "3", etc...
def addMana(self, color):
#color might be an int, so convert to string
color = str(color)
if not self.isNeeded(color):
raise RuntimeError("ManaCost : addMana() error, mana not needed - " +color)

#are all colored mana requirements paid?
if(self.manaPool.get(color, 0) is 0):
self.manaPool["C"] -= 1
else:
self.manaPool[color] -= 1

Tuesday, February 26, 2008

Download

The link at the top of my blog was old. I must have forgotten to update it. If your version doesn't say "MTG Forge - 2/2008" in the title bar, download the latest version for Windows http://www.mediafire.com/?yoier22omw2

If you don't have Windows or just want a zip file, download http://www.mediafire.com/?um2cdgkqvis Windows people can double-click on "run-forge.exe" while everyone else will run "run-forge.jar" This should run on any system that can run Java like Linux, Mac, etc...

Remember, in order to keep your decks, make a backup of the file “all-decks2” and just overwrite the new file after you install.

Things you may not know about MTG Forge

1. In the Deck Editor you can sort the cards by clicking on the column, much like Magic Online. Unlike MTGO you sort multiple times. For instance if you want to sort by color and type, first click on the column name “color” then “type”. Side note, you will get a different result if you click on “type” then “color”. You can also sort by mana cost.

2. The Deck Editor can also “reverse sort” a column by clicking on the column again. For example, click on “color” once and then click on it again. (As a side effect when you save your sealed or draft deck, MTG Forge will unintentionally “sort” it.)

3. When you are booster drafting, instead of selecting the card and then pushing the button “Choose Card” you can just right-click. Right-clicking chooses the highlighted card, not the card that the mouse is over.

4. MTG Forge was written by one determined, bored individual :)

5. Macintosh users seem especially glad to be able to play MTG Forge since MTGO is PC only.

6. MTG Forge is mentioned on the Wikipedia entry on Magic. And yes, I put it there.

7. The source code is pretty much horrible now. I’ve added little things here and there. The code works but it isn’t pretty (it smells). Smelly code isn’t necessarily bad, but in this case it shows that it is time to move on. That is why I’m working on version 2.0 in Python. Me and Java had a good run, but Python offers more flexibility (and more runtime errors).

8. The computer can’t play Counterspell (but you’ve probably figured that out).

9. You can use the computer’s activated abilities. This means that you can use that Royal Assassin if you really want to. I’ve meant to fix this, but I haven’t.

10. You target the same creature multiple times with Hex (destroy 6 target creatures). So if the computer has 3 creatures in play, you can target each creature twice and destroy them. Technically Hex should only be able to target 6 different creatures.

11. If you want to copy a deck, just use “Save As”. (I know this isn’t rocket science, but maybe it will help someone.)

12. The Generate Deck option works a little different for constructed and sealed. Constructed generated decks will have single copies of cards (singleton), while sealed will have multiple copies. Sealed decks will also have a maximum of 3 rares per deck.

13. At last count, MTG Forge has been downloaded over 11,000 times. I don’t know exactly how many times it has been downloaded since I have multiple versions and no central download site.

14. You can add creatures to MTG Forge by modifying the file “cards.txt”. Cards.txt supports a number of keywords such as flying, fear, haste, vigilance, trample, and unblockable. Also supported are mana abilities like Birds of Paradise and Llanowar Elves “tap: add G”. Note: a creature can only generate 1 mana. Other abilities include cards like Daggerclaw Imp “This creature cannot block” and Cloudcrown Oak “This creature can block as though it had flying”.

Monday, February 25, 2008

Quick Update

I’m vigilantly working on MTG Forge 2.0 in Python using PyGame and PGU. I reworked how the cards are displayed. The square cards in Shandalar are adequate but not very pleasant. A reader sent me his soon-to-be-released Python version of Magic, and I liked how the cards were card-shaped. So I ripped off his idea (and a little of his code) and voila. The red number is the damage the card has received so far and the blue is the assigned combat damage. Usually the assigned combat damage is unimportant but occasionally it is needed if you want to save a creature with a bounce or giant growth effect.

There are still several key things to do. I still need to make sure the cards can’t be moved out of their zone, you can’t just put a card from your hand into play. Hook up the mouse to the user interface, I can move cards right now but nothing else. Create a stack and attack/block widget like Shandalar. The widget will have two rows, the top is the attacking cards (or card on the stack) and the bottom will show the blocking creatures (or the targets that the card has). And write another widget to show you some cards and let you select one for Demonic Tutor type effects.

Hopefully I can get this visual stuff done so I can program cards. I am not really good at drawing stuff on the screen and I have to look at the documentation 5 times before I get it. After I program 50 cards I’ll have to program a deck editor, and I am not looking forward to that. Even though the current deck editor looks simple, it took a lot of effort. I’ll probably program something like it since it is reasonably powerful and still easy to use. Then I’ll release a beta version to my adoring public :)

Tuesday, February 19, 2008

Quick Update

Well I'm slowly working on MTG Forge 2.0. Currently I can display a card, like Shandalar, and move it around with the mouse. The program reads in a regular card picture file and crops it. I have draw 6 layers just to display a card like that.
1.The card picture
2.Card name in white
3.Card name in black (for a shadow effect to make it easier to read)
4.Attack/Defense in white
5.Attack/Defense in black
6.The card frame itself

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 :)

Monday, February 11, 2008

Suggest a topic

So far I've written about 30,000 words in this blog and I'm running out of topics. Please give me suggestions about what YOU are interested in. The articles that seem to be the most popular are the ones that talk about programming. My recent column about Python generated lots of comments. Articles about just Magic don't seem to get a response (less comments). Obviously the sweet spot is articles that talk about both Magic and programming. I typically post 2 articles a week, ranging from 250 to 500 works in length. So please my loyal readers, tell me what you want to read about. :)

On a side topic I updated the MTG Forge link because Battles of Wits would just let you win the game even if you didn't have 200 cards in your library. I've created a decent Battles of Wits deck that wins approximately 1/3 of the time.

Friday, February 8, 2008

MTG Forge - New Version


Download MTG Forge (Updated link: Battle of Wits fixed) (In order to save your decks, make a backup of the file “all-decks2” and just overwrite the new file after you install.)

12 new cards, I did include a few Morningtide cards that I was able to program like Bitterblossom (At the beginning of your upkeep, you lose 1 life and put a 1/1 black Faerie Rogue creature token with flying into play) and Reach of Branches (makes a 2/5 token and returns to your hand when you play a Forest). Both of these cards seem very good. I doubt Indomitable Ancients (2WW, 2/10) and Prickly Boggart (1/1, Fear) will turn any heads, but they were easy to add.

I also added the granddaddy of “build a deck around me” cards. Can you guess it? What card literally cries out for a one-of-a-kind, custom deck? OK, I’ll tell you, Battle of Wits (At the beginning of your upkeep, if you have 200 or more cards in your library, you win the game). And it is going to be a challenge to build a winnable deck in MTG Forge since you only have a limited card pool.

Obviously you will want Demonic Tutor but MTG Forge doesn’t have any other cards to fetch Battle of Wits. Maybe I need to add Vampiric Tutor and Idyllic Tutor in the future. I probably should give a gold star to the first person who wins with Battle of Wits but I don’t want to look through a deck list that long. Too bad MTG Forge doesn’t let you import and export decks. (My wish list for MTG Forge is longer than my arm.)

I was able to fix a few bugs. Upkeep effects like Juzam Djinn do not trigger during your next game. Generated sealed decks no longer contain random gold cards. And now you can stop at End of Turn by selecting the menu option. Hopefully this feature will make a lot of blue mages happy :)

And by the way, I’m not always sure what features to add. Do “the people” want to be able to import and export decks? Or do you want some other feature (that hopefully I can program)? It seems like MTG Forge needs its own webpage and forums. I haven’t ever setup a real webpage (only really simple stuff) nor have I ever managed a forum. I’m open to any ideas that you may have.

Monday, February 4, 2008

How do I choose which cards to add?

I get asked this question occasionally via e-mail, so I thought I would give you my long winded answer :) Well first of all, I have to be able to program the card. Fireball and Mind Twist are both iconic cards, but currently I can’t implement X spells or abilities. Many card choices are eliminated like Eternal Dragon (5/5, pay 3WW: move this creature from your graveyard to your hand), because they are too complicated.

Second, the card has to be powerful or fun. No one really wants to play with Eager Cadet. He is a feisty 1/1 but who wants that when you can use Isamaru, Hound of Konda or Savannah Lions who both attack for 2. Practically no one has ever played with a real card board Ancestral Recall or Juzam Djinn and my program lets you experience that kind of rush.

MTG Forge also has all the Moxes which are insanely powerful. Mudbutton Torchrunner (1/1, deals 3 damage to creature or player when destroyed) strikes me as funny and Hex (4BB, destroy six creatures) is just awesome if you can pull it off. A few times the computer really devastated me with Hex. Relentless Rats is the ultimate fun card and now you can build that deck that you have been dreaming of since it was printed way back in Fifth Dawn.

I also try to program cards that generate lots of interest like the planeswalkers and cash rares like Thoughtseize and Damnation. I added Flametongue Kavu because the magazine Inquest (when it was around) talked so much about 187 creatures. (187 is the police term for murder, so a 187 creature kills another creature.) Flametongue is just an insane card and those are the kind of cards that catch my attention. When Kiki-Jiki, Mirror Breaker first came out there were a ton of questions about him. I love using Kiki-Jiki and I’m glad that MTG Forge has that card.

Finally, I try to pick cards that are good for sealed and draft. Limited has always interested me and I have always thought of MTG Forge as a limited simulator. Constructed (all of MTG Forge’s cards) can get old but the limited portion of the game was the most interesting part to me. Limited games seem to offer more variety and surprise than constructed since you don’t know what cards you are playing against. The only constructed games I play use the “Generate Deck” that I so enjoy.

I played Shandalar a lot when I first downloaded it but I got bored of the constructed games. Shandalar’s sealed deck option was very fun even though the sets weren’t designed for sealed (I always seemed to have more red cards that any other color.) Some cards, like walls, really annoyed me and I wanted to remove them.

MTG Forge lets you add or remove cards by editing the files common.txt, uncommon.txt or rare.txt I am very happy that MTG Forge lets you mulligan unlike Shandalar. Sometimes a mulligan can make all the difference between winning and losing.