Thursday, May 29, 2008

Programming Cards With Events

The hardest Magic cards are the ones they say, “Do X when Z happens.” I was thinking that each card could register an event with the rules engine. Gruul War Plow says, “Creatures you control have trample.” The event code could look something like this.

engine.addEvent(CreatureComesIntoPlay)
{
Add trample to that creature
}

Events could also implement replacement effects. Leyline of the Void says “If a card would be put into an opponent's graveyard, remove it from the game instead.”

engine.addEvent(ReplaceOpponentGraveyard)
{
Remove card from the game
}

Events are limited type of observers. The event code only wants to know if a specific action took place. While this idea is primitive, it seems like a good way to split up the division between the card code and the rules engine.

The event code would be part of the overall code for that card. It is a good idea to keep the code for each card as separate and self-contained as possible in order to avoid hard-to-find errors and long “if” statements. Many details still have to be hammered out like what arguments does each event need?

9 comments:

Anonymous said...

The problem is that you will need multiple events. In this simple case of Grull War Plow, you'll need five of them, at least:

1. Give trample to every creature that comes into play.
2. Give trample to any permanent you control that changes type so it's now a creature (and don't forget Grull War Plow is self-animating!)
3. Give trample to any creature you gain control of.
4. Stop giving trample to any creature you lose control of (but of course, it should still have trample if it has some other source of it!)
5. Stop giving trample to any creature you control that stops being a creature (although this is less important since trample on noncreature permanent doesn't affect the game much).

Silly Freak said...

the "simpler" way, at least I feel it so, is to register some sort of "manipulator" to the engine. whenever the game has to know if a creature has trample, the manipulator changes the result to true for creatures you control.
It's also the cleaner way, as Gruul War Plow's ability is static, and not triggered.

Nanocore said...

Ah, this is what makes Magic fun and continually changing... the ability to break/manipulate the rules. All cards start with a base rule or action. If actions/events are added to this card, there has to be a way to get back to the base set of rules. Also, there really isn't a limit to what extra actions/events can be added to the card. So, by saying that, it appears to inherently imply that there is a "list" of events/actions applied to the card. Then when the action (damage/combat/end of turn etc) is executed, the list needs to be traversed. Potentially, there are several lists on the card one for the action being executed. This pattern (ie, list of actions/events) can/should also be applied to a particular game phase too. Generally, these lists would probably be implemented as a lifo stack where you push and pop events/actions.

Ah, now this is the fun stuff!!

Anonymous said...

nanocore - that's basically what Incantus does. Static abilities register for certain events (mainly a card coming into play or changing so that it matches a particular criterion) and then adding the effect to that card. If that card leaves, changes in a way to become invalid, or the card with the static ability leaves play, then you have to disable/remove it.

Forge said...

MTG Forge does "simple events." Cards like Glorious Anthem keep a an "old list" of cards that were given +1/+1. The Glorious Anthem code is execute ever time state effects are checked, so the 2nd time the code removes +1/+1 from the "old creatures" and adds +1/+1 to all creatures in play. That way if a permanent became a creature, it would have +1/+1. I probably could do Grull War Plow in MTG Forge, I think, lol.

Forge said...

Currently MTG Forge can't handle multiple events like *cough* Incantus. ;)

Forge said...

To further explain, every time a card changes zone the card is copied. But the copy code just uses the card's name to copy it, it doesn't copy any of the attributes like attack or defense. So cards like Glorious Anthem might have references to "old card objects" that are not in the game area anymore, but thats not a problem. After the Glorious Anthem code is called again, the old card references are gone.

Copying the card using its card name makes sense when you remove a card from the game or put the card in the graveyard.

Anonymous said...

Copying card also makes sense with things like Momentary Blink which removes a card and puts it back - the card that comes back is the same physical card, but it's a completely new object that is not affected by any of its previous attachments (like spells that used to target it won't find it etc.) I think this is a very useful technique.

Forge said...

Copying Card objects was the only way for me to stay sane. Because when a card goes to the graveyard the Card object either has to have a method like "reset to normal" or you just make a copy of the card. Copying the card seemed easier, so I went with that idea.