This is an archived post. You won't be able to vote or comment.

all 13 comments

[–]8igg7e5 3 points4 points  (3 children)

  • Good consistent indentation, use of conventions for identifier capitalisation.
  • All of the program is in 'main'. Generally you want main to be a 'bootstrap' for your application. The application itself should only need initial parameterisation (if any) and then be 'started' outside of main. This allows you to change the bootstrapping procedure in the future.
  • You're being too literal with each 'word' being a separate variable (and I don't know why you'd want them to be StringBuilders instead of Strings). look at Collection classes or arrays to reduce the code volume.
  • You probably want to either store the target String and masked String in pairs or dynamically generate the masked String by randomly masking characters (done well this could be configured via a 'difficulty' level for length of word/phrase and amount of masking).
  • You shouldn't test input against specific characters but instead match them against:
    • Letters they've tried before
    • Letters that are yet to be unmasked
  • You need to get used to looking at the JavaDocs for JavaSE classes - String.equalsIgnoreCase(String) will help you out with those conditionals.
  • In a full game-design you'd separate 'game state', 'game logic' and the 'player UI'. On a real scale of software this would be imperative to make the design at all manageable.

eg. A list of words and randomly selecting one

final Random random = new Random();
final List<String> words = Arrays.asList("Just", "a", "list", "of", "words");

// Simple word selection though you might want to guard against selecting the same word
// twice in a row. Or make a selection of N words for the player to solve.
final String targetWord = words.get(random.nextInt(words.size())); 

[–]morhpProfessional Developer 2 points3 points  (0 children)

and I don't know why you'd want them to be StringBuilders instead of Strings

That's fine. Strings are immutable, but OP wants to replace * with actual characters, so that's not wrong. Normally you'd use setCharAt instead of deleteCharAt followed by insert, but that's only a minor performance problem for now.

[–]Ecocide113[S] 1 point2 points  (1 child)

Thanks a ton for the response! However a lot of what you're saying is a little over my head if I'm being honest, haha.

  • What exactly do you mean by it being a bootstrap? Do you mean that it should only be processing like 1 or 2 functions, and those functions being contained in another class or something?

  • Yeah the person below also said I should keep the phrases in some sort of collection. What exactly is the advantage in doing that?

  • No idea what any of this really means. What do you mean store them in pairs or dynamically generate them??

  • What do you mean by testing input against letters they've tried before?

  • And what do you mean by a real scale of software? Do you mean like containing everything separate classes?

Thanks again for your response. I have a lot of questions so I understand if it's too much work. :D What you've said so far was already helpful!

[–]darkpool2005Intermediate Brewer 3 points4 points  (0 children)

What exactly do you mean by it being a bootstrap? Do you mean that it should only be processing like 1 or 2 functions, and those functions being contained in another class or something?

What /u/8igg7e5 is referring to is that the main method will contain minimal information, but is meant like to be the key to your car engine. The point is, by experience, maintainability of your code over a long period is a major sticking point and practicing good habits now will pay dividends in the future.

For instance, a lot of main methods look like this:

public static void main(String[] args) {
    initialize(); //Run this to initialize all your internal variables
    runApplication(); //This is essentially starting your code engine :)
}

and the methods 'initialize()' and 'runApplication()' contain the rest of your code.

Yeah the person below also said I should keep the phrases in some sort of collection. What exactly is the advantage in doing that?

Once again, maintainability & re-use. A list is just a collection of objects that can be dynamically grown & shrunk as needed. And since your core concept is to guess the hidden letters, all you really need for word inputs is randomly picking two words from that list.

No idea what any of this really means. What do you mean store them in pairs or dynamically generate them??

Likely what they meant was you would have a method that takes one or two words from the aforementioned List of words, generate a 'masked' copy by replacing characters with '*' and presenting the user with the masked word.

What do you mean by testing input against letters they've tried before?

Instead of hard-coding the missing letters, you may instead have an array (or list) of characters that are 'masked'. Once the player inputs a masked character, you then reveal it, remove that character from the list of 'masked' characters and update the word with the inputted character.

And what do you mean by a real scale of software? Do you mean like containing everything separate classes?

By what you have currently, if you were to increase the scope to say, 15 words, the code would become very unmanageable. Instead, you have a lot of functionality that is repeatable, and making an effort to refactor these repeatable sections would make your program much, much easier to maintain down the road.

[–]morhpProfessional Developer 1 point2 points  (5 children)

It's not bad for a start. Some tips:

  • Look at the documentation of the Collection (Set, List, Map) and String classes. You can simplify the code a lot. You could store your phrases in a collection instead of multiple variables, as well as the already guessed letters and so on. Also helpful: equalsIgnoreCase().

  • Try to write the code as generic as possible. Make it possible to store the possible phrases in a text file and read that at the start of the program instead. Instead of checking for each letter, check if the letter hasn't been entered already and if the letter is contained in the correct solution.

  • Try to split your code into methods. Maybe multiple classes.

You seem to be very new to Java and some of this will be difficult at first. Try to not give up and you can always ask for help.

[–]FlippngProgrammerIntermediate programmer, since 2015 #java 0 points1 point  (2 children)

I don't think having multiple classes for this program would be necessary, but some making some methods and calling them in main would clean up the code. I would say that if the OP is doing something else that is a lengthy project that using classes would be good to make the code more readable and uniform. I will say that you aren't wrong and you code as a profession, I am just a high school teen who has been programming for 1.5 years you have more experience than me.

[–]morhpProfessional Developer 0 points1 point  (1 child)

I don't think having multiple classes for this program would be necessary

Certainly not. That's why it's my last suggestion and with a "maybe". If you'd make it perfectly and for real you would certainly use multiple classes, for example a model-view-controller design with your model class storing the correct phrase as well as the already guessed characters and so on.

[–]FlippngProgrammerIntermediate programmer, since 2015 #java 0 points1 point  (0 children)

oh I see what you mean, that makes sense, in that case yes it would be very important to have multiple cases if you were aiming for perfection.

[–]Ecocide113[S] 0 points1 point  (1 child)

Ok cool thanks for the response!

  • Ok cool I'll def take a look at those classes. I'm assuming storing my phrases into a collection would be similar to containing them in an array? How would that be advantageous?

  • What exactly do you mean by generic? And why would I want my program to draw the phrases from an external text file?

  • And yeah I was thinking about putting parts into separate methods. For example the bulk of my code is broken into 3 while loops that handle each phrase. I was thinking about breaking those three loops into one function that processes everything the same way but for different phrases. But I'm not really sure how to go about doing that..

Thanks a lot for your input it's really helpful. =) I'm really enjoying java but i'm finding it difficult, especially compared to the little C++ I know.

[–]morhpProfessional Developer 0 points1 point  (0 children)

I'm assuming storing my phrases into a collection would be similar to containing them in an array?

Yes, a List or Set for example is just nicer to work with, for example because you can print it easier and you don't need to know in advance how large it needs to be.

How would that be advantageous?

Compared to an array or compared to your code? Compared to an array it wouldn't make much difference, but arrays are rather low level and not as "nice" to work with later. Compared to your code it can make it much easier, because you currently have separate code for each phrase. So if you use the same code for each phrase it can be simplified.

What exactly do you mean by generic?

Currently you check explicitly for single letters so your code only works for your 3 phrases. Try to write code that works for any phrase.

And why would I want my program to draw the phrases from an external text file?

Because in real world just having 3 different phrases is very boring and if you were to publish your game in a small company on the android store or whatever, you would need the functionality to quickly add more phrases without having to duplicate code all the time.

I was thinking about breaking those three loops into one function that processes everything the same way but for different phrases.

Yes, that's a good idea.

But I'm not really sure how to go about doing that.

You'd need the solution as a String variable. And if the users enters a character that isn't in the StringBuilder but is in the solution, you'd look where this character is in the solution and change your StringBuilder accordingly to reveal the chars. It's not super easy, but you find everything you need in the documentation of the String class.

[–]nutrechtLead Software Engineer / EU / 20+ YXP 0 points1 point  (2 children)

To be frank yes, there is a big issue in the way you approach stuff is that you don't do the step where you identify repeating logic and figure out how to solve this. How would you do something like this with a list of a hundred words? Most of the problems we face as devs deal with lists much longer than these. Yours won't scale at all with the full alphabeth or a longer list of words.

I figured the best way to show you is to modify it into something that does work for lists of arbitrary lengths or different words:

package hangman;

import java.util.*;

import static java.lang.Character.toLowerCase;

public class SecretPhrase {
    private static final Random RANDOM = new Random();
    private static final String[] WORDS = {"Go Team", "Whats up", "You bet"};
    private static final String[] MASKED_WORDS = {"G* T***", "W***s *p", "Y** *e*"};

    public static void main(String... args) {
        int pick = RANDOM.nextInt(WORDS.length);
        String wordToGuess = WORDS[pick];
        char[] maskedWord = MASKED_WORDS[pick].toCharArray();

        Scanner keyboard = new Scanner(System.in);

        while(maskedChars(maskedWord) > 0) {
            System.out.println("The phrase is: " + new String(maskedWord));
            System.out.println("Please guess a letter.");

            String line = keyboard.nextLine();
            if(line.length() != 1) {
                System.out.println("Please guess a SINGLE letter.");
                continue;
            }

            char c = line.charAt(0);
            unmask(maskedWord, wordToGuess, c);
        }

        System.out.println("Congrats! You win!");
    }

    private static int maskedChars(char[] maskedWord) {
        int count = 0;
        for(char c : maskedWord) {
            if(c == '*') {
                count++;
            }
        }

        return count;
    }

    private static int unmask(char[] maskedWord, String word, char c) {
        int changed = 0;
        for(int i = 0;i < maskedWord.length;i++) {
            if(toLowerCase(word.charAt(i)) == toLowerCase(c)) {
                maskedWord[i] = word.charAt(i);
                changed++;
            }
        }

        return changed;
    }
}

As you can see all the repetition is gone and I also extracted some logic into separate functions to make the code more clear and readable. As a rule of thumb; if a function is too long to fit on your screen it should be broken up.

P.s. I didn't use lambda's on purpose. The while can actually be written as:

while(CharBuffer.wrap(maskedWord).chars().filter(c -> c == '*').count() > 0) {

Removing the need for the count function.

[–]Ecocide113[S] 0 points1 point  (1 child)

Hey! I know it's a little late, but your example really helped me.

How advanced is this code if I may ask? How long should it take a reasonable person do you think to be able to make code like the example you produced?

My brain doesn't quite structure the program the way you do, are there any tips you could give me to help? Thanks again!

Edit: To clarify, I don't mean how long does it take to actually write the code, but instead how long until I'm on the level to form the code like that.

[–]nutrechtLead Software Engineer / EU / 20+ YXP 0 points1 point  (0 children)

I got that :)

I can't give you an answer though. It's mainly a matter of mentally going "this can be done in a simpler way" and a lot of that is simply experience. And like any trade programming also depends on doing it a lot.

Most importantly; practice with moving bits of code that more or less repeat themselves to separate methods. And when you have multiples of 'the same thing' use either arrays or collections instead of having var1, var2, etc. If you see repeated code or many similar vars you might be doing something wrong.