Project 0: Canfield Solitaire

Due: Tuesday, 29 September 2015 at midnight

A. Introduction

This initial programming assignment is intended as an extended finger exercise, a mini-project rather than a full-scale programming project. The intent is to give you a chance to get familiar with Java and the various tools used in the course.

You'll start with a working program that plays Canfield solitaire (see Rules of Canfield, and augment it in two ways. The first problem is to provide an "undo" feature, allowing the player to take back a move. The second problem is to do something about the clunky text interface and to provide an alternative spiffy graphical user interface (GUI).

We will be grading solely on whether you manage to get your program to work (according to our tests) and to hand in the assigned pieces. There is a slight stylistic component: the submission and grading machinery require that your program pass a mechanized style check style61b, which mainly checks for formatting and the presence of comments in the proper places. Please consult the style rules and usage instructions for more information.

B. Rules of Canfield

Canfield (or Demon in England) was apparently introduced in the Canfield Casino in Saratoga Springs, New York. It's played with an ordinary deck of 52~playing cards. Initially, the player deals the cards as follows:

The result looks like this:

Initial configuration

The foundation piles are built up (i.e., by increasing rank) in suit starting from the four cards whose rank is the same as that of the base card: the card that is initially dealt to the first foundation pile (this is four in the example), and wrapping around from King to Ace, if needed (so that last card to go on a foundation pile in the example above is a three). The tableau piles are built down in alternating colors (red on black on red), again wrapping around from Ace to King, if needed. You may not build on the base card (for example, in Figure 1, you may not play the 3♠ from the top of the reserve to the 4♦ in the tableau. Figure 2 shows an example from the middle of a possible game.

Mid Game

You may turn over cards from the stock to the waste in groups of three, or, if there are fewer than three cards in the stock, may turn over the rest of the stock onto the waste. Only the last card turned over (i.e., usually the third) is then visible on the waste pile. The top of the waste pile may be played to the tableau or foundation, if legal. When the stock is exhausted, the waste can be turned over (face down) to make a new stock.

You may move the top card of the reserve (if one is left) to a foundation pile, if legal, or to a tableau pile, if legal.

You may move the top card of a tableau pile to a foundation pile, if legal. Also, you may move an entire tableau pile to the top of another tableau pile if this results in a proper tableau pile (alternating colors built down). Whenever a tableau pile becomes empty (because its cards are moved to the foundation or to another tableau pile), it is automatically filled with the top card from the reserve. If the reserve is empty, you may move the top card of the waste to an empty tableau pile.

Finally, you may move a card from the foundation back to a tableau pile, if legal. This is sometimes useful for making it possible to move another card or cards to the tableau.

The goal is to move as many cards as possible to the foundation piles. In the original casino game, a player would pay $50 for a deck of cards and then get $5 back for each card played to the foundation (so that one would always get back at least $5, since there is always at least one card on the foundation). Hence, the maximum profit in a game is $210 (52 × 5 - 50).

C. Your tasks

The starter code that you'll get from the repository actually plays Canfield, but only a rather awkward textual interface is implemented. To run it, use

java -ea canfield.Main --text

(-ea just tells the Java interpreter to check any assert statements you may have added to your code; by default it ignores assertions.) The command to run with a GUI (currently just a non-working stub) is simply

java -ea canfield.Main

Your job is to make two additions:

We'll test your undo command by playing games with the text interface. Don't change how the program deals cards or displays the state of play so that the automated tests can interpret the output from your program. We'll also test your tests by seeing if it catches errors in some of our own (deliberately damaged) implementations.

The specifics of your GUI are vague (to encourage creativity) so we'll "test" it by checking manually to see if you've managed to get something reasonable to work.

D. Quick Overview of Project Structure

The skeleton we provide you is a form of the Model-View-Controller (MVC) architecture. One class, canfield.Game is the model: it embodies the current state of the game and contains all the game logic: mostly, what moves are legal at any given time. A second class, canfield.TextPlayer, serves both as a view, which consults the model and displays it, and a controller, which directs changes (for us, makes moves) in the model. For the GUI version, there are two skeletal classes, canfield.CanfieldGUI, intended as a controller, and canfield.GameDisplay, which should display a view of the game.

The class Player is an abstract class that describes the common characteristics of both TextPlayers and GUIPlayers (the latter class simply creates a CanfieldGUI, which does all the work.)

Finally, Main chooses what kind of Player to create—TextPlayer or GUIPlayer—depending on command-line options, and then calls on it to do everything else.

E. Approaching the Problem

First, this is largely a code-reading and documentation-reading exercise:

Second, the undo command is probably easiest. Figure out how to save a sequence of games and how to restore a game to each item in this sequence in reverse order. We suggest doing this in Game. Write unit tests (the skeleton GameTest.java is provided for you) for your solution. Then look at TextPlayer and figure out how to add a new command. Add an integration test to the testing directory to check that your whole solution works (actually, do that earlier, so that you have tests of your undo work as you're writing it).

Third, fool around with the example in the gui package (run it with java gui.Main). Use it to test your understanding of the machinery, and borrow from it to help fill in the canfield.CanfieldGUI and canfield.GameDisplay classes.

Fourth, in implementing your GUI, try to divide the work into pieces. For example, start with drawing the reserve pile in paintComponent. Then, go on to the foundation, stock, and waste piles. Finally, do the tableau. Try to factor your code as you go, avoiding repeated sequences of code by identifying things that get done in many places and turning them into methods. By the way, it might be nice to have some easy way to set up situations from the middle of a game so that you can easily check that your display methods are working. This might involve writing a few extra methods that you use only during development.

When you've gotten to the point that you can draw the game state nicely, you can start adding things to canfield.CanfieldGUI and canfield.GameDisplay to allow user input. The structure here is different from TextPlayer: that class reads from the input and then interprets and executes a command all in loop. By contrast, the typical game-playing GUI has a selection of methods that respond to events, so that the command-interpretation logic is spread out among the methods that respond to these events (see also this article on event-driven programming). For example, the logic for turning over a card on the stock might get invoked by the mouse-click method, whlle that for moving a card to a foundation pile might be triggered by the release of a mouse button. You will certainly want a method somewhere to figure out from a mouse event (which contains the location of the event) which pile of cards is involved. This method would typically be used by all of the event routines.

The bottom line, however, is not to try to do everything at once. Proceed incrementally. As you do, by the way, be sure to commit your changes, so that each commit corresponds to a feature or method implemented or a bug fixed. If you do so, backing out of changes becomes easier (Git, in fact, allows you to revert a single change you made at some time in the past, leaving everything you've done since then untouched. But this is only possible if you've been diligent to commit each distinct step in your progress.)

Also, beware that GUI development can be a time sink, as you fuss to get appearances perfect or to add bells and whistles that occur to you. "Good enough" will be good enough for this assignment!

F. Version Control, Starter Code, and Submission

As usual, you can get the starter code by using either of the following procedures:

$ cd  ~/repo
$ git fetch shared
$ git checkout -b Proj0 shared/proj0
$ git push -u origin Proj0

In this case, the Proj0 branch will contain only the source code for Project #0. The branch name is actually arbitrary. As a slight change from previous advice, I suggest capitalizing it to avoid confusion with the proj0 directory (all lower case).

Alternatively, you simply add Project #0 to your master directory:

$ cd  ~/repo
$ git fetch shared
$ git checkout master
$ git merge -m "Start project 0" shared/proj0
$ git push

Both techniques will add a new proj0 directory to your repo directory, commit it into your repository, and copy this change to your central repository (the one that we maintain for you on the instructional machines).

We've said this before, but since we're moving into somewhat larger projects, it's important to repeat it. It is important that you commit work to your repository at frequent intervals. Version control is a powerful tool for saving yourself when you mess something up or your dog eats your project, but you must use it regularly if it is to be of any use. Feel free to commit every 15 minutes; Git only saves what has changed, even though it acts as if it takes a snapshot of your entire project.

The command git status will tell you what you have modified, removed, or possibly added since the last commit. It will also tell you how much you have not yet sent to your central repository. You needn't just assume that things are as you expect; git status will tell you whether you've committed and pushed everything.

If you are switching between using a clone of your central repository on the instructional computers and another at home (or on your laptop), be careful to synchronize your work. When you are done working on one system, be sure push your work to the central repository:

$ git status       # To see what needs to be added or committed.
$ git commit -a    # If needed to get everything committed.
$ git push

When you start working on the other system, you then do

$ git status          # To make sure you didn't accidentally leave
                      # stuff uncommitted or untracked.
$ git checkout B      # Check out whatever branch you are working
                      # on (B is hw1, Proj0, master, etc.)
$ git pull --rebase   # And get changes from your central repo.

assuming you've got branch B in the local repository clones on both systems. The first time you start working on a pre-existing branch on a different machine (say you started the project on the instructional machines and want to work on your home system, but B isn't there yet), use

$ git status          # Make sure everything's clean.
$ git fetch origin
$ git checkout -b B origin/B

As usual, submit your project by committing and tagging it:

$ git tag proj0-0   # Or proj0-1, etc.
$ git push
$ git push --tags

Be sure to respond to all prompts and to make sure the messages you get indicate that the submission was successful. Don't just "say the magic words" and assume that everything's OK.