Java:Assignment - Guessing game

From Juneday education
Jump to: navigation, search

Assignment 1 - Guessing game

Notes to teachers, supervisors and tutors

Expand using link to the right to see the full content.

Goals with the assignment

The first assignment is aimed at giving the students a task which is simple enough for everyone to successfully finish, while still constituting a piece of software which actually does something and contains realistic parts which structurally resembles an object oriented design of a simple game.

The game is a simple guessing game where the player thinks of a number in some interval. The computer makes educated guesses by asking if its guess is less than or equal to the number the player is thinking of. The player answers (truthfully) T (for true) if the question is correct, and, F (for false) otherwise. Based on this input, the computer uses binary search to zoom in on the number the player is thinking of.

The assignment is constructed so that the student gets an almost complete program but is tasked to complete the missing parts. The instructions are meant to guide the student through this process.

The idea behind making the first assignment easy enough for every student to successfully complete, is to strengthen the student’s self-confidence while still conveying useful insights and techniques for programming in Java. The decision for choosing an interactive game as the program for the assignment, is based on our belief that this gives some meaning to the assignment since the student upon completion can run the software and see it work using different numbers for the computer to “guess”. We think this creates an interest for what else a computer program can do. We have noticed that students find batch-like programs to be strange. A typical batch-like program creates a few objects of some class, typically supplying different state to the objects via the constructor. Then the program asks the objects about their state, and print the state to the screen:

Person p1 = new Person("James");
Person p2 = new Person("Lisa");
System.out.println("p1 is called " + p1.name());
System.out.println("p2 is called " + p2.name());

A typical comments from students regarding the above code is “Yes, but I knew what the names of p1 and p2 were, I created the objects myself on the lines immediately before printing this!”

The only realistic scenario for batch programs as the above is when you write test code (which is part of this lab as a suggestion in one of the hints).

We have made an effort for making also the code and design of the program interesting for the students who want to learn from reading code and trying to understand the whole program.

The decision to make the topic about a game

We have consciously avoided more technically or mathematically focused topics (like prime number generators, simple compression or encryption algorithms, sorting algorithms etc) since it is our firm belief that such topics both could be mathematically and/or technically challenging while at the same time giving an unjustified impression of programming as always being a theoretical and technical activity. This could turn some students off. Note that the binary search used for the guessing part (which is already implemented for the students) contains some technical and mathematical aspects which might be inspiring for those students who would not be discouraged by a more technical assignment.

We have also avoided (what we have found to be) classical assignments of reading data from a file, manipulating the data and then saving it back to a file. Such file-based assignments tend, in our opinion, to be challenging to students for the parts doing file i/o and exception handling compared to the rest of the program. Also, such assignments, we find, tend to be quite mechanical and not the type of programs that the student actually would find interesting to run. Some examples of such assignments include reading a stock inventory file, generate some reports and save them on disk, reading a data file and printing out statistical findings about the data, etc. While such programs have some real life applicability, they are not much fun to run over and over.

For students who find the assignment too easy or not challenging enough, we have a number of non-mandatory optional tasks at the end.

Things we predict will be hard for the students

A classic problem when faced with the first assignment is that it is a lot of text to read and understand. The first question usually is “What should we do?” or “What is the task?”. So be prepared that at least some students will have a hard time getting started because they don’t understand what the assignment is about. The solution to this problem usually is to find a way to encourage the students to read the assignment once again. And then, convince them to start with the first task and work in small increments.

A thing about the game logic which may be hard for some students to understand is the concept of “less than or equal to”. If the player is thinking of the number N and the game asks “Is your number less than or equal to N?” it is possible that some students will miss the “or equal to” part and answer “F” (erroneously, since N is <= N, indeed). This is important that all students understand however, since comparisons usually are central to e.g. the for-statement.

The very first task is to create a constructor from scratch, and the next few tasks are about completing incomplete methods. A problem for the students here, will be to know if they are done with a sub task. For instance, how do they know that they have completed the first task and written a working constructor? In the hints section for the first task, we encourage the students to write a toString() method for Guesser which returns information about the two instance variables low and high (which should be given values via the constructor parameters). Then they can use the toString() to verify that the constructor really saves the parameters correctly. But before they even venture into writing tests, they should be encouraged to make sure that the code they write actually compiles. This, our experience tells us, cannot be stressed enough. Encourage the students to save the source code and compile, and do so often! If they change something, they should make sure that the program compiles, so that they don’t test an old compiled version. The good thing about compiling often, is that small mistakes quickly are caught by the compiler, and knowing how to read compilation errors is a very useful skill.

Students who have great difficulty to write a simple constructor as the one in task 1, should be encouraged to review the chapters “Objects in Java” and in particular “Classes”. If watching the video lectures and doing the exercises again doesn’t help, they need some face time with a tutor. Make sure to take notes about all the difficulties the students have during the assignments, so that you are prepared to customize any repetition sessions you may plan to give before the written exam. Supervision (live or via email) is the best source for tuning extra lectures and repetition sessions before the exam!

Task 2 is very simple. Here they are supposed to finish the start() method, which is very simple. The start() method should simply call, in order, rules() and doGuesses(). Once again, the students might wonder if they are done and have succeeded. This time it is simpler, if they have succeeded, the Guesser.java file not only compiles, it should be possible to write a test which creates a Guesser object and invokes start(). The whole game should start but since the getReply() method isn’t implemented yet, the game logic will be flawed (do guesses will interpret anything the user inputs as F (false). This last part may be the source of some confusion: How can the game compile and run, but still not work the way it is planned?

Task 3 is probably the most challenging part. Here, the student should complete the getReply() method so that it reads a T or an F from the user (and keeps asking for valid input until the user manages to actually answer T or F!). There are a few parts of this method which we predict will be hard for the students. First, it is to understand the role and function of the method. Be prepared that not all students immediately will understand this. The method’s role is to encapsulate user input and validation, and return input only when the user has provided valid input. Historically, we have seen that students have a hard time to grasp the concept of a method “returning” a value. Many students are used to see methods which only print stuff to standard out, and think that that is the normal way of “returning”. We have tried hard to better explain the difference between a side effect such as printing and a proper return statement that returns a value from some expression. Now, the getReply() method is tricky, because it does many things (the students may think). It reads input into a variable, but if the input is not valid, it also writes an error message back to the user. Make sure the students understand that these are just steps on the way to get a valid value (a “T” or an “F”) from the user. Only when this is accomplished, the method returns that value back to the caller.

The other thing which probably will be hard for some students with this method, is the logic for continuously asking for a value if the value last given is not valid.

We have provided some code examples and pseudocode to help with this logic. We suggest the following simple logic:

  • read input into variable
  • while input is invalid (input is not “T” and input is not “F”) // whatabout null?
    • write error messge
    • read input again into the variable
  • return the variable

Here, the students will be faced with a number of challenges. Do they understand the logic behind our suggested pseudocode? Are they comfortable with the syntax and semantics of the while-loop? Do they understand how to check validity, that is, to check that invalid input means that the variable is not “T” AND the variable is not “F”? Do they know how to handle null in relation to the equals() method? Do they understand that the user may produce a null value (by pressing Ctrl-D)? We still think that this method is good to have even in the first assignment. It shows the students the importance of reviewing previous chapters and section (while, methods, return-statement, null, equals) yet it is not extremely hard to write. But be generous to the students during supervision and facetime! This assignment is also meant to give the students a feeling of accomplishment, not a do-or-die assignment meant to flunk the weaker students.

To test that the getReply() method works, the students need to run start() on a Guesser object, which in turn will run rules() and doGuesses(). It is doGuesses() which uses (calls) the getReply() method. Encourage the students to run it like this and try with various invalid input (replies other than “T” or “F”). Encourage the students to play around with null and equals, both null as a literal value and null produced by readLine() in combination with Ctrl-D.

Task 4 is probably quite straight forward. Once the students have managed to get a fully functional Guesser class, they should write a game program in a stand-alone class (with only a main method to begin with). The main method of the program should simply create a Guesser object with the parameters (0, 1000). Then invoke the start() method and let the game begin. This time, the class they are writing should be called simply GuessingGame.

We don’t think this task will be hard for any of the students. The only challenge, as we see it, is to get the signature of the main method right, make sure the class name corresponds with the file name, and that the classpath allows the program to use Guesser (if they are not put in explicit packages, and are in the same directory this wouldn’t be a problem).

Task 5 is a continuation of the task 4. Now, the students should enhance their program so that the user running the program provides the values for low and high (the interval of the possible numbers, and the arguments to the constructor). This is a recap from some of the exercises using the args array in main.

The things we predict will be a problem here, is to understand that Integer.parseInt will throw an exception if it cannot parse the argument. The students are not required to handle this in the mandatory parts, but the optional more challenging tasks include fixing this weakness. Another thing which is bound to happen, is that the students run the program with 0 or 1 arguments, and get an array indexing exception (ArrayIndexOutOfBounds). This will also be dealt with in the optional more challenging tasks. If the students encounter any of this, explain to them what happened and encourage them to do at least the first few optional tasks, or show them how to give valid arguments.

What the assignment is about

This first assignment is about writing a simple interactive game. We have provided most of the code, but there are missing parts we need you to complete and write for us.

Description of the game

Your task is to complete code for a game where the player thinks of a number in some interval, e.g. a number between 0 and 1000 inclusive. The game works like this: The player thinks of a number in the given interval, and the computer tries to guess what number the player is thinking of. The computer will try to use as few guesses as possible. In order to make good guesses, the computer asks the player questions about its guesses.

If the player is thinking of the number 333, the computer might start with a guess of 500, but ask the player “Is your number less than or equal to 500?”. The player must then answer truthfully by entering “T” for true (it is true that 333 is less than or equal to 500) or “F” for false.

When the computer is certain to have the correct guess, it will simply notify the user by outputting something like this: “You were thinking about 333 (took me 10 guesses)”.

The game software consists of a class Guesser which does all the job of talking to the player and making the questions until it finds the number. But the Guesser class is not complete and you need to write the missing parts for it to be complete. When the class is complete, you need to write a small class with a main method (a program) which creates a Guesser instance and starts the game.

Therefor, the first part of the assignment is to complete the code for the Guesser class, and the second part is to write a small program (a Java class with a main method) which uses the Guesser class to create and use a Guesser object.

Example run of the game

Here’s an example run where the player is thinking of 333:

Think of a number between 0 and 1000
I'm going to ask a few questions in order to guess the number.
Please answer T for true, and F for false
Is the number less than or equal to 500?
T
Is the number less than or equal to 250?
F
Is the number less than or equal to 375?
T
Is the number less than or equal to 313?
F
Is the number less than or equal to 344?
T
Is the number less than or equal to 329?
F
Is the number less than or equal to 337?
T
Is the number less than or equal to 333?
T
Is the number less than or equal to 331?
F
Is the number less than or equal to 332?
F
You were thinking about 333 (took me 10 guesses)

API for the Guesser class

Guesser class diagram

The Guesser class will have a very simple API. A public constructor which takes two arguments (lower bound and upper bound), and only one public instance method, start(). The start() method will start the game.

Internal logic of the class

The finished class will have the following logic: First an instance is created with the lower bound and upper bound passed to the instance via the constructor. The constructor saves these values in instance variables called low and high. Next, the user of the instance calls the method start(). This method does two things: First it calls a private helper method which prints out the rules for the game to standard out. Next, it calls the private doGuesses() method, which calculates the first question and guess. The doGuesses() method has in turn a private helper method for reading the reply (T or F) from the user. When the doGuesses() method is sure it has the correct number, it prints a message to standard out and the method exits (the game is finished). A guesser object

The top part of the object is the public API - the constructor and the start() method. The lower part is private helper methods (called from start() ).

Unfinished source code

You can download the source code for the assignment here. The source code is not finished, and it is your task to complete it into a fully functional application.

The first part of the assignment is to complete the Guesser class. Later you will write a small program which uses the Guesser class.

Task 1 - write a constructor for the class

The idea behind the design of the Guesser class is that users of the class can construct a guesser object using a constructor to which the user of the class passes a lower bound and an upper bound for the interval of the possible numbers where the Guesser object will try to find the number the player of the game is thinking of.

The class declares two int instance variables, low and high, where the Guesser object will keep track of the initial lower and upper bound. If users of this class wants a game where the legal numbers are between 0 and 1000, the constructor should accept these two values and store them in low and high, respectively.

Look at the source code file (Guesser.java) for a comment indicating where you should write this constructor.

When you have written this constructor, you should be able to write test code to verify that the constructor exists. You can do this in a new class for the actual game program. We suggest that you create a class called GuessingGame (in a file called GuessingGame.java) with just a main method (public static void main(String[] args){ ... some code ...} ).

In the main method of your new class, verify that you can create a Guesser instance by calling the constructor with the arguments 0 and 1000:

 Guesser guesser = new Guesser(0, 1000);

If the GuessingGame.java compiles, then you know that your call to the constructors are valid (at least according to the compiler, which is an important first step - it is good to compile often, so that you quickly spot any syntactic errors).

Hints for Task 1

If you don’t remember how to write code for a constructor, please revisit the chapter Classes and in particular the section about defining constructors.

Remember that if the parameters (formal arguments) to the constructor have the same name as some instance variables, you can use the operator this to access the instance variable. For instance, this.low would signify the instance variable low. We recommend that you always use the operator “this” when accessing instance variables (inside the class where they are declared!), to be extra clear.

Stuff to think about (for Task 1)

The parameters to the constructor should be used for the initial interval of possible numbers. What would happen if a caller of the constructor swapped the order of the arguments? Would the code work for an interval of 1000...0 ?

Whose responsibility should it be to check that the parameters are valid? The caller’s or the constructor’s?

One way for a constructor (or method) to reject arguments, is to throw an IllegalArgumentException if the parameters are not meaningful. To reject invalid arguments for the constructor (in the case low is not less than high), you may use a statement such as this:

throw new IllegalArgumentException("The first parameter must be lower than the second parameter");

You would have to check whether it is the case first, of course, using a simple if statement.

In this lab, it is enough that the caller checks the validity of the arguments, so you don’t have to check this in the constructor of Guesser. You don’t have to do the check in the constructor if you don’t want to.

How do we know the constructor works? One way to test that the constructor not only compiles, but also saves the arguments for low and high, is to add a toString() method to the class. The toString() method could return a (reference to a) String which contains high and low and look like this:

public String toString(){
  return "low: " + low + " high: " + high;
}

Then create a test class called TestGuesser (in TestGuesser.java) with a main method which only creates an instance (or several) of Guesser and passes different values to the constructor and after each such creation prints the object by passing a reference to it to System.out.println() and you could verify that the output matches what you expected. Such a test could help using similar techniques for the other methods you are about to complete later in the assignment. Write separate tests for the separate methods you want to test.

A different test approach for the TestGuesser (if you choose to write a test at all) is to test if the toString() method returns what you expected using code:

if(p1.toString().equals("low: 0 high: 1000")){
  System.out.println("Test with 0, 1000 passed!");
}else{
  System.err.println("Test with 0, 1000 failed!");
}

Or, you can use our test program here to test that the constructor works.

Task 2 - complete the start() method

If you look at the source code in Guesser.java you will also see a comment indicating that you should complete the now empty start() method. This task should be really simple. You need only two statements to complete the start() method; first a call to the rules() method, then a call to the doGuesses() method.

Hints (for Task 2)

If you don’t remember how to call a method, please review the Objects in Java chapter. You need to look at the rules() and doGuesses() methods, in order to see if you need any arguments in order to call them.

Stuff to think about (for Task 2)

It is common for methods to call other methods. The start() method serves as a simple API for users of the Guesser class. The idea with this method is that it should be easy to use an object of class Guesser - create the object (instance) using the constructor and pass along the lower and upper bound for valid numbers. Next, just call start() without any arguments and let the game begin.

To start the game, the object needs to do two things. First show the rules to the player. This is done in a separate, private helper method called rules(). It should be called as the first instruction in start(). Next, it starts to do the guessing, which is done by calling a second private helper method called doGuesses(). Both rules() and doGuesses() are hidden from users of objects of this class, since they are private. The call to doGuesses() should be the second and last thing that happens in the start() method.

When doGuesses() is called from start(), control is passed to this method. Inside doGuesses(), values to ask the player questions about are calculated and printed to standard out. In order to get a reply back from the player (the player is supposed to type in T or F), doGuesses() uses a private helper method (which is not fully implemented but with your help it will be, in a task later on) called getReply() which reads a String from the console, and if the String is one of “T” or “F”, this String is passed back to doGuesses() as a String reference.

When doGuesses() has found the correct number, doGuesses() is finished and the JVM returns control to the method start() again. The start() method, however, has no more statements, so it too returns and control goes back to the code which called start() - in this case it will be the GuessingGame main method, which you will write in a later task (if you didn't write it for testing the constructor in Task 1).

Task 3 - finish the getReply() method

This method is also incomplete and you must implement it. The purpose of getReply() is to handle the input from the console (the player uses the keyboard to give answers), check that the input is valid and return the valid input to the caller (the caller is the doGuesses() method but that does not matter when you write the method).

Hints (for Task 3)

To read a String from the console, you may use the following code (in Windows CMD, Mac OS and GNU/Linux):

System.console().readLine()

That call reads a line of text and returns it. So it can be used as an expression of type “reference to String”. This in turn, means that you can use the call in an assignment, for instance as in this snippet (this is just an example - again for Windows CMD, Mac OS and GNU/Linux):

 String name;
 System.out.println("What is your name?");
 name = System.console().readLine();

If you are running Eclipse or cygwin, there is no System.console unfortunately. So you'll have to use something else, like a Scanner object.

Here's the code for using a Scanner which reads from System.in:

  String name;
  System.out.println("What is your name?");
  name = new Scanner(System.in).nextLine();

After the code in the example above (if it occurred in some program), the variable name would be assigned a reference to a String with whatever text the user entered.

Now, the getReply() method should only accept an answer of “T” or “F”. Anything else typed in by the player must be rejected with a message and then the player should be given a new chance to enter a reply again.

One way to continuously ask for new input while the player insists on giving invalid input, is to use a while loop.

The strategy could be as follows: First read the input into the variable reply. While reply is not equal to “T” and not equal to “F”, write “Please answer T or F!” and read new input into reply.

Pseudo code would be:

read line into reply; 
while( not reply equals T and not reply equals F){
  write please answer T or F;
  read line into reply;
}
return reply;

Why does this work? First you read a line from the player into the variable reply. Next you enter a while loop, only if reply does not equal “T” and does not equal “F” either. This means, that if the player entered a valid reply the first time, the while loop will not execute at all (the condition is false). If, on the other hand, the player starts by entering something illegal such as “I am Groot”, the condition for the while loop will be true because “I am Groot” is not equal to “T” and it is also not equal to “F”. In the while loop, the error message is shown, and reply is assigned a new value using the console-readline call. Only when the player learns how to answer correctly, the condition becomes false, and the while loop exits. Then it is safe to return reply.

How do we check Strings for equality? We have shown you the equals method work on Strings in several chapters and examples, and you can read about it in the online API documentation for java.lang.String. But here is a short recap, using code examples:

String s = "";
if(s.equals("")){
  System.out.println("s is empty");
}
s = "Adam";
if( !s.equals("Eve") ){
  System.out.println("s is not equal to Eve");
}else if( s.equals("Adam") ){
  System.out.println("s is, however, equal to Adam");
}else{
  System.out.println("We know that s is not equal to Eve"+
                     " and s is not equal to Adam");
}

Here we'll show you a method which works in all cases below (showing you the syntax for both System.console() and Scanner):

  private String readLine(){
    // If there is no System console, use a scanner
    if(System.console() == null){
      return new java.util.Scanner(System.in).nextLine();
    }else{
      // there was a System console, so we can use it
      return System.console().readLine();
    }
  }

You don't need to use that method. It exists here to show you that you can write code that would work with a System.console() if it exists, and with a Scanner(System.in) if there is no System.console().

Stuff to think about (for Task 3)

What about null? What happens if we have a String reference which is null (not referencing any object at all) and we use the reference to check equality with some String literal:

String s = null;
if(s.equals("Barcelona")){
  System.out.println("Ay, caramba");
}

To find out what happens, create a small test class with a main method and put the above inside the body of the main. For the impatient, we can show what happens here (but we recommend that you do the experiment yourself) when the program executes:

Exception in thread "main" java.lang.NullPointerException
	at YourClass.main(YourClass.java:9)

You cannot use a null reference (any reference variable with the value null) to call an instance method or access an instance variable for the simple reason that there is no instance referred to by the variable!

What does this mean in terms of testing for equality? It means that in order to create a robust program, you should always check for null references before calling equals on a String! However, this makes the while or if condition quite long:

 if (s != null && s.equals("Barcelona")) {
   // ...
 }

There is an idiom to work around this problem. It uses the fact that you can use null as an argument to equals (it will return false). And you can call equals on String literals! So a more compact (but equally robust) way of formulating the same test and handle null is to write:

 if ("Barcelona".equals(s)) {
   System.out.println("Ay, caramba");
 }

Actually, there's yet one more way to safely compare two strings, without worrying about one of them being null. You can use the static method Objects.equals(Object o1, Object o2):

  if (Objects.equals(s, "Barcelona")) {
    System.out.println("Ay, caramba");
  }

Note that Objects is a class in java.util and its equals method is a static convenience method which has nothing to do with the equals method that you inherit from the class java.lang.Object.

But, would System.console().readLine() ever return null? Surely, the user cannot enter a null using the keyboard? If the user enters null using the keyboard, that would be interpreted as a java String with the value "null", right? Right, but the user can actually send something that will be read as null, by pressing Ctrl-d (the Control key and D simultaneously). This means, that if you want your program to be robust, you must use one of the idioms above to protect against null, if the input comes from System.console().readLine() .

Task 4 - Write a program using the Guesser class

This is the second part of the assignment, and here you will have to write all code from scratch. It is now time for you to write your own small program using the Guesser class you have completed in the previous steps.

Verify that you now have a constructor as described in task 1, that you have completed the start() method as described in task 2, and, that you have completed the getReply() method as described in task 3. You should now be able to write the actual game program. Create a class called GuessingGame which only has a standard main method. The class should be defined in a file called GuessingGame.java .

(You might have started this class in Task 1 if you chose to write it for testing the constructor - if so, just use that class)

The code for the main method should contain two simple steps:

  1. Create a Guesser reference variable and assign it a (reference to a ) new Guesser object, passing the values 0 and 1000 to the constructor.
  2. Use the Guesser reference variable to call the start() method

Compile and run your game and verify that it works. If it doesn’t work, consult with your classmates and the supervisors and hunt the problem down and fix it.

Task 5 - let the player choose the interval

In the previous task, the version of your GuessingGame program always uses the interval 0...1000. What if we let the user (the player) decide what interval to use? The constructor is created in such a way that the caller of the constructor provides the lower bound and the upper bound of the interval. This allows for flexibility, such as letting the player choose the interval. Your task is to change your GuessingGame program to accept two arguments from the command line, lower and upper bound. The program should be run like this:

$ java GuessingGame 0 10000

if the player wants to have an interval between 0 and 10 000.

In order to accomplish this, you must use the args array of the main method, and convert the two first elements to int values, and use those values as arguments to the constructor of Guesser.

Hints (for Task 5)

The args array in the signature of the main method is an array of String references. The first argument (if any) will end up in args[0], the second argument (if any) will end up in args[1] etc. But the constructor in the Guesser class wants two values of type int. In the example command line above, the first argument will be "0" (of type String) and the second argument "10000" (also as a String). So, how do we convert the String "0" to an int of value 0? The class java.lang.Integer has a helpful static method we can use for exactly this purpose, as shown by the following example code:

 String zero = "0";
 int z = Integer.parseInt(zero);

Note that if the String represented by the argument to parseInt is not possible to convert into an int, there will be an exception and if we don’t handle that, the program will crash with a report similar to:

Exception in thread "main" java.lang.NumberFormatException: For input string: "notAnInt"

The error message above means that there was a call to Integer.parseInt() with an argument of a String representing the text "notAnInt" and that String is of course hard to interpret as an integer value. Only String literals which contain characters representing actual integer values are possible for parseInt() to successfully convert to the corresponding int value.

Now you know how to convert an argument of type String, like "0" or "10000" to the type int, so now you should have the tools for completing the Task 5. You can as the first step of the main method, create two int variables and assign them the values you get from using parseInt() with args[0] and args[1] as values. Then you can use those variables as arguments to the constructor of Guesser.

Optional extra challenges for Assignment 1

If you feel that you need more challenges, you will find some optional voluntary extra tasks here.

Optional task 1 - Check arguments

Make sure that your program has the two required arguments for low and high, and write a usage message otherwise. A usage message is a text explaining how a command or program is supposed to be invoked from the command line. Here’s an example run where the user doesn’t provide the two mandatory arguments:

$ java GuessingGame
Usage: java GuessingGame low high
 where low is an integer for the lower bound
 and high is an integer for the upper bound
 of the interval for the numbers of the GuessingGame

Having this check and usage message makes your program more self explanatory. If the user who wants to play forgets to provide the lower and upper bound for the valid numbers, the program exits with a message explaining how to start it.

Hints (for optional task 1)

To check the number of arguments to a program, you use the length variable in the args array. You have used this in some of the exercises to the chapter Objects in Java. Review this chapter if you don’t remember how to check the number of arguments.

The usage method should only be printed if the user fails to give the correct amount of arguments. This means that you should start with a check for this, and if the check shows that the number of arguments is wrong, you should print the usage message, and then exit the program. Use an if-statement to check if the number of arguments is wrong, if so do the usage printing and then call:

 System.exit(1);

Calling System.exit() with a number immediately terminates the program. So this needs to be inside the if-block, after the usage message printing, so that it only is executed when the arguments are wrong. Code after the if-statement will execute as normal if the condition for the if-statement is false (that is, the number of arguments were correct). Pseudo code:

 if number of arguments are not 2 {
   print usage message
   System.exit(1);
 }
 code to start the game...

Stuff to think about (for optional task 1)

The printing of the usage message consist of several lines of text. It might be a good idea to move this code out to a method instead, and call this method inside the if-statement. But how do we call a method from the main method? Remember that the main method is a static method. That means that the main method runs independently from any particular instance of the class it is defined in. Calling a method in the same class from such a “static context” means that either do we need to create an instance of the class GuessingGame itself, and use that in order to call some instance method like usage(), or we need to call another static method. The usage() method doesn’t need any instance variables of the GuessingGame class in order to do its job - its job is simply to do some printing using System.out.println(). This means that the usage() method is a good candidate for a static method.

We suggest that you make usage() a private static void method of the GuessingGame class. It will only be used as a helper message to keep the main method cleaner and smaller. It is perfectly legal, then to call usage() from the static context in main, without the need of creating any objects.

The pseudo code from above would now read:

 if number of arguments are not 2 {
   usage();
   System.exit(1);
 }
 code to start the game...

The signature of the usage method would simply be:

 private static void usage()

Optional task 2 - handle the parsing of the arguments

As was said in task 5 of the mandatory part of the assignment, calling Integer.parseInt() will fail with an exception if the argument String to be parsed is not possible to convert into an int. The exception that was thrown by parseInt() was of class NumberFormatException. You may catch and handle that case using exception handling code. The chapter Exceptions is a good place to start if you need to refresh your memory about how to catch an exception of a named class like that.

Your task here is to add a try-catch block around the parsing of the arguments. The arguments should be parsed after the number of arguments check, of course, since there is no point in trying to parse arguments if there aren’t any.

Create two int variables called low and high. Then start a try-block ending with a catch-clause catching NumberFormatException. In the block of the catch clause, write an error message explaining that the arguments could not be interpreted as integer values, and explain to the user that both arguments must be numbers, then exit the program.

Hints (for Optional task 2)

You must declare your int variables outside the try-catch block if you plan to use them after the try-catch block, because if you declare them inside the block, they will be local variables to the block and not known outside the block.

Alternatively, you can declare them inside the block but then you must also use them inside the block. Here is pseudo code to explain the first alternative (declaring outside, using after):

 int low=0;
 int high=0;
 try{
  parse args...
 }catch(NumberFormatException nfe){
  write error message
  System.exit(2);
 }
 Guesser guesser = new Guesser(low, high);
 ....

Alternative 2, declaring and using inside block:

 try{
   int low  = parse int from args[0];
   int high = parse int from args[1];
   Guesser guesser = new Guesser(low, high);
   use guesser...
 }catch(NumberFormatException nfe){
  print error message and exit program
 }
 // No more code, main ends here

Use the alternative you find most intuitive.

Stuff to think about (for optional task 2)

Why did we give different arguments to System.exit in the example code? We wrote System.exit(1) for the case of the wrong number of arguments problem, and System.exit(2) for the number format exception problem. The reason is that we can use different numbers for different cases and write a manual explaining what different numbers mean.

When a program (following the posix standard) exits, it always has an exit code in form of an integer value. A program which exits normally should have the exit code 0. Any other value than 0 means that the program exited abnormally (there was a problem). It is always possible to check the exit code from a program from the shell. In bash, you can check the number like this:

 $ javac GuessingGame.java 
 $ echo $?
 0

(Normal termination, all went well, exit code was 0).

When something goes wrong, the exit code is not 0. Here’s the exit code from javac when there is a syntax error:

 $ javac GuessingGame.java 
 GuessingGame.java:3: error: ';' expected
     int low = 0
               ^
 1 error
 $ echo $?
 1

(Compilation error, exit code was 1)

When something else goes wrong, the exit code varies:

 $ javac Bogus.java
 javac: file not found: Bogus.java
 Usage: javac <options> <source files>
 use -help for a list of possible options
 $ echo $?
 2

(File not found, exit code was 2)

It is good practise to use different exit codes for different error situations (if you document it so that the user can look the codes up, it is even better of course!).

Optional task 3 - Use an object to parse and hold the arguments

This task is a little harder. The idea here is to remove all error handling and parsing from the main method, and use an object for this instead. The goal is to have an object which holds the two arguments low and high, and also knows if something went wrong and what happened.

The use of the helper object could make the main method small and look like this:

    IntervalParser range = new IntervalParser(args);
    if(! range.couldParse()){
      System.err.println(range.getErrorMessage());
      usage();
      System.exit(range.getErrorCode());
    }
    Guesser guesser = new Guesser(range.low(), range.high());
    guesser.start();

Example runs using this code:

$ java GuessingGame
You must provide two arguments
Usage: java GuessingGame low high
 where low is an integer for the lower bound
 and high is an integer for the upper bound
 of the interval for the numbers of the GuessingGame

$ java GuessingGame a b
The arguments must be numbers.
Usage: java GuessingGame low high
 where low is an integer for the lower bound
 and high is an integer for the upper bound
 of the interval for the numbers of the GuessingGame

$ java GuessingGame 3 1
The lower bound must be less than the upper bound.
Usage: java GuessingGame low high
 where low is an integer for the lower bound
 and high is an integer for the upper bound
 of the interval for the numbers of the GuessingGame

You still need the usage method as a private static method of the GuessingGame class.

Your task here, is to write the IntervalParser class. We have provided a skeleton for you here.

The work for you is in the constructor where all checks are carried out. If a check fails (or number format exception is thrown), you set the variable errorCode to something unique and the error String variable to an appropriate message (you may use the messages from the sample runs above).

Optional task 4 - understanding the guessing algorithm

The strategy used by the doGuesses() method in Guesser, is to ask as few questions as possible until there is only one possible number left which must be the number the player is thinking about. To use as few questions as possible, the method is using what is called a binary search. The idea is that the fastest way to find the number, is to start in the middle of the range, and throw away the half which cannot hold the number. Which half to throw away is determined by the answer to the question.

If the range is 0...1000 the half is 500. The method asks the player: is your number less than or equal to 500? If the answer is T (for true), then the upper half is thrown away (if it’s less than or equal to 500, it cannot be greater than 500!). Then a new middle is calculated (250). Every time, half of the numbers left are thrown away. This means that for the interval 0...1000 at most ten questions are ever needed, because, if you divide 1000 in half, you can only do so ten times before you reach 0 (in integer division, 1/2 is 0).

Another way of putting it, is that if you start with 1, and double it ten times, you arrive at 1024.

Yet another way to express that, is that 2^10 is 1024.

Now, your task is to figure out, how many questions (at most) would be needed to find a number between 0 and 100? Between 0 and 10 000? Between 0 and 100 000? Between 0 and 1000 000?

Hints (for Optional task 4)

The task is about finding out how many times you can divide the upper bound (if the lower bound is 0) before you reach zero. This number corresponds to the logarithm of the upper bound to base 2. The number of questions needed to find a number in 0..200 for instance, is realted to the logarithm of 200 bound to base 2. A less formal way to express that, is “What number should I raise 2 to the power of, to get 200?” It should answer the equation 2x = 200. If you have a calculator present, you can look up log2(200) which is ~ 7.64. That means that 27.64=200. Then we can say that we can divide 200 about 7.64 times, but we can’t deal with fractions here. Since, 7 times would be too little (27 < 27.64), we need to round up to 8. So the program would need 8 at most guesses.

If you are not into maths and logarithms, you can actually test your way to the answer. You can start with 1 and double it until you get a number equal to or greater than the upper bound.

You might be surprised how few times you need to double 1, to reach quite large numbers.

A manual test for finding the maximum number of questions needed for 200 would read:

1*2=2 1
2*2=4 2
4*2=8 3
8*2=16 4
16*2=32 5
32*2=64 6
64*2=128 7
128*2=256 8 (256 > 200, stop)

Answer: 8 questions.

You could also work your way down from 200 until you get 0:

200/2=100 1
100/2=50 2
50/2=25 3
25/2=12 4 (integer division)
12/2=6 5
6/2=3 6
3/2=1 7 (integer division)
1/2=0 8

Answer: 8 questions.


Links

Further reading

  • TODO - add links here

Where to go next

There is one more assignment waiting for you. Consult with your teacher whether that assignment is voluntary or mandatory: Java:Assignment - Address book

« PreviousBook TOCNext »