kinKuruna
kinKuruna

Reputation: 1

Java/ javaFX Hangman help counting down missed attempts

Hello to all Stackoverflowers. I am fairly new to Java and programming to begin with and this is my first ever question on StackOverflow so please excuse any mistake or idiotic code I may have written.

I am making a Hangman game in Java/JavaFX. I have gotten so far that I am able to read and randomly pick a word from a text file. Replace each character of the word with a '%' and later check if a entered letter exists in the hidden word and replace the '%'sign with the correct letter.

Basically the game works other than counting the amount of mistakes, and when a certain amount of mistakes have been made you lose.

So lets start here: I have a class that generates a random word from the file. That word is then returned to another class with a method that replaces all charcters in to the '%'

private loadWord LW = new loadWord();   
private String unGuessedWord;
private String correctWord;
private StringBuffer sb;

public String replaceWord(){
    correctWord = LW.readWords();

    char pos[] = new char[correctWord.length()];
    for(int i = 0; i < correctWord.length(); i++){
        pos[i] += '%';
    }
    unGuessedWord = new String(pos);
    return unGuessedWord;
}

This method has now turned every letter in the "correctWord" to '%'.

This next method will check if the letter guessed by the player is in the "correctWord" and if that is true, show the correct letter in its correct position:

    public String letterMatch(char guess){

    sb = new StringBuffer(unGuessedWord);

    for (int i = 0; i < correctWord.length(); i++) {
        if(correctWord.charAt(i) == guess){
            sb.setCharAt(i, guess);
        }
    }
    unGuessedWord = new String(sb);
    return unGuessedWord;
}

It works as intended. Let me just show the "FX" part:

play.setOnAction(e->{
        gE = new GenerateWord();
        hiddenText.setText(gE.randomWord());
        play.setText("RESTART");
        guesses.clear();
    });

This is a Button that when pressed generates and shows the "unGuessedWord" as '%' signs.

playerField.setOnAction(e->{
        hiddenText.setText(gE.letterMatch(guessField.getText().charAt(0)).toUpperCase());
        }
    });

"playerField" is a normal TextField in javaFX where the user can enter text. "hiddentext" is a Text which is the word to guess, it only displays '%' until a correct letter has been guessed.

What I need help with is to count the amount is mistakes/wrong guesses the user makes and after for example 6 wrong letters the game ends. And for each mistake draw a bodypart of the "man".

(how I see it in my head):

int lives = 6;
int dead = 0;
//some code
lives--;
if(lives == dead);
//end game

Im stuck here and I dont know what to do, nothing I try works or not as intended.. Please help me out here. I apologize for any mistake I have made in this question or if my code is just simply horrible.

Upvotes: 0

Views: 1514

Answers (2)

James_D
James_D

Reputation: 209674

JavaFX provides observable properties and collections, along with an associated binding API, for managing data that is presented in a JavaFX UI. So, while you could make your game work with the kind of code you have, a more idiomatic approach is to create a "model" class representing the state of the game, using these classes.

So you might think of writing a class like this:

public class HangmanGameState {

    private final StringProperty word = new SimpleStringProperty();

    public StringProperty wordProperty() {
        return word ;
    }

    public final String getWord() {
        return wordProperty().get();
    }

    public final void setWord(String word) {
        wordProperty().set(word);
    }

    private final ObservableList<Character> guesses = FXCollections.observableArrayList();

    public ObservableList<Character> getGuesses() {
        return guesses ;
    }


    // this value determined by wordProperty() and guesses, 
    // so we make it "read only":
    private final ReadOnlyIntegerWrapper wrongGuesses = new ReadOnlyIntegerWrapper();

    public final ReadOnlyIntegerProperty wrongGuessesProperty() {
        return wrongGuesses.getReadOnlyProperty();
    }

    public final int getWrongGuesses() {
        return wrongGuessesProperty().get();
    }

    public HangmanGameState() {

        // bind wrong guesses to other object state:
        wrongGuesses.bind(Bindings.createIntegerBinding(() -> 
            (int) guesses.stream()
                .map(c -> c.toString())
                .filter(word.get::contains)
                .count(),
            guesses, word);

    }

    public void restartGame(String newWord) {
        guesses.clear();
        word.set(newWord);
    }
}

(As a "reader exercise", you can create a ReadOnlyBooleanWrapper wordGuessed which is bound to word and guesses.)

Now in your UI you do:

HangmanGameState game = new HangmanGameState();
Label wordLabel = new Label();

wordLabel.textProperty().bind(Bindings.createStringBinding(() -> {
    StringBuilder text = new StringBuilder();
    for (char c : game.getWord().toCharArray()) {
        if (game.getGuesses().contains(new Character(c))) {
            text.append(c) ;
        } else {
            text.append('%');
        }
    }
    return text.toString();
}, game.wordProperty(), game.getGuesses());

TextField guessField = new TextField();
guessField.setOnAction(e -> {
    if (guessField.getText().length > 0) {
        game.getGuesses().add(guessField.getText().charAt(0));
    }
});



IntegerProperty livesRemaining = new SimpleIntegerProperty();
final int totalLives = 6 ;
livesRemaining.bind(games.wrongGuessesProperty().multiply(-1).add(totalLives));

Label livesRemainingLabel = new Label();
livesRemainingLabel.textProperty().bind(livesRemaining.asString("Lives Remaining: %d"));
livesRemaining.addListener((obs, oldValue, newValue) -. {
    if (newValue.intValue() <= 0) {
       // show "You lose, play again?" alert...
    }
});

That was just typed in, so there are probably typos etc, but it should give you the idea of how this is intended to be structured in JavaFX.

Upvotes: 1

DevilsHnd - 退した
DevilsHnd - 退した

Reputation: 9192

You already have a method (the letterMatch() method) which checks the supplied character to see if it's in the Correct Word. This works but in the same method you actually also know that the supplied letter doesn't work. You need to hold that fact and act upon it after the all characters in the Correct Word have been checked.

You need to assume that any Character argument supplied within the guess parameter of the letterMatch() method is wrong at the beginning of your method. To do this establish a boolean variable (we'll name it: letterCorrect) and initialize it as false:

boolean letterCorrect = false;

Now we're assuming that no matter what was supplied, it's simply wrong. We need proof that it's correct and this is where the for-loop comes into play. As the for-loop iterates though the Correct Word and checks the supplied character, we determine if the character is correct and if it is, well then our assumption is wrong and therefore we set our letterCorrect variable to true. We also want to break out of our for-loop at this time (more on this later). So our for-loop should look like this:

boolean letterCorrect = false;  // Assume the supplied character is wrong.
for (int i = 0; i < correctWord.length(); i++) {
    if(correctWord.charAt(i) == guess) {
        // The supplied character is Correct!
        sb.setCharAt(i, guess);
        letterCorrect = true;  //change our assumption to true.
        break;  
    }
}

After we've checked all the characters within the Correct Word with the character that was supplied it's our boolean letterCorrect variable which quickly tells us whether or not the supplied guess character was right or wrong. So, we check it's condition, is it true or is it still false?:

if (!letterCorrect) { 
    lives--; 
    if(lives == dead) {
        System.out.println("YOUR DEAD!");
        //end the game code or Method call here...
    }
}

If it's a correct character then we just go on our merry way and let the code finish within the letterMatch() method. But, if our initial assumption was correct and the supplied character was indeed wrong then our assumption would not have changed, our boolean letterCorrect variable would still be false and if it's false then we decrement our globally declared lives integer variable. We also quickly check that lives hasn't reached death but if it has we exit the game.

Now, you noticed we've used the break; statement within our for-loop. We actually need this in case there are Correct Words which contain two or more of the same characters within that Word. For example, take the word loop, if the User supplies the character 'o' which is a correct character, the letterMatch() method would process both o's within the word loop (loo) in a single call to the method and this would be a give-away as to what the fourth character might be. By placing the break; statement within the for-loop we ensure that the User must supply each character of the Correct Word no matter how many of the same characters might be within that Correct Word.

Here is what the complete letterMatch() method might look like:

public String letterMatch(char guess){
    sb = new StringBuffer(unGuessedWord);
    boolean letterCorrect = false;
    for (int i = 0; i < correctWord.length(); i++) {
        if(correctWord.charAt(i) == guess){
            sb.setCharAt(i, guess);
            letterCorrect= true;
            break;  
        }
    }
    if (!letterCorrect) { 
        lives--; 
        if(lives == dead) {
            System.out.println("YOUR DEAD!");
            //end the game code or method call here...
        }
    }
    unGuessedWord = new String(sb);
    return unGuessedWord;
}

As a side note, you may want to also want to take care of Upper & Lower letter case.

Upvotes: 0

Related Questions