Match-Communication

This is all the communication that goes on between partners. I will give a lengthy example for those who are interested, and for me to know in a few month time what I did...:

"a player clicks a card to play it",

let's say we clicked the portal card: Ebon Dragon Internally the swing event gets generated and after a while there is a passage within the HumanPlayer - class which reads:

    getMatch().playCard(this, e.mCard);

This looks easy - and is meant to look easy, since AI-programmers can thus easyly play cards wihtin their code. What it actually does in match is:

public void playCard(MatchPlayable p, Card card)
{
    Communication c = Communication.createAskMatchToPlayCard(p, this, card);
    addCommunication(c);
}

Which is:

  1. create a communication instance,

  2. put it on the communication stack with all other pending communications

  3. return! (which means do nothing yet!)

This is "sort of" event driven. The next code line (but this is not a must) within the HumanPlayer says:

    getMatch().playerDone(this);

Which tells the match, that the player has placed all its current requests and is finished. Since the HumanPlayer is not multi threaded, this call should be last befor a player is really done to not cause stack issues (none encountered yet), perhaps one day I will change human player to be also in an different thread.

(Just for info: The computer player "really" sends a message, this looks like...

    callPhaseDone();
...
    private void callPhaseDone()
    {
        Runnable caller = new Runnable() {
            public void run() {
            getMatch().playerDone(MatchComputerPlayer.this);
            } };
        try {
            java.awt.EventQueue.invokeLater(caller);
        }
        catch (Throwable e){e.printStackTrace();}
    }
...

This "mostly" ensures, that match allways runs in the same thread, so there are no thread conflicts. )

Whithin the match instance in general following division is made:
...
    switch (mStateClass) {
        case STATE CLASS COMMUNICATION: {
            matchInternCommunication = handleCommunication();
            break;
        }
        case STATE CLASS STACK: {
            matchInternCommunication = handleStack();
            break;
        }
        case STATE CLASS PHASE: {
            matchInternCommunication = handlePhase();
            break;
        }
    }
...

Within these "handle" calls each "special" communication is handled. To stay at the example:

private boolean handleCommunication() {
    Communication c = mCStack.read();
    ...
    switch (currentCommunictionType){
        case COMMUNICATION CPLAY CARD I: {
            return doPlayCard(c);
        }
    ...

In doPlayCard() the card is actually played out (well, it is initiated...). Notice the "c" is all the info the doPlayCard() gets, within that, all communication parameters are included which are needed to play a card. Ok, within doPlayCard() it gets more "dirty", since (as you will see further down) information is possibly needed about the last completed communication, and that one is as yet NOT included in the "c" - actually that might be a good idea - have to think about it (but I'm afraid to change a "running" system...).

The next crucial part within doPlayCard() is:

...
    MatchStackItem s = MatchStackItem.createPlayCreature(p, card, CardSituation.CARD PLAYED KEY);
    addStack(s);
    Communication c2 = Communication.createAskPlayerForManaToPlayCard(p, this,card );
    addCommunication(c2);
...

With that two things happen:

  1. I create a new stackItem, which puts the card on the stack (stack will be handled AFTER all outstanding communications are done)

  2. create a NEW communication, which asks the player to pay mana for the card

Next statement is "return" :-).
So we go again to the communication loop, which tells the player to actually pay some mana...: (the actual code till it really is called is involved, but what it comes down to is)

...     mPlayers[mPlayerActive].waitingForYou(); ...

Than the match waits till it is called again from the player to do something. For now it has completly forgotten about all things (apart from having a communication stack and a Stack stack, but stack is not interesting now, since a communication is pending, and the communication is in a state where a player must answer.
For info, the createCommunication() static method does:

    public static Communication createAskPlayerForManaToPlayCard(MatchPlayable p, Match m, Card card) {
        Communication c = new Communication( p,
COMMUNICATION CMANA PAYMENT I,
COMMUNICATION RESULT TYPE MANA);
        c.E.mInitiatorType=COMMUNICATION TALKER TYPE MATCH;
        c.E.mInitiatorMatch=m;
        c.E.mAnswerType=COMMUNICATION TALKER TYPE PLAYER;
        c.E.mAnswerMatch=m;
        c.E.mAnswerPlayer=p;
        c.E.mTargetCardTo=card;
        ...

There you can see
    c.E.mInitiatorType=COMMUNICATION TALKER TYPE MATCH"
and
    "c.E.mAnswerType=COMMUNICATION TALKER TYPE PLAYER"
which handles which communication partners are involved. )

At this point I will take a shortcut. The PayMana session follows the same scheme as above. When the player taps a land, a getMatch().tapCard(this, e.mCard); is called, which leads to a communication, which taps a card, which adds mana to the pool etc. The whole communication process is called over and over till player does not click a land, but clicks "PayMana". Here we take up the thread again... after communications are all set, finally wihtin match the following method is called:

...
private void setManaPayment(Communication c)
{
    MatchPlayable p = c.E.mAnswerPlayer;
    Card card = c.E.mTargetCardTo;
    if (subMana(p, card) < 0) {
        D.addLog("Payment cancled for card: " + card, 3);
         c.setMessage("Cancled - not enough mana!");
        c.setResultMana(false);
        c.setCancled(true);
        c.setSuccessfull(false);
    }
    else {
        c.setResultMana(true);
        c.setSuccessfull(true);
    }
    updateStatus(p);
    return;
}
...

We take it, that the player payed enough mana, the communication request done by the doPlayCard() method is set to be successfully ended, and that mana was set. Successfully handled communications MUST be passed to the initiator, so he can acknowledge them. In this case this is the match. Within communication handling there is a passage:

...
    if (c.E.mInitiatorType == COMMUNICATION TALKER TYPE MATCH)     {
        if (c.isResultSet() ) {
            setAnsweredCommunication(c);
            return true;
    } }
...

This must be done by the initiator of the communication! (within the players there is a corresponding code passage) As a special case (and to save Card programmers all these coding insights), the Card Initiator type is also handled from within the match. With setting a communication to the state "answered" it is removed from the Communication stack and moved to the one and only "special" location:
mLastAnsweredCommunication.
From that position it can be referenced from its initiator. This is possible and does not cause conflicts, because there can only be ONE LastAnsweredCommunciation. (must think again, if we still could get rid of this, and set it somewhere to its initiatorCommunication... hm...).
So.
After all this trouble just for paying some mana, there is still our first communication on the stack (remember? the COMMUNICATION CPLAY CARD I - communication?). That one was not finished! Our card is not played yet. So after handling some internal issues the doPlayCard() method is called again.

Now we go into the part of the method that we skipped befor, the part with the "lastCommunication". Since communications are handled Stack wise, we KNOW, that the last communication must be the one, that we initiated, otherwise we wouldn't be called now. So by getting mLastAnsweredCommunication, we know that this is our request from befor. If there is no last communication, than we were NOT called befor (as was the case above). Here the code:

...
if (mLastAnsweredCommunication!=null) {
    if (mLastAnsweredCommunication.isCancled() ) {
        / / do cancel
        disposeCommunication(mLastAnsweredCommunication);
        D.addLog("Payment cancled for card: " + card, 3);
        removeStackItem();
        setPlayerWarning(p, "Payment cancled for card: " + card);
        c.setBooleanResult(false);
        c.setSuccessfull(false);
        c.setCancled(true);
    }
    else if (mLastAnsweredCommunication.isSuccessfull() )
    {
        / / play card
        disposeCommunication(mLastAnsweredCommunication);
        c.setBooleanResult(true);
        c.setSuccessfull(true);
    }
    return false;
}
...

If mana payment was cancled, remove the item we put on stack, since the card will NOT be played. Set the result to "our" (calling) communication (means the player clicked on a card to be played) to cancled - end go out.

Otherwise set result to successfull - and go out. At ANY rate we must dispose out own last communication! (this clears mLastAnsweredCommunication (sets to null) amongst other things). This communication (the play card - communication) will be finished off by the player.
( Within HumanPlayer:

...
    Communication c = getMatch().getCurrentCommunication();
    if (c.isResultSet() ) {
        if (!c.isSuccessfull() ) {
            mDisplay.displayShortMessage(c.getMessage() );
        }
        getMatch().setAnsweredCommunication(c);
        getMatch().disposeCommunication(c);
    }
...

With all that done, one communication round has completly finished. The communication stack is empty! Next thing is that there is an item on the stack.