YCP Logo Assignment 4: Klondike (Solitaire)

Early submission deadline (for 5 point bonus): Friday, Oct 16th by 11:59 PM

On time submission deadline: Wednesday, Oct 28th by 11:59 PM

Getting Started

Use your solution to Lab 9 as a starting point.

Rename the project to CS201_Assign4 in Eclipse by right-clicking on the name of the project (CS201_Lab9), choosing Refactor->Rename, and entering the name CS201_Assign4 in the resulting dialog.

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.

Also, see the transcript of the game (with user input in bold).

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 working piles of cards
  • four finished piles of cards

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

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

A finished 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 finished 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 working pile to a working pile or a finished 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 working 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 working 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 working 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 specifications

Your program should shuffle the main deck, deal cards into the working 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 finished pile
  • all hidden and exposed cards in each working 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.

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.

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() + 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 = working piles, 8-11 = finished 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 working 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:

  • Initializing and printing the initial state: up to 40/100
  • Allowing user to choose card to move: up to 45/100
  • Validation that selected card is legal: up to 50/100
  • Support drawing next card from main deck (adding current top card to waste pile): up to 60/100
  • Allowing user to enter destination pile: up to 65/100
  • Checking that destination pile is legal: up to 70/100
  • Game loop: up to 90/100
  • Checking end of game condition: up to 100/100

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

There is one extra credit option:

  • Implement a graphical user interface (GUI): up to 150/100

The GUI implementation will be similar to Lab 7 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.

Note that submissions received by the early submission deadline receive an automatic 5 point bonus.

Submitting

Export your finished program to a zip file (right-click on the project, then Export...->Archive File). Upload the zip file to the marmoset server as project assign4. The server URL is

https://camel.ycp.edu:8443/

IMPORTANT: after uploading, you should download a copy of your submission and double-check it to make sure that it contains the correct files. You are responsible for making sure your submission is correct. You may receive a grade of 0 for an incorrectly submitted assignment.