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 (yuck!) C, but for now let's do it in English.

Speaking of English, you probably already did RPS in English. If you got that working and you saved it (or get it from the Done archive, then) use it here. Otherwise use mine (below). There will be some differences, but we are playing the same game, the same logic, just a different language.
 

Don't Get Too Creative

This is the second of four programs you already did in English, and you are translating them to Java as a way to become familiar with the peculiarities of Java. The important thing to remember is, Don't Make Unnecessary Changes. After you are comfortable with the conventional way to do Java, you can experiment with your own creativity. If your browser has tabs or separate windows, open a new tab (or window) with this link to The Six Things in Java, so you can refer to it as often as needed.

The purpose of this assignment (like the Guessing Game you just finished) is to become comfortable with Java syntax. It will take time. (Real Soon Now) We hope to have a compiler that will help you like the "Assume"s did in PBJ. Just get it working with no errors or "Assume"s.

Let me remind you that you are working with an early version of this tutorial, and if it's not clear to you what to do, or why the Replit (or BlueJ) compiler doesn't like what you gave it, that's my fault, not yours. Summon a Mentor, and two things will happen:

1. Somebody will come and work with you to get past the problem.

2. I will make the documentation better -- maybe next week, more likely next term. We need your help. Really.

-- or use the "Ask" feature in Zoom.

RPS Game Review

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 in the computer, the human player will type in the first letter of the thing they chose, and the computer will print out whatever it chose. Here's my English version:
 

Repeat
  OneRPSplay
  Next

"OneRPSplay"
  Synchronization 
  PresentPlays
  DecideWinner
  Done

"PresentPlays"
  Variable HumPlay
  Variable myPlay
  Random r < 3
  if r=0 then Let myPlay = "R"
  if r=1 then Let myPlay = "P"
  Otherwise Let myPlay = "S"
  Input HumPlay,1
  Print "I played " myPlay
  Print "you played " HumPlay
  Done

  "Synchronization"
  Print "One"
  Wait 1 second
  Print "Two"
  Wait 1 second
  Print "Three"
  Done

"DecideWinner"
  let score = 0
  if myPlay = "R" then if HumPlay = "P" then let score = 1
  if myPlay = "R" then if HumPlay = "S" then let score = -1
  if myPlay = "P" then if HumPlay = "R" then let score = -1
  if myPlay = "P" then if HumPlay = "S" then let score = 1
  if myPlay = "S" then if HumPlay = "P" then let score = -1
  if myPlay = "S" then if HumPlay = "R" then let score = 1
  print "(Score6)=" score
  if score>0 Print "You win"
  if score<0 Print "I win"
  otherwise Print "Tie"
  Done

Let's look at what's going on here for a few minutes, before we start coding in Java. Usually we begin by defining a Top-Down Design, but that was already done for the English program, and it gave us -- well, it gave me; yours might be different -- four subroutines, "OneRPSplay" called from inside a Repeat loop in the main program, and it calls each of the other three subroutines once in turn. Your code is probably very similar. The English program is now our "design" for Java, and we want to preserve how it works while changing the code to Java spelling.

The main program in English is whatever is not inside a subroutine, in our case just that three-line Repeat at the top. In Java the main program is another subroutine, but named "main".

In Replit, I suggest you get a clean clone ("fork") of the StartHere Repl from the NWAPW page, here:

https://replit.com/@NWAPW/#StartHere
then rename it "RPS" or "RockPaperScissors" or some such. It already has a "main" subroutine, all you need to do is replace the "REPLACE THIS LINE" line with the Java code necessary to do our Repeat. We'll get to that shortly.

We need to add four (or five, or however many you have) subroutines after the end of the main subroutine, but before the final brace of the whole program. I would insert some blank lines between those last two lines, then for each subroutine, add two extra lines with blank lines separating it from its neighbors, like this (for my first subroutine "OneRPSplay", you would use your own subroutine names, but if you have spaces in your names, either remove them or replace them with underscore '_' characters, because Java insists on one-word names):

  } //~main

  public static void OneRPSplay() {
  }

}

The final brace is of course still the end of the whole program, and the two lines you added are the subroutine header, up to and including its opening brace, followed by its closing brace on the second line. All the new Java subroutine code goes between those two braces in each case. This should now compile and Run (and still print out my "Hello World" message). Try it. If it fails, fix it now (get help from the Mentor if necessary).

Don't forget, Java subroutine names must be one word, so delete your spaces (or replace them with underscore '_').

Another way -- this is better if you have your whole English program inserted and commented out -- is every place you have a new subroutine...

...
  Done

"DecideWinner"
...

you replace those three lines (or leave them as comments) and insert...
...
  // Done
  }

// "DecideWinner"
public static void DecideWinner() {
...

Then of course you need to change all your subroutine calls from (for example) "Do DecideWinner" to {DecideWinner();} (you can leave the outer braces off, it works the same). If your English code has a subroutine name with no quotes and no "Do", it's still a subroutine call (English assumes the word "Do" if you left it off, but Java makes no assumptions like that) so you still make it a Java subroutine call.

Replit has what is called an "outlining" feature, little triangles in the margin on the first line of each subroutine, which if you click on it, closes the whole subroutine down to one line. Recall what we said about THE MAIN THING back when you did Seaman in English, that we want to focus on just one subroutine at a time; flipping those triangles into the closed positions helps with that.

Now let's think about variables. The English computer can often figure out what your variables are from how they got used, but Java insists that you declare every one of them. Find your variables and decide what data type(s) to use, then declare them in their respective subroutines, right after the subroutine header.

I don't know about your program, but two of my variables (HumPlay and myPlay) get used in more than the one subroutine they are declared in. Cut those declarations out of the subroutine(s) where they are declared, and paste them into the "class" part of the program, after the first line (with its brace) but before the main subroutine header, with the extra word 'static' in front, like this, shown in blue:

class Hello {
  static char HumPlay, myPlay;

  public static void main() { // (in StartHere)
  ...

Of course your variable names are probably different. In my program, the human and computer plays are single characters, so the data type char is appropriate. If you have full-word names "rock" and so on, then you need to use type String. However, Java has no native compare for the String type, you might consider changing your computer and human plays to a single letter (type char).

Each time you make a significant change, it's a good idea to test it with another Run. That way, if you make a mistake and the compiler error message doesn't make sense to you, at least you know what you changed since the last successful Run, and look for errors there. The word 'static' is required outside subroutines because this is not yet OOPS.

Except for the delay and the random number, you have already done everything in Java that is used in this program. So let's work on those two issues.

For dramatic effect, I would like the computer to print out the three numbers (you may have used words) 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("One... ");
  Thread.sleep(1000);
  System.out.print("Two... ");
  Thread.sleep(1000);
  System.out.print("Three... ");
Notice I used "System.out.print" (not println), so they all come out on the same line.

When I tried to compile this, I expected the compiler 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, 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 line at the front, before the first Thread.sleep call:

try {
and then another line at the end, after the last:
} 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, or rather 'r'/'p'/'s') before seeing the human play, so we do that next. In the English program the computer plays randomly (later you 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;" or maybe 'int r;" to match the English usage (in either case 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 "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:

  r = rn.nextInt(3);
  if (r==0) myPlay = 'r';
  else if (r==1) myPlay = 'p';
    else myPlay = 's'; // (r==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? ");
  HumPlay = Zystem.ReadLetter();
Don't forget that Zystem.ReadLetter does not capitalize the way the English Input command did, and that you will get the Enter key inputs also, so you need to allow for (and bypass) them. I suggest a while-loop, something like this:
while (true) {
  HumPlay = Zystem.ReadLetter();
  if (HumPlay >= 'A') break; // we got a letter (most likely)
  } // end of while = keep trying
The repeat in (my) main program is about the same, except there's no exit. Maybe you should look to see if the human player typed a 'q' and break if they do. That would exit the loop (still in the main program) and you could print out the total score (if you counted). The Java code for main looks something like this:
public static void main() { // (in StartHere)
  while (true) {
    OneRPSplay(); // calls the subroutine
    if (HumPlay == 'q') break;
    } // (end of while) otherwise do another play
  System.out.println("Final score..");
} // end of main


I think you can do the rest, it's just like what you have already done, right?

If you didn't make any mistakes, this should compile and run. If you did, the computer will tell you about them and you can fix them.

Next: Arrays

<<Previous | ToC | Next >>

Revised: 2023 February 3