4
\$\begingroup\$

I'm done with this "Guessing word game" but I have a very ugly method named startGuessing().

I tried to make it cleaner and more readable but I couldn't. How I can improve this method?

public class GameHangman {

private static String[] WORDS = {"programming", "fish", "cairo", "arabs", "quraan", "sunnah",
        "editor", "photography", "artist", "software", "write", "read", "gaming",
        "lunch", "participate", "lunch", "dinner", "house", "books", "animals"};

public static void startGame() {

    Scanner input = new Scanner(System.in);

    //Choose a random word
    String word = chooseWord();

    //Start guessing game with chosen word and count missed times
    int missed = startGuessing(word, input);

    //Display result
    System.out.println("The word is " + word + (missed == 0 ? " you are correct" : " you missed " + (missed == 1 ? " one time" : " " + missed + " times")));

    //Ask for another game
    askForAnotherGame(input);
}

private static void askForAnotherGame(Scanner input) {
    System.out.print("Do you want to start another word? Enter y or no>");
    char gameCase = input.nextLine().charAt(0);
    if (gameCase == 'y')
        startGame();
    else
        System.exit(1);
}

private static int startGuessing(String originalWord, Scanner input) {
    StringBuilder replacedWord = new StringBuilder(hideRealWord(originalWord));
    int missing = 0;
    boolean wrongGuessing;
    int wordLength = originalWord.length();

    while (wordLength > 0) {
        System.out.print("(Guess) Enter a letter in word " + replacedWord.toString() + " ");
        char ch = input.nextLine().charAt(0);
        wrongGuessing = true;
        for (int j = 0; j < originalWord.length(); j++) {
            if (originalWord.charAt(j) == ch) {
                replacedWord.setCharAt(j, ch);
                wrongGuessing = false;
                wordLength--;
            }
            if (ch == replacedWord.charAt(j))
                System.out.println(ch  + " Already in the word");
        }
        if (wrongGuessing) {
            System.out.println(ch + " is not in the word");
            missing++;
        }

    }
    return missing;
}

private static String hideRealWord(String originalWord) {
    //System.out.println(originalWord);
    for (int i = 0; i < originalWord.length(); i++) {
        char ASTERISK = '*';
        originalWord = originalWord.replace(originalWord.charAt(i), ASTERISK);
    }
    return originalWord;
}

private static String chooseWord() {
    return WORDS[(int) (Math.random() * WORDS.length)];
    }
}
\$\endgroup\$
3
\$\begingroup\$

Break the method down

You can improve the method by trying to put it in smaller methods.

There is a while loop that is a good candidate for this.

The code becomes:

private static int startGuessing(String originalWord, Scanner input) {
        StringBuilder replacedWord = new StringBuilder(hideRealWord(originalWord));
        int missing = 0;
        boolean wrongGuessing;
        int wordLength = originalWord.length();

        while (wordLength > 0) {
            guess();
        }
        return missing;
    }

Use fields in the GameHangman object

Now, you will notice things will break down, as you need the state from your method in the guess() method. So, you'll need to pass all the information around. You will notice this is kind of hard.

Here comes OOP around. You can store the state in an Object. Currently, your game is all static methods. If you start re-writing the game to use an instance of HangmanGame and use fields it will become easier.

For example, good candidates for object-state are:

  • Scanner
  • Current word
  • Original word
  • Number of incorrect guesses
  • Remaining length of the word

Now, it becomes something like this:

public class GameHangman {

    private final static String[] WORDS = { "programming", "fish", "cairo", "arabs", "quraan", "sunnah", "editor",
            "photography", "artist", "software", "write", "read", "gaming", "lunch", "participate", "lunch", "dinner",
            "house", "books", "animals" };

    private Scanner input;
    private String originalWord;
    private int missing = 0;
    private boolean wrongGuessing;
    private int wordLength ;

    public GameHangman()
    {
        this.input = new Scanner(System.in);
    }

    public void start() {

        boolean keepPlaying = true;

        while (keepPlaying)
        {
            // Choose a random word
            this.originalWord = chooseWord();

            // Start guessing game with chosen word and count missed times
            int missed = startGuessing();

            // Display result
            System.out.println("The word is " + originalWord + (missed == 0 ? " you are correct"
                    : " you missed " + (missed == 1 ? " one time" : " " + missed + " times")));

            // Ask for another game
            keepPlaying = userWantsAnotherGame();

        }
    }

    private boolean userWantsAnotherGame() {
        System.out.print("Do you want to start another word? Enter y or no>");
        char gameCase = this.input.nextLine().charAt(0);
        return (gameCase == 'y');
    }

    private  int startGuessing() {
        StringBuilder replacedWord = new StringBuilder(hideRealWord(this.originalWord));
        this.missing = 0;
        this.wordLength = this.originalWord.length();

        while (wordLength > 0) {
            guess(replacedWord);

        }
        return missing;
    }

    private void guess(StringBuilder replacedWord) {

        System.out.print("(Guess) Enter a letter in word " + replacedWord.toString() + " ");
        char ch = this.input.nextLine().charAt(0);
        wrongGuessing = true;
        for (int j = 0; j < this.originalWord.length(); j++) {
            if (this.originalWord.charAt(j) == ch) {
                replacedWord.setCharAt(j, ch);
                wrongGuessing = false;
                wordLength--;
            }
            if (ch == replacedWord.charAt(j))
                System.out.println(ch + " Already in the word");
        }
        if (wrongGuessing) {
            System.out.println(ch + " is not in the word");
            missing++;
        }
    }

    private static String hideRealWord(String originalWord) {
        String hiddenWord = originalWord;
        for (int i = 0; i < originalWord.length(); i++) {
            char ASTERISK = '*';
            hiddenWord = hiddenWord.replace(hiddenWord.charAt(i), ASTERISK);
        }
        return hiddenWord;
    }

    private static String chooseWord() {
        return WORDS[(int) (Math.random() * WORDS.length)];
    }
}   

More improvements

Still lots of room for improvement, but at least the startGuessing is shorter and more readable.

More things to improve:

  • Think of which variables you really need. For example, you don't really need word-length; you just need to check if the currently-guessed word is equal to the original word.
  • String is immutable, so you use a StringBuilder. While this works, it feels kind of wrong to replace characters in a StringBuilder. Let's use a char[] to store the current state of the word that is being guessed. For easier comparison and character comparison, we can put the original word in a char[] as well.
  • The main loop of starting a game is called recursively. This might cause a stack overflow if you play many games. Let's convert it to a while loop.

Now, keeping close to your solution, but with these improvements:

import java.util.Scanner;

public class GameHangman {

    private final static String[] WORDS = { "programming", "fish", "cairo", "arabs", "quraan", "sunnah", "editor",
            "photography", "artist", "software", "write", "read", "gaming", "lunch", "participate", "lunch", "dinner",
            "house", "books", "animals" };

    private Scanner input;
    private char[] originalWord;
    private char[] currentWord;
    private int misses;

    public GameHangman() {
        this.input = new Scanner(System.in);
    }

    public void start() {

        boolean keepPlaying = true;

        while (keepPlaying) {
            // Choose a random word
            this.originalWord = chooseWord();
            this.currentWord = hideRealWord(originalWord);
            this.misses = 0;
            while (!this.originalWord.equals(this.currentWord)) {
                guess();
            }

            // Display result
            System.out.println("The word is " + String.valueOf(this.originalWord) + (misses == 0 ? " you are correct"
                    : " you missed " + (misses == 1 ? " one time" : " " + misses + " times")));

            // Ask for another game
            keepPlaying = userWantsAnotherGame();

        }
    }

    private boolean userWantsAnotherGame() {
        System.out.print("Do you want to start another word? Enter y or no>");
        char gameCase = this.input.nextLine().charAt(0);
        return (gameCase == 'y');
    }

    private void guess() {

        System.out.print("(Guess) Enter a letter in word " + String.valueOf(currentWord) + " ");
        char ch = this.input.nextLine().charAt(0);

        //Check if already made this guess
        for (int j = 0; j < this.currentWord.length; j++) {
            if (this.currentWord[j] == ch) {
                System.out.println(ch + " Already in the word");
                misses++;
                return;
            }
        }

        boolean isCorrect = false;
        for (int j = 0; j < this.originalWord.length; j++) {
            if (this.originalWord[j] == ch) {
                currentWord[j]=  ch;
                isCorrect = true;
            }
        }
        if (!isCorrect) {
            System.out.println(ch + " is not in the word");
            misses++;
        }
    }

    private static char[] hideRealWord(char[] originalWord) {
        char[] hiddenWord = new char[originalWord.length];
        for (int i = 0; i < originalWord.length; i++) {
            hiddenWord[i] = '*';
        }
        return hiddenWord;
    }

    private static char[] chooseWord() {
        return WORDS[(int) (Math.random() * WORDS.length)].toCharArray();
    }
}
\$\endgroup\$
1
\$\begingroup\$

I you prefer short code over very efficient code, you can improve the hideRealWord method:

private static String hideRealWord(String originalWord) {
    return originalWord.replaceAll(".", "*");
}

The "." is interpreted as a regular expression, and the dot means any character except newline.

\$\endgroup\$

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.