It is currently 18 Apr 2024, 17:33
   
Text Size

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

Postby 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.

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;}
So a Speaker can internally be a Duck or a Cat by calling Speaker.setSound(). (Sorry for this lame example.)

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() {} 
This is all of the mouse events that MTG Forge processes. You cannot do any other actions like clicking on a mana pool.

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);
Clicking on the player is handled by selectPlayer(). When you press either the OK or Cancel buttons selectButtonOK() or SelectButtonCancel() is called. These methods will be called even if you change the text of the OK or Cancel Buttons. Typically in getMessage() you will use the ButtonUtil class to disable or enable whichever buttons that you want to. You can change the text of the OK and Cancel buttons by using ButtonUtil.getOK() and getCancel() and the MyButton method setText(String).

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

Postby 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

Postby 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

Postby 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.
}
Sometimes it is just easier to use AllZone.Display.getChoice() instead of coding a custom Input class. These method show a dialog popup box. getChoice() will never return null while getChoiceOptional() will return null if the user clicks on the cancel button or closes the dialog box.

Code: Select all
  public Object getChoice(String message, Object choices[]);
  public Object getChoiceOptional(String message, Object choices[]);
Usually the argument "Object choices[]" will be a Card array but it also could be a String array which has "Yes" and "No".
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

Postby DennisBergkamp » 01 Aug 2009, 10:04

Thanks for this, a lot of the Input stuff still confuses me :)
User avatar
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

Postby 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.

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()
Remember that showMessage() is always called first and here we disable the OK button so the user can only click on the Cancel button, ButtonUtil.enableOnlyCancel(). When the user click on his or his oppoenents life points selectPlayer() is called. SelectPlayer() will exit at stop() or stopSetNext() which means "stop this Input and start using this other Input".

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

Postby 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?
The Force will be with you, Always.
User avatar
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

Postby mtgrares » 05 Aug 2009, 18:01

So, if an input class only implements a method for selectPlayer, then that prevents them from selecting a card? And vice-versa?
Yes, the selectCard() method is empty and nothing happens when the user clicks on a card and vice versa.

What would it take to handle alternative mouse input, like to toggle creature selection in combat?
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.

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()
If I understand your question correctly the way to add and remove creatures when you are attacking would be something like this:

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()
To add and remove creatures when blocking would modify Input_Block. Blocking is complicated because the the user has to click on the attacker and then the blocker. Input_Block stores the attacker as the variable currentAttacker.

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

Postby 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.
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

Postby 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

Postby 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

Postby 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
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

Postby 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.
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

Postby 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.

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);
  }
}
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.

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()
}
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.
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


Return to Developer's Corner

Who is online

Users browsing this forum: No registered users and 34 guests


Who is online

In total there are 34 users online :: 0 registered, 0 hidden and 34 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 34 guests

Login Form