Assignment 2: The Model
Due: Tue 09/27 at 8:59pm; self-evaluation due Wed 09/28 at 8:59pm
Starter files: code.zip
1 Purpose
The primary goal of this assignment is to practice implementing an interface based on given specifications, and by choosing an appropriate data representation that helps in providing the functionality promised by the interface.
Please ensure
all of your source code is in the cs3500.hw02
package. Your tests may
live in whichever package makes the most sense. (See Package Management below.)
2 The game of Freecell
2.1 Context
In the next three homeworks, you will implement the popular single-player game “Freecell”.
Freecell is a Solitaire-type game, which uses the regular deck of 52 suit-value
cards. There are four suits: clubs (♣), diamonds (♦),
hearts (♥), and spades (♠). Hearts and diamonds are
colored red; clubs and spades are colored black. There are thirteen values:
ace (written A
), two through ten (written 2
through 10
),
jack (J
), queen (Q
) and king (K
). In Freecell, aces are
considered “low”, or less than a two. (In other games, they’re considered
“high”, or greater than a king.)
The game play is divided among three types of card piles. First, there are four foundation piles, one for each suit (shown in the top right in figure above), and indexed 1 through 4. These four piles are initially empty, and the goal of Freecell is to collect all cards of all suits in their respective foundation piles. A card can be added to a foundation pile if and only if its suit matches that of the pile, and its value is one more than that of the card currently on top of the pile (i.e. the next card in foundation pile 2 in the figure above can only be 3♣). If a foundation pile is currently empty, any ace can be added to it: there is no required ordering of suits in the foundation piles.
The second type of pile is the cascade pile (the eight piles in the bottom of figure above), also indexed starting from 1. Usually a game of Freecell has eight cascade piles, but our game will allow as few as four. Because the initial deal of the game is shuffled (see below), a cascade pile may initially contain cards in any order. However, a card from some pile can be moved to the end of a cascade pile if and only if its color is different from that of the currently last card, and its value is exactly one less than that of the currently last card (e.g. in the figure above, the next card in cascade pile 1 can be 4♦ or 4♥ while the next card in cascade pile 3 can be 10♠ or 10♣). This sequence of decreasing-value, alternating-color cards is called a build. (Different variants of Freecell, or other solitaire games, differ primarily in what builds are permitted.)
Finally, the third type of pile is the open pile (top left in figure above). Usually a game of Freecell has four open piles, but our game will allow as few as one. An open pile may contain at most one card. An open pile is usually used as a temporary buffer during the game to hold cards.
2.1.1 Game play
Play starts by dealing the full deck of 52 cards among the cascade piles, in a round-robin fashion (e.g. in a game with eight cascade piles, pile 1 will have card indices 0, 8, 16,..., pile 2 will have card indices 1, 9, 17, ... and so on (assuming card indices in the deck begin at 0). The player then moves cards between piles, obeying the rules above. The objective of the game is to collect all cards in the four foundation piles, at which stage the game is over.
In this homework you will write the model for this game. The model will maintain the game state and allow a client to start a game and move cards.
2.2 The Interface
You will implement the interface IFreeCellModel<K>
, where K
is
the card type. We deliberately make the interface generic over the card type,
because we do not know—
getDeck
should return a valid deck of cards that can be used to play a game of Freecell.startGame(List<K> deck, int numCascades, int numOpens, boolean shuffle)
takes a deck and starts a new game of Freecell with the specified number of cascade and open piles. If shuffled istrue
it shuffles the specified deck before dealing, otherwise it deals the deck as-is. This method throws anIllegalArgumentException
if any of the parameters are invalid.move(PileType sourceType, int sourcePileNumber, int cardIndex, PileType destinationType, int destPileNumber)
actually implements moving a card according to the rules.PileType
is an enumerated type with valuesOPEN
,FOUNDATION
andCASCADE
. It moves cards beginning at indexcardIndex
from the pile numbersourcePileNumber
of typesourceType
to the pile numberdestPileNumber
of typedestinationType
. All indices and pile numbers in this method start at 0 —e.g. in the figure above, to move 4♦ in cascade pile 8 to cascade pile 1 this method would be called as move(PileType.CASCADE, 7, 4, PileType.CASCADE, 0)
. This method throws anIllegalArgumentException
if the move is not possible.getGameState
returns the current state of the game as aString
object (see description below).isGameOver
returnstrue
if the game is over, andfalse
otherwise.
2.3 Examples
You must check that your FreeCellModel
implementation of the
IFreeCellModel
interface works as specified. Please review the
testing recommendations. Since you are
testing the public-facing behavior of this interface, following those
guidelines means that you should not place this testing code in
the cs3500.hw02
package, but rather place it in the default
package. Nothing in your tests should rely on implementation details at all.
2.4 Your Implementation
Implement the IFreeCellModel<K>
interface in a class called
FreeCellModel
. You will need to:
Design a representation for the game skeleton. Think carefully about what fields and types you need to represent the game state, and how possible values of the fields correspond to game states.
You are free to represent a single card in any way you wish, and the
getDeck
method must return a complete standard deck of playing cards (i.e. 52 cards with ace, 2, ..., jack, queen, king in each of clubs, diamonds, hearts and spades) as aList
of card objects.Validity constraints: A deck is invalid if it does not have 52 cards, or if there are duplicate cards, or if there are invalid cards (invalid suit or invalid number).
Printing each card: A client for your Card implementation should be able to use its
toString
method to print them. Cards should be printed as their value, followed by the suit symbol above: for example,"A♦"
or"3♣"
.Your choice of representation may significantly affect how easy or difficult it is to implement the necessary operations, and you will be graded on the appropriateness of this choice. (Hint: A good representation “properly” represents the data and entity that the class is supposed to represent, instead of being convenient for testing and debugging.)
Design the
startGame
method to start a new game of Freecell by distributing the deck in round-robin fashion as described above. It is possible that some cascade piles have more cards than others, and this is OK. This method should check if the inputs are valid (valid deck, at least 4 cascade piles and at least 1 open pile). If shuffle istrue
then the cascade piles should look different compared to if it wasfalse
.Design the
getGameState
method to print the game state in the format illustrated below. The details of the output are described in the Javadoc for this method in the provided code. The following shows the output corresponding to the screenshot above:F1: A♠ F2: A♣, 2♣ F3: A♥, 2♥ F4: O1: O2: O3: O4: 8♦ C1: K♠, 3♦, K♥, 8♠, 5♥, 5♦, 5♠ C2: 4♣, 5♣, 4♠, 9♦, K♣ C3: 2♠, 9♠, 8♣, 10♦, 8♥, Q♠, J♦ C4: 10♥, J♠, Q♦, 6♣, 3♣, J♣ C5: 6♦, 3♥, 10♠, Q♥, 6♠ C6: 9♣, 7♣, 7♥, K♦, 4♥, 3♠, 2♦ C7: Q♣, J♥, 10♣, 9♥ C8: 7♦, 7♠, 6♥, A♦, 4♦
NOTE: The string you return should not have a newline at the end of the last line, and there should be no spaces after the colons for O1, O2 or O3. (Try selecting and highlighting this text in your browser, to see exactly where the lines end.)
If a game has not begun,
getGameState
should return an empty string.Design the
move
method so that only one card can be moved at a time. This means that only the last card in a cascade pile can be moved to another pile.Implement a
public
default constructor (i.e., with zero arguments) for your class, and also any other constructors you think are needed. Reminder: A constructor is a good place to initialize any instance variables.
Be sure to properly document your code with Javadoc as appropriate. Method implementations that inherit Javadoc need not provide their own unless they implement something different or in addition to what is specified in the inherited documentation.
File Hw02TypeChecks.java
contains a small class designed to help you detect when your code
may not compile against the grading tests. In particular, if your
project cannot compile while Hw02TypeChecks.java
—
2.5 Testing
Read the testing recommendations again.
3 Package Management
To make sure that your packages are in the correct layout, you should tell IntelliJ to do the following. Do this early, before you’ve written much code, to ensure that your files wind up in the right locations automatically, instead of having to fix it afterward:
When you create a new project, you should see something like this:
Notice that the
src
directory is marked blue, which means IntelliJ believes that this directory contains the source files of your project. To create a new package, right-click on thesrc
directory, select New -> Package:In the dialog box that pops up, enter the new package name, e.g.
cs3500.hw02
.To create new files within a particular package, right-click on the package folder and select New -> Java Class. If you want to create a new file in the default package, then select the
src
directory itself.To create a test directory, right-click on the project itself, and select New -> Directory:
In the dialog box that pops up, enter “test” as the name. Notice that the directory is marked brown, meaning that it’s nothing special to IntelliJ, yet. Right-click on the directory, select Mark Directory As -> Test Sources root:
The directory will now turn green, indicating that IntelliJ believes your tests should live in this directory.
The
src
andtest
directories can parallel each other in structure. Conceptually, IntelliJ will combine them before compiling and running your tests, so if you have acs3500.hw02
package within thetest
directory, the files will effectively be in the same directory ascs3500.hw02
in thesrc
directory. However, keeping your sources and tests separated is always a good idea, so you don’t inadvertently release your tests as part of your source!
4 List of Deliverables
The interface (
IFreeCellModel.java
)Implementation of the interface(
FreeCellModel.java
)any additional classes you saw fit to write
tests in a JUnit test class, of course!
Again, please ensure all of your project’s source is in the cs3500.hw02
package. The autograder will give you an automatic 0 if it cannot compile your
code! You may place your tests in whichever package makes the most sense.
5 Grading Standards
For this assignment, you will be graded on
Whether your interface specifies all necessary operations with appropriate method signatures,
whether your code implements the specification (functional correctness),
the appropriateness of your chosen representation,
the clarity of your code,
the comprehensiveness of your test coverage, and
how well you follow the style guide.
6 Submission
Wait! Please read the assignment again and verify that you have not forgotten anything!
Please submit your homework to https://cs3500.ccs.neu.edu/ by the above deadline. Then be sure to complete your self evaluation by the second deadline.