Input class - how to write you own in 5 hours or less
Post MTG Forge Related Programming Questions Here
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
Input class - how to write you own in 5 hours or less
by mtgrares » 31 Jul 2009, 17:51
Rob had a question about how to write an Input class so I thought I might tell you a little bit about it.
The Input class handles all of the mouse input. All phases are Input objects. The Input class handles choosing a card’s target, paying the mana cost and all of the phases. I explain the Input class here on my blog.
Let me tell you a little about the background of the Input class. It is supposed to be modeled from the State Pattern which changes how the object's internal guts and how it acts while externally the object looks the same.
Let me try to show you some pseudocode.
All Input classes extend the class "Input" which has a stop() method. When an Input class calls stop() it means, "I'm done so you can go to the next Input."
The user interface (gui) uses the InputControl.getInput() to decide which Input is next. (InputControl only deals with Inputs that represent phases.) The InputControl has different internal states representing phases (like Main1 or declare attackers) as well as cards such as Giant Growth where you choose a card.
The methods that you typically override in Input are:
The showMessage() method is always the first method that is called, typically is shows a message in the upper left part of the screen in the text box such as "Choose a card to discard." You can go crazy and use showMessage() as init() or the object's constructor since it is always called first, sometimes a dialog box is put into showMessage(), JOptionPane.showMessage(...). For examples of craziness see any of the planeswalkers in CardFactory.
selectCard() is called when you, the human player, clicks on a card whether it is in your hand, on the battlefield, or one of your opponents cards on the battlefield. (I'm not sure how playing flashback cards are handled.) You will typically use on of these two PlayerZone methods in order to restrict which cards can be chosen. String zone is anything in Constant.Zone and String player is either Constant.Player.Human or Constant.Player.Computer.
p.s. I hope that helps. That is probably more of a brain dump than a tutorial. Dennis feel free to cut and paste this to the top of the Input.java which might help others.
The Input class handles all of the mouse input. All phases are Input objects. The Input class handles choosing a card’s target, paying the mana cost and all of the phases. I explain the Input class here on my blog.
Let me tell you a little about the background of the Input class. It is supposed to be modeled from the State Pattern which changes how the object's internal guts and how it acts while externally the object looks the same.
Let me try to show you some pseudocode.
- Code: Select all
Interface (or abstract class) Sound
public void makeSound()
class Duck implements Sound
public void makeSound() {print("quack");}
class Cat implements Sound
public void makeSound() {print("meow");}
class Speaker
private Sound s;
public void makeSound() {s.makeSound();}
public void setSound(Sound s); {this.s = s;}
All Input classes extend the class "Input" which has a stop() method. When an Input class calls stop() it means, "I'm done so you can go to the next Input."
The user interface (gui) uses the InputControl.getInput() to decide which Input is next. (InputControl only deals with Inputs that represent phases.) The InputControl has different internal states representing phases (like Main1 or declare attackers) as well as cards such as Giant Growth where you choose a card.
The methods that you typically override in Input are:
- Code: Select all
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() {}
The showMessage() method is always the first method that is called, typically is shows a message in the upper left part of the screen in the text box such as "Choose a card to discard." You can go crazy and use showMessage() as init() or the object's constructor since it is always called first, sometimes a dialog box is put into showMessage(), JOptionPane.showMessage(...). For examples of craziness see any of the planeswalkers in CardFactory.
selectCard() is called when you, the human player, clicks on a card whether it is in your hand, on the battlefield, or one of your opponents cards on the battlefield. (I'm not sure how playing flashback cards are handled.) You will typically use on of these two PlayerZone methods in order to restrict which cards can be chosen. String zone is anything in Constant.Zone and String player is either Constant.Player.Human or Constant.Player.Computer.
- Code: Select all
public boolean is(String zone);
public boolean is(String zone, String player);
p.s. I hope that helps. That is probably more of a brain dump than a tutorial. Dennis feel free to cut and paste this to the top of the Input.java which might help others.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 31 Jul 2009, 17:59
And Inputs cannot be stacked, that is why if you play Keiga, the Tide Star while you have one in play (so they both go to the graveyard since they are legendary), you only get to steal one creature instead of two.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 31 Jul 2009, 18:30
This webpage has a decent explanation of the state patter. Sometimes it helps to see how someone else sees it. You can skip the intro and basically look at the UML diagram and the next picture of the vending machine.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 31 Jul 2009, 18:44
Arg, I keep remembering more details. The computer never uses the Input class, so typically you will see something like this in CardFactory.
- Code: Select all
resolve()
{
if(Computer)
automatically make the computer discard
else
use Input class so the human player can discard.
}
- Code: Select all
public Object getChoice(String message, Object choices[]);
public Object getChoiceOptional(String message, Object choices[]);
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by DennisBergkamp » 01 Aug 2009, 10:04
Thanks for this, a lot of the Input stuff still confuses me 

-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 03 Aug 2009, 19:55
I understand that the Input class is confusing. I probably need to be more concise. The user interface (gui) receives the actual mouse click and forwards them to the current Input class, gui -> Input, so the Input class basically just receives events from the mouse. And I reduce all mouse clicking to either clicking on a card, a player, or a button (ok and cancel). CardFactory.input_targetPlayer(final SpellAbility spell) is a good start.
Hopefully this helps a little but I'll answer any questions that you might have in the future. In the barely-working version 2 that I have I had to add Input.clickManaPool(String manacolor) so the user could click on the mana pool.
- Code: Select all
public static Input input_targetPlayer(final SpellAbility spell)
{
Input target = new Input()
{
private static final long serialVersionUID = 8736682807625129068L;
public void showMessage()
{
AllZone.Display.showMessage("Select target player");
ButtonUtil.enableOnlyCancel();
}
public void selectButtonCancel() {stop();}
public void selectPlayer(String player)
{
spell.setTargetPlayer(player);
if(spell.getManaCost().equals("0"))
{
AllZone.Stack.add(spell);
stop();
}
else
stopSetNext(new Input_PayManaCost(spell));
}
};
return target;
}//input_targetPlayer()
Hopefully this helps a little but I'll answer any questions that you might have in the future. In the barely-working version 2 that I have I had to add Input.clickManaPool(String manacolor) so the user could click on the mana pool.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by Rob Cashwalker » 03 Aug 2009, 20:35
So, if an input class only implements a method for selectPlayer, then that prevents them from selecting a card? And vice-versa?
What would it take to handle alternative mouse input, like to toggle creature selection in combat?
What would it take to handle alternative mouse input, like to toggle creature selection in combat?
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 05 Aug 2009, 18:01
Yes, the selectCard() method is empty and nothing happens when the user clicks on a card and vice versa.So, if an input class only implements a method for selectPlayer, then that prevents them from selecting a card? And vice-versa?
The easiest way would be to just add or remove the creature from the object Combat. You would use Combat.removeFromCombat(Card) which accepts attackers and blockers.What would it take to handle alternative mouse input, like to toggle creature selection in combat?
- Code: Select all
public void removeFromCombat(Card c)
{
//is card an attacker?
CardList att = new CardList(getAttackers());
if(att.contains(c))
map.remove(c);
else//card is a blocker
{
for(int i = 0; i < att.size(); i++)
if(getBlockers(att.get(i)).contains(c))
getList(att.get(i)).remove(c);
}
}//removeFromCombat()
- Code: Select all
Input_Attack
public void selectCard(Card card, PlayerZone zone)
{
//Combat.getAttackers() returns Card[]
CardList attackList = new CardList(AllZone.Combat.getAttackers());
if(attackList.contains(card))
AllZone.Combat.removeFromCombat(card);
//below is the normal stuff in Input_Attack.selectCard()
else if(zone.is(Constant.Zone.Play, Constant.Player.Human) &&
card.isCreature() &&
card.isUntapped() &&
CombatUtil.canAttack(card)
)
{
if(! card.getKeyword().contains("Vigilance"))
{
card.tap();
//otherwise cards stay untapped, not sure why this is needed but it works
AllZone.Human_Play.updateObservers();
}
AllZone.Combat.addAttacker(card);
//for Castle Raptors, since it gets a bonus if untapped
for (String effect : AllZone.StateBasedEffects.getStateBasedMap().keySet() ) {
Command com = GameActionUtil.commands.get(effect);
com.execute();
}
GameActionUtil.executeCardStateEffects();
CombatUtil.showCombat();
}
}//selectCard()
- Code: Select all
Input_Block
{
public void selectCard(Card card, PlayerZone zone)
{
//this is the toggle
if(AllZone.Combat.getAllBlockers().contains(card))
AllZone.Combat.removeFromCombat(card);
//below is the normal stuff that is already in Input_Block.selectCard()
//is attacking?
else if(CardUtil.toList(AllZone.Combat.getAttackers()).contains(card))
{
currentAttacker = card;
}
else if(zone.is(Constant.Zone.Play, Constant.Player.Human) &&
card.isCreature() &&
card.isUntapped() &&
CombatUtil.canBlock(currentAttacker, card))
{
if(currentAttacker != null && (! allBlocking.contains(card)))
{
allBlocking.add(card);
AllZone.Combat.addBlocker(currentAttacker, card);
}
}
showMessage();
}//selectCard()
}
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 05 Aug 2009, 18:05
I know people have requested that attackers and blockers could be "undone" and toggled. The code above should work but it is totally untested.
I burned out a few brain cells on this topic in general and the above post. Some of the code in Input_Attack.selectCard() and Input_Block.selectCard() is very hard to understand, I remember lots of insane bugs like clicking on the computer's creature and have him attacking for you. I also had LOTS of errors while paying the mana cost, 1G would work but not GGG so I had to implement 20 or so very basic tests to make sure that I didn't goof.
I burned out a few brain cells on this topic in general and the above post. Some of the code in Input_Attack.selectCard() and Input_Block.selectCard() is very hard to understand, I remember lots of insane bugs like clicking on the computer's creature and have him attacking for you. I also had LOTS of errors while paying the mana cost, 1G would work but not GGG so I had to implement 20 or so very basic tests to make sure that I didn't goof.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 05 Aug 2009, 18:08
In my above code after I say "below is the normal stuff that is already in Input_Block.selectCard()" I did add an "else" underneath for both Input_Attack and Input_Block.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 07 Aug 2009, 17:34
And you probably should modify showMessage() to say "You have removed Eager Cadet" or something like that.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by zerker2000 » 25 Aug 2009, 17:16
Abstaract idea for inputing that I am in no position to test:
- Code: Select all
Input getnext(Ability source, ArrayList[Object] targets, int number, String[] message){
boolean targetCard;
void showMessage(){
if (targets.length() < number)
stopsetnext(new InputPayManaCost(source));
setMessage(message[number]);
targetCard=(targets[number] instanceof Card);
setButtonOnlyCancel();
}
void SelectCancel(){
//insert something apropriate here
}
void selectPlayer(Player){
if(!targetCard){
targets[number]=player;
number++;
stopsetnext(getnext(source, targets[], number, message[])
}}
void selectCard(Player){
if(targetCard){
targets[number]=player;
number++;
stopsetnext(getnext(source, targets[], number, message[])
}}
}
O forest, hold thy wand'ring son
Though fears assail the door.
O foliage, cloak thy ravaged one
In vestments cut for war.
--Eladamri, the Seed of Freyalise
Though fears assail the door.
O foliage, cloak thy ravaged one
In vestments cut for war.
--Eladamri, the Seed of Freyalise
- zerker2000
- Programmer
- Posts: 569
- Joined: 09 May 2009, 21:40
- Location: South Pasadena, CA
- Has thanked: 0 time
- Been thanked: 0 time
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 28 Aug 2009, 18:32
zerker2000 maybe the code would work. The devil is really in the details so I can't really tell.
If you think you understand Input then you probably do. For complex Input classes see any of the tap activated abilities in CardFactory, Hex (6 targets), or the hardest of all, any planeswalker.
If you think you understand Input then you probably do. For complex Input classes see any of the tap activated abilities in CardFactory, Hex (6 targets), or the hardest of all, any planeswalker.
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Input class - how to write you own in 5 hours or less
by mtgrares » 02 Oct 2009, 15:09
I wrote this for my blog but it is a pretty good starting point for trying to understand the Input class.
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.
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.
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.
- Code: Select all
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);
}
}
- Code: Select all
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()
}
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.
- Code: Select all
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);}
}
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
14 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 39 guests