Assignment 2: Playing with Cards, Part 1: The Model
Due: Thu 09/26 at 8:59pm; self-evaluation due Fri 09/27 at 9:59pm
Starter files: code.zip
Note: The description may make assignments seem longer than they are. Distilling the description to make a list of all the things you are actually supposed to do will go a long way in having a good plan to tackle it. Read the description several times to confirm this list before acting on it!
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.pyramidsolitaire.model.hw02
and
cs3500.pyramidsolitaire.view
packages. Note that the model package
refers to hw02
, and the view does not.
(See Package Management below.)
2 The game of Pyramid Solitaire
2.1 Context
In the next three assignments, you will implement a game called “Pyramid Solitaire.” This is a card game played by a single player. The image above shows a Pyramid Solitaire layout.
2.1.1 Game Play
A standard play of this game starts by shuffling a standard deck of 52 cards, and dealing them out into a 7-row triangle, where each card is partially covered by the two cards beneath it. The remaining cards are placed face-down in a pile called the stock. Some number of cards (typically three) from the stock are then turned face-up.
We say that a card is exposed if it is not covered by any cards in rows beneath it. We give cards values: number cards have value equal to their number, Jacks have value 11, Queens have value 12, and Kings have value 13. The primary rule of the game is: You are allowed to remove either one or two exposed cards, if the sum of their values is 13. (Therefore, you can remove a King by itself, or a Queen and ace, or a Jack and a two, etc.) You may also turn over one (or more depending on the variation) card from the stock, and try to use that to remove a card from the pyramid. The visible card(s) from the stock is called a “draw” card.
The score of the game is the sum of the values of all remaining cards in the pyramid. The goal of the game is to obtain the lowest score: a perfect zero means the player has eliminated all of the cards in the pyramid.
3 Building Pyramid Solitaire
In this assignment you will design the model for this game. The model will maintain the state of the game and update itself when a client specifies moves. You are not required to make the game playable by a user at this point: only you-the-programmer can manipulate the model right now, and there is no mechanism yet for you-the-player to actually specify moves and play the game.
3.1 Expected operations
In order to play the game, the client would expect the following operations:
start a new game, make a move, get the current state of the game, get the
current score and know when the game has ended. These operations have been
specified and documented in the provided PyramidSolitaireModel
interface.
You are not allowed to change the interface in any way!
A short explanation of most of the interface follows:
Because we do not specify how you are going to implement your cards, the interface is parameterized by a type
K
.1We deliberately do not name this parameterCard
: even if it might be easier to read, it would probably shadow yourCard
class (if you named your class as such), which would cause confusion. Additionally, we avoid the single letterC
since that has connotations of “C is for class,” and becomes more confusing the more fluent you get in Java. Your implementation class should specialize this type parameter to your particular card class.Your card implementation should behave like a proper “good citizen of Java,” and implement its own
toString
,equals
andhashCode
methods. (See below for some hints.)startGame(List<K> deck, boolean shuffle, int numRows, int numDraw)
slightly generalizes the standard description above, by letting the player specify the height of the pyramid and the number of cards to be displayed in the draw pile at any given time. Additionally, to make the game more easily testable, this method supplies a deck of cards to be used, and specifies whether the model should shuffle the cards before dealing them, or should use the order given by that deck.(Note that this functionality is more appropriately placed in a constructor rather than a method, but in order for our tests to run over your code, we unfortunately have to compromise slightly on the design here.)
remove(int row1, int card1, int row2, int card2)
is called to try to eliminate cards from the game. The card positions(row, card)
are counted starting from row 0 at the top of the pyramid, and card 0 at the left of each row. It will throw anIllegalArgumentException
if the move cannot be made.remove(int row, int card)
is called to try to eliminate a single card from the game. The card position(row, card)
is counted starting from row 0 at the top of the pyramid, and card 0 at the left of each row. It will throw anIllegalArgumentException
if the move cannot be made.removeUsingDraw(int drawIndex, int row, int card)
is called to try to eliminate a card from the pyramid in combination with a “draw” card. The draw card is specified by its index (which will always be zero when there can be only one draw card); the pyramid card is specified by its row and card as above.discardDraw(int drawIndex)
is called to discard the specified draw card and replace it with a new card from the top of the stock. If the stock is empty, the specified card is discarded and not replaced.K getCardAt(int row, int card)
returns the card at the specified coordinates.isGameOver()
returnstrue
if the game is over, andfalse
otherwise.getScore()
returns the current score in the game.
Clarification: when a draw card is discarded or used, it should be replaced with the next card from the stock pile. Any other draw cards should not be affected.
3.2 Examples
You must check that your BasicPyramidSolitaire
implementation of the
PyramidSolitaireModel
interface works as specified. We recommend that you
create an empty stub implementation, and then proceed to write your tests, all
before you start the actual implementation. This will allow you to
understand how your class will be used, which will help you to implement
it. 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.pyramidsolitaire.model.hw02
package, but rather place it in the default package.
Note: If you want to check that your model implementation passes
additional checks that are not entirely specified by the interface
(e.g., that getDeck
returns cards in a particular order), you should
write an additional test class that you would indeed place in the
cs3500.pyramidsolitaire.model.hw02
package. Be mindful of which test
cases you place in which test class!
3.3 Your Model Implementation
Implement the PyramidSolitaireModel
interface in a class called
BasicPyramidSolitaire
:
Design a suitable representation of this game. Think carefully about what fields and types you will need, and how possible values of the fields correspond to game states.
Positioning: A position is specified using a pair
(row, card)
, assuming that the pyramid is laid out from top to bottom and left to right. The row and card numbers start at 0 at the top of the pyramid and 0 at the left, increasing top to bottom and left to right respectively. For instance, in the example output shown below, theQ♣
is at position (4, 1).Instantiating the game: Your class should define at least one constructor with zero arguments, which initializes your game into a state that’s ready for someone to call
startGame
and begin playing. You may define whatever other constructors you wish; consider carefully all the methods you are expected to implement, and design your code to avoid as much duplication as possible.Encapsulation: Your
BasicPyramidSolitaire
class should not have any public fields, nor any public methods other than constructors and the public methods required by thePyramidSolitaireModel
interface.
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
—
3.4 Rendering the model in text
In order to see the contents of your model, while still keeping the
implementation details separate from the rendering details, you will create and
implement the following class in the cs3500.pyramidsolitaire.view
package:
public class PyramidSolitaireTextualView {
private final PyramidSolitaireModel<?> model;
// ... any other fields you need
public PyramidSolitaireTextualView(PyramidSolitaireModel<?> model) {
this.model = model;
}
@Override
public String toString() {
... render the model here
}
}
(Note the use of wildcards in the generics here. Your class should work for
any instance of the PyramidSolitareModel
, regardless of what class is
being used to represent cards. We could have created a type parameter for the class,
PyramidSolitaireTextualView<K>
, and then written
PyramidSolitaireModel<K>
instead. But this is subtly different, and
implies that users of the view class would need to know what class was
used to implement cards, and that’s a detail that just doesn’t matter. So
instead we can just use a ?
to indicate “there is some type here, but we
neither know nor care what it is.”)
There are four possible outputs for your toString
method:
If the game is not started, your
toString
should return the empty string""
, and nothing else.If the pyramid is emptied, your
toString
should simply return the string"You win!"
, and nothing else.If there are no remaining moves available (and therefore the game is over), your view should simply print
"Game over. Score: ##"
, where "##" is the current score of the pyramid.Otherwise, render the following. An individual card should be rendered as its value followed by its suit (which is one of the following four characters:
'♣' '♦' '♥' '♠'
). (Note: to incorporate these characters into your program, copy and paste them from this Web page into IntelliJ.) Each card should “visually” be three characters wide: for a one-digit card, you may need to add an extra space for padding. If a position is blank, you should fill it with spaces. There should be one blank space between each (3-character-wide) column of cards. If a row is completely empty, you should still render a blank line. Beneath the pyramid is the word"Draw: "
, followed by a comma-space-separated list of the card(s) currently available in the stock. The following shows a possible output, after some cards have been removed (the highlighting is there only to illustrate exactly where any spaces are):A♣ 2♣ 3♣ 4♣ 5♣ 6♣ 7♣ 8♣ 10♣ 10♥ J♣ Q♣ K♣ A♦ 2♦ 3♦ 4♦ 6♦ 7♦ 8♦ 9♦ 10♦ K♦ A♥ Draw: 5♥
NOTE: The text you produce should not have a newline at the end of the last line. Also, there should be no spaces after the last card in a row. Clarification: if no draw cards are available, you should eliminate the trailing space after
"Draw:
" as well.
3.5 Testing
Read the testing recommendations again.
Note: We give some of our test methods mnemonic names, so that you can try to deduce what our tests are checking for. Just because we have a test for a given scenario, though, does not mean that you shouldn’t write your own test case to confirm your understanding!
4 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. If it isn’t marked blue, you need to tell IntelliJ that it should be: right-click on thesrc
folder and select Mark Directory As -> Sources root. To create a new package, right-click on thesrc
directory, select New -> Package. In the dialog box that pops up, enter the new package nameTo 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. Right-click on the directory, select Mark Directory As -> Test Sources root. Henceforth, you should add any test classes in this folder. See the tutorial video for a demo of this.
The
src/
andtest/
directories can parallel each other in structure. 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!
5 List of Deliverables
The model interface (
PyramidSolitaireModel.java
)Implementation of the model interface (
BasicPyramidSolitaire.java
)Implementation of the view (
PyramidSolitaireTextualView.java
)Any additional classes you saw fit to write
Tests in one or more JUnit test classes
Again, please ensure all of your project’s sources are in the
cs3500.pyramidsolitaire.model.hw02
and
cs3500.pyramidsolitaire.view
packages, accordingly. Please ensure that
your project’s test cases are in the default package. Note that the model
package refers to hw02
, and the view does not. The autograder will give
you an automatic 0 if it cannot compile your code! Place your tests in the
default package.
6 Grading Standards
For this assignment, you will be graded on
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
how well you have documented your code
how well you follow the style guide.
7 Submission
Wait! Please read the assignment again and verify that you have not forgotten anything!
Please compress the src/
and test/
folders into a zip file and
submit it. After submission, check your submitted code to ensure that you see
two top-level folders: src/
and test/
. If you see anything else,
you did not create the zip file correctly! Please do not include your
output/
or .idea/
directories —
Please submit your assignment to https://handins.ccs.neu.edu/ by the above deadline. Then be sure to complete your self evaluation by the second deadline.
1We deliberately do
not name this parameter Card
: even if it might be easier to read,
it would probably shadow your Card
class (if you named your class as
such), which would cause confusion. Additionally, we avoid the single letter
C
since that has connotations of “C is for class,” and becomes more
confusing the more fluent you get in Java.