7.5

## 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!

### 1Purpose

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.)

### 2The game of Pyramid Solitaire

#### 2.1Context

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.1Game 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.

### 3Building 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.1Expected 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 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. 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 and hashCode 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 an IllegalArgumentException 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 an IllegalArgumentException 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() returns true if the game is over, and false 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.2Examples

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!

Implement the PyramidSolitaireModel interface in a class called BasicPyramidSolitaire:

1. 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.

2. 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, the Q♣ is at position (4, 1).

3. 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.

4. Encapsulation: Your BasicPyramidSolitaire class should not have any public fields, nor any public methods other than constructors and the public methods required by the PyramidSolitaireModel 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.javaunmodified!—is a part of it, then it won’t compile for the course staff either.

#### 3.4Rendering 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.5Testing

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!

### 4Package 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 the src folder and select Mark Directory As -> Sources root. To create a new package, right-click on the src directory, select New -> Package. In the dialog box that pops up, enter the new package name

• 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. 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/ and test/ 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!

### 5List 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.

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.

### 7Submission

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 — they’re not useful!

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.