Showing posts with label ImageIcon. Show all posts
Showing posts with label ImageIcon. Show all posts

Tuesday, May 14, 2013

Adding Graphics to BlackJack - Part II

Graphical BlackJack Adding Features

Graphical BlackJack Adding Features

Introduction

These final series of articles on Graphical Blackjack are written in near real time. I am reremebering some Java Graphics programming. I am sharing the design choices and reporting on the mistakes I make along the way. Since the target audience is the Novice programmer it's important for the Novice to see how new ground is broken in the Software Development. Most books walk you down the path of perfect design and implementation for their coding examples. While good design and documentation is important it assumes perfect knowledge in how an API works. If you are just learning how an API functions you can still be productive. The Novice programmer should not be afraid to fly by the seat of their pants. I hope seeing how design decisions affect a program and how easy it is to rip up code and reimplement will give you the courage to experiment on your own.

In this Graphics investigation another programming skill being displayed is the adaptation of code for a new purpose. In a commercial Blackjack Game the PlayingCard object might have to be designed as a completely new kind of object just to get that professional Vegas game look. As you will learn (if the ultimate goal is getting functional rapidly) we can adapt existing Java objects to do our bidding and a functional Graphics program can be put together quickly.

Without much introduction in the text below I introduce a very high level concept of Design Patterns. The subject matter of Design Patterns has been blogged about be Developers for a long time. I provide a source reference but the subject is something the reader needs to investigate further.

Problems with JFrame

The first try at adding graphics to the blackjack program dealt with getting a window to open and getting a card picture to display. Now the rest of the PlayingCards in CardDeck need to be displayed. Rather than turning CardDeck into a Graphics object, just add a call to place all the PlayingCards it has in the PlayingCard array into the JFrame.

/**
 * test routine to see how all the cards are displayed from the CardDeck
 * CardDeck is not a Graphics object itself but a container for the 
 * Graphics object PlayingCard.
 * @param f JFrame to add cards to
 */
public void addPlayingCardsToFrame(JFrame f) {
    for (PlayingCard c : deck) {
        f.add(c);
    }
}

Add the following line to the "run" method in the BlackJack class

  • this.card.addPlayingCardsToFrame(frame);

So the graphics method calls (in BlackJack.run()) now look like this:

JFrame frame = new JFrame("BlackJack");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(300,300));
PlayingCard p1 = new PlayingCard(2,2);
frame.getContentPane().add(p1);
this.card.addPlayingCardsToFrame(frame);
frame.pack();
frame.setVisible(true);

When the program is executed the following window get's displayed:

  • What happened to the 3 of diamonds displayed before?
  • Where are all the rest of the playing cards added to the frame?
  • Why did it jump to the King of diamonds and just display that?

52 Card Pile Up

It turns out that they all got displayed! but, in the exact same place. Leaving the last card to be displayed as the only one visible. This is not the functionality I was hoping for. I was looking for the cards to be equally spaced in the JFrame for all to see

The problem stems from the fact that JFrame (in its current default state) doesn't know how to place my current implementation of PlayingCard. It ends up calling PlayingCard's paint method (at some point) but that method always starts it's painting at 0,0.

The Java Swing API has "manager" objects that tell the JFrame how to place the objects that have been added to the frame. These are "Layout" managers and they properly space objects based on various algorithms developed for displaying objects in a window.

Content Pane

The JFrame, according to the tutorials at the Oracle Website has a ContentPane that is already using the default layout manager FlowLayout. What the FlowLayout manager does is space objects out appropriately from left to right using the space available to determine how many cards to place horizontally and then it continues to place them vertically as rows of the same size until it runs out of JFrame space. To see all the objects at once the user must resize the JFrame with the mouse allowing more horizontal and vertical room. When this happens the FlowLayout Manager is supposed to redisplay the objects in the space available. The objects "Flow" into the amount of space.

This is not happening to the PlayingCard class. While we could delve deeper into the Graphics2D library and try to make this low level graphics API work appropriately the question comes up, why bother? From glancing at the Tutorials at the Oracle site things like JLabel and JButton get placed on the screen properly with the FlowLayout manager. Taking the high road the card image just has to extend one of these types and it should get placed properly (if the tutorials are to be believed).

JLabel

The following Tutorial at

has just the functionality this project needs. By creating a JLabel object that displays an Icon, maybe we can trick the JFrame into displaying all the cards just by inheriting from the JLabel object.

JLabel is usually used to present text labels next to text boxes (JTextField class) used for user input. The combination of the two objects is the way to present a Graphical Form to be filled out by the user. The Developers behind the Swing API ingeniously decided that a picture is as good as text for a JLabel. After reading through the Tutorial I came up with this implementation to try:

  • Change the CardImageFactory to produce "ImageIcon" objects (after all I had originally taken this out).
  • Change the PlayingCard class to inherit (ie. extend) from the JLabel class and modify its constructor to store the image
  • Adjust the testing method in CardDeck to add all the cards to the Content Pane from the JFrame object
  • Make the call to the CardDeck method in the "run" method of the BlackJack class

CardImageFactory

After renaming the Factory method call in CardImageFactory the code looks very close to the original from example posted on stackoverflow.com.

public static ImageIcon makeCardIcon(int rank, int suit) {
         int x = (rank * width) / PlayingCard.RANK;
         int y = (suit * height) / PlayingCard.SUIT;
         int w = width / PlayingCard.RANK;
         int h = height / PlayingCard.SUIT;
         //return fullDeckImg.getSubimage(x, y, w, h);
         BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h);
         return new ImageIcon(cardImg);
}

PlayingCard

Having PlayingCard "extend" JLabel the class inherits the "setIcon" method from JLabel. Using the ImageIcon created from the factory call makeCardIcon there is nothing more that needs to be done. Crazy right. All that work with Graphics2D before was unnecessary. Just place the ImageIcon into the appropriate field in the super class and FlowLayout manager will find it and display it within the JFrame. That's the beauty of Object Oriented. Small changes can have awesome effects.

/*
 * PlayingCard.java - Playing card class for BlackJack
 */
package blackjack;

import javax.swing.JLabel;

/**
 * Object model of a simple playing card. Class has been adapted to be displayed
 * in a JFrame 
 * 
 * @author Nasty Old Dog
 */
public class PlayingCard extends JLabel{
    public static final int RANK=13;
    public static final int SUIT=4;
    private int cardno;
    private int score = 0;


    private String faceVal = "A23456789TJQK";
    private String suit = "CSHD";

    public PlayingCard(int cardno, int score)
    {
        this.cardno = cardno;
        this.score = score;
        this.setIcon(CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT));
    }

    /**
     * Get the card scoring value for blackjack face cards = 10 ace = 11 
     * all other cards equal their face value. Aces can equal 1 at times and 
     * is handled elsewhere
     * @return 
     */
    public int card_value()
    {
        return this.score;
    }

    /**
     * convert cardno into text string of face value and suit
     * @return 
     */
    public String getCardText() {
        int card_val = this.cardno % 13;
        int card_suit = this.cardno % 4;
        return this.faceVal.substring(card_val, card_val + 1)
                + this.suit.substring(card_suit, card_suit + 1);
    }

    /*
     * paint the image for the Playing card as Graphics2D image
     * @param _g 

    @Override
    public void paint(Graphics _g) {
        Graphics2D g = (Graphics2D) _g;
        g.drawImage(img,5*this.cardno%13,5*this.cardno%4,this);
    }
     * 
     */
}

The paint method is all commented out. All the work is now done with two lines in the above code

  • public class PlayingCard extends JLabel{
  • this.setIcon(CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT));

CardDeck

CardDeck will not need to be turned into a graphics object. But it does act as a container for graphics objects (remember PlayingCards is now a JLabel). So to assist in testing this program a method has been added "addPlayingCardsToFrame". You would think the parameter for this class should be a JFrame. But there are other objects that provide "ContentPane"s so a more general parent class has been used "Container". Testing may be extended to include those Swing objects (other than JFrame) so why not use the more general case.

/**
 * test routine to see how all the cards are displayed from the CardDeck
 * CardDeck is not a Graphics object itself but a container for the 
 * Graphics object PlayingCard.
 * @param p ContentPane (for now from JFrame) to add cards to
 */
public void addPlayingCardsToFrame(Container  p) {
    for (PlayingCard c : deck) {
        p.add(c);
    }
}

Design Patterns

There is one more change to CardDeck that has nothing to do with Graphics but it has been something that's been bothering me about the design of the code ever since it was changed into Object Oriented code. The Blackjack score value was hard coded into multiple classes because I was too lazy to decide how to best represent the functionality and at what level it should be placed.

There is no one right answer but my thinking was that a PlayingCard is a playing card, meaning in real life it is plastic coated piece of paper with pictures on it. It has no real notion of where the value comes from. That value comes from the type of game that is being played. It would be nice to move the game specific details out of PlayingCard, CardDeck, and Hand. Right? because if you needed PlayingCards that knew how to play BlackJack they should be called BlackJackPlayingCards and not PlayingCards implying some general usability.

For the next leap in your development as a Software Engineer/Computer Scientist you should get, read, and try to understand the book "Design Patterns" by the "Gang of Four". Once Object Oriented programming became established it was found to be an expressive platform to capture Higher Level concepts. While the code examples in the original book are written in C++ many authors have followed the Gang of Four and implemented examples in almost every language imaginable. The book codifies many things programmers used to have to work for a couple of years in industry to learn.

Strategy Pattern

So the strategy pattern looks like it will work. Since I don't plan on implementing different strategies at the present moment I'm not going to create a Strategy Interface that the Strategy Class should implement. Instead just a singlen BlackJackStrategy class will be used without the full hierarchy shown in the book.

BlackJackStrategy Class

The BlackJackStrategy Class is the final resting ground of the cardscore array. It will have a method call to return the BlackJack score of a card encapsulating the use of the card score array. The other method "getScore" takes a hand parameter and implements the scoring algorithm that used to be in the Hand object. The Hand sort method stays with the Hand class and returns an ArrayList of PlayingCards that are used to score the hand. This effectively isolates game specific information to one class. If you were to design a whole suite of card games you would extend this pattern to encompass the game logic of the other games.

/*
 * BlackJackStrategy.java
 */
package blackjack;

import java.util.ArrayList;

/**
 * This is a Strategy Design Pattern roughly implemented as Cataloged in 
 * the book Design Patterns by the Gang of Four. Hands and CardDecks are 
 * rather neutral objects. The stuff that makes a game Blackjack should be
 * in a separate class that gets passed to the CardDeck and Hand objects when
 * their constructor is called. In essence making a BlackJack Hand or BlackJack
 * CardDeck. This will pull all the card value arrays and scoring to a central
 * class. The biggest difference is that this class should be abstracted 
 * so that classes like CardDeck and Hand don't need to specify a particular
 * Strategy. The idea is to keep them agnostic and let the upper levels of the
 * program decide what kind of strategy will be imposed. For now however the 
 * class is being used to separate out the card_score array and the scoring
 * algorithm for hands. The ultimate goal would be to create a card game
 * platform where you only have to modify the strategy to create a new type
 * of card game. 
 * 
 * @author Nasty Old Dog
 */
public class BlackJackStrategy {
    private int card_score[] = {11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10};
    public final static int RANK = 13;

    BlackJackStrategy() {
        super();
    }

    /**
     * Take an internal int cardno representation and return it's blackjack 
     * value
     * @param cardno an integer between 0 and 51 that represents a specific card
     * @return the blackjack scoring value of the card
     */
    public int getCardScore(int cardno) {
        return this.card_score[cardno%RANK];
    }

    /**
     * determine the score of a blackjack hand, sort the hand then figure out
     * which Aces should be 1 or 11.
     * @param h the Hand class passed in
     * @return returns the score of the hand
     */
    public int getScore(Hand h) {
        ArrayList<PlayingCard> sorted = h.sortHand();
        int score = 0;
        for (PlayingCard i : sorted) {
            // Need to test each card to see if it is an ACE
            // If an ACE and score is < 11 use 11 as ACE value otherwise use 1
            // I haven't done a proof but I believe that checking the score
            // less than 11 is sufficient to handle ACEs and you won't bust a
            // hand incorrectly
            if (i.card_value() == 11) {
                // Processing ACE
                if (score >= 11) {
                    // ACEs must all be 1
                    score += 1;
                }
                else {
                    score += 11;
                }
            }
            else {
                // Just add card_value it's not an ACE
                score += i.card_value();
            }
        }
        return score;
    }
}

Then the CardDeck Constructor needs a BlackJackStrategy parameter to set the score of the PlayingCards created.

public CardDeck(BlackJackStrategy strategy) {
    for (int i = 0; i < 52; i++) {
        deck[i] = new PlayingCard(i, strategy.getCardScore(i));
    }
}

The Hand Class needs a little rework to have the sort method return an ArrayList rather than use an internal field variable. The constructors need to be modified to create a Strategy field. By overloading the Constructors no changes to the Hand calls do not have to be changed in BlackJack

/*
 * Hand.java
 * 
 */
package blackjack;

import java.util.ArrayList;

/**
 * models a hand of cards as one would expect in any card game.
 *
 * @author Nasty Old Dog
 */
public class Hand {
    ArrayList<PlayingCard> cards = new ArrayList<PlayingCard>();
//    ArrayList<PlayingCard> sorted = new ArrayList<PlayingCard>();

    private String displayName = "change in constructor";
    private BlackJackStrategy strategy = null;    

    public Hand()
    {
        this.displayName = "Hand";
        this.strategy = new BlackJackStrategy();
    }

    public Hand(String displayName)
    {
        this.displayName = displayName;
        this.strategy = new BlackJackStrategy();
    }

    public Hand(String displayName, BlackJackStrategy strategy)
    {
        this.displayName = displayName;
        this.strategy = strategy;
    }

    public void deal(CardDeck deck)
    {
       this.cards.add(deck.deal());
    }

    public ArrayList<PlayingCard> sortHand() {
        // sort hand make Aces 11 for sorting purposes
        // Use the insertion sort code and modify it by using the card_value
        // method as the way to sort

        ArrayList<PlayingCard> sorted = (ArrayList<PlayingCard>) this.cards.clone();

        // The clone statement does a deep copy of the array list
        // Just to prove it I print out the sorted field individually
        // If you had to copy individually (like for the AP) the code would
        // follow the structure below
        System.out.print("Cloned: ");
        for (PlayingCard i : sorted)
            System.out.print(i.getCardText() + " ");
        System.out.print("Size: " + sorted.size());
        System.out.println();
        PlayingCard tmp;
        for (int i = 1; i < sorted.size(); i++) {
            for (int j = i;
                    j > 0
                    && sorted.get(j).card_value() < sorted.get(j-1).card_value();
                    j--) {
                // swap values

                tmp = sorted.get(j);
                sorted.set(j, sorted.get(j - 1));
                sorted.set(j - 1, tmp);
            }
        }
        return sorted;
    }

    public int scoreHand() {
        return this.strategy.getScore(this);
    }

    public void display() {
        System.out.println(this.displayName);
        for (PlayingCard i : cards) {
            System.out.print(i.getCardText() + " ");
        }
        System.out.println();
    }

}

Final Touches

Last but not least Java Swing API method calls need to be added to the BlackJack run method:

//SwingUtilities.invokeLater(new Runnable() {
//            public void run() {
        JFrame frame = new JFrame("BlackJack");
        Container contentPane = frame.getContentPane();


        // Set the layout manager for the JFrame using the contentPaine
        FlowLayout layout = new FlowLayout();
        contentPane.setLayout(layout);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(300,300));

        PlayingCard p1 = new PlayingCard(2,2);
        contentPane.add(p1);
        this.card.addPlayingCardsToFrame(contentPane);
        frame.pack();
        frame.setVisible(true);
//            }
//});

The calls that are commented out are reminders that I am not using the Swing API in the standard way. If you think for a moment how are all the mouse clicks going to be handled and what if we had a multiplayer game? The Swing API is meant to run in a separate thread of control and most of the examples on the The Java Tutorial site use this way of starting up the Swing API calls. I may or may not need this ultimately but I thought I would include it as a reminder it may become necessary.

The Full Code (for all the classes)

Output

References

  1. The Java Tutorials "How to Use Icons" The Java Tutorials: How to Use Icons
  2. Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. "Design Patterns: Elements of Reusable Object-oriented Software". Reading, MA: Addison-Wesley, 1995. Print.

Author: Nasty Old Dog

Sunday, May 12, 2013

Graphics Based Black Jack

AP Computer Science: After the Exam: Graphical BlackJack

AP Computer Science: After the Exam: Graphical BlackJack

The AP COMP SCI test is done

Congratulations to every one that took the 2013 AP Exam. This is probably the last series AP Computer Science Articles I write (at least until another one of my kids decides to take the course). Whatever score you end up getting I hope you pursue a few computer courses in college. Computers are involved in every aspect of business and manufacturing and understanding the concepts of building working programs will make you better managers and business owners. Too many managers in the 21st Century think that understanding how to use a spreadsheet is all they require to manage a 21st Century business. Instead I propose that great managers understand what it takes to get things accomplished and understand what goes into making a great product. My hope for the new generation of entrepreneurs is that you persue your talents with passion and take us forward in ways the spreadsheet jockeys can't even dream of, much less understand.

Now that you have some time and aren't worried about taking tests. I wanted to take the BlackJack Program to its next evolutionary step and add a Graphical User Interface to the BlackJack Program. I haven't used the Java Swing classes in a long time and have never considered myself a User Interface Programmer so I will step you through the process as an example of how at least one programmer treads into uncharted territory.

Spicing up BlackJack

First things first I'm going to steal some pictures and code. It seems legal because the developer has placed them on his(or her) website for free. So go to http://www.jfitz.com/cards/index.html and download the classic-cards.zip. That file contains a .png file of all the card faces needed to implement the game. The picture of all the cards in one file is there to. You are viewing it at the above site. By using your browser to do a "Copy Image Location" you can save the http address for use in the program.

Now go to the following message board site: stackoverflow.com There you will find a piece of code that uses the cards you downloaded. The code there shows you how to use them directly from the web without downloading prior to using them. Feel free to cut and paste the code into your IDE and see how it works. For this project I'm going to adapt one portion of that code: there is a "Factory" class called CreateCards. This grabs portions of the .png file to cut out single card images and store them in objects in a list.

class CreateCards {
   private static final int SUIT_COUNT = 4;
   private static final int RANK_COUNT = 13;

   public static List<ImageIcon> createCardIconList(String pathToDeck)
         throws MalformedURLException, IOException {
      BufferedImage fullDeckImg = ImageIO.read(new URL(pathToDeck));
      int width = fullDeckImg.getWidth();
      int height = fullDeckImg.getHeight();
      List<ImageIcon> iconList = new ArrayList<ImageIcon>();

      for (int suit = 0; suit < SUIT_COUNT; suit++) {
         for (int rank = 0; rank < RANK_COUNT; rank++) {
            int x = (rank * width) / RANK_COUNT;
            int y = (suit * height) / SUIT_COUNT;
            int w = width / RANK_COUNT;
            int h = height / SUIT_COUNT;
            BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h);
            iconList.add(new ImageIcon(cardImg));
         }
      }
      Collections.shuffle(iconList);
      return iconList;
   }
}

Now the above code is not quite what we will use but it is informative. The author (pseudonym: Hovercraft Full of Eels) shows how to take the full deck image and divide it up into individual card images. The BlackJack program we created has a bit of a problem because we used modulo to create card face values and suits.

Instead of looping through the suits and "rank" (rank is what I originally termed face_value), use modulo to get the rank and suit. Change the text model order for the suits:

  • was: private String suit = "HDSC";
  • now: private String suit = "CSHD";

Now the card images get set up in a one to one correspondence image to text. Pretty simple change for some borrowed code.

This program doesn't really need the ImageIcon object. That provides some capabilities to move the image around on the screen. For a simple blackjack program there isn't really a need for this functionality. The code has been adapted further to use the BufferedImage class directly.

Hide the Image Down Deep Inside

To adapt the above code the question arises where do you store the image? The PlayingCard object is the obvious choice. A PlayingCard should know how to display itself.

The CardDeck object already loops 52 times to create all the PlayingCard's by calling the PlayingCard constructor method. By creating a BufferedImage field in the PlayingCard object and using the CardImageFactory method makeCardImage in the constructor, all the images will be associated with the PlayingCard Objects with very little coding. All that is needed is to properly map from our cardno in a PlayingCard to the appropriate image in the large image file.

First the new factory class CardImageFactory:

package blackjack;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

/**
 * Factory class to create images for each card so it may be displayed 
 * graphically. Everything is static but initCardImageFactory method 
 * must be called first to set up the full image of all the cards in memory
 * this way the call to makeCardIcon does not make 52 web accesses
 * 
 * @author Nasty Old Dog
 */
public class CardImageFactory {
   private static final int SUIT_COUNT = 4;
   private static final int RANK_COUNT = 13;
   private  static BufferedImage fullDeckImg = null;
   public static   int width = 0;
   public static   int height = 0;

      /**
    * Initialized the .png image in memory and sets up width and height 
    * readying size parameters to cut the image up into icons. This also
    * isolates IO exceptions to this method so the individual calls 
    * do not have to have exception handling.
    * 
    * @param pathToDeck the http address of the full card image file
    * @throws MalformedURLException
    * @throws IOException 
    */
   public static void initCardImageFactory(String pathToDeck) 
           throws MalformedURLException, IOException{
      fullDeckImg = ImageIO.read(new URL(pathToDeck));
      width = fullDeckImg.getWidth();
      height = fullDeckImg.getHeight();
   }

   /**
    * For the structure of the current Black Jack program this returns 
    * individual card images instead of a full list. This allows you to 
    * develop a class hierarchy where the image is stored at the individual
    * PlayingCard object level.
    * 
    * @param rank card face value number 0 - 12
    * @param suit suits 0 - 4
    * @return 
    */
   public static BufferedImage makeCardIcon(int rank, int suit) {
            int x = (rank * width) / PlayingCard.RANK;
            int y = (suit * height) / PlayingCard.SUIT;
            int w = width / PlayingCard.RANK;
            int h = height / PlayingCard.SUIT;
            return fullDeckImg.getSubimage(x, y, w, h);
            // BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h);
           // return new ImageIcon(cardImg);
   } 

   /**
    * The original image list created from the example found at: 
    * http://stackoverflow.com/questions/9692465/java-how-do-i-drag-and-drop-a-control-to-a-new-location-instead-of-its-data
    * created by author who goes by pseudonym: "Hovercraft Full of Eels"
    * @return
    * @throws MalformedURLException
    * @throws IOException 
    */
   public static List<ImageIcon> createCardIconList()
         throws MalformedURLException, IOException {
      //BufferedImage fullDeckImg = ImageIO.read(new URL(pathToDeck));
      width = fullDeckImg.getWidth();
      height = fullDeckImg.getHeight();
      List<ImageIcon> iconList = new ArrayList<ImageIcon>();

      for (int suit = 0; suit < SUIT_COUNT; suit++) {
         for (int rank = 0; rank < RANK_COUNT; rank++) {
            int x = (rank * width) / RANK_COUNT;
            int y = (suit * height) / SUIT_COUNT;
            int w = width / RANK_COUNT;
            int h = height / SUIT_COUNT;
            BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h);
            iconList.add(new ImageIcon(cardImg));
         }
      }
      Collections.shuffle(iconList);
      return iconList;
   }

}

I left the "Eels" original method in the class as a reminder of where all the code came from. I split the file access from the image chopping because the file access routines throw exceptions. I want to handle those right away. So they have been placed in the "initCardImageFactory" method. This method is called in the main method to set up images in memory to be cut up when calls to the PlayingCard constructor are made. There's no sense trying to even create PlayingCards or running the program further if the program can't access the image file. As a rule I don't like to have exceptions in constructors the code becomes ugly when trying to create an object.

The other problem with creating a CardImageFactory object is where will the object reside so that the PlayingCard object has access to it. It could be set up in CardDeck and then passed in to the PlayingCard constructor but this again makes the code ugly. It is better to make it a set of static fields and method calls and limit where they are called from.

Java Swing and Graphics2D Libraries

Now that the decision has been made about where the image creation will be done one more thing that is needed is some code that will open a window for all this great graphics to be displayed in. Once that is in place the code changes can be added to the PlayingCard object so that PlayingCard instances (the objects created by CardDeck) get displayed properly.

The Java Swing Library is a graphics API. It provides windows for the program to run in. It has a call to "add" graphic objects into the window. The blackjack program needs a window object and a class that the window object knows how to display.

  • JFrame class: from the Swing API provides a graphics window
  • JComponent class: Swing API class that can be displayed in a JFrame

Any object that has a graphical representation will need to extend JComponent. This gives the object the ability to be used within a JFrame. It also needs a paint method. JFrame will call the paint method on all the objects that have been added to it when 2 method calls take place:

  • frame.pack();
  • frame.setVisible(true);

Assuming the PlayingCard object has been modified the following code is all we need to display a card:

JFrame frame = new JFrame("BlackJack");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PlayingCard p1 = new PlayingCard(2);
frame.getContentPane().add(p1);
frame.pack();
frame.setVisible(true);

The PlayingCard Class

The PlayingCard Object is where most of the action takes place. The constructors have the calls to makeCardIcon have been placed. The next changes that are necessary is the object must extend JComponent and have a "paint" method. The paint method takes a "Graphics" object as a parameter. This will ultimately be supplied by JFrame after we add the PlayingCard to the list of objects to be displayed.

There is some ugly casting of the "Graphics" object to the "Graphics2D" object. This harkens back to the early days of Java and the use of the AWT drawing classes. The Swing drawing classes are an improvement over the AWT classes because the Swing classes make calls directly to the windowing system of the computer. So windows computers will see MS Windows style windows and Mac users will see Mac style windows. To make this change happen without killing off the AWT API and all the programs that were created the idea of supplying a Graphics2D object came about. It implements everything that the AWT had so you can send it to an old AWT style program and it will think it the old "Graphics" object. But in newer code where you know your using a version of Java that supports Graphics2D you can cast the graphics object to Graphics2D and get all the extra functionality that the new class provides.

Without further ado the PlayingCard Class now looks like:

/*
 * PlayingCard.java - Playing card class for BlackJack
 */
package blackjack;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;

/**
 * Object model of a simple playing card. Class has been adapted to be displayed
 * in a JFrame 
 * 
 * @author Nasty Old Dog
 */
public class PlayingCard extends JComponent{
    public static final int RANK=13;
    public static final int SUIT=4;
    private int cardno;
    private int card_score[] = {11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10};
    private BufferedImage img = null;

    private String faceVal = "A23456789TJQK";
    private String suit = "CSHD";

    public PlayingCard(int cardno)
    {
        this.cardno = cardno;
        this.img = CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT);
    }

    public PlayingCard(int cardno, int[] card_score)
    {
        this.cardno = cardno;
        this.card_score = card_score;
        this.img = CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT);
    }

    /**
     * Get the card scoring value for blackjack face cards = 10 ace = 11 
     * all other cards equal their face value. Aces can equal 1 at times and 
     * is handled elsewhere
     * @return 
     */
    public int card_value()
    {
        return card_score[cardno % 13];
    }

    /**
     * convert cardno into text string of face value and suit
     * @return 
     */
    public String getCardText() {
        int card_val = this.cardno % 13;
        int card_suit = this.cardno % 4;
        return this.faceVal.substring(card_val, card_val + 1)
                + this.suit.substring(card_suit, card_suit + 1);
    }

    /**
     * paint the image for the Playing card as Graphics2D image
     * @param _g 
     */
    @Override
    public void paint(Graphics _g) {
        Graphics2D g = (Graphics2D) _g;
        g.drawImage(img,10,10,null);
    }
}

Testing the program so far

The BlackJack class (the main class for this project) looks like this:

/*
 * BlackJack a simple implementation upgrade for graphical card display
 */
package blackjack;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Scanner;
import javax.swing.JFrame;

/**
 * This class controls the game logic of this simple implementation of 
 * Black Jack. A more profession version would allow for multiplayer games
 * via a game server so you could play your friends on the internet. 
 * Some improvements would be:
 * <p>
 *    Adding insurance (players could insure bets against a dealer having blackjack) <p>
 *    Increase the payout for a blackjack hand to 1.5 x the original bet <p>
 *    Double Down <p>
 *    Splitting pairs <p>
 * 
 * @author Nasty Old Dog
 */
public class BlackJack {

    int card_score[] = {11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10};
    CardDeck card;
    Hand player;
    Hand dealer;

    public BlackJack()
    {
        this.card = new CardDeck();
    }

    /**
     * run method that does all the work of this class
     */
    public void run() {
// **************************************************************
//   Added graphics code
        JFrame frame = new JFrame("BlackJack");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        PlayingCard p1 = new PlayingCard(2);
        frame.getContentPane().add(p1);
        frame.pack();
        frame.setVisible(true);
// **************************************************************
        this.card.display();
        this.card.shuffle();
        this.card.display();

        // Now I just need to create the hand objects
        this.player = new Hand("Player Hand");
        this.dealer = new Hand("Dealer Hand");
/*
 *               .
 *               .
 *               .
 *      The rest of the code for run() remains unchanged from it's 
 *      previous incantation
 *               .
 *               .
 *               .
 */

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            // initialize the CardImageFactory to use the full card image file
            CardImageFactory.initCardImageFactory("http://www.jfitz.com/cards/classic-playing-cards.png");
            BlackJack game = new BlackJack();

            game.run();
        } catch (MalformedURLException ex) {
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }        
    }
}

The call to init the CardImageFactory has been placed in the main routine. The calls to the JFrame have been added and one PlayingCard object is created to see if we can create a window that displays a PlayingCard as a graphic instead of the text we have been doing before.

The Graphics

For this project I took the 4 BlackJack Classes created earlier (BlackJack, CardDeck, PlayingCards, and Hand) and added the CardImageFactory class and put them all into a new project. This helps to isolate the program so it can be packaged as a stand alone program. When you make the changes to BlackJack and PlayingCards you should see a window pop up.

It needs to be enlarged and the window is responsive to mouse commands. When it is enlarged we see a card was created and displayed using the PlayingCard.

Conclusion

The small window that opens up when the program is run is annoying. Nobody wants to have to reset the size with the mouse to see what is going on. The addition of one more method call on the JFrame object will fix the problem:

  • frame.setPreferredSize(new Dimension(300,300));

Adding this will open a window where the full card is displayed.

The other interesting behavior this program has is that the window is in control of terminating the program. If you kill the window the whole program terminates. For a graphics program this is the expected behavior. Right now the BlackJack program lives in a text based world that is separated from the graphics based world. It would be nice as the program transitions to span both worlds at the same time so a direct comparison between the graphics presentation and the text based presentation can be made. This may be beyond the scope of this project as JFrame objects are meant to run in a multi-threaded environment and weaving control back and forth may not be the best way forward.

For the entire source code download the following and unzip:

blackjacksrc.zip

NEXT STEPS

  • Modify CardDeck so it displays the entire deck of cards as a test to see that all images have been accounted for
  • Modify Hand so it displays it's cards graphically
  • Add graphic controls for the user to control the game
  • Weave in the blackjack control code into the graphics code to complete the game

References

  1. stackoverflow.com "Java: How do I drag and drop a control to a new location instead of its data?" answer by author pseudonym "Hovercraft Full of Eels" stackoverflow.com Link
  2. The Java Tutorials (oracle.com) "Using Swing Components: Using Top-Level Containers" Top-level Containers Link
  3. Hardy, Vincent J. "Java 2D API Graphics" Palo Alto, CA: Sun Microsystems, 2000.

Author: Nasty Old Dog

Validate XHTML 1.0