CS 124, Fall 2011
Lab 11: Poker Solitaire (with Arrays and GUI Programming)

In this lab, you will work with a two-dimensional array of objects. You will also be setting up a GUI and processing events. The lab is more tutorial than usual, and there are some code snippets on this page that you can copy into your program. You will implement a very simple kind of solitaire. The executable jar file PokerSolitaire.jar can be used to run the completed program. Here is an applet version of the game:

The next available card from a deck is shown on the right. There are 25 spaces that can each hold a card. Click on an open space to place the next available card in that space. A new card appears. (After all 25 spaces are filled, the next card space is replaced by the message "CLICK TO PLAY AGAIN"; clicking that area will erase all the cards and allow you to start over.) The goal is to get the best poker hands possible in each row, in each column, and along each of the two diagonals. The applet does nothing except allow you to place cards. It doesn't make any attempt to score the game or tell you how well you've done.

To start, create a new project named lab11. Open the directory /classes/cs124 in a file browser, and copy the cards directory into the src folder of your project. This directory defines a package named cards. For Java to find the files in a package, they must be stored inside a directory with the same name as the package. So, copy the entire directory into src, not the contents of the directory.

This lab is due at the beginning of lab next Thursday. You should copy your work into your homework folder by that time.


Setting Up a Simple GUI

You have worked on GUI programs in previous labs, but this is the first time that you are being asked to set one up from scratch. In the simple GUI for this program, there are no buttons or other components. There is just one large drawing area on which you will draw all the cards. We need to start with a GUI component to represent the drawing area.

Begin by creating a new Java class. This class will represent the drawing area of the program. You can name it what you want. My examples assume that the name is PokerSolitaire. As with most GUI programs, you should start by importing the GUI classes. You can do that with these import statements at the top of the file:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

To use the class for a drawing area, you can make it a subclass of JPanel. That is, the beginning of the class should look like:

public class PokerSolitaire extends JPanel {

To draw on a JPanel, you need a paintComponent method, so you should add the following to your class:

protected void paintComponent( Graphics g ) {
    super.paintComponent(g);
}

You have worked with paintComponent and Graphics before. The graphics context, g, can be used to draw on the panel, using methods such as g.drawRect(x,y,w,h), g.fillect(x,y,w,h), g.setColor(c), and g.drawString(str,x,y). (Those are the only methods that you will need for this lab.) The first line of the paintComponent method fills the entire panel with its background color.

Your class will also need a constructor. For now, you only need to do two things in the constructor: (1) Set a background color for the panel. This can be done with the setBackground(c) method. For example

setBackground( new Color( 80, 0, 0 ) );

for a dark red background. And (2) Set a "preferred size" for the panel. It is often important for a GUI component to know how big it would like to be. This is called its preferred size. In this case, the size of the panel should be 650-by-700 pixels. You can tell the panel its preferred size by calling its setPreferredSize method, which takes a parameter of type Dimension. The Dimension specifies the preferred width and height. In this case, use the command

setPreferredSize( new Dimension(650,700) );

(In addition to being drawing areas, JPanels can be used as containers for other components. When a JPanel is used as a container, it can compute its preferred size, based on the preferred sizes of its contents. For a panel used in this way, it is not necessary to set a preferred size for the panel (and not usually a good idea).)

So, you have a way of making panels. But a panel can't appear on the screen until you put it into a window or applet. To have a complete program, you need a main routine. The main routine will create a window, put a panel into the window, set the size and other properties of the window, and make the window visible on the screen:

public static void main(String[] args) {
   JFrame window = new JFrame();
   PokerSolitaire content = new PokerSolitaire(); // Create the JPanel
   window.setContentPane( content );
   window.pack();  // Eet size of window, based on preferred size of content.
   window.setResizable(false);  // User can't resize window.
   window.setLocation(200,100);  // Set location of window on the screen.
   window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   window.setVisible(true);  // Show window on the screen.
}

This main routine should probably go into a separate class. To run the program, you have to run the class that contains the main routine. (Alternatively, you could put the main routine in the PokerSolitaire class, which allows you to run that class as an application. But you should understand that the main routine is not part of the objects that you create from the class, and it's not related to the job of the class to define such objects.)

At this point, you should be able to run your program and see a large blank area with the background color that you specified.


The Deck and Cards

The cards package contains classes named PokerCard and PokerDeck. To use these classes, you must import them from the cards package. For example,

import cards.*;

These classes are similar to the Deck and Card classes that we looked at in class. For example, the PokerDeck class includes the methods shuffle(), dealCard(), and cardsLeft() that we discussed in class. But a PokerCard has the extra ability to draw itself in a graphics context. If card refers to an object of type PokerCard, you can tell the card to draw itself in a graphics context g, with its upper left corner at the point (x,y) by calling

card.draw(g,x,y);

You will need an instance variable of type PokerCard to represent the next card in the game. You will need an instance variable of type PokerDeck to represent the deck of cards for the game. And you will need an instance variable of type PokerCard[][] to represent the two dimensional array of cards on the game board. You can initialize the variables in the class's constructor, but they must be declared as instance variables.

There are 5 rows and 5 columns of cards on the board, so you need a 5-by-5 array. If cards is the name of the instance variable of type PokerCard[][], you can create the array by saying

cards = new PokerCard[5][5];

When the array is first created, every spot in the array contains a null, indicating that no card is present in that spot. As the game proceeds, you will gradually replace all the nulls with references to cards.


Drawing the Cards

For the next step, add drawing code to the paintComponent method. This method must draw a pattern of 5 rows and 5 columns. Each spot in the pattern is occupied by a blank rectangle or by a playing card, depending on whether the corresponding spot in the cards array is null or refers to a card object. For the spot in row r and column c, the upper left corner is at precisely the point

x = 30 + 90 * c;
y = 30 + 130 * r;

To make a blank spot, you should draw a rectangle with a width of 79 and a height of 123, and with its upper left corner at the point (x,y) given above. The width and height are chosen to match the size of a card image. For drawing a card card at (x,y), you can use the method card.draw(g,x,y) that was mentioned above.

To process all the elements of the cards array, you have to use the typical nested for loop pattern. You will need this pattern in paintComponent and later in the program:

for ( int r = 0; r < 5; r++ ) {
   for ( int c = 0; c < 5; c++ ) {
       PokerCard card = cards[r][c];  // can be null or an object
          .
          .  // process the card value.
          .
   }
}

In addition to drawing the card grid, the paintComponent method has to draw the next card for the game. That value should already be stored in an instance variable that you created earlier. You can draw it with its corner at (520,100).

At this point, you should be able to run the program and see the grid of empty spaces, plus one actual card image in the "next card" spot.


Setting Up Event Handling

Next, you should make the program respond to mouse clicks. This is an example of setting up event handling and writing code to respond to the events. An event-handler in Java is an object that includes some subroutines that will respond to events. There is only one kind of event that we have to worry about for this program: the event that occurs when the mouse is pressed. There are several ways to define event handlers in Java. We will stick to the simplest, which is to have the panel itself handle the events.

To make it possible for the PokerSolitaire panel to handle mouse events, you have to declare that the PokerSolitaire class "implements MouseListener". MouseListener is a so-called interface. An interface consists of list of methods, giving just the name, parameters, and return type of each method. For a class to implement the MouseListener interface, it must include definitions for all the methods that are listed in the interface. Also, you must add "implements MouseListener" to the class declaration:

public class PokerSolitaire extends JPanel implements MouseListener {

As soon as you do this, the line will be marked as an error in Eclipse. This is because the "implements MouseListener" is a promise to define certain methods, and you have not yet fulfilled that promise. There are five methods in the MouseListener interface. You can let Eclipse write the methods for you, or you can just add the following method definitions yourself:

/**
 *  This method will respond to mouse clicks on this panel.
 */
public void mousePressed(MouseEvent evt) {
}

/*  Other methods required by the MouseListener interface, 
    which have nothing to do in this class. */

public void mouseClicked(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }

There is one more step to enable mouse-handling, and it will look a little mysterious. Add the following line to the constructor of the PokerSolitaire class:

addMouseListener(this);

Here "this" is a special variable that refers to the panel object itself. We will be covering "this" in class soon. The command "addMouseListener(this)" tells the panel that when a mouse event occurs, it should call the mouse-handling method in the object "this."

To check that you have wired up the event handling correctly, you might want to add a System.out.println statement to the mousePressed method. If you run the program and click on the panel, the output from that statement should appear in the Console.


Finally, the Game

With the GUI and event-handling in place, you can implement the action of the game. Everything happens in the mousePressed method, which is called when the user clicks on the panel. Recall that the parameter, evt, has methods evt.getX() and evt.getY() that tell you the coordinates where the mouse was pressed.

You have to go to every spot in the grid and check whether the user clicked within that spot. If so, and if there is not already a card at that position, you should put the next card into that spot, and you should get a new next card from the deck.

Do not do any drawing in mousePressed. All the drawing takes place in paintComponent. In mousePressed, you should just change the appropriate values in the instance variables. For example, to put a card in row r, column c, you just have to assign the card to the array element cards[r][c].

Before returning from the mousePressed method, you should call

repaint();

This instance method in the panel object tells the panel to redraw itself. Basically, it causes paintComponent to be called. This will draw the panel, taking into account the changes that you have made to the instance variables.


When the Game Ends

For full credit, you should implement one last feature: When all 25 spots in the grid are full, the game ends. It is not possible for the user to place any additional cards. At that point, you should draw a message in the panel telling the user that the game is over, and you should make it possible for the user to start a new game by clicking somewhere in the panel. (My applet version of the game does this.)