22
\$\begingroup\$

I am a first year CS student. We are currently learning Java and my latest assignment was to create this random number game. I am hoping to get some feedback on code style etc so that I know what not to do in the future and so that I can avoid getting into bad habits from the start. I don't know all that much so any explanations/tips at all will be amazing!

import java.util.Random;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        boolean win = false;
        boolean running = true;
        System.out.println("Please enter the upper limit: ");
        Game game = new Game(scan.nextInt());

        while (running) {

            System.out.println("Please enter a guess ");

            while (!win) {
                if (game.guess(scan.nextInt())) {
                    win = true;

                }
            }
            System.out.println("Would you like to play again y/n?");
            String playAgain = scan.next();
            if (playAgain.equalsIgnoreCase("y")) {
                game.reset();
                running = true;
                win = false;
            } else {
                running = false;
                System.out.println("Game over");
            }
        }
    }
}

Game class:

import java.util.Random;

public class Game {

    private int randomNumber;
    private int tries = 0;
    private int range;

    public Game(int range) {
        this.range = range;
        generateRandomNumber();
    }

    private void generateRandomNumber() {
        Random rand = new Random();
        this.randomNumber = rand.nextInt(range) + 1;
    }

    public boolean guess(int guess) {
        tries++;
        if (guess == randomNumber) {
            if (tries > 1) {
                System.out.println("You got the number in " + tries + " tries.");
                return true;
            } else {
                System.out.println("You got the number in " + tries + " try.");
                return true;
            }
        } else if (guess > randomNumber) {
            System.out.println("Too high try again");
            return false;
        } else if (guess < randomNumber) {
            System.out.println("Too low try again ");
            return false;
        }
        return false;

    }

    public void reset() {
        this.tries = 0;
        generateRandomNumber();

    }

}
\$\endgroup\$
  • 7
    \$\begingroup\$ This is pretty good for a beginner. I think you'll do well. Welcome to Code Review! \$\endgroup\$ – RubberDuck Oct 30 '14 at 10:50
  • \$\begingroup\$ It is so hard for me not to comment on otherwise well-formatted code that uses K&R bracing. Oh wait, I just did. :) Really does look solid for a beginner coder! :D \$\endgroup\$ – Almo Oct 30 '14 at 21:01
  • 1
    \$\begingroup\$ @Almo Allman is the way to go \$\endgroup\$ – chbaker0 Oct 31 '14 at 2:06
12
\$\begingroup\$

Unused imports - Warnings

You're using Eclipse. Eclipse and NetBeans are IDE's that will probably (aside from turning them off) give you warnings about your code.

An unused import is such a thing.

In Main, you don't use java.util.Random. So you should remove it.

Your IDE will give you other warnings from time to time. If you look at the warnings it gives you, it will help in both debugging ("hey, you're calling a function on a null variable here!") and in cleaning up your code ("Use String.isEmpty() instead of string == """ - I know NetBeans even just allows you to click a button and it will fix it for you).

Duplicate Code - Refactoring

Whenever you can, try to not state things multiple times.

Take this function.

public boolean guess(int guess) {
    tries++;
    if (guess == randomNumber) {
        if (tries > 1) {
            System.out.println("You got the number in " + tries + " tries.");
            return true;
        } else {
            System.out.println("You got the number in " + tries + " try.");
            return true;
        }
    } else if (guess > randomNumber) {
        System.out.println("Too high try again");
        return false;
    } else if (guess < randomNumber) {
        System.out.println("Too low try again ");
        return false;
    }
    return false;
}

What it's supposed to do:

Increase tries by 1. If the guess is correct, report the number of tries taken. If the guess is incorrect, report whether it is lower or higher than the hidden number. Return true if the guess was correct, and return false if the guess was incorrect.

Now right now you're just comparing one int with another, so there's no reason to define guess == randomNumber as isGuessCorrect yet.

What we can do, however, is remove duplicate code from branches.

        if (tries > 1) {
            System.out.println("You got the number in " + tries + " tries.");
            return true;
        } else {
            System.out.println("You got the number in " + tries + " try.");
            return true;
        }

That code can become this:

if (tries > 1) {
    System.out.println("You got the number in " + tries + " tries.");            
} else {
    System.out.println("You got the number in " + tries + " try.");
}
return true;

Another thing you could do, since it's a simple string, is to use a ternary:

System.out.println("You got the number in " + tries + ((tries > 1) ? " tries." : " try."));
return true;

A similar thing can be done to the "too high too low part".

But here's another special case...

If a number is not equal to and not lower to a certain value, then obviously it must be higher than that value.

So the code

if(is equal){
  print got number in x tries
  return true
} else if(is lower){
  print try higher
  return false
} else if(is higher){
  print try lower
  return false
}

Could also be

if(is equal){
  print got number in x tries
  return true
} else if(is lower){
  print try higher
  return false
} else {
  print try lower
  return false
}

Which would make the final snippet look something like this:

public boolean guess(int guess) {
    tries++;
    if (guess == randomNumber) {
        System.out.println("You got the number in " + tries + ((tries > 1) ? " tries." : " try."));
        return true;
    } //implicit else because return would end the function

    if (guess > randomNumber) {
        System.out.println("Too high try again");
    } else {
        System.out.println("Too low try again ");
    }
    return false;
}

You might even want to consider exporting this whole message printing to separate functions:

public boolean guess(int guess) {
    tries++;
    if (guess == randomNumber) {
        printCorrectGuess(tries);
        return true;
    } //implicit else because return would end the function

    if (guess > randomNumber) {
        printTooHigh();
    } else {
        printTooLow();
    }
    return false;
}

private void printCorrectGuess(int tries){
    System.out.println("You got the number in " + tries + ((tries > 1) ? " tries." : " try."));
}

private void printTooLow(){
    System.out.println("Too low try again ");
}

private void printTooHigh(){
    System.out.println("Too high try again");
}

And (we can always go further) extracting the condition can make it easier to handle return values:

public boolean guess(int guess) {
    tries++;
    boolean isGuessCorrect = guess == randomNumber;
    if (isGuessCorrect) {
        printCorrectGuess(tries);
    } else if (guess > randomNumber) {
        printTooHigh();
    } else {
        printTooLow();
    }
    return isGuessCorrect;
}

private void printCorrectGuess(int tries){
    System.out.println("You got the number in " + tries + ((tries > 1) ? " tries." : " try."));
}

private void printTooLow(){
    System.out.println("Too low try again ");
}

private void printTooHigh(){
    System.out.println("Too high try again");
}

Which turns a large function into several smaller functions, each easier to grasp than the original function you started with.

\$\endgroup\$
  • 2
    \$\begingroup\$ Personally, I think printTooHigh is excessive function splitting. \$\endgroup\$ – Almo Oct 31 '14 at 3:22
  • \$\begingroup\$ @Almo It kinda is right now, but later you'll have to make some sort of message box appear, or there's i18n concerns, or there's more than a single print statement. By extracting the function anyway, we make the guess function operate on a single level of abstraction - case 1 do this, case 2 do that, case 3 do that, rather than a mix of function calls and print statements. \$\endgroup\$ – Pimgd Oct 31 '14 at 8:23
  • \$\begingroup\$ Thank you a lot for the response. Your explanations on how to shorten the returns was very helpful. Also, thank you for showing me the ternary thing, I hadn't heard about it before, I will definitely be using it in the future though! \$\endgroup\$ – jjacobson Oct 31 '14 at 11:02
  • \$\begingroup\$ @jjacobson keep in mind that ternary is only to be used for simple things like this; Code can get very complicated by having ternaries in them very fast. \$\endgroup\$ – Pimgd Oct 31 '14 at 11:05
  • \$\begingroup\$ @Pimgd Thank you for the heads up. I am going to read up on them so that I can use them properly. \$\endgroup\$ – jjacobson Oct 31 '14 at 11:10
15
\$\begingroup\$

This is a pretty good implementation of what you've set out to do. The naming of things is clear and the code is pretty well split up into methods.

Main Class

I'd maybe put a slightly clearer explanation of what the user is expected to do at the start, as well as your "enter an upper limit". This is usually an excuse to break out your finest ascii art.

Something else to consider is what if your user doesn't enter an integer? That will break your program, so putting in some error handling that asks your error-prone meatbag to try again and get it right this time, please. Your way of checking is to put the scan.nextInt() in a try/catch block and catch an InputMismatchException.

When you're asking the player if they want to play again, you set running to true, but there's no way for it not to be true, so that's unnecessary.

Game class

You're seeding rand every time you're calling generateRandomNumber(), which isn't really going to be an issue here, as you're not calling that method quickly/often, but you should really only seed a random number generator once in a program, otherwise you'll start having it return the same value over and over again, so maybe move your Random rand = new Random() to the constructor?

In guess(), I'd rearrange the first section to this:

 if (guess == randomNumber) {
        if (tries > 1) {
            System.out.println("You got the number in " + tries + " tries.");
        } else {
            System.out.println("You got the number in " + tries + " try.");
        }
        return true;
 }

You're repeating yourself less and the return value is the same regardless. The same goes for the next part, instead, I'd have:

 else if (guess > randomNumber) {
        System.out.println("Too high try again");
    } else if (guess < randomNumber) {
        System.out.println("Too low try again ");
    }
    return false;
\$\endgroup\$
  • 2
    \$\begingroup\$ "Something else to consider is what if your user doesn't enter an integer?" You get an InputMismatchException. \$\endgroup\$ – Pimgd Oct 30 '14 at 10:26
  • 1
    \$\begingroup\$ Thank you very much for the response. I had thought about a user entering bad information and asked my professor if we should write this with that in mind. He told me not to worry about it this time since its our first project. Otherwise I definitely would have done that. \$\endgroup\$ – jjacobson Oct 31 '14 at 10:50
11
\$\begingroup\$

This looks quite good to me. In particular, I like how Game is its own class. That was the right decision, so good job.

One thing I would recommend is not having "side effects" in Game. In this case, that means not writing to System.out; move the output up to main, where you're also doing the input. Though it doesn't make much difference in a small program like this, it generally leads to more reusable, testable code, and is a good habit to get in to.

The only other thing I can see right now is that you could make range final. That means that once assigned to, its value cannot be changed. It makes your intention clear to people reading your code, and the compiler will tell if you that rule has been violated.

\$\endgroup\$
7
\$\begingroup\$

Your main() function is clumsy due to the superfluous use of two flag variables, win and running. In general, using flag variables to control execution flow is poor style. There are more assertive ways to get to where you want to go:

  • To run the game at least once, then possibly again and again, use a do-while loop.
  • Where practical, write the loop condition to perform the right test.
public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    System.out.println("Please enter the upper limit: ");
    Game game = new Game(scan.nextInt());

    do {
        game.reset();
        System.out.println("Please enter a guess ");

        while (!game.guess(scan.nextInt())) {
            // Loop until a correct guess
        }

        System.out.println("Would you like to play again y/n?");
    } while (scan.next().equalsIgnoreCase("y"));
    System.out.println("Game over");
}

As for Game.guess(), I would restructure the conditions with an assertion to emphasize that exactly one of the three branches must apply. (Your final return false; should be unreachable.) The pluralization of "try" can be treated as an internationalization problem to be solved with MessageFormat. (Pluralization in English is simple relative to some other languages. MessageFormat is better prepared to handle such complexity. You might as well take advantage of the built-in library that is built to solve the pluralization problem.)

import static java.text.MessageFormat.format;

…

public boolean guess(int guess) {
    tries++;
    if (guess > randomNumber) {
        System.out.println("Too high try again");
        return false;
    } else if (guess < randomNumber) {
        System.out.println("Too low try again ");
        return false;
    }
    assert guess == randomNumber;
    System.out.println(format("You got the number in {0} {0,choice,1#try|1<tries}.", tries));
    return true;
}
\$\endgroup\$
2
\$\begingroup\$

Nice start!

You should move all the game-control logic to Game, so that your main loop will be

while (!game.isGameOver())
{
     game.nextMove()
}

You can code the 'Do you want to play another?' nicely outside this loop.

I would also introduce a Player, which can be asked for a move. So the Game will ask the Player for input:

class Game
{
    public Game (Player p)
    {
        player = p;
    }

    Player player;
    public void nextMove()
    {
        //or something similar
        guess(player.getNextInput());
    }
}

You can move all the IO (System.out etc) to the Player class, because Game does not need to know about it.

You now can easily implement a TestPlayer or a AIPlayer without having to change anything in the game logic.

As guess() is game logic, it should return something the Game knows, and notify the player. For example a MoveResult, which might contain some code (enum), variables and a message.

   public MoveResult 
   {
      public static enum {TOO_HIGH, TOO_LOW, RIGHT}
      String message;
   }

And in Game

    public void nextMove()
    {
        //or something similar
        MoveResult result = guess(player.getNextInput());
        player.notify(result);
    }
\$\endgroup\$
0
\$\begingroup\$

My first impression was that your code looks good: clean and consistent styling, sane method lengths, good variable names. The only thing that jumps out at me (that others haven't mentioned) is a complete lack of documentation.

When you're the only developer working on a quick, small project, it's ok to omit comments. When you move on to long-term projects, or when you start working with other developers, you should really use well-thought-out documentation. The documentation I wrote has saved me (and my colleagues) much more time than I spent writing it; and as I write it, I rethink the program and often find flaws in my design, which I can then improve (see rubber duck debugging).

With Java, learn how to write Javadoc comments. It will be beneficial to get in the good habit while you're still learning. As for the CONTENT of the documentation, read the Javadocs of any libraries you use (especially from big providers like Sun/Oracle & Apache).

\$\endgroup\$
  • \$\begingroup\$ You can improve your answer by adding some code and some more references. \$\endgroup\$ – bhathiya-perera Oct 31 '14 at 15:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.