Wednesday, October 7, 2009

How MTG Forge Uses the Mouse for Targeting

I couldn't think of a shorter title. Today I'm going to talk about how MTG Forge uses the state pattern to handle all of the mouse input. I'm afraid this is going to be a little bit technical and boring, so feel free to stop at any time. This is probably most interesting to those few people who actual want to program Magic or another card game from scratch.

I'm going to try to explain my idea but fundamentally it all comes down to using the state pattern. The state pattern lets you change the behavior of an object. Let me show you an example in Java.

interface Mouse
{
void clickCard(Card c, Zone z);
}

class MouseControl
{
private Mouse m;

public void setMouse (Mouse mouse)
{
m = mouse;
}

public void clickCard(Card c, Zone z)
{
m.clickCard(c, z);
}
}
MouseControl really doesn't do much and just passes along the information to the Mouse object. The Mouse object overrides clickCard() and does the really "work." To select a target for Giant Growth you would override Mouse.clickCard() to only allow the player to target a creature. The pseudo-code would look like this.

class GiantGrowth implements Mouse
{
public void clickCard(Card c, Zone z)
{
if(Zone.equals("Battlefield") && c.isCreature())
{
//process and go to the next Input
//if you read below, this is where you would call stop()
}
}//clickCard()
}
In MTG Forge I don't have a Mouse interface, I call it Input, and MouseClick is renamed InputControl. (Really Input is just an Adapter class, an interface with methods that don't do anything.) The Input class also has methods to click on a player, so you can target a player. The user interface only uses InputControl and doesn't care which specific Input object is currently being used.

The Input and InputControl classes can become very complicated especially when you consider the rare situations where Inputs can be "stacked" such as if you have a Keiga, the Tide Star in play and you play another one. Both cards go to the graveyard because they are legendary and the player will get to gain control of two creatures. Currently MTG Forge does not "stack" Inputs and doesn't correctly handle the above example with Keiga, the Tide Star.

Hopefully now you understand the power of the state pattern. In MTG Forge all of the phases are implemented as Inputs. Input objects are used for mulligans at the beginning of the game and to pay for mana costs. When I programmed MTG Forge I took many shortcuts in order to get finished, but this is one area which works beautifully and is one of the cornerstones of MTG Forge's success.

p.s.
Below is the Input class from MTG Forge which has many more methods. While handling mouse inputs is nice, really I want to tie the text of a card such as "Target Creature" or "Target Player" with the mouse so the Input class shows text to the user using showMessage(). Whenever InputControl switches to a another Input, it calls the showMessage() method first, so the user will know what is going on.

The Input class also handles if the user clicks on one of the two buttons. Typically the buttons show the text "OK" and "Cancel" so the Input methods are named selectButtonOK() and selectButtonCancel(). The ok and cancel buttons can display different text such as "yes" or "no" and one or both buttons can be disabled.

Whenever an Input object gets done executing, usually when the user clicked on a creature, the stop() method lets InputControl know that it can go on to the next Input. The next Input is typically an Input that represents a phase such as Main 1 or Declare Attackers. When a spell or ability is on the stack, an Input is also used in that situation.

For a card like Giant Growth the user chooses a target creature then pays for the card. In the targeting code, which is an Input, after the user has click on a creature the Input would call the method stopSetNext(Input in) which stops the current Input and allows the argument to be next. So in the Input used to choose the target creature, after the user has clicked on a creature it would called the method stopSetNext(new PayManaCostInput()) so the user can pay the mana cost.

I know this is a lot of information but if you are serious about programming a game like Magic, you really need to use the state pattern to handle the mouse. More information about MTG Forge Input's can be found on the forum here.

public class Input 
{
//showMessage() is always the first method called
public void showMessage() {AllZone.Display.showMessage("Blank Input");}

public void selectCard(Card c, PlayerZone zone) {}
public void selectPlayer(String player) {}
public void selectButtonOK() {}
public void selectButtonCancel() {}

public void stop()
{
//this lets the InputControl know that it can go on
//to the next Input
//this method works like exit()
}

public void stopPayCost(SpellAbility sa)
{
AllZone.GameAction.playSpellAbility(sa);
}

//exits the "current" Input and sets the next Input
public void stopSetNext(Input in) {stop(); AllZone.InputControl.setInput(in);}
}

10 comments:

Silly Freak said...

the state pattern isn't really complete without inheritance:

class MouseController implements Mouse

this way, you can just give a mouse (which is the thought behind an interface), and it remains transparent to the class that needs a Mouse (which is the thought behind the state pattern)

GuillermoMila said...

Hey Forge, can I make a request? In the next version, can you release Fireblast(Visions), Goblin Grenade(Fallen Empires) and Rathi Dragon(Tempest) cards? Thanks a lot for develop this program!

GuillermoMila said...

I forgot, Flame Rift and Incinerate as well! Thanks!

Forge said...

"The state pattern isn't really complete without inheritance"

Well at least you seem to understand what I'm talking about, but yes MouseController could implement the Mouse interface. Personally I don't see any pros or cons either way, but it is always good to know of alternatives.

Forge said...

This is one of my longer articles that took longer to write. I try to limit myself to 150-300 words.

Silly Freak said...

ok, I have looked at the source; InputControl is only used once. I thought about a more classic example.
Say you have a panel and some mouse listener.

panel.addMouseListener(firstListener);

at some point, you want a different behavior

panel.removeMouseListener(firstListener);
panel.addMouseListener(secondListener);

however, you don't want that for some reason. say, the change has to stay transparent from the panel. here comes the state pattern

panel.addMouseListener(control);
control.setMouseListener(firstListener);
//and when you need the change
control.setMouseListener(secondListener);

it's necessary here for the controller to implement mouse listener

it comes even better when a control has more than one state object. you can nest controls into one another to get the effect you want if the control implements the same interface it takes as state

Forge said...

Good example Silly Freak. I know that this article probably excluded 90% of my readers but I like being technical sometimes.

Forge said...

And this article isn't really technical since "really technical" would mean more lines of (boring) source code.

rising fruition said...

Forge, thanks for the link to the forums, and the further link in the forum to sourcemaking.com. That was a new site to me and I found lots of good stuff about Design Patterns. And also links to other programming-related items, which sucked me in for over 3 hours!

Forge said...

Well Design Patterns are great so I try to use them as much as possible.