Wednesday, September 3, 2008

Programming Cards is Hard

The main problem with trying to make a computerized representation of Magic is this: how do you program the cards? How do you code Wrath of God or Giant Growth? As I see it, you have three options.

1. Program each card directly into the programming language.
2. Use text or XML.
3. Combine options one and two. First you use option two and then you generate the source code from the text or XML.

Let me explain each option in more detail. I use option one in MTG Forge. I write out the functionality of each Magic card directly in Java. This option is the most inflexible because you can’t easily change cards.

Option two is the most flexible and I use it in the cards.txt file. I can easily add creatures that only need a few keywords. I’ll show you a sample of cards.txt in case you have never seen it.

Serra Angel
3 W W
Creature Angel
no text
4/4
Flying
Vigilance

Option three is complicated. In essence you are adding another step every time you compile. First the source code is generated and then you compile everything. I include it here strictly as a theoretical option.

Now for the summary. I’m going to get technical so it is ok to skip this part. Option 1 creates cards at compile time while option 2 creates cards at runtime. As a programmer we all know that runtime is generally good. The trick is how do you condense a Magic card into reusable parts that can be written as text?

p.s.
Everywhere I say text I mean plaintext but I didn’t want to confuse people. Does the average person know what plaintext means?

p.p.s. The Firemox project uses XML for all of its cards.

p.p.p.s.
The other main problem when trying to program Magic is how to layout the screen. Even the folks at Wizards are having a tough time with that.

Here is an example of option one, a card hard coded in Java. This is the actual code for Stone Rain (2R, sorcery, destroy target land) taken from MTG Forge. I stripped out the AI routines, but they are pretty good. If you have 2 Forest and 1 Mountain in play, the AI will target the Mountain.
if(cardName.equals("Stone Rain") )
{
SpellAbility spell = new Spell(card)
{
public void resolve()
{
//is target card in play?
if(AllZone.GameAction.isCardInPlay(getTargetCard()))
AllZone.GameAction.destroy(getTargetCard());
}

};//end - SpellAbility

spell.setBeforePayMana(CardFactoryUtil.input_targetType(spell, "Land"));

card.addSpellAbility(spell);
return card;
}//end Stone Rain

23 comments:

Unknown said...

You can embed DSL into your application and describe cards using it. You can also use some scripting language (e.g. JavaScript, which AFAIK comes with Java starting from Java SE 6).

There is also no need to precompile cards' code, you can compile it in runtime. Overhead will be insignificant.

Gando the Wandering Fool said...

There is also Lua Script which can be quite good for that sort of thing. Javascript works great though, if you are aware of it's pitfalls.

As I see it Forge, approach the cards with as much modularity as you can so your program is easier to code and so that you can get a better sense of the overall without being overwhelmed by it's enormity.

What I imagined for the AI is not that each card code would have AI code embedded in it which sounds awefully complicated and messy but that your program would work in simple steps so to speak.

First it would load a "deck".

Then it would put the cards in each deck together using the Card Rules Engine and the data for the card.

Then the AI engine would be hooked into the deck.

Unknown said...

There is also Lua Script which can be quite good for that sort of thing.

It is call Lua, not Lua Script. Greatest scripting language ever, I adore it's simplicity and power.

Jesse said...

Can someone post the benefits of MTG forge using DSL or a scripting language over programming the cards in java.

With the vastness of MTG cards I don't know if DSL would be feasible. Am I wrong on this?

Would a scripting language bring any positives?

Gando the Wandering Fool said...

It is call Lua, not Lua Script. Greatest scripting language ever, I adore it's simplicity and power.
ignorance is its own reward, n'est pa? Thanks for the spelling/typo pullup. (forgot to say 'the Lua scripting language' and capitalized the S in script.)

Not sure if Id characterize Lua as the greatest ever but it is definitely has it's uses. The most obvious being portability.

Gando the Wandering Fool said...

Jesse there are a number of advantages to softcoding data (like card rules/info) with script rather than hard coding it.

Flexibility. If you decide to change what cards are used you simply edit the script...no need to recompile the objects or fish for the correct code.

Less Conflicts. Which a program like MTGForge the number of lines of code can get out of hand and it becomes hard to maintain and track the code properly. Scripting the data means less headaches.

User Availability. This one is a big plus with me. Being able to download a program and immediately modify the data portion to suit my needs is awesome. It becomes harder to make this managable and easy for the user to do with hard coded data.

Potentially will speed up the program. Assuming the scripts run at the start up, after they are loaded there may be some improvement in efficiency in data manipulation and usage.

I'm certain this list is not complete nor are my explainations neccessarily thorough or succinct so I hope you guys will catch whatever I missed.

Anonymous said...

Jesse,

Gando covers most of the reasons for using a scripting language.

Incantus uses python for scripting the card code (well, all of the Incantus is written in python). It makes it easy to extract out general properties of abilities and provide a domain specific language for magic, so that it becomes easy to code cards.

Forge said...

Tune in on Monday and Wednesday for more dramatic revelations.

Unknown said...

What, you're gay or something? (grinning, ducking and running for cover....)

Anonymous said...

I will only accept dramatic revelations if they lead to a happy end!

alivingspirit said...

Gando says it best. Convert everything to script. It is harder but in the end all the code you make is reuseable. If you think about it, all abilltys and spells do the same thing in different mixes.
When:
At the beginning of your upkeep
tap
end of turn
Target:
choose a taget
opponent chooses targets
Effect:
take control of..
do damage to...
draw card

Got to run. I planned for this to be a longer post.

Jesse said...

I dont think all cards do the same thing in different mixes. It would be nice if someone would make sort of a venn diagram of cards and see how much they overlap. You could start with a small group of cards and match this diagram up with some possible DSLs and see where we are from there.

Add more difficult cards and see if the DSL can be adjusted to take into account the new diagram.

Unknown said...

certainly not ALL cards do the same things in different combos, but from my research, a whole LOT of them do. Enough of them, that the abilities and functions warrant a "keyword" implemented in Forge. Like "Pump [cost]:[pboost]/[tboost]" is a rudimentary script command. When combined with another keyword line like "Regenerate: [cost]" then you have a complete card programmed in a few lines - the implementation code still benefits from compile-time speed, but the extensibility of run-time interpretation.

There are so many basic actions that the cards do, that with a mix-and-match list of at least a dozen basic actions, I'm confident we could realize the vast majority of cards without resorting to a full-on scripting language. Most of these actions Forge already can do, it's simply a matter of adapting the code to be generalized.

The real trick is that the actions themselves are fairly universal.... What varies most often is the "cost" of an ability. Mana costs and tap costs are simple to deal with already in Forge. But many cards use alternative costs, like sacking a creature... which is difficult to express in a couple characters like a mana cost - because it could be any creature, or a certain type of creature... or well, all the various variations.

So we start with just simple costs, and simple universal actions. And then combine them together.

Gando the Wandering Fool said...

So we start with just simple costs, and simple universal actions. And then combine them together.

You said a mouthful there. :D

Anonymous said...

This approach - implementing basic actions and doing mix+match - is basically how MagMa works, isn't it?

Unknown said...

Yeah, it's also how FireMox works, I'm sure, except somehow Firemox uses the XML for the entire rules engine too....

But mixing and matching plain text keywords is a lot more intuitive than MagMa's characters, and probably more intuitive than FireMox's XML, though an XML format would be better to encapsulate the whole card definition. Code-wise it's easier (IMHO) to parse the plain text than using a generic XML parser, which has to sequentially enumerate through every element. Cards.txt would be bigger in size with all the angle brackets, slashes and equal signs....

Gando the Wandering Fool said...

omd.sourceforge.net

for a (very old) perspective on xml in magic. Honestly I think if we were not so distracted by life etc this project would have gone further but as it is Dhibb's Deck Mentor which came out of the project was a great start.

Forge said...

I plan to encode in text like the following.

Royal Assassin
1 B B
Creature Human Assassin
1/1

Activated Ability
Text: tap: Destroy target tapped creature.
Cost: tap
Target: tapped creature
Resolve: destroy target creature

Gando the Wandering Fool said...

Wouldnt it be better to encode the card like this:

Name: Royal Assassin
Cost: 1BB
Type: Creature
Subtype: Human Assassin
Power: 1
Toughness: 1
Rules[Rules.length]* = TapToDestroyTarget
Rules[0].Cost = TAP
Rules[0].Target = ANY
Rules[0].TargetNum = 1
Rules[0].Condition = IFTAPPED
and have TapToDestroyTarget
have all the information for that ability:

TapToDestroyTarget = function(){
local int Cost = NONE
local int Target = NONE
local int TargetNum = 0
local int Condition = NONE
if statement = true {do something here to destroy said card.}
}
?

I just think hard coding it like the format you used above is asking for the same troubles you have already to repeat themselves.

(*self incremented element)

Unknown said...

gando, at that rate, why don't we just write out the actual java code for each card? Or, duh - use JavaScript itself.

And if each card is explicitly written like that, then that's a lot of repetitive text. Only benefit is it compresses well, so we don't have to worry about downloading all that extraneous information.

The format forge is proposing is one step up from the simple keywords we've worked on like Pump, and many steps down from explicitly coding each and every card, whether it's explicitly defined in the source code, or explicitly defined in run-time interpreted script (including real scripting languages or purpose-built).

Gando the Wandering Fool said...

Im sorry did I give the impression I want someone to explicitly code every single card? If so I miscommunicated badly. I want the cards to be as modular as possible. Because my experience with this type of coding is limited (no java...havent coded in C++ in over a decade) I may have mistated my intention but I think Ive been fairly clear at least verbally speaking. No I DO NOT want each card explicitly coded. I want each card that comes along to be templated. So that you stick all the necessary parts on it like an assembly line and off it goes to the printers so to speak. The data should be entirely separated from the code except where absolutely necessary. Thats my 2.5 cents. If it offends or seems illogical...Sorry.

Minh said...

How about a card descriptions parser that would generate Java code ?

The boost in productivity would be massive:
* All the creatures without special ability (eg. Grizzly Bear) would be available instantly without any effort.
* The common game mechanics such as Haste or Viligance would have to coded only once instead of once for every cards having the mechanics.

And for all the non generic ability, simply create a template that would then require custom code.

Anonymous said...

...please where can I buy a unicorn?