CPSC 124, Fall 2005
Lab 11: Arrays

In this lab, you will be working with arrays of objects of type GraphicalCard. A GraphicalCard represents a playing card from a standard poker deck. You will also use a Deck object, which represents a deck of such cards. The first part of the lab is a short warm-up exercise in which you will create an array, fill it with cards, and do a few simple things with it. The main part of the lab is to make a program like this applet:

You can run the same program as an independent application using the file NotVideoPokerComplete.jar, which you can find in the directory /classes/f05/cs124. In this game, you are dealt a hand of cards when you click the "Deal" button. You can then click on the cards that you want to discard. When you click the "Draw" button, any card that you have discarded will be replaced with a new card. At that point, you can start again with a new deal. The object of the game is to get the highest poker hand that you can. The game is called "Not Video Poker", since in real Video Poker, you would place bets and receive payoffs depending on your hand. This program does not make any attempt to evaluate your hand (which would make it a much harder program to write). Don't worry if you don't know poker -- you don't need to understand poker to do this lab.

To begin for the lab, you can start a new Eclipse project and import all the files from the directory /classes/f05/cs124/lab11: cards.png, Deck.java, GraphicalCard.java, NotVideoPokerApplet.java, and NotVideoPokerApplication.java.

The Applet and Application files will have errors in them, since they refer to a class that does not yet exist. You will create this class later.

The GraphicalCard class defines several constants, including four constants that represent the four suits: GraphicalCard.SPADES, GraphicalCard.HEARTS, GraphicalCard.CLUBS, and GraphicalCard.DIAMONDS. The value of a card is represented by one of the integers 1 through 13, with 1 representing an Ace. An object card of type GraphicalCard includes instance methods card.getSuit() and card.getValue() that return the suit and value of the card. The GraphicalCard class also defines methods for drawing images of cards. The actual card images are found in the file cards.png, which must be present for the drawing routines to work properly. The drawing methods will be discussed below.

The Deck class contains a parameterless constructor that creates a deck of 52 cards in some standard order. The objects in a Deck are actually of type GraphicalCard. If deck is of type Deck, deck.shuffle() shuffles the cards into a random order (after first returning to the deck any cards that have been dealt). The function deck.dealCard() deals one card from the deck. That is, it removes the next card from the deck and returns it as the value of the function. The return value of deck.dealCard() is of type GraphicalCard.

Part 1 (Warm-up): An Array of Cards

First of all, to get rid of the errors in the project, create a new class named VideoPokerPanel that extends JPanel. (Just specify javax.swing.JPanel as the "Superclass" when you create the class.) For now, you don't have to put anything inside the class. This should get rid of the errors in NotVideoPokerApplet and NotVideoPokerApplication.

Next, before starting to work on the Poker application, you will write a short program that creates and uses an array of GraphicalCards. Create a new class with a main() routine. The main() routine should do the following:

Exercise 1: Complete the warm-up program, as described above, and make a printout to be turned in next Tuesday. There is no need to add comments to this program!


Part 2: Panel in a Panel

The NotVideoPoker program uses a main panel with a BorderLayout. In the SOUTH position of the layout is a JPanel that contains two buttons. The large area in the CENTER position that displays the cards is also a panel. The center panel is different from the south panel because it is not simply a generic JPanel. It belongs to a subclass of JPanel that has been extended to override the paintComponent method. So, you will actually need two subclasses of JPanel, one for the main panel and one for the subpanel that displays the cards. The VideoPokerPanel class that you have already created represents the main panel of the program.

It will be easiest to create the card panel class as a nested class within the VideoPokerPanel class. A nested class is just a class that is defined inside another class. The nice thing about doing this is that the nested class has access not just to its own variables and methods but also to the variables and methods of the class within which it is nested. If you call the nested class CardPanel, then the VideoPokerPanel class will look like this in outline:

          import java.awt.*;
          import java.awt.event.*;
          import javax.swing.*;
          
          public class VideoPokerPanel extends JPanel {
          
              class CardPanel extends JPanel {
                 
                 .
                 .  // methods of the CardPanel class
                 .
                 
              } // end of nested class CardPanel
              
              .
              .  // variables, constructor, methods of the VideoPokerPanel class
              .

          } // end of class VideoPokerPanel

You can put the nested class at the beginning, at the end, or in the middle of the main class. It doesn't matter. Within the VideoPokerPanel class, you can use CardPanel in exactly the same way that you would use it if it were defined in a separate file.

In this part of the lab, you will design most of the GUI interface of the program. In the next part of the lab, you will program the game logic.

You will need the following instance variables in the VideoPokerPanel class. (Some of these could actually be in the nested CardPanel class, but remember that CardPanel has access to the variables defined in VideoPokerPanel, so putting all the variables in the outer class will work.)

Add a constructor to the VideoPokerPanel class. Here, you can create all your instance variables and lay out the applet. Use a "SOUTH" panel to hold the buttons, as was done in last week's lab. After you create the CardPanel object, add it to the CENTER position. In order for the application window to be sized correctly, the card panel should have a proper preferred size. In my version of the program, the preferred size of the card panel is 575-by-225. If the name of your CardPanel variable is cardPanel, then you can set its preferred size to this value with:

          cardPanel.setPreferredSize( new Dimension(575,225) );

I'm sure that you will want to set background colors for all the panels. You are not required to use the same colors that I used. By the way, the brown border around my applet was added with:

          setBorder(BorderFactory.createLineBorder(new Color(120,80,30), 3));

BorderFactory is a class that is used to create borders of various types. Any JComponent can have a border, which can be set by calling its setBorder method.

The user of your program will generate ActionEvents by clicking the buttons and MouseEvents by clicking on the card panel. To handle these events, you will need to implement the ActionListener and MouseListener interfaces. It is probably makes the most sense to let your main VideoPokerPanel class implement ActionListener and let CardPanel implement MouseListener (although, in fact, any class can be a listener for any object). Add the implements ActionListener and implements MouseListener to your classes, and provide definitions for the methods that these interfaces define. Don't forget the trick of letting Eclipse write the outlines of the methods for you! You will program the methods in Part 3.

In the constructor, add the main VideoPokerPanel as an ActionListener to the two button objects ("button.addActionListener(this)"), and add the CardPanel object as a MouseListener to itself ("cardPanel.addMouseListener(cardPanel)").

Finally for this part of the lab, add a paintComponent method to the CardPanel class:

              public void paintComponent(Graphics g) {
                 .
                 .  // Draw the five cards and the message
                 .
              }

For now, as a test, draw five face down cards. The command for drawing a face-down card is GraphicsCard.drawFaceDown(g,this,x,y), where x and y are the coordinates where the upper left corner of the card is to be placed. The card is 79-by-123 pixels. Use a for loop to draw the cards. The x coordinate for the i-th card is 30+i*109. (This allows a 30-pixel border on the left plus 109 pixels for each of the previous cards -- that's 79 pixels for the card plus 30 pixels between cards.) My program uses y = 33.

Once you've done all this, you should be able to run the NotVideoPokerApplication and see what your interface looks like. Of course, the program won't do anything yet.


Part 3: Not Video Poker

We now turn to programming the game logic (which is actually easier than the interface!). If you have not already created the Deck and GraphicsCard[] objects, do so now.

The paintComponent method has to be modified so that it can draw face-up cards as well as face-down cards. The cards will be in the GraphicsCard[] array. Let's say that a discarded card will be represented by a null in this array. Modify the for loop in the paintComponent method to draw a face-down card when the i-th element of the array is null and to draw a face-up card when it is not null. If your array variable is named cards, then the command for drawing the i-th card face up is cards[i].draw(g,this,x,y), where x and y are the coordinates of the top left corner of the card.

When the user clicks the "Deal" button, you should deal a new hand of cards. This is part of the actionPerformed method, and should be done when the source of the event is the "Deal" button. (Look back at last week's program if you've forgotten how this works.) To deal, first of all, you should shuffle the deck. Then, use a for loop to deal a card into each position of the array. Finally, you need to call the CardPanel's repaint method to get the new cards to actually appear on the screen. If the name of your CardPanel variable is cardPanel, you can do this with cardPanel.repaint().

The response to the "Draw" method is to replace any discarded cards with new cards from the deck. When a card is discarded, the corresponding position is set to null. To respond to the "Draw" button, you should use a for loop to go through the array and replace any array element that is null by dealing a new card from the deck. Do not replace the non-null elements. Also, don't shuffle the deck in this case. And don't forget to call the card panel's repaint method!

Your response in the actionPerformed method can also include changing the message that is displayed to the user on the card panel.

The discarding of cards takes place when the user clicks on a card in the CardPanel. You can code the response to these clicks in the mousePressed method. (The other four methods of the MouseListener interface should be left empty.) When the use clicks on a card, simply set the corresponding element in the array of cards to null, and call repaint() to effectuate the change in the picture that is shown on the screen. The only hard part is determining which card the user clicked (if any). Remember that the x and y coordinates where the user clicked are given by e.getX() and e.getY(). You need to test each card to see whether these coordinates are within the boundaries of that card. If you used the same coordinates as me, then the i-th card extends from 33 to 156 in the y direction and from 30+i*109 to (i+1)*109 in the x direction.

At this point, the program is functional, but the interface could be improved by stopping the user from taking illegal actions. Only one of the "Deal" and "Draw" buttons should be enabled at any given time. Furthermore, the user should not be able to discard cards except when the "Draw" button is enabled. So, at the beginning of the mousePressed routine, you should test the state before proceeding. You can handle this in at least two ways: Add a boolean instance variable that you set to true when it is legal to draw cards. Or test the state of the "Draw" button directly, using the button.isEnabled() method.

Exercise 2: Complete the NotVideoPoker program as described here. For full credit, you should enable and disable the "Deal" and "Draw" buttons at appropriate times. Also, include appropriate Javadoc comments for your classes, methods, and important instance variables. Turn in a printout next Tuesday.


For Extra Credit

My version of the program allows the user to change her mind about discarding a card. That is, if the user clicks a face-down card after discarding it, the card is turned face up again. Note that it has to be the same card that was originally dealt into that position, not a newly dealt card. For a couple of points of extra credit, you can do the same thing in your program. You can't simply set the array element to null when the user discards a card, since you need to remember the card in case the user clicks it again. One solution is to leave the card in the array, but to use a second array (an array of boolean will work) to record whether each card is face up or face down. In the paintComponent method, consult this second array to decide whether to draw the card face up or face down.

Also, I encourage you to post your program as an applet on your web site. The instructions for doing this are similar to the other applets that you have posted. (Don't forget to include the image file, cards.png, in the .jar file.) If you would like help, please ask!


David J. Eck, November 2005