Learn Programming in Java


<<Previous | ToC | Next >>

Rock-Paper-Scissors

Always the first thing to do in programming any new program is to think about what you want it to do. When you have programmed as long as I have, you can think in Java or Python or even (yuk!) C, but for now let's do it in English.

Speaking of English, maybe you already did RPS in Tom's Kitchen Computer version of English. If you got that working, then you are halfway there. There will be some differences, but we are playing the same game, just a different language.

Each player -- both simultaneously -- chooses one of three things to shape their hand to model: a fist for rock, the hand flat, palm down for paper, or the first two fingers extended and open in a "V" like a pair of scissors. When people do it, they synchronize by pounding their fist on the other open hand three times, and the third time they open up the fist into either paper or scissors (or leave it a fist), both at the same time.

Scoring is simple: Paper covers (wins against) rock, scissors cut (wins against) paper, and rock breaks (wins against) scissors. If both players chose the same thing, it's a tie (no score).

In the computer, scoring is easy: we compare the two plays, and if they are different, whichever wins that hand gets their score increased.

Without hands and eyes, the computer will type out the name of the thing it chose, and the human player will type in the first letter of the thing they chose. We can program the computer not to cheat, so the sequence is this:

1. The computer prints out the sequence "1... 2... 3..." (for effect, it means nothing ;-) then makes its choice and then prints a question mark "?" to prompt the human to enter their choice.

2. The human types in 'r' or 'p' or 's' (and enter) for their play.

3. The computer prints out the score, then starts over with #1. This would be in a loop.

4. The computer could do this all day, it never gets tired, but the human could instead type in 'q' to quit or 'b' to erase the scores back to 0 and begin again.

I always declare my variables at the front, so I can find them when the program gets big; Java rules are a little inconsistent, but putting the variables at the front is still a good idea. What do we need for variables?

Obviously, we need a score (number) for each, the computer and the human, call them "computerS" and "humanS" and initialized =0. And we need a (character, declared type "char") variable for each, to hold the computer's and human's plays, call them "cplay" and "hplay". Can you do that? You can give them any names you want, this is your program, just remember to use the same names in the rest of the program.

Then we need a "while (true) { ... }" loop to wrap around the game play (we will put the rest of the game where those three dots are). All this (including the variable declarations) goes in place of the "REPLACE THIS LINE" line we have been replacing in main(). Or you can make a new class (with a new name and a new main() routine) the way we did for the guessing game. Don't try to run it, there's no way out of the loop yet.

Because we have already thought carefully about this game, and we know that both scores start out zero, and later in the game the user might type in the command 'b' to start over with zero scores, at the front of the loop we could test for zero scores, and if so, print out a greeting:

while (true) {
  if (humanS==0) if (computerS==0)
    System.out.println("Welcome to Rock-Paper-Scissors.");
  System.out.println("Here we go...");


Because the scores are never negative, you could add them together and test them both in a single "if (humanS+computerS==0)" with the same effect.

Now for the synchronization part. For dramatic effect, I would like the computer to print out its three numbers at one-second intervals. I Googled "Java delay" (no quotes) and found several answers, but the simplest seems to be "Thread.sleep(milliseconds)" which I found here. Later we can learn about threads, but for now, this just works:

  Thread.sleep(1000);
  System.out.print("1... ");
  Thread.sleep(1000);
  System.out.print("2... ");
  Thread.sleep(1000);
  System.out.print("3... ");
Notice I used "System.out.print" so they all come out on the same line.

When I tried to compile this, I expected BlueJ to tell me that it doesn't know about class Thread. That didn't happen, which explains why I couldn't find any help or teaching websites that said how to "import" it. Instead, BlueJ (actually Java) complained about exceptions. I planned that topic for a later session (see "Exceptions" and maybe also "Threads" in the "Things You Need to Know" page), and at least one website said you don't need to worry about it unless you have other threads running (you don't). I solved it by adding one word at the front, on the same line as the outer while loop:

while (true) try {
and a little more at the end of the same loop:
} catch (Exception ex) {}
Exceptions are a convenient way to deal with rare catastrophes in robust software. They mostly don't happen in carefully planned pedagogical programs like you will get from me. You need to know about exceptions, but not today. Back to the main topic...

To be perfectly fair, the computer needs to make its choice (rock, paper, or scissors) before seeing the human play, so we do that next. Let's have the computer play randomly (later we might try some strategy to anticipate the human play). I Googled "Java random integer" and StackOverflow almost always has the best answers, in this case for a random integer between 1 and 10:
Random rn = new Random();
int answer = rn.nextInt(10) + 1;
Obviously we are interested in only three values (instead of ten) so I'd put 3 inside the parentheses. The "+1" is also unnecessary, its purpose is to raise the output from 0..9 to 1..10, and we don't care about that, we just need to deal with the three numbers we get, which will be 0, 1, 2. Notice that both of these lines are declarations, so we want to put them at the top with the other declarations. Except we don't need that random number until the computer is ready to decide its play, so declare the integer variable as "int answer;" (uninitialized).

Random is a Java library class (like Thread), which not everybody wants to tell you what to import. Whenever you see in one of the help sites, a Java class name like Thread or Random (they always start with a capital letter), you can expect that you probably will need to import it. You will be looking for a line that starts with "import" and ends with the class you are trying to use, in this case "Random;". I suppose you could just guess and randomly try different standard Java class path names, but there are so many of them! I Googled "Java random import" and the correct spelling of the import line came up in the first hit. What you need at the front of your program (at the very top, before the green box "public class Hello" line) is

import java.util.Random;


So now, after printing out the third synchronization number, we query the random number generator for an answer to make the computer's choice:

  answer = rn.nextInt(3);
  if (answer==0) cplay = 'r';
  else if (answer==1) cplay = 'p';
    else cplay = 's'; // (answer==2)


OK, the computer has decided its own play, it's ready to ask for the human play. You remember Zystem.ReadLetter() in the guessing game? We can use it again here:

  System.out.print("Your play? ");
  hplay = Zystem.ReadLetter();


We are only interested in one of five inputs here, letters 'r', 'p', 's' for the human play, and 'b' or 'q' to start over or quit. What happens if the user types something different? The programmer must always be thinking of these kinds of contingencies. I would wrap another loop around the input, and break out if it's one of the desired five:

  while (true) {
    System.out.print("Your play? ");
    hplay = Zystem.ReadLetter();
    if (hplay == 'r') break;
    if (hplay == 'p') break;
    if (hplay == 's') break;
    if (hplay == 'b') break;
    if (hplay == 'q') break;
    System.out.println("Please choose a game play (r/p/s)");
    System.out.print("  or else b to start over or q to quit. ");
    } // (end of user input while-loop)


If the user decided to quit or restart, we can do that immediately after the (inner) loop:

  if (hplay == 'q') break; // exit the outer while-loop
  if (hplay == 'b') {
    computerS = 0;
    humanS = 0;
    continue;} // go back to the beginning
Next we print out the computer's choice and decide the scoring:
  System.out.print("I played ");
  if (cplay == 'r') System.out.print("rock,");
  else if (cplay == 'p') System.out.print("paper,");
    else System.out.print("scissors,");
  System.out.print(" you played ");
  ... (you can do this part, right?)
  if (cplay == 'r') if (hplay == 's') {
    System.out.print(" Rock breaks scissors.");
    computerS++;}
  if (hplay == 'r') if (cplay == 's') {
    System.out.print(" Rock breaks scissors.");
    humanS++;}
  ... (there are four more scoring combinations, you can do that, right?)


Notice that nothing needs to be done for the non-scoring plays -- unless you want to. Then finally, print out the score and go back for the next play...

  System.out.println(" Score, you: " + humanS + " me: " + computerS);
} // end of outer while-loop


If you didn't make any mistakes, this should compile and run. If you did, well the computer will tell you about them and you can fix them. I certainly made enough mistakes in laying this out (but I fixed them before you saw it ;-) Oops, there's one I didn't catch: The Java compiler will probably tell you about it if you take that last line as the end. What is missing there?
 

Next: Arrays

<<Previous | ToC | Next >>

Revised: 2021 June 1