Assignment 3: Playing with Cards, Part 2:   The Controller
1 Purpose
2 Preliminaries:   Improving your view
3 The Controller interface
4 The Controller implementation
5 Deliverables
6 Grading standards
7 Submission
7.5

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 — it should be very short!

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

  1. 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 the startGame method on the model. It should throw an IllegalArgumentException if the provided model is null. It should throw an IllegalStateException 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:

  1. Think about which additional fields and types it needs to implement the promised functionality.

  2. Design a constructor PyramidSolitaireTextualController(Readable rd, Appendable ap) throws IllegalArgumentException. Readable and Appendable are two existing interfaces in Java that abstract input and output respectively. The constructor should throw the IllegalArgumentException if and only if either of its arguments are null.

    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 the Appendable object by way of a PyramidSolitaireTextualView.

    Hint: Look at the Readable and Appendable interfaces to see how to read from and write to them. Ultimately you must figure out a way to transmit a String to an Appendable and read suitable data from a Readable object. The Scanner class will likely be useful, as will the lecture notes.

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

    1. Transmit game state to the Appendable object exactly as the view of the model provides it.

    2. Transmit "Score: N", replacing N with the actual score.

    3. 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 of rm1 7 7 should cause the controller to call the 2-argument remove method on your model with appropriate inputs (see note below).

      • rm2 followed by the row and card numbers of the cards. Example: an input of rm2 7 1 7 3 should cause the controller to call the 4-argument remove 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 of rmwd 1 7 4 should cause the controller to call the removeWithDraw method on your model with appropriate inputs.

      • dd followed by the number of the draw card to be discarded. Example: an input of dd 1 should cause the controller to call the discardDraw 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.

    4. 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 the Appendable 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 an IllegalArgumentException if a null model is passed to it. If the Appendable object is unable to transmit output or the Readable object is unable to provide inputs (for whatever reason), the playGame method should throw an IllegalStateException to its caller. The playGame 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:

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

6 Grading standards

For this assignment, you will be graded on

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.