Assignment 3: Playing with Cards, Part 2: The Controller
Due: Mon 10/07 at 8:59pm. Self-evaluation due Tue 10/08 at 9:59pm
Starter files: code.zip
1 Purpose
The goal of this assignment is to practice writing a controller. While the model in a program represents the game state, the controller “runs” the program, effectively facilitating it through a sequence of operations. This assignment mimics the style of the previous assignment, in that you are provided an interface that you must implement and adequately test. The class you implement will act as the controller and work with the model that you created in Assignment 2: Playing with Cards, Part 1: The Model. This controller will “run” a game of Pyramid Solitaire, asking for input and outputting the game state. Since the game will still be text-based and the input/output will be limited to the console and keyboard, the notion of a “view” will be minimal in this assignment.
The only starter file is a type-checking file. You are expected to use your code
from Assignment 2 as the starting point for this assignment. However, please ensure
all of your new code is in the cs3500.pyramidsolitaire.controller
package. Additionally, your
code from Assignment 2 should remain in the
cs3500.pyramidsolitaire.model.hw02
and cs3500.pyramidsolitaire.view
packages.
2 Preliminaries: Improving your view
In Assignment 2, you implemented a PyramidSolitaireTextualView
class,
such that its toString
method provided the desired output. This was
not ideal design, but it was convenient at the time. For this
assignment, you will refine that view to be slightly more flexible. Enhance
your class such that it now implements the following interface, which you
should place in cs3500.pyramidsolitaire.view
package:
interface PyramidSolitaireView {
/**
* Renders a model in some manner (e.g. as text, or as graphics, etc.).
* @throws IOException if the rendering fails for some reason
*/
void render() throws IOException;
}
This view interface is tiny, but it abstracts away the idea that views are
intrinsically String
-based.
You should then add a second constructor to your
PyramidSolitaireTextualView
class, that takes in both a model and an
Appendable
(see below), and implement render
such that it appends
the current textual output to that Appendable
. You should
preserve your toString
method, since it is useful, but you should
also implement this render
method —
3 The Controller interface
The interface for the Pyramid Solitaire controller must support the following
functionality (as an interface PyramidSolitaireController
that you
should place in the cs3500.pyramidsolitaire.controller
package):
A method1Note the use of generic type parameters here. In section 4.3 on the previous assignment, we deliberately chose for your textual view to not know what concrete type you defined to implement cards. Here, it is possible that you may need to give a name to the type of cards (e.g. to iterate over them for some reason), so we give you a type parameter to do so.
<K> void playGame(PyramidSolitaireModel<K> model, List<K> deck, boolean shuffle, int numRows, int numDraw)
. This method should play a new game of Pyramid Solitaire using the provided model, using thestartGame
method on the model. It should throw anIllegalArgumentException
if the provided model isnull
. It should throw anIllegalStateException
only if the controller is unable to successfully receive input or transmit output, or if the game cannot be started. The nature of input/output will be an implementation detail (see below).
4 The Controller implementation
Design a class PyramidSolitaireTextualController
that implements the
PyramidSolitaireController
interface above (also in the
cs3500.pyramidsolitaire.controller
package). You will need to:
Think about which additional fields and types it needs to implement the promised functionality.
Design a constructor
PyramidSolitaireTextualController(Readable rd, Appendable ap) throws IllegalArgumentException
.Readable
andAppendable
are two existing interfaces in Java that abstract input and output respectively. The constructor should throw theIllegalArgumentException
if and only if either of its arguments arenull
.Your controller should accept and store these objects for doing input and output. Any input coming from the user will be received via the
Readable
object, and any output sent to the user should be written to theAppendable
object by way of aPyramidSolitaireTextualView
.Hint: Look at the
Readable
andAppendable
interfaces to see how to read from and write to them. Ultimately you must figure out a way to transmit aString
to anAppendable
and read suitable data from aReadable
object. TheScanner
class will likely be useful, as will the lecture notes.The
<K> void playGame(PyramidSolitaireModel<K> model, List<K> deck, boolean shuffle, int numRows, int numDraw)
method should play a game. It should “run” the game in the following sequence until the game is over.Note: Each transmission described below should end with a newline.
Transmit game state to the
Appendable
object exactly as the view of the model provides it.Transmit
"Score: N"
, replacing N with the actual score.If the game is ongoing (i.e. there is more user input and the user hasn’t quit yet), obtain the next user input from the
Readable
object. A user input consists of a “move” specified by a move type followed by a sequence of values (separated by any type of whitespace):rm1
followed by the row and card numbers of the card. Example: an input ofrm1 7 7
should cause the controller to call the 2-argumentremove
method on your model with appropriate inputs (see note below).rm2
followed by the row and card numbers of the cards. Example: an input ofrm2 7 1 7 3
should cause the controller to call the 4-argumentremove
method on your model with appropriate inputs.rmwd
followed by the number of the draw card and the row and card in the pyramid. Example: an input ofrmwd 1 7 4
should cause the controller to call theremoveWithDraw
method on your model with appropriate inputs.dd
followed by the number of the draw card to be discarded. Example: an input ofdd 1
should cause the controller to call thediscardDraw
method on your model with an appropriate input.
Note: To make the inputs more user-friendly, all row and card numbers in the input begin from 1. This will affect the inputs that your controller passes along to your model.
The controller will parse these inputs and pass the information on to the model to make the move. See below for more detail.
Note: this section has been rewritten to make the expected output clearer. If the game is over, the method should transmit the final game state one last time (which will either be the message
"You win!"
or the message"Game over. Score: N"
) The method should then end.
Key points:
Quitting: If at any point, the next value is either the letter
'q'
or the letter'Q'
, the controller should transmit the following in order: the message "Game quit!", the message "State of game when quit:", the current game state, and the message "Score: N" with N replaced by the final score. The method should then end. For example:Game quit! State of game when quit: 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♥ Score: 156
Bad inputs: If any individual value is unexpected (i.e. something other than
'q'
,'Q'
or a number) it should ask the user to re-enter that value again. For example, if the user is trying to make a two-card move, and has entered the row and column of the first card correctly, but entered the row of the second card incorrectly, the controller should continue attempting to read a value for that second card’s row before moving on to read the value for the second card’s column. You should behave similarly for the other commands. Once all the numbers are successfully read, if the model indicates the move is invalid, the controller should transmit a message to theAppendable
object saying"Invalid move. Play again. X"
where X is any informative message about why the move was invalid (all on one line), and resume waiting for valid input. Hint: You should probably design a helper method to retry reading inputs until you get a number or a'q'
/'Q'
. Using that helper consistently will make it much easier to implement the desired retrying behavior described here. That helper probably should not be responsible for determining if a number is a valid coordinate —that’s the model’s job — but that helper does need to return either the user’s number or their desired to quit the game. Think carefully about the signature of this method before you start implementing it... Error handling: The
playGame
method should throw anIllegalArgumentException
if anull
model is passed to it. If theAppendable
object is unable to transmit output or theReadable
object is unable to provide inputs (for whatever reason), theplayGame
method should throw anIllegalStateException
to its caller. TheplayGame
method must not throw any other exceptions, nor should it propagate any exceptions thrown by the model.Write sufficient tests to be confident that your code is correct. Note: once the model has been tested thoroughly (which you hopefully did in Assignment 2), all that remains to be tested is whether the controller works correctly in all cases. The lecture notes on mock objects will be essential here.
Be sure to properly document your code with Javadoc as appropriate. Method implementations that inherit Javadoc need not provide their own unless their contract differs from the inherited documentation.
Added: if you had to change your implementation from Assignment 2, please document your changes in a README file that explains what you changed and why. This doesn’t have to be long; a simple bullet-point list will suffice. But having this documentation will make your TAs’ grading job a lot easier!
5 Deliverables
At a minimum, we need the following files, in their appropriate directories:
The model interface (
PyramidSolitaireModel.java
)Your implementation of the model (
BasicPyramidSolitaire.java
)The view interface (
PyramidSolitaireView.java
)Your implementation of the view (
PyramidSolitaireTextualView.java
)The controller interface (
PyramidSolitaireController.java
)Your implementation of the controller (
PyramidSolitaireTextualController.java
)Any additional classes necessary to compile your code
Tests for all your implementations in one or more JUnit test classes. You should include at least all your tests from Assignment 2, and add to them...
A brief README file explaining what changes from Assignment 2 you made, and why.
As with Assignment 2, please submit a zip containing only the src/
and
test/
directories with no surrounding directories, so that the
autograder recognizes your package structure. Please do not include your
output/
or .idea/
directories —
6 Grading standards
For this assignment, you will be graded on
Whether your interfaces specify all necessary operations with appropriate method signatures,
Whether your code implements the specifications (functional correctness),
The clarity of your code,
How well your code follows the design principles discussed so far,
The comprehensiveness of your test coverage, and
How well you follow the style guide.
7 Submission
Please submit your homework by the above deadline. Then be sure to complete your self evaluation by the second deadline.
1Note the use of generic type parameters here. In section 4.3 on the previous assignment, we deliberately chose for your textual view to not know what concrete type you defined to implement cards. Here, it is possible that you may need to give a name to the type of cards (e.g. to iterate over them for some reason), so we give you a type parameter to do so.