Due: Friday, October 19th by 11:59 PM

Updated 10/17: Instructions for using COMTOR added

Getting Started

This assignment will use your code from Lab 10 as a starting point.

Download CS201_Assign4.zip and import it into your Eclipse workspace (File->Import->General->Existing projects into workspace->Archive file.) You should see a project called CS201_Assign4 in the Package Explorer.

Right click on StartAssignment.java and choose Run As->Java Application. In the console window, type yes when prompted. When the program completes, right click on the project (CS201_Assign4) and choose Refresh. You should see your code from Lab 10.

Your Task

Your task is to implement the card game called Klondike. You may be familiar with the game from the classic Windows game "Solitaire".

You can import my solution into Eclipse in order to see how the game should work:

CS201_Assign4_Solution.zip

Note that this solution has no source code other than Main.java, the class containing the main method. You are welcome to use the Main.java from the solution as a starting point for your program, although you are not required to.

Also, see the transcript of the game (with user input in bold). It shows how the gameplay proceeds.

This is a substantial project. Do not wait until the last minute to start it!

Rules of the game

A game of Klondike consists of

  • the main deck, which is a pile of cards with the top card turned over
  • seven tableau piles of cards
  • four foundation piles of cards

A tableau pile consists of 0 or more hidden cards at the bottom of the pile. If a tableau pile is non-empty, then it has at least one exposed card on the top of the pile. A tableau pile may have more than one exposed card. Note that a tableau pile never contains a hidden card placed on top of an exposed card.

At the beginning of the game, the first tableau pile has one card, the second has two cards, etc.

A foundation pile contains cards of the same suit, arranged in order from Ace at the bottom of the pile to King at the top of the pile. (In Klondike, Aces are low.) There are four foundation piles, one for each suit.

On each turn, a player may either

  • draw another card from the main deck
  • move one or more cards

Drawing a card from the main deck means removing the current top card and placing it in a waste pile. The new top card on the main deck is then exposed. If the main deck is empty, then all of the cards are transferred from the waste pile back to the main deck. (Following the transfer of cards from the waste pile back to the main deck, they should appear in the order in which they originally occurred.)

Moving a card transfers one or more cards from either the main deck or a tableau pile to a tableau pile or a foundation pile. Moves must be done following the rules of the game, which are as follows:

  • Only the top card may be removed from the main deck.
  • Only exposed cards may be moved. (Hidden cards may not be moved.)
  • The cards moved are placed on top of whatever pile they are moved to.
  • When multiple cards are moved (from a tableau pile), the cards cannot be removed from the "middle" of the pile. Instead, a chosen card and all cards on top of it must be moved.
  • The colors of the cards in a tableau pile must alternate red and black. Clubs and Spades are black suits, and Diamonds and Hearts are red suits.
  • When a card or cards are moved onto an empty tableau pile, the new bottom card must be a King.

Following a move, the top card of the pile the card or cards were moved from is exposed (if the pile is not empty.)

Progam requirements

Your program should shuffle the main deck, deal cards into the tableau piles, and then allow the user to play the game by repeatedly drawing the next card from the main deck, or moving exposed cards. Before each turn, the program should print a representation of the game state showing

  • how many cards are in the main deck,
  • which card is on top of the main deck
  • the top card in each foundation pile
  • all hidden and exposed cards in each tableau pile (print "xx" for hidden cards)

User input must be validated. If the user enters invalid input, the game state should be printed again and the user reprompted to enter valid input.

All of your classes and methods must be documented with javadoc comments. A javadoc comment describes the purpose of a class or method. For methods, it also describes important details about the method such as what parameter values it takes, what kind of value it returns, and which exceptions (if any) it can throw.

Your methods must contain appropriate comments. I expect to see comments describing important sections of code: for example, to determine when a move is legal, to move cards, the check to see if the user has won the game, etc.

All comments should be well-written and professional.

Hints

These hints describe the approach I used. You may follow this approach, or you may use your own design.

Important consideration:

In good object-oriented design, it is important to separate the user interface classes from the model classes.

A model class is one that represents a concept in the problem domain of the program. In the case of Klondike, the model classes would be Game, Deck, Card, Suit, Rank, etc.

A user interface class is one that allows the user to interact with the program.

One important way you can tell if you have separated the user interface from the model classes in a program that has a textual interface is that you will not have any calls to user input or output methods (e.g., System.out.println, nextInt on a Scanner, etc.) in the methods of the model classes.

The Main.java provided in the solution code contains all of the user input and output code.

toString methods

You can add a toString method to classes where you want to provide a way to convert objects into strings for printing.

To use the card suit characters (♣,♦,♥,♠), you will need to change the text encoding of your Java source files to UTF-8. See this StackOverflow question for details.

The toString method is defined in the java.lang.Object class as follows:

public String toString()

I defined the toString method of the Card class as

public String toString() {
        return rank.toString() + " of " + suit.toString();
}

I defined the toString method of the Rank enumeration as

public String toString() {
        if (this == TWO) {
                return "2";
        } else if (this == THREE) {
                return "3";
        }
        // ... etc ...

Similarly, the toString method in the Suit enumeration can be defined as

public String toString() {
        if (this == CLUBS) {
                return "♣";
        } else if (this == DIAMONDS) {
                return "♦";
        }
        // ... etc ...

Defining toString methods makes it easy to include concise representations of card objects in the program output.

CardLocation

An instance of the CardLocation class represents one of the cards the player can choose to move. Its data consists of the index of one of the piles, and the index of a card within the chosen pile.

Game class

Write a class called Game representing the current state of the game. This class can have methods to determine which moves are legal, and to carry out those moves.

Here are some of the methods I added to my Game class:

  • public Deck getMainDeck()
  • public Deck getPile(int index)
  • public boolean isFinished()
  • public boolean isValidTakeLocation(CardLocation takeLocation)
  • public boolean isValidMove(CardLocation takeLocation, int placePileIndex)
  • public void moveCards(CardLocation takeLocation, int placePileIndex)
  • public void drawNextCard()

You can see my implementation of Main.java to see how these methods are used.

The most interesting methods are isValidTakeLocation and isValidMove. isValidTakeLocation determines if the card selected by the user can be legally moved. isValidMove determines if it is legal to move the card selected by the user to a particular pile.

The moveCards method moves the cards selected by the user to a particular pile.

Deck class

You can use instances of the Deck class to represent the piles. Since the piles are numbered (0 = main deck, 1-7 = tableau piles, 8-11 = foundation piles), an array of references to Deck objects is a good way to store the Deck objects representing the piles.

You can also use a Deck object to represent the waste pile.

If you use the Deck class to implement your piles, you will want to move the code in the constructor which adds the 52 cards to a separate method. The Deck representing the main deck should start out with 52 (shuffled) cards. You can use the drawCard method on the main deck to deal cards to the seven tableau piles.

Here are some of the methods I added to my Deck class:

  • public void add(Card card)
  • public Card getTopCard()
  • public void setExposeIndex(int index)
  • public int getExposeIndex()
  • public int indexOfTopCard()
  • public ArrayList<Card> removeCards(int index)
  • public void placeCards(ArrayList<Card> cardsToPlace)

The "expose index" of a deck is the index of the first exposed card in the deck. For example, if a deck has 6 cards and the expose index is 3, then the cards with indices 3, 4, and 5 are exposed.

The removeCards and placeCards methods are used to move cards from one pile to another. removeCards removes all cards starting at the one whose index is given. placeCards places a sequence of cards on top of a deck.

Grading

Your grade will be determined as follows (out of 100 points):

  • Initializing and printing the initial state: 30
  • Player can move cards: 15
  • Program correctly checks that moves are legal: 5
  • Support drawing next card from main deck (adding current top card to waste pile): 5
  • Game loop (player can make progress my making a series of moves): 20
  • Checking end of game condition: 5
  • Good use of classes and methods: 10
  • Javadoc comments: 5
  • Code comments: 5

Credit may be deducted for poor programming style (inconsistent indentation, poorly-chosen variable/method names, failure to make fields private, unnecessary use of static fields, etc.)

There is one extra credit option:

  • Implement a graphical user interface (GUI): up to 50 points

The GUI implementation will be similar to Lab 6 and Assignment 3. The GUI should be able to use a Game object in order to represent the game state, check and carry out legal moves, etc.

Using COMTOR

COMTOR is a system for automatically checking Java source code to analyze its use of comments. It runs several automatic checks, such as

  • checking whether Javadoc comments are specified for classes and methods
  • checking spelling
  • checking for offensive words

I highly recommend that you use COMTOR to check your program before you submit it. Here's how:

  • Download SubmitToCOMTOR.java into the src/edu/ycp/cs201/cards directory of your CS201_Assign4 project in Eclipse
  • Refresh the project (select CS201_Assign4 project in package explorer, then press F5 or right-click and choose "Refresh")
  • Right-click SubmitToCOMTOR.java and choose Run As->Java Application.
  • In the console, enter your email address
  • You will receive an email with a link to the COMTOR report

Submitting

When you are done, submit the lab to the Marmoset server using either of the methods below.

Important: after you submit, log into the submission server and verify that the correct files were uploaded. You are responsible for ensuring that you upload the correct files. I may assign a grade of 0 for an incorrectly submitted assignment.

From Eclipse

If you have the Simple Marmoset Uploader Plugin installed, select the project (CS201_Assign4) in the package explorer and then press the blue up arrow button in the toolbar. Enter your Marmoset username and password when prompted.

From a web browser

Save the project (CS201_Assign4) to a zip file by right-clicking it and choosing

Export...->Archive File

Upload the saved zip file to the Marmoset server as assign4. The server URL is

https://cs.ycp.edu/marmoset/