Monday, August 25, 2008

Shared Zones and Incorrect Rules

The good news is that I’m making real progress on MTG Forge version 2. I was programming the code for the different zones and I came to the conclusion that Magic is all about moving cards. You draw a card. You play a card. You destroy a creature. Practically everything you do in Magic (and any card game) involves moving cards.

My other great realization was concerning zones. Some zones like “in play” are shared while others like your hand and graveyard are not shared. While I like to keep as close to the rules as I can, I decided to make all zones shared, including your hand and graveyard.

Cards in your hand will be cards that you control (because you can steal your opponent’s cards occasionally) while all cards in your graveyard will be cards that you own. I was very tired of writing code like “move card from Player’s Hand to Player’s Graveyard”. Now I can just write “move card from Hand to Graveyard.” While this seems like a minor issue, MTG Forge is full of statements that just move cards. If I can make those statements shorter, everyone is happier.

p.s.
I might really cheat and write “move card to Graveyard” and let the program find the zone that the card is currently in.

7 comments:

WilLoW said...

I'm using a "Move card to graveyard" method myself, that lets the code find where the card is, and overall it works well.
You want that method to call the "upper" method that does the actual move though, for clean code.

Something like:
moveToGraveyard(card){
zone = findZone(card);
zone.move(card,graveyard);
}

Zone::move(card, targetZone){
this.removeCard(card);
targetZone.addCard(card);
}

How do you handle cases where card goes from player1's play zone to player2's graveyard ? (player1 is controller, but player 2 is owner)

Incantus said...

What i do is to store a zone attribute with each card, and then the zone has a list of cards it contains. Each card also has a move_to(zone, position) function, which calls zone.move_card(self, position), which handles the actual card move (removing it from the previous zone, sending the appropriate events for triggers, and guaranteeing that multiple cards that enter a graveyard or play at the same time can be ordered). Finally, each card has a link to its owner (which never changes) and it's controller (which can change, and only valid in play), and each player has a link to all his relevant zones (the play zone is shared, although there is some syntactic sugar where i can say:

card.controller.play.get(isCreature)

and that will return all cards controlled by the same player as card.

The benefit of an extra level of indirection for zone changes is that it becomes easy to do replacement effects and come's into play effects.

For willow's question, i say:

card.move_to(card.owner.graveyard)

but i'm thinking of changing it to take a text string (like "graveyard"), since a card can only move to it's owner's zones.

Silly Freak said...

a card can only be in a non-shared zone that belongs to that card's owner, right? at least, that what wizards tries to make sure. "return to hand" or "put into graveyard" effects are always worded to reference the owner, not the controller, because of that.

why have you, forge, chosen to take the controller for the hand? the controller actually only exists for in-play...

Forge said...

"How do you handle cases where card goes from player1's play zone to player2's graveyard ? (player1 is controller, but player 2 is owner)"

Every card has an owner and controller. (You own a card if you started the game with it in your library.)

The card's owner never changes during the game. If a card is going to the graveyard, it will always be put into its owner's grave.

Forge said...

"why have you, forge, chosen to take the controller for the hand? the controller actually only exists for in-play..."

I think it makes sense even though I have programmed the whole thing. If it doesn't work out, I'll just change it. I code on the fly, so nothing I say is written in stone.

Forge said...

"a card can only be in a non-shared zone that belongs to that card's owner, right?"

In a regular game of Magic the answer is yes.

If hypothetically all zones are shared the answer is no, since player A may steal a card from player's B library or hand.

Gando the Wandering Fool said...

In hand a card belongs to its owner. If a card allows a player to play a card from someone else's hand the controller of the card once played may change to the play who played it but it still is controlled by the player who owned it while it's in their hand.

I think it is too easy to become confused the way you are doing this and you overcomplicating something that is fairly direct.

I think Incantus's approach sounds right. each card needs to have an owner attribute that never changes and a controller attribute that does change as needed and a zone attribute that changes as it changes zones.

So player 1 has a hymn to tourach and an animate dead. He hymn's his opponent for 2 cards at random and one of them is a creature. The hymned cards go from player2's hand to their graveyard. Then player1 casts animated dead (which stays in play because we follow the rules in this hypothetical forge :D) on said creature. Now the animated dead is controlled and owned by player1. the Creature animated is controlled by player1 and owned by player2. Should the creature die or have the animate disenchanted it goes to player2's graveyard because the owner attribute says so. While the animate dead goes to player1's graveyard because It's owner's attribute says so.

So:

card Name = Animate Dead
card.CostColored = B
card.CostUncolored = 1
card.ConvertedCost = 2
card.Controller = 1
card.Rules = "Gain control of... put into play from..."
card.Owner=1
card.Zone= (hand1, play1, grave1, removed1,deck1,play2, grave2, removed2,deck2)

card Name = some creature
card.CostColored = who cares
card.ConvertedCost = who cares
card.Controller = 2
card.Rules = "who cares"
card.Owner=2
card.Zone= (play1, grave1, removed1,deck1,hand2, play2, grave2, removed2,deck2)

As you can see the attributes for Owner and Controller are separated as is the zone. Attribute (with the possible zones listed as a limit to what that value can be. You add error trapping to ensure that the unwanted value never occurs.

Now as Ive stated many times before I don't know java at all. I know C/C++ from 12+ years ago so Im probably not the best to even talk about approaches to how to do this in the code but I know that objects can be quite easily manipulated through processes and methods if their members are set up right. So the question is how to set up the members? And what relationship to give each object type with each other? I recognize a congnizant resitance here which does not like the way magic mechanics work. I think you need to overcome that resistance to overcome this problem.

If Im wrong Im wrong but its what Ive seen.