Wednesday, April 15, 2009

Phases in MTG Forge

Magic’s phases are simple to understand but complicated to program (much like combat and other parts of Magic.) MTG Forge implements the phases as a huge array since the phases always progress in the same order. In order to makes progressing to the next phase as simple as possible, the array also includes each place that a user can "stop and play a card" which is called priority.

Let me show you a part of the array and then explain it in more detail.
String phaseArray[][] = 
{
{"Human", "Human", "Untap"},

{"Human", "Human", "Upkeep"},
{"Human", "Computer", "Upkeep"},

{"Human", "Human", "Draw"},
{"Human", "Computer", "Draw"},

{"Human", "Human", "Main1"},
{"Human", "Computer", "Main1"},
}
This is a two-dimensional array that holds whose turn is it (active player), the player who has priority and the name of the phase. The line
{"Human", "Computer", "Upkeep"}
means that the active player is the human and the computer currently has priority during the Upkeep.

This array is part of the Phase object. The most important methods of Phase is getPhase(), getActivePlayer(), getPriorityPlayer(), and nextPhase(). From the example above, getActivePlayer() would return "Human" and getPriorityPlayer() would return "Computer". nextPhase() is used throughout the program to advance to the next phase. Progressing from one phase to the next is very easy, all you have to do is to increment the array index. If there are no attacking creatures those phases are skipped by calling nextPhase().

The Phase object works well but MTG Forge’s phase problem seems to be in the code that observes Phase. I love the observer pattern and use it throughout MTG Forge but sometimes it causes me trouble if I have too many observers that also change the object that they are observing.

For example objects A, B, C are observing Z and when B is notified of a change from Z it modifies Z. (B only modifies Z once every 24 hours so there isn't an infinite loop.) If Z changes, A, B, C are called twice, once when Z was first changed and then later when B changes Z. And to further complicate the issue, A and C may be called before B changed Z or after. I’ve tried to make this example as simple as possible but it is still hard to understand. Basically the problem is that when Z changes, A,B,C are each called twice, once for the initial change of Z and then again when B changes Z.

The problem is when you have multiple observers and one of the observers changes the object that it is observing, then the other observers don't work correctly. If you know of a solution for this, please let me know.

I’ve come up with a solution that I call an "ordered observer". From the example above when Z changes, the observers would be notified in this order A, B, A, B, C instead of A, B, C, A, B, C thus reducing the number of times that C is called. You can download the full Java code here.

p.s.
You can also read more about programming Magic's phases here. What other topics would you like to read about?

class OrderedObserver
{
private boolean shouldContinue;

void updateObservers()
{
shouldContinue = true;
notifyObservers();
shouldContinue = false;
}

//if updateObservers() is called while looping through all observers
//the loop stops and then starts over
void notifyObservers()
{
OrderedObserver ob;
for(int i = 0; i < list.size(); i++)
{
ob = (OrderedObserver) list.get(i);
if(shouldContinue)
ob.notify(this);
}
}//notifyObservers()

}//end class
Magic’s phases (some of these are technically steps)
Untap
Upkeep
Draw
1st Main
Begin Combat
Declare Attackers
After Declare Attackers
Declare Blockers
After Declare Blockers
Combat Damage
End of Combat
2nd Main
End of Turn
Cleanup

11 comments:

Forge said...

I hope my post wasn't too long. It is a little complicated but computer programming is complicated sometimes (for better or worse).

Silly Freak said...

i think you write wrote about the observers not long ago? it was this post. however, "not working correctly" sounds like a weak description of the problem. is there a thread in the forum on the problem? just from how the observers are called, i can neither see the real problem (ABABC works already) nor the solution (obviously)

Forge said...

The real problem seems to be composed of smaller problems. The main problem is that some of the phase observer code is called too many times because it is just a bug or the phase observer code changes phase and bad things happen.

I did write about this awhile ago and I was trying to shed more light on the situation. The fact it that phases in MTG Forge are very hacky because I didn't plan or test well. (The phase problem is why the computer can't play first.)

Basically I'm not sure where exactly I went wrong but more testing (or automated testing) would have helped find errors earlier in the project because I don't know how to fix the phase problem when the code is so embedded in MTG Forge, it is like open heart surgery.

Forge said...

Basically the Phase object with its big array seems fine and I have problems with some of the phase observer code.

Forge said...

And I'm using testing and the "ordered observer" code for MTG Forge version 2 and I have all phases working as well as letting the user choose the phase stops. I did find a few errors in version 2 with the phase code but I found them early and corrected then. Basically phases suck because you have to get the 100% right or it will break in the future.

Unknown said...

The only real problem I see with Forge and phases currently is that there is no precombat Main phase for the computer. This means that blocking with animated lands is impossible, as well as you can't tap creatures to prevent them from attacking, which is something that makes control stronger. That's all i'd like to see.

DennisBergkamp said...

Yes, this would also solve the current situation of being unable to bring in creatures with flash as blockers.

Vecc said...

I agree in the sense that, out of all phase issues, that's the most bothersome. Having a 'free' priority, like we get at Computer's End of Turn (given the checkbox is marked) would make for a simple solution to this, but I think phases need a lot more work than that for a lot of interactions to run smoothly (Mistbind/Vendillion Clique at upkeep, as a brute example), especially on the AI side, I suppose. But a 'temporary' solution for a pre-AI combat phase sounds nice. :>

Vecc said...

Lots of cool arts among the spoiled cards, by the way.

Forge said...

Some cards are less usable, like man-lands and flash creature, since MTG Forge has missing phases.

Forge said...

I'll look into adding a "precombat" phase stop.