Assignment 09

Task

Write a simple rock-paper-scissors (aka, jan-ken-pon) game that plays a single round. Place the game logic into its own class that is separate from the user interface details.

Textbook: 4.2
Concepts: Constants. Review of: OOP, encapsulation, conditionals, random numbers, methods, etc.

Steps

Step 1: RockPaperScissors

Write a class named RockPaperScissors. It should have the following contents.

First, declare the following public static final int constants: ROCK, PAPER, SCISSORS, WIN, LOSE, DRAW, and NO_MOVE. Assign unique int values to each of them. (If you want to, you can reuse some of the same values you assign to the first three constants when you assign values to the second three. However, NO_MOVE should not share a value with any of the other constants.)

Then you will need to write the bodies of the following constructor and three methods. The required behavior of each is defined in the javadocs given before the method. You should document your methods like this; you are welcome to just copy the /** javadocs */ provided here.

/**
 * A rock-paper-scissors game.
 * <p>
 * The rules are simple: the two players each choose one of the three
 * possible moves.  Then, paper beats rock, rock beats scissors, 
 * and scissors beats paper.  If the two moves are the same 
 *(for example, both rocks) then the game is a draw.
 * <p>
 * This class requires only that the current (human) player specify
 * a move.  The other (computer) player's move will be generated
 * randomly.
 *
 * @author ???
 */
public class RockPaperScissors {

  /**
   * Constructs a new rock-paper-scissors object that can 
   * play multiple games.
   */
  public RockPaperScissors()


  /**
   * Given a valid player's move, plays a single game,
   * updating game state  accordingly.
   * <p>
   * Playing a game involves the following steps:
   * <ul>
   * <li>randomly generating a computer move and storing it
   * <li>comparing the computer move to the player's move passed as an
   *     argument to this method and updating the stored game outcome
   *     accordingly. The game outcome is whether the player experienced
   *     a WIN, LOSE, or DRAW.
   * </ul>
   * <p>
   * If the passed player move is not valid (ie, not ROCK, PAPER, or 
   * SCISSORS), this method does nothing (that is, no state is changed) 
   * and returns false.  If the move is valid and a game is played, this 
   * method returns true.
   */
  public boolean play(int playerMove)

  
  /**
   * Returns the computer move used in the last game.
   * That is, returns the computer move generated and stored by the
   * last call to the play method.
   * <p>
   * This method does not generate a new move, so repeated
   * calls to this method will continue to return the same value until
   * the play method is called again.
   * <p>
   * Returns the value of one of the ROCK, PAPER, or SCISSORS constants.  
   * If this method is called on a new game object before play has been
   * called for the first time, returns NO_MOVE instead.
   */
  public int getComputerMove()


  /**
   * Returns the outcome for the player in the last game played.
   * That is, returns the outcome that was stored by the last call
   * to the play method.
   * <p>
   * This method does not generate a new outcome, so repeated
   * calls to this method will continue to return the same value until
   * the play method is called again.
   * <p>
   * Returns either WIN, LOSS, or DRAW.  If this method is
   * called on a new game object before play has been called for
   * the first time, returns NO_MOVE instead.
   */
  public int getOutcome() 
  
}

Declare any instance variables you need to produce the required behavior of these methods. Try to limit your instance variables to the smallest number possible, and remember to make them all private.

Hint: All of the game logic should be in the play method. All the get methods can be implemented in one line that simply return an expression or the value from an instance variable.

Optional: You may write additional methods if you like. For example, a method like this:

  public String getComputerMoveAsString() 

might be useful, especially once you start writing your main method in Step 3. This method would work like getComputerMove(), but it would return a String (such as "rock", "paper", or "scissors") rather than an int (such as one of the ROCK, PAPER, or SCISSORS constants).

Step 2: Test your game class

Test that your class meets the required specification. To help you do that, here is a tester class:

As always, your class should compile with this tester program. Have a look through the tester class to see what it is doing. As mentioned within the tester class, note that it does not test everything. You may want to write some additional tests or at least visually verify that your code meets these untested specifications.

Step 3: UsernameA09

Now write a text-based user interface that uses your RockPaperScissors class so that someone can play a single game.

This involves the following steps:

• Ask the user to enter a move, either as a string or as an int. (You only have to support one of these two input modes, though it is possible to support both.)

You can have them enter one of the words "rock", "paper", or "scissors". Sample prompt:

  Enter your move (rock, paper, or scissors):

Or you can instead print a short menu explaining how the numbers 1, 2 and 3 map to "rock", "paper", or "scissors", and then ask them to enter the corresponding number. Something like this would work (though you could possibly do it clearly in a single line instead). Sample menu prompt:

  1) Rock
  2) Paper
  3) Scissors
  Enter the number of your move:

• Once you have the player's move, play a game of RockPaperScissors.

• Print the computer's move as a string (not as a number!): either "rock", "paper", or "scissors" on its own labeled line. Sample:

  Computer's move: paper

• Then determine whether the human player experienced a win, loss, or draw.

When displaying output, you may refer to the computer as "computer", "PC", "I", or "my". You may refer to the player as "player", "user", "human", or "you". (Case does not matter.) Game outcomes can be "win", "won" or "wins"; "lose" or "lost"; or "tie" or "draw". Three sample outcomes:

  You won!

  The human loses.

  Tie game.

The game should only play one round and then quit.

What to Submit

Upload your UsernameA09.java file (including your working non-public RockPaperScissors class) to Tamarin.

When you submit, Tamarin will only perform a basic evaluation of your code. In lab next week (07 Oct), you will be assigned to groups and will evaluate each other's code in a code review session. Attendence is required for this lab. Also, to ensure we have some code to look at, you must turn in your A09 on time!

After that review session, you will have a chance to resubmit this assignment as UsernameA09r.java (note the r, for revised). You may only resubmit if you turned in A09 on-time. If you resubmit, your final grade for A09 will be your A09r grade. If you cannot or choose not to resubmit, your A09 grade will be your final grade.

After the A09r deadline, Tamarin will perform a more thorough grading of both A09 and A09r.

Grading [10 points]

1 - Compiles
Your program compiles successfully.
7 - RockPaperScissors
This class should have:
2 - UsernameA09
Your user interface (UI) is clear on what input is required (format, range, etc) (0.25).
Valid input is your choice of (1, 2 or 3) or ("rock", "paper", or "scissors") (0.5).
Program ends gracefully if input is invalid in any way (0.5).
Computer's move is labelled (using one of the allowed terms) and printed as a string (not a number) (0.5).
Prints the outcome of the game using one of the terms described above (0.25).

FAQs

Reminders
This question usually isn't asked, but based on past experience, I'd recommend you double-check these things before submitting:
Why do we need to define all these constants? How do we use them when writing our code?
Well, first of all, consider the best way to represent a move or a game outcome using one of the Java data types you know.

If the outcome was only win or lose, we could use a boolean variable to track it. However, there are 3 possible outcomes (4 including NO_MOVE). And there's also more than 2 possible moves. So we can't use boolean values to represent them.

Using Strings can get messy because comparing them tends to be error-prone. For example, "rock", " rock", "Rock", "ROCK" are not equivalent Strings. It's easy for small typos like this to cause bugs. Also, it's easy for beginning programmers to forget that you should always use .equals (not ==) to compare Strings, which can cause more bugs.

So the best choice here is to represent moves and outcomes using int values.

However, using ints quickly gets confusing. For example, within your RockPaperScissor class, you might have logic like this (shown here in pseudocode):

  if computer has 1:
    if player has 1:
      outcome is 2
    else if player has 2:
      outcome is 3
    else...

It's hard to spot any logic errors when using int literals like this. For example, do I have 2 and 3 in the right place when assigning an outcome here? Or did I get them backwards? It's hard to tell which values are supposed to correspond with which outcomes.

Similarly, what about when I go to use the RockPaperScissors class later? Suppose I do this in my main/user interface class:

   int result = rps.getOutcome();
   if (result == 2) {
     System.out.println("You won!");
   }

Did I do this correctly? Does 2 mean win? I don't know. I'd have to define somewhere in the documentation of RockPaperScissor or getOutcome() which int values correspond to which outcomes so that the programmer that later (re)uses the RockPaperScissors class will know what the returned values mean.

Constants solve both of these problems. First, it makes your own code within RockPaperScissors easier to read (and so easier to understand and write correctly). For example:

  if computer has ROCK:
    if player has ROCK:
      outcome is LOSE
    else if player has PAPER:
      outcome is DRAW
    else...

Now behind the scenes (ie, when this code compiles), all these constant variables just turn into int values, equivalent to the first example above. But using the constants instead of the literals makes it very easy to see any logic errors here: these outcomes are both wrong! They should be DRAW and WIN instead.

And now I don't have to tell the programmer that uses the RockPaperScissors class what the returned int values mean in terms of rocks or paper or winning or losing. I can just refer them to the constants I defined, and they can just use them too:

   int result = rps.getOutcome();
   if (result == RockPaperScissors.WIN) {
     System.out.println("You won!");
   }

(Since this code is outside the RockPaperScissors class, I do have to include the class name before the constant.)

Hopefully this shows you both the usefulness of constants and how to use them in your code.

In regard to public boolean play(int playerMove), I don't really get how to use boolean. Am I supposed to use an if here somehow?

The boolean here is your return value. You are supposed to return true if the playerMove was valid and you played a game. But you should return false if the playerMove was not valid. So you'll need an if statement to check if the playerMove value was illegal. But then, based on that, you'll return one of the two boolean literals: either true or false.

To illustrate, one way to do this is something like:

  public boolean play(int playerMove) {
    if (playerMove is not equal to ROCK, PAPER or SCISSORS) {
        return false;  //stops execution of the method right here
    }
    // The rest of your code goes here, which will only be reached if
    // the above if statement failed.
    // After playing the game, end the method with:
    return true;  
  }

Of course, you'll have to turn the condition given in this if statement into proper Java code first. And there are other valid ways to structure this:

  public boolean play(int playerMove) {
    if (playerMove is valid) {
      //play the game (most of the code in this method is here)
      return true;
    }else {
      return false;  //playerMove was invalid  
    }
  }

When you write methods, you always want their behavior to be well-defined for any possible values that might get passed to them. So the error-checking of playerMove should happen within the play method. On the bright side, that means you don't need to repeat that same testing code in your main/user interface class:

  //ask user for their move
  int move = keybd.nextInt();
  
  boolean worked = rps.play(move);
  if (worked) {
    //print computer's move and game outcome
  }else {
    //play returned false, so move entered by player must have been invalid.
    System.out.println("Sorry, but you did not enter a legal move.");
  }

(Note that this example assumes you're asking the user to enter ints rather than Strings and that the values of your ROCK, PAPER, and SCISSORS constants correspond to 1, 2, and 3.)