JCardGame
 
 


In the following tutorial we introduce to the fundamental ideas of the JCardGame framework. The paradigm of "Learning by Examples" is applied where each little example is fully functional and has a special didactical intension. The examples are not yet real games to play with, but serves as demonstration of a specific feature of JCardGame. All sources and sprite images are included in the JCardGame distribution.

 

Ex04: Dealing Out Single Cards

Most card games starts by dealing out a card deck. Normally all players get a fixed number of the deck that is shuffled randomly by the dealer. In same games it is usual to cut the deck by some other player before the cards are distributed. In JCardGame all these operations are implemented in the library. First you define the card deck as seen in the previous examples. The suit and rank names and their order is user configurable by defining a Suit and Rank enumeration. The card images are loaded from disk files when the deck is instantiated. In the following examples we assume that four players are sitting around a square game table. In our opinion the game layout is very important for all card games running on a computer to give the player the feeling of a real game. The application represents the view of one player, called the drawer. The other players are either simulated by the computer or running the same application remotely and are linked via the Internet. Normally the drawer sees his cards face up and the card piles of the other players with the face down laying on the game table. For our examples we choose to show all player's hands in RowLayout.

The method dealingOut(int nbPlayers, int nbCards) takes the number of players nbPlayers and a number of cards nbCards. It returns an array of hands with nbPlayers + 1 elements. The elements at index 0..nbPlayer - 1 contains hands each with nbCards, the element at index nbPlayers contains the remaining cards in the deck. An optional parameter decides if the cards are randomly shuffled or ordered (by the order given in the Suit and Rank enum.)

Dealing out is a dynamical process animated be showing cards moving from the deck to the player's hand. The animation is easily shown by using the comfortable hand transfer() method that takes the card to transfer and the hand as target. Once the card arrives at its destination the hand is sorted with RANKPRIORITY.

import ch.aplu.jcardgame.*;
import ch.aplu.jgamegrid.*;

public class Ex04 extends CardGame
{
  public enum Suit
  {
    SPADES, HEARTS, DIAMONDS, CLUBS
  }

  public enum Rank
  {
    ACE, KING, QUEEN, JACK, TEN
  }
  //
  private final Deck deck =
    new Deck(Suit.values(), Rank.values()"cover");
  private final int nbPlayers = 4;
  private final Location[] handLocations =
  {
    new Location(300525),
    new Location(75300),
    new Location(30075),
    new Location(525300),
    new Location(300300)
  };
  private Hand[] hands;
  private Hand talon;

  public Ex04()
  {
    super(60060030);
    dealingOut();
    setStatusText("Dealing Out...done.");
  }

  private void dealingOut()
  {
    setStatusText("Dealing Out...");

    hands = deck.dealingOut(nbPlayers, 0);  // All cards in talon
    talon = hands[nbPlayers];

    for (int i = 0; i < nbPlayers; i++)
    {
      hands[i].setView(thisnew RowLayout(handLocations[i]30090 * i));
      hands[i].draw();
      hands[i].setTouchEnabled(true);
    }

    talon.setView(thisnew StackLayout(handLocations[nbPlayers]));
    talon.draw();

    while (!talon.isEmpty())
    {
      for (int i = 0; i < nbPlayers; i++)
      {
        Card top = talon.getLast();
        talon.transfer(top, hands[i], false);
        hands[i].sort(Hand.SortType.RANKPRIORITY, true);
      }
    }
  }

  public static void main(String[] args)
  {
    new Ex04();
  }
}

Execute the program locally using WebStart.

 

GGLogo
Download Android app for installation on a smartphone or emulator.
Download
sources (CardEx04.zip).
Create QR code to download Android app to your smartphone.
Install/Start
app on a USB connected smartphone or a running emulator.

(This is a WebStart signed by the University of Berne, Switzerland. It installs some helper files in <userhome>.jdroidtools.If you did not install the Android SDK, you may install a slim version of the Android-Emulator in <userhome>.jdroidemul using this link, To start the emulator, execute ExecEmul.jar found in <userhome>.jdroidemul)


jcardgameex04

 

Ex05: Dealing Out Cards in Batches

Dealing out single cards is unusual in card games where many cards are distributed. The dealer often deals out in batches of several cards. To simulate the dealing out in batches, first all cards are put in a hand that serves as talon. Then a single card back is shown that moves from the talon to the player's hand and three cards are inserted into the hand. Then the hand is displayed using RANKPRIORITY. In the following example each player will get a total of 9 cards and only the drawer's cards are face up. Like in real games the dealing out is fast now.

import ch.aplu.jcardgame.*;
import ch.aplu.jgamegrid.*;

public class Ex05 extends CardGame
{
  public enum Suit
  {
    SPADES, HEARTS, DIAMONDS, CLUBS
  }

  public enum Rank
  {
    ACE, KING, QUEEN, JACK, TEN, NINE, EIGHT, SEVEN, SIX
  }
  //
  private final Deck deck =
    new Deck(Suit.values(), Rank.values()"cover");
  private final int nbPlayers = 4;
  private final Location[] handLocations =
  {
    new Location(300525),
    new Location(75300),
    new Location(30075),
    new Location(525300),
  };
  private final Location talonLocation = new Location(300300);
  private final int packetSize = 3;
  private Hand[] hands;
  private Hand talon;

  public Ex05()
  {
    super(600600);
    dealingOut();
    setStatusText("Dealing Out...done.");
  }

  private void dealingOut()
  {
    setStatusText("Dealing Out In Packets...");

    hands = deck.dealingOut(nbPlayers, 0);  // All cards in shuffled talon
    talon = hands[nbPlayers];

    for (int i = 0; i < nbPlayers; i++)
    {
      hands[i].setView(thisnew RowLayout(handLocations[i]30090 * i));
      hands[i].draw();
    }

    CardCover talonCover = new CardCover(this, talonLocation, deck, 10);

    while (!talon.isEmpty())
    {
      for (int i = 0; i < nbPlayers; i++)
      {
        for (int k = 0; k < packetSize; k++)
        {
          hands[i].insert(talon.getLast()false);
          talon.removeLast(false);
        }
        if (talon.isEmpty())
          talonCover.removeSelf();
        CardCover cardCover = new CardCover(this, talonLocation, deck, 10);
        cardCover.slideToTarget(handLocations[i]20truetrue);
        cardCover.removeSelf();
        if (== 0)
        {
          hands[i].setVerso(false);
          hands[i].sort(Hand.SortType.RANKPRIORITY, false);
        }
        else
          hands[i].setVerso(true);
        hands[i].draw();
      }
    }
    hands[0].setTouchEnabled(true);
  }

  public static void main(String[] args)
  {
    new Ex05();
  }
}

Execute the program locally using WebStart.

 

GGLogo
Download Android app for installation on a smartphone or emulator.
Download
sources (CardEx05.zip).
Create QR code to download Android app to your smartphone.
Install/Start
app on a USB connected smartphone or a running emulator.

(This is a WebStart signed by the University of Berne, Switzerland. It installs some helper files in <userhome>.jdroidtools.If you did not install the Android SDK, you may install a slim version of the Android-Emulator in <userhome>.jdroidemul using this link, To start the emulator, execute ExecEmul.jar found in <userhome>.jdroidemul)

 

Ex06: Putting Cards on a Game Table, Trick-Taking Games

In the following examples we develop the prototype of a trick-taking game which is one of the most common type of card game, found everywhere on the world. In a typical trick-taking game in each round each player puts a card on the table following some restricting rules. Then the trick rules decides which player wins the round. He obtains the cards and starts the next round.

We begin with a very simple trick-taking game that is played by young children to get motivated for card games. We deal out the cards to four players who stores them in a card stack. In each round, the top card of the stack is put on the table. The trick rule is very simple: The trick is taken by the highest card specified by the rank order. For same ranks, the specified suit order decides. What makes this game simple (and boring) is the fact that there is no restriction nor choice what card can be played..

We implement the game in three steps with increasing complexity. This corresponds to the normal developing process in computer programming. In the first step we deal out the cards to the player's card stack. By double-clicking on one of the stacks we simulate the draw by transferring the card to a "bid" location near the center of the game table. In this way we assure that everybody knows the card played by each player which is very important to apply the trick rules later on.. Right clicking on any player's stack will transfer the cards to this player's stock location. For the moment there are no other restrictions.

Most of the code remains the same as in the last example, but some new ideas are worth to be mentioned. To fine-tune the transfer() method of the class Hand, an instance of TargetArea may be created. TargetArea not only defines the target location (that can be any location not only hand locations) but also the card orientation during the transfer, the distance to move per simulation cycle and a flag to indicate if the card moves on top or below other card actors. The constructor is overloaded by setting the parameters to usual default values. When a player draws a card, the transfer() method moves the card automatically to the hand's bid location because in initBids() the target location of each player's hand is set to its bid location. In normal use the transfer() method blocks until the card reaches its target. To speed up the transfer of all cards to the stock, we move them simultaneously by using the non-blocking version of transferNonBlocking() in transferToStock().

import ch.aplu.jcardgame.*;
import ch.aplu.jgamegrid.*;

public class Ex06 extends CardGame
{
  public enum Suit
  {
    SPADES, HEARTS, DIAMONDS, CLUBS
  }

  public enum Rank
  {
    ACE, KING, QUEEN, JACK, TEN
  }
  //
  private final Deck deck =
    new Deck(Suit.values(), Rank.values()"cover");
  private final int nbPlayers = 4;
  private final Location[] handLocations =
  {
    new Location(300525),
    new Location(75300),
    new Location(30075),
    new Location(525300),
    new Location(300300)
  };
  private final Location[] bidLocations =
  {
    new Location(300350),
    new Location(250300),
    new Location(300250),
    new Location(350300),
  };
  private final Location[] stockLocations =
  {
    new Location(400500),
    new Location(100400),
    new Location(200100),
    new Location(500200),
  };
  private Hand[] hands;
  private Hand[] bids = new Hand[nbPlayers];
  private Hand[] stocks = new Hand[nbPlayers];
  private Hand talon;

  public Ex06()
  {
    super(60060030);
    dealingOut();
    setStatusText("Dealing out...done. Double click to play.");
    initBids();
    initStocks();
  }

  private void dealingOut()
  {
    setStatusText("Dealing out...");

    hands = deck.dealingOut(nbPlayers, 0);  // All cards in talon
    talon = hands[nbPlayers];
    talon.setVerso(true);

    for (int i = 0; i < nbPlayers; i++)
    {
      hands[i].setView(thisnew StackLayout(handLocations[i]90 * i));
      hands[i].draw();
      hands[i].setTouchEnabled(true);
    }

    talon.setView(thisnew StackLayout(handLocations[nbPlayers]));
    talon.draw();

    while (!talon.isEmpty())
    {
      for (int i = 0; i < nbPlayers; i++)
      {
        Card top = talon.getLast();
        top.setVerso(== 0 ? false : true);
        talon.transfer(top, hands[i]true);
      }
    }
  }

  private void initBids()
  {
    for (int i = 0; i < nbPlayers; i++)
    {
      bids[i] = new Hand(deck);
      bids[i].setView(thisnew StackLayout(bidLocations[i]));
      bids[i].addCardListener(new CardAdapter()
      {
        public void atTarget(Card card, Location loc)
        {
          card.getHand().draw();
        }
      });
      hands[i].setTargetArea(new TargetArea(bidLocations[i]));
      hands[i].setTouchEnabled(true);
      final int k = i;
      hands[i].addCardListener(new CardAdapter()
      {
        public void leftDoubleClicked(Card card)
        {
          card.setVerso(false);
          card.transfer(bids[k]true);
        }

        public void rightClicked(Card card)
        {
          transferToStock(k);
        }
      });
    }
  }

  private void initStocks()
  {
    for (int i = 0; i < nbPlayers; i++)
    {
      stocks[i] = new Hand(deck);
      double rotationAngle;
      if (== 0 || i == 2)
        rotationAngle = 0;
      else
        rotationAngle = 90;
      stocks[i].
        setView(thisnew StackLayout(stockLocations[i], rotationAngle));
    }
  }

  private void transferToStock(int player)
  {
    for (int i = 0; i < nbPlayers; i++)
    {
      bids[i].setTargetArea(new TargetArea(stockLocations[player]));
      Card c = bids[i].getLast();
      if (== null)
        continue;
      c.setVerso(true);
      bids[i].transferNonBlocking(c, stocks[player], true);
    }
  }

  public static void main(String[] args)
  {
    new Ex06();
  }
}

Execute the program locally using WebStart.

In the next development step we force the players to wait their turn. After the round is complete, the cards in the bid hands are evaluated and transferred automatically to the winning player's stock. Only this player can start the next round. Again we only explain the new points of interest. After the four cards are played, the bid locations has to be evaluated to determine who wins the round. We perform this evaluation in the method transferToWinner() by inserting the cards into a temporary hand eval that is never shown. Then we call the hands getMaxPosition() method that returns the index of the card with the highest rank priority defined in the Rank enum. If more than one card have the same highest rank priority, the cards are compared using the suit priority defined in the Suit enum. Because the card index corresponds to the players index, we simply pass the index to transferToStock() that moves the card from the bid locations to the winning player's stock.

Because of the non-blocking transfer we encounter a problem: The game cannot continue until all cards arrived at their destination. It is a very bad idea to introduce some kind of delay to wait for the cards to arrive. The time delays must be long enough to ensure that the cards arrived in every circumstances, but not too long because the you block the program execution. Because the application environment varies from one program execution to the other, you will never be sure that the program runs correctly, a nightmare for serious programmers. You put the normal execution thread into a wait state and let the callback system inform by an event notification you when the card arrived. To stop momentarily the current thread, Monitor.putSleep() is used (from the package ch.aplu.util). Fortunately JCardGame provides a callback notification atTarget() when a card arrives at its destination. We wait until all four cards arrived and call Monitor.wakeUp() to reanimate the waiting thread. (Monitor.putSleep()/wakeUp() are based on the Object.wait()/notify() mechanism.)

import ch.aplu.jcardgame.*;
import ch.aplu.jgamegrid.*;
import ch.aplu.util.Monitor;

public class Ex06a extends CardGame
{
  public enum Suit
  {
    SPADES, HEARTS, DIAMONDS, CLUBS
  }

  public enum Rank
  {
    ACE, KING, QUEEN, JACK, TEN
  }
  //
  private final Deck deck =
    new Deck(Suit.values(), Rank.values()"cover");
  private final int nbPlayers = 4;
  private final Location[] handLocations =
  {
    new Location(300525),
    new Location(75300),
    new Location(30075),
    new Location(525300),
    new Location(300300)
  };
  private final Location[] bidLocations =
  {
    new Location(300350),
    new Location(250300),
    new Location(300250),
    new Location(350300),
  };
  private final Location[] stockLocations =
  {
    new Location(400500),
    new Location(100400),
    new Location(200100),
    new Location(500200),
  };
  private Hand[] hands;
  private Hand[] bids = new Hand[nbPlayers];
  private Hand[] stocks = new Hand[nbPlayers];
  private Hand talon;
  private int currentPlayer = 0;
  private int nbMovesInRound = 0;
  private int targetCount = 0;

  public Ex06a()
  {
    super(60060030);
    dealingOut();
    setStatusText("Dealing out...done. Starting player: " + currentPlayer);
    initBids();
    initStocks();
    hands[0].setTouchEnabled(true);
  }

  private void dealingOut()
  {
    setStatusText("Dealing out...");

    hands = deck.dealingOut(nbPlayers, 0false);  // All cards in talon
    talon = hands[nbPlayers];
    talon.setVerso(true);

    for (int i = 0; i < nbPlayers; i++)
    {
      hands[i].setView(thisnew StackLayout(handLocations[i]90 * i));
      hands[i].draw();
    }

    talon.setView(thisnew StackLayout(handLocations[nbPlayers]));
    talon.draw();

    while (!talon.isEmpty())
    {
      for (int i = 0; i < nbPlayers; i++)
      {
        Card top = talon.getLast();
        top.setVerso(== 0 ? false : true);
        talon.setTargetArea(new TargetArea(handLocations[i]));
        talon.transfer(top, hands[i]true);
      }
    }
  }

  private void initBids()
  {
    for (int i = 0; i < nbPlayers; i++)
    {
      bids[i] = new Hand(deck);
      bids[i].setView(thisnew StackLayout(bidLocations[i]));
      bids[i].addCardListener(new CardAdapter()
      {
        public void atTarget(Card card, Location loc)
        {
          targetCount++;
          if (targetCount == nbPlayers)
            Monitor.wakeUp();  // All cards in stock->continue
        }
      });

      hands[i].setTargetArea(new TargetArea(bidLocations[i]));
      final int k = i;
      hands[i].addCardListener(new CardAdapter()
      {
        public void leftDoubleClicked(Card card)
        {
          card.setVerso(false);
          card.transfer(bids[k]true);
          hands[currentPlayer].setTouchEnabled(false);
          currentPlayer = (currentPlayer + 1) % nbPlayers;
          if (nbMovesInRound == 3)
          {
            setStatusText("Evaluating round...");
            nbMovesInRound = 0;
            currentPlayer = transferToWinner();
            stocks[currentPlayer].draw();
          }
          else
            nbMovesInRound++;
          if (hands[currentPlayer].isEmpty())
            setStatusText("Game over");
          else
          {
            setStatusText("Current player: " + currentPlayer);
            hands[currentPlayer].setTouchEnabled(true);
          }
        }
      });
    }
  }

  private void initStocks()
  {
    for (int i = 0; i < nbPlayers; i++)
    {
      stocks[i] = new Hand(deck);
      double rotationAngle;
      if (== 0 || i == 2)
        rotationAngle = 0;
      else
        rotationAngle = 90;
      stocks[i].
        setView(thisnew StackLayout(stockLocations[i], rotationAngle));
    }
  }

  private int transferToWinner()
  {
    delay(3000);
    Hand eval = new Hand(deck);
    for (int i = 0; i < nbPlayers; i++)
      eval.insert(bids[i].getFirst()false);
    int nbWinner = eval.getMaxPosition(Hand.SortType.RANKPRIORITY);
    setStatusText("Round winner: " + nbWinner);
    transferToStock(nbWinner);
    return nbWinner;
  }

  private void transferToStock(int player)
  {
    targetCount = 0;
    for (int i = 0; i < nbPlayers; i++)
    {
      bids[i].setTargetArea(new TargetArea(stockLocations[player]));
      Card c = bids[i].getLast();
      if (== null)
        continue;
      c.setVerso(true);
      bids[i].transferNonBlocking(c, stocks[player], true);
    }
    Monitor.putSleep();  // Wait until all cards are transferred to stock
    stocks[player].draw();
  }

  public static void main(String[] args)
  {
    new Ex06a();
  }
}

Execute the program locally using WebStart.

In the final step we attribute card values (card points) to each card and announce the total number of points of each player's stock when the game is over. Because most of the code remains the same, we only show how to handle the card values.

The values are most easily defined by declaring a class MyCardValues that implements Deck.CardValues. Its method int[] values(Enum suit) returns the values of each card in a rank as an element of an integer array. (The order corresponds to Rank enumeration.) The array may change from one suit to another, but in our example all suits uses the same values: 11 for ACE, 4 for KING, 3 for QUEEN, 2 for JACK, 10 for TEN. An instance of this class is passed to the Deck constructor to let him know what values to attribute to each card.

class MyCardValues implements Deck.CardValues
{
  public int[] values(Enum suit)
  {
    int[] defaultValues = new int[]
    {
      1143210
    };
    return defaultValues;
  }
}

private final Deck deck =
  new Deck(Suit.values(), Rank.values()"cover"new MyCardValues());

It is extremely simple to get the total pips for a hand, just call hand.getScore() in a method showResult() to display the game score in the status area.

private void showResult()
{
  String text = "Game over. Summary: ";
  for (int i = 0; i < nbPlayers; i++)
  {
    text += "Player # " + i + ": " + stocks[i].getScore();
    if (< nbPlayers - 1)
      text += "; ";
  }
  setStatusText(text);
}

Now it's time to relax by playing some game rounds (and thinking about improvements).

Execute the program locally using WebStart.

 

GGLogo
Download Android app for installation on a smartphone or emulator.
Download
sources (CardEx06b.zip).
Create QR code to download Android app to your smartphone.
Install/Start
app on a USB connected smartphone or a running emulator.

(This is a WebStart signed by the University of Berne, Switzerland. It installs some helper files in <userhome>.jdroidtools.If you did not install the Android SDK, you may install a slim version of the Android-Emulator in <userhome>.jdroidemul using this link, To start the emulator, execute ExecEmul.jar found in <userhome>.jdroidemul)