Monday, January 5, 2009

MTG Forge Architecture

I’m going to discuss the current software architecture that MTG Forge uses. In order to explain some of the quirks I have to remind you MTG Forge was built one piece at a time and I did very little overall architecture planning. I explain various other details in readme-compile.htm that comes in the MTG Forge zip file.

The main classes are GameAction, Card, CardFactory, Input, Phase, and AllZone. GameAction implements such methods as destroy(Card) and newGame(). The Card class has many actions associated with a physical card such as getName() and getAttack(). Since I knew that creating cards was going to be a complicated process, CardFactory’s goal is just to create new Card objects.

Phase’s most important methods are getPhase() and nextPhase(), which advances to the next phase such as Main1 to Declare Attackers. The Input class handles all of the mouse input and uses the State software pattern. AllZone holds all of the zones in Magic such as the player’s hand or the in play zone and is a global variable, public static, because so many different parts of the program need to access the various zones.

Observers also play an important part in MTG Forge. The user interface, gui, observes all of the zones and each zone observes all of the cards in that zone. The Phase object is also observed and whenever Phase.nextPhase() is called, the user interface gets the correct Input class. The Input class for the Main1 phase would let you play any card, while the Input object representing the “Declare Attackers” phase lets you decide which creatures to attack with.

Many of the card drawing problems that MTG Forge has with Black Vise and cards that generate upkeep effects are due to Phase.nextPhase(). To fix these problems in version 2 I have slightly altered the observer code to work in a more predictable way. I’ll try to explain this in more detail next time. :)

12 comments:

Forge said...

Architecture tends to be a little bit dry (uninteresting). I've been using token pictures because Magic has a ton of tokens that I haven't seen.

Anonymous said...

This reminds me, it would be cool to add the token pictures into MTGForge, since I think we have quite a few tokens right now (Voja, Soldiers, Pegasuses (Pegasi?), Goblins, Giant Warriors, etc.)

Forge said...

Currently MTG Forge loads the card picture based on the card name, it probably should load the picture based on the whole card (is the card a token, etc...)

Forge said...

Most tokens don't have a name, so MTG Forge doesn't show anything. Tokens made by Kiki-Jiki and Llanowar Mentor create tokens that DO have a name.

Anonymous said...

Actually, if the token doesn't have a name, then it's name is the same as the creature type line (so for ex. "Elf Warrior")

nantuko84 said...

yes, Anonymous is right: just look at faerie rogue token - it has the same name "Faerie Rogue" at the top:
http://www.magicmadhouse.co.uk/catalog/images/faerie_rogue.jpg

about static variables: as far as I know it is not so good to use classes with members that are all static (like AllZone), it's better to use singleton pattern instead: smth like
AllZone.getInstance().getGraveyard()

Forge said...

Yeah global variables tend to be bad and hard to debug (which they are). In version 2, I use the singleton pattern which is similar to a global variable but it doesn't have any nasty side effects.

Silly Freak said...

the most important reason to use singleton objects is that you can clear the state after a game. I remember some bugs that triggered abilities triggered also in the next game. if you do something like AllZone.drop() to create a new AllZone object you can be sure that every modification on the game is gone

Anonymous said...

Wow, such a thing might actually work in MTG Forge?

In Incantus, we still don't have "new game" capabilities (that is, playing a new game after you've already played one [i]without[/i] forcing you to quit and restart) because the sheer number of things to keep track of is just too much. Even if we were to destroy ever card object and zone object and even player object and replace them all with fresh, new versions, we would still might have some continuous effect code running.

Silly Freak said...

the best would be to keep all these "static classes" tracked by a songle object, something like:
Game.getInstance().getAllZone();
so, all these singleton objects can be dropped with a single Game.drop();

if it's not clear, it would somethin like this:

public class Game {
private static Game instance;

private AllZone allZone;

private Game() {
allZone = new AllZone();
}

public static void drop() {
instance = null;
}

public static Game getInstance() {
if(instance == null) instance = new Game();
return instance;
}


public AllZone getAllZone() {
return allZone;
}
}

Forge said...

When a new game starts I could create new global variables, the problem is that the rest of the program doesn't reinitialize itself. The rest of the program doesn't assume that the global variables will ever change.

Silly Freak said...

sounds like a flaw in your initial design. you should only cache the global variables in local methods, but not in attributes. if you have to, for example in the gui, you could use some kind of observation.