Monday, November 15, 2010

How to NOT Write a Magic Clone


Let's say that you wanted to write your own implementation of Magic (for some unknown reason). Naturally you would create a Card class which mimics a real, cardboard card. And for some crazy reason you wanted to add your favorite card from Planeshift Singe (R, Instant, "Deals 1 damage to target creature"). Obviously you are not a "power gamer" but that is ok, because you like who you are....maybe.

The simplest way to implement effects like Singe is to allow a Card object to deal damage to another Card object. The code for Singe would look something like this:
Singe.resolve()
{
  getTargetCard().addDamage(1)
}
So far so good, one card down, a bazillion to go. Now you want to code a card that says, "Whenever you deal damage, you draw 1 card". Suddenly your world comes crashing down because your simple implementation doesn't work. Card objects should not deal damage to each other, so you use a layer of indirection. Singe could be written as:
Singe.resolve()
{
  RulesEngine.addDamage(getTargetCard(), 1)
}
Now you want to add another card, Awe Strike (W, Instant, "The next time target creature would deal damage this turn, prevent that damage. You gain life equal to the damage prevented this way").

The question is this, "Can we add Awe Strike using the method above?" Truthfully the answer is maybe. If every card uses the rule engine hopefully you should be able to code Awe Strike. Unfortunately the devil is always in the details.

The goal when programming a Magic clone is to give as much information as possible to the rules engine so it can do the insane things that Magic cards do like Painter's Servant

If the rules engine isn't given enough information eventually your Magic clone will hit the wall and not be able to implement a card or keyword.

Keep on tapping,
mtgrares

p.s.
The craziest card I know of is Warp World (5RRR, sorcery) which dumps all of your permanents into your graveyard and then you draw that many cards and put those permanents onto the battlefield. The Magic 2010 set faq lists 6 separate sub-steps.


--“All problems in computer science can be solved by another level of indirection.”

--You can read more about my idea of making the rules engine object huge because it has to handle everything in the whole game.  (Most of the RulesEngine object would just act as a "wrapper" for other objects/methods.)  Big objects are usually to be avoided because they have too much complexity but I don't see another way to handle a game like Magic.  You can read a previous post about this subject here.

3 comments:

lorg said...

It seems to me that the critical thing about writing magic is to make card actions "data" instead of "code".
To do that you'll have to write a small DSL for card effects, and then the complexity of cards would be limited by the complexity of the DSL and its environment.
Also, my recommendation would be to have a hard separation between this DSL and the actual language you program the game in, to avoid security issues.

my %d cents :)

Forge said...

Making cards into code tends to be error prone because you will want to change the code sometime. I think that cards should be in a totally separate format, like plain text or XML.

I wrote about plain text cards here. You have to ignore my comments about Forge version 2 because I keep starting over again.

Shock
R
Instant

Spell
Text: Shock deals 2 damage to target creature or player.
Target: creature or player
Resolve: damage target creature or player, 2

Forge said...

It seems to me that the critical thing about writing magic is to make card actions "data" instead of "code".

Yes, code changes but "data" doesn't. (Well card data doesn't change, except if there is an oracle update.)