Course Links

Exercises

Resources

External

For this assignment, you will build a GUI-based program that will use a LinkedList as its underlying data type. The program itself will allow the user to move piles of cards around in a window, much as in a solitaire game. Actually implementing a game would be too complicated for a weekly assignment, but this program should show you how it would be feasible to construct such a program.

You may work in pairs again for this assignment, or by yourself. If you choose to work in a pair you must find a partner different from those you have worked with on any previous assignments.

Predefined Classes

Because of the program's complexity, this week you will be starting with some code already written, and extending its functionality. Before you start, you will want to familiarize yourself with the classes provided, which you will be extending. You should do this by reading their Javadoc pages. Begin with CardTable, which organizes the graphical output and runs the GUI. It combines the roles of window component and GUI manager, which have been separate in our previous work. Then look at CardPile which implements a linked list of cards by extending LinkedList. You will probably not need to edit Card and CardGame. The former keeps track of the image data needed to draw a card on screen, while the latter is a wrapper that simply invokes a CardTable instance and starts it running. When you are ready, download all the files. You will need to unpack this file as follows:

gunzip CardGame.tar.gz
tar -xvf CardGame.tar
ls

Compile and run the program as distributed to check that it is working properly. To work properly, the program must be able to load the card images from the web. (Credit for the images goes to J. Fitz.) In its current form, the program initializes all the elements so that the top pile contains a full deck of cards, and the other four piles are empty. The user has no way of modifying the placement of the cards as the program is currently written. Your job is to add this functionality to the program. The amount of new code to be written is relatively small, but this assignment may represent a greater debugging challenge than previous assignments.

Stage One

The first stage of the assignment involves simply testing the CardPile class and adding some functionality to it. Your testing code will go in the constructor of CardTable -- currently it creates a single row of face-down cards at the top of the window, but you can move cards between piles and make other changes. Begin by writing a loop to flip over all the cards in the first pile. Then add some calls to remove cards from the first pile and add them to the other (originally empty) piles. This will get you used to moving cards around. A few sample moves have been provided in the comments to get you started. Uncomment them to see what they do, than add a few more of your own in the same spirit.

Next, fill in several list operation methods in CardPile, designed to split or merge lists at specified points. The call signatures and Javadoc descriptions of the methods are already given in the file, near the FILL IN comments. Try to implement these methods in the most efficient way you know -- but don't get confused trying to do something too fancy. You don't need to use iterators here -- a while loop using methods like removeLast() and addLast() will do fine. Also, please avoid using the addAll method for this part of the assignment. When finished, test your new methods by adding some more lines to the CardTable constructor. Make sure that you thoroughly test all the methods you just wrote, or they could cause trouble later!

In particular, check to see that the cards are coming out in the right order -- it is easy to reverse them accidentally in some cases. Also make sure that cards are neither created nor destroyed. Keep in mind that your code should handle any possible special cases, for example when one of the lists you are working with is empty. Test these conditions. (Also, note the difference between list whose value is null and a non-null list that happens to be empty. When initialized properly, none of your fixed piles should be null, although they may be empty. Calling a non-static method on a pile that is null will always generate a NullPointerException.)

Stage Two

Now you are ready to add interaction!

To implement this behavior, you will need to write the following event handlers (note that these events come from both the MouseListener and MouseMotionListener interfaces; you will have to create and register a listener for each (you can do this in the CardTable constructor). Also note that cardUnderMouse, pileUnderMouse, and movingPile are fields of CardTable and will therefore be accessible to its nested classes.):

mouseClicked
When the click is a double-click, traverse the list of cards in the current pile starting from the card under the mouse, and flip them over using Card.flipCard(). To distinguish a double click from a single (or triple, etc.) click, you can consult the getClickCount() method of the MouseEvent.
mousePressed
This should remember the pile nearest to the click location and the card under the click location (if any), by storing them in pileUnderMouse and cardUnderMouse. (These will be needed later if the user is initiating a drag event.)
mouseDragged
This handler must look at movingPile to decide what must happen. Normally all it needs to do is update the coordinates of movingPile to match the current mouse location. However, if movingPile is null, then this indicates that the user is beginning a new drag sequence. In this case you have to separate the pile of cards that are being dragged from the fixed pile where they started. All the cards in pileUnderMouse from cardUnderMouse onwards should be split off and put in movingPile using the split method you wrote. (If cardUnderMouse is null then the entire contents of pileUnderMouse should be transferred. If you are clever you can write the split function so that this is its default behavior.) Once the piles are split you can set the movingPile coordinates as usual.
mouseReleased
If movingPile is non-null, then we are ending a drag sequence. The moving pile must be inserted just after the card at the location where the mouse was released, or appended at the end of the appropriate pile if the mouse is not released over any specific card. Also, movingPile should be reset to null since releasing the mouse ends the drag sequence.

Debugging

This program is likely to generate more logic and run-time errors than previous assignments you have worked on. One frustratingly common error when working with lists is the infinite loop -- your program will seem to freeze, doing nothing. In reality, it is probably working away executing some set of lines over and over forever. This can happen if you iterate over a list improperly, for example.

Debugging this sort of error can be tedious, because the computer won't be able to tell you where it is getting stuck. You will have to play detective -- deduce where the problem is by printing messages at regular points in the code, commenting out sections you suspect may be in error, etc. Once you have pinpointed the section of code that is causing trouble, you should be able to see what it is doing wrong. Sometimes it is helpful to draw a picture of the list structure and the way your program is modifying it, simulating the program execution step by step until you see what is wrong. Also, remember that it is possible for one piece of your program to set up a problem (like a null reference) that will only become evident later on. The source of a problem could thus be at any point in the program before the place where it becomes evident. If you don't see a problem in the lines where things are getting stuck, use print statements to check that all variables hold the values you think they do. The validate method can be very useful here as well, to make sure a list is properly structured. If something is not right, trace the problem upstream to its source.

As a reminder, don't panic when your program generates an uncaught exception. This most often results from trying to do something with a variable that has not been properly initialized:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
        at CardPile.split(CardPile.java:159)
        at CardTable$Responder.mouseDragged(CardTable.java:167)
        at java.awt.Component.processMouseMotionEvent(Component.java:5536)
        at javax.swing.JComponent.processMouseMotionEvent(JComponent.java:3144)
        at java.awt.Component.processEvent(Component.java:5257)
        at java.awt.Container.processEvent(Container.java:1966)
        at java.awt.Component.dispatchEventImpl(Component.java:3955)
        at java.awt.Container.dispatchEventImpl(Container.java:2024)
        at java.awt.Component.dispatchEvent(Component.java:3803)
        at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4212)
        at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3909)
        at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3822)
        at java.awt.Container.dispatchEventImpl(Container.java:2010)
        at java.awt.Window.dispatchEventImpl(Window.java:1774)
        at java.awt.Component.dispatchEvent(Component.java:3803)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:463)
        at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:242)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:163)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:157)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)

All this error text can look daunting at first glance, but in fact all the information you need to know is contained in the first two lines:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
        at CardPile.split(CardPile.java:159)

This tells us what went wrong (we must have tried to do something with an object whose value was null) and the exact line in the program that caused the error. By looking at this line, we may be able to immediately see what is wrong. If we are not so lucky, the root cause may not be clear. Perhaps the object is not supposed to have a null value -- then we need to do some detective work to figure out how it got a value we didn't expect. Back up through all the parts of your program that handle the object, printing out information about its status until you figure out where things went wrong.

Note that an event handler that generates an uncaught exception will not finish executing any statements past the point where the exception was triggered. This may leave your data structures in an inconsistent state and generate further problems down the line. For your convenience, validation methods have been provided to print and check the internal consistency of your list structures. You should call these regularly during development.

To Submit

Submit your complete stage 2 files to folder assign4.

Quick Start

Extra Credit

If you finish the assignment and want to work on extra credit, an exercise you can try is to replace the use of LinkedList<Card> in your program with your own implementation of linked lists. This is not something you would normally do, but in this case it is a way of checking that you really understand the nuts and bolts of the linked list implementation. You can begin with the example DL_IntList file from class and modify it to do what is needed. You don't have to implement every method of LinkedList -- only the ones used by your program. Note: this is a pretty significant piece of extra credit, and not to be attempted unless you have the main assignment fully working. Turn in copies of both the original program and the extra credit version with EC appended to all the class names.