Due: Tuesday, 29 September 2015 at midnight
Navigation
- Introduction
- Rules of Canfield
- Your Tasks
- Quick Overview of Project Structure
- Approaching the Problem
- Version Control, Starter Code, and Submission
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:
- 13 cards are dealt to a pile called the reserve, with only the top card face up.
- One card is dealt face up to the first of four foundation piles (the other three are initially empty).
- Four cards are dealt face up below the foundation to start four tableau piles.
- The remaining 34 cards (face down) form the stock.
- Next to the stock is an (initially empty) waste pile.
The result looks like this:
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.
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:
- Add a new command:
undo
(u
for short). This moves the game back one move. By repeating it enough times, you can return to the initial state of a game immediately after the deal. Trying to undo the initial position has no effect. - Add additional integration tests, and possibly unit tests. to check that your changes work.
- Implement an option to use a proper GUI (such as shown in the figures of this document).
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 TextPlayer
s and GUIPlayer
s (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:
- Read
Game.java
to figure out how it works and how you might implement the undo facility - Read
TextPlayer.java
to see how reading and interpreting commands works. - Read the contents of the
gui
package in your skeleton to see an example of using ourucb.gui
package and Java'sjava.awt
package to implement a graphical user interface. - Read the ucb package docs to see what other methods are available.
- Read the Graphics and the Graphics2D documentation to see how to actually draw things on the screen.
- Read the MouseEvent documentation to see what's in an event—an object that contains information about a user interaction.
- Read the comments in
testing/test1.inp
to see an example of an integration test script.
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.