Abdul Ahmad
Abdul Ahmad

Reputation: 10021

Swing threading issue?

I have a game that I wrote with Java that runs perfectly on command line. However, I've been building a GUI for it and have been changing it so it runs on the GUI but I'm having issues. It's a hangman game that allows the player to guess letters to try to guess the hangman word. If the player makes a correct guess, the game displays a certain message, and if the player makes an incorrect guess, the game displays a different message. The game stops working after I've made two guesses using the GUI version though... I've been trying to fix it for a couple of days but no luck...I've tried calling javax.swing.SwingUtilities.invokeLater but it's still giving me issues. Any help would be appreciated, heres the code (p.s. I'm still in the process of moving stuff from command line to GUI):

import java.util.ArrayList;
import java.util.Scanner;

public class HangmanTwo {
    private String[] wordList = {"apple", "orange"};
    private String chosenWord;
    private String playerGuess;
    private int numberOfIncorrectGuesses = 0;
    private boolean playerWon = false;
    private boolean playerPlaying = false;
    public static String uInput1;
    private boolean start = false;
    private ArrayList<String> lettersOfChosenWord;
    private ArrayList<String> incorrectGuessArrayList2 = new ArrayList<String>();
    private ArrayList<String> underScores;
    private boolean showHangman = false;
    HangmanGuiGui hh = new HangmanGuiGui();

//Print game instructions to player
void printGameInstructions() {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            hh.buildGui();
            hh.textFieldSouth.requestFocus();
            hh.textAreaCenter.append("Welcome to Hangman! \n");
            hh.textAreaCenter.append("To play, type in a letter as your guess, then press ENTER! \n");
            hh.textAreaCenter.append("If you think you know the word, type in the whole word and see if you got it right! \n");
            hh.textAreaCenter.append("But be careful! Guessing the word incorrectly will cost you a limb! \n");
            hh.textAreaCenter.append("To start playing, type 'start' and press ENTER. \n");
        }
    });

}

//Ask player if they want to start the game
void askToStart() {
    uInput1 = "waitingforinput";
    while (!start) {

        if(uInput1.equals("waitingforinput")) {

        } else if ((uInput1).equals("start")) {
            start = true;
            uInput1 = "waitingforInput";
        } else {
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    hh.textAreaCenter.append("Please type the word 'start' then press the ENTER key to begin playing. \n");

                }
            });


            uInput1 = "waitingforinput";
        }
    }
}

//Game picks random word from word list
void pickRandomWord() {
    int lengthOfWordList = wordList.length;
    int pickRandomWord = (int) (Math.random() * lengthOfWordList);
    chosenWord = wordList[pickRandomWord];
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            hh.textAreaCenter.append("The word is " + chosenWord.length() + " letters long\n");

        }
        });

}

//Make an arraylist to hold each letter of the chosen word at each index
void makeArrayListOfChosenWord(){
    lettersOfChosenWord = new ArrayList<String> ();
    for (int i = 0; i < chosenWord.length(); i++) {
        lettersOfChosenWord.add(chosenWord.substring(i, i+1));
    }
}

//Make an arraylist of underscores that holds as 
//many underscores as letters in the chosen word
void makeArrayListUnderScore(){
    underScores = new ArrayList<String>();
    for (int i = 0; i < chosenWord.length(); i++) {
        underScores.add("_");
    }
    for (int i = 0; i < underScores.size(); i++) {

        //hh.textAreaWest.append((underScores.get(i)).toString()); 
        //show the underscores in the text area

    }
}

//get a guess from the player
void getPlayerGuess() {
    boolean getGuess = true;
    uInput1 = "waitingforinput";
    while (getGuess) {
        if (uInput1.equals("")) {
            //javax.swing.SwingUtilities.invokeLater(new Runnable() {
                //public void run() {
                    hh.textAreaCenter.append("Please guess a letter\n");
                //}
                //});
            uInput1 = "waitingforinput";
        } else if (uInput1.equals("waitingforinput")) {

        } else {
            playerGuess = uInput1;
            //javax.swing.SwingUtilities.invokeLater(new Runnable() {
                //public void run() {
                    hh.textAreaCenter.append(playerGuess + "\n");
                //}
                //});


            getGuess = false;
        }
    }

}

//if the player wins, set the variable playerWon to true
void setPlayerWon(boolean a) {
    playerWon = a;
}

//start the game and play it
void playGame() {
    playerPlaying = true;
    while (playerPlaying && !playerWon) {
        getPlayerGuess();
        if (playerGuess.equals(chosenWord)) {
            playerPlaying = false;
            wordWasGuessed();
        }else if (numberOfIncorrectGuesses < 6) {
            checkPlayerGuess();
                if (playerWon) {
                    playerPlaying = false;
                    wordWasGuessed();
                } else if (numberOfIncorrectGuesses == 6) {
                    playerPlaying = false;
                    gameOver();
                }
        }
    }
}

//check the player's guess and see if its correct or not
void checkPlayerGuess(){
    //update number of incorrect guesses
    if (lettersOfChosenWord.contains(playerGuess)) {
        System.out.println("Correct guess!");
        if (!showHangman) {
            displayNoose();
        }
        displayHangman();
        replaceUnderScoreWithLetter();
        if (!underScores.contains("_")) {
            setPlayerWon(true);
        }
    } else if (!lettersOfChosenWord.contains(playerGuess)) {
        checkIncorrectGuessArrayList();
    }
}

//check the incorrectguess array list and add incorrect letters to it
void checkIncorrectGuessArrayList() {
    if (incorrectGuessArrayList2.contains(playerGuess)) {
        System.out.printf("You already guessed %s, try again!", playerGuess);
    } else if (!incorrectGuessArrayList2.contains(playerGuess)) {
        if (numberOfIncorrectGuesses < 6) {
            System.out.println("You guessed wrong, try again!");
            incorrectGuessArrayList2.add(playerGuess);
            ++numberOfIncorrectGuesses;
            displayHangman();
            printArrayListUnderScore();
        }
    }
}

//replace the underscores with a letter
void replaceUnderScoreWithLetter() {

    while (lettersOfChosenWord.contains(playerGuess)) {
        int indexOfPlayerGuess = lettersOfChosenWord.indexOf(playerGuess);
        underScores.set(indexOfPlayerGuess, playerGuess);
        lettersOfChosenWord.set(indexOfPlayerGuess, "_");
        incorrectGuessArrayList2.add(playerGuess);
    }
    printArrayListUnderScore();
}

//show the underscores to the player
void printArrayListUnderScore() {
    for (int j = 0; j < underScores.size(); j++) {
        System.out.print((underScores.get(j)).toString());
    }
}

void resetAllValues(int resetNumberIncorrectGuesses, boolean hangmanshow) {
    numberOfIncorrectGuesses = resetNumberIncorrectGuesses;
    lettersOfChosenWord.removeAll(lettersOfChosenWord);
    incorrectGuessArrayList2.removeAll(incorrectGuessArrayList2);
    underScores.removeAll(underScores);
    showHangman = hangmanshow;
}

void displayNoose(){
        System.out.println(" ___,");
        System.out.println(" l ");
        System.out.println(" l");
        System.out.println("_l_");
}


//Display a growing hangman with each incremental wrong guess
    void displayHangman(){
        switch (numberOfIncorrectGuesses) {
        case 1: firstWrongGuess();
        showHangman = true;
        break;
        case 2: secondWrongGuess();
        break;
        case 3: thirdWrongGuess();
        break;
        case 4: fourthWrongGuess();
        break;
        case 5: fifthWrongGuess();
        break;
        case 6: sixthWrongGuess();
        break;
    }
    }

    void firstWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l");
        System.out.println("_l_");
    }
    void secondWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l  l");
        System.out.println("_l_");
    }
    void thirdWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l /l");
        System.out.println("_l_");
    }
    void fourthWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l /l\\");
        System.out.println("_l_");
    }
    void fifthWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l /l\\");
        System.out.println("_l_/");
    }
    void sixthWrongGuess(){
        System.out.println(" ___,");
        System.out.println(" l  o ");
        System.out.println(" l /l\\");
        System.out.println("_l_/ \\");
    }

//what happens if the chosenWord was guessed
void wordWasGuessed() {
    hh.textAreaCenter.append("******\n");
    hh.textAreaCenter.append("GOOD JOB! YOU GUESSED THE WORD!\n");
    hh.textAreaCenter.append("You wanna play again? (y/n)\n"); 
    resetGame(0, false, false);
    boolean playAgain = false;
    while (!playAgain) {
        Scanner s = new Scanner(System.in);
        String userInput = s.next();
        if (userInput.equals("y")) {
            playAgain = true;
            resetAllValues(0, false);
            startGame();
        } else if (userInput.equals("n")) {
            playAgain = true;
            System.out.println("Ok...See you next time!");
        } else {
            System.out.println("please type y or n, then press enter!");
        }
    }
}

//what happens when the player loses and game is over
void gameOver() {
    System.out.println("Aww you lost... the word was " + chosenWord);
    System.out.println("You wanna play again? (y/n)");
    resetGame(0, false, false);
    boolean playAgain = false;
    while (!playAgain) {
        Scanner s = new Scanner(System.in);
        String userInput = s.next();
        if (userInput.equals("y")) {
            playAgain = true;
            resetAllValues(0, false);
            startGame();
        } else if (userInput.equals("n")) {
            playAgain = true;
            System.out.println("Ok...See you next time!");
        } else {
            System.out.println("please type y or n, then press enter!");
        }
    }
}

//reset the game
void resetGame(int resetNumberIG, boolean resetPlayerWon, boolean resetPlayerPlaying) {
    numberOfIncorrectGuesses = resetNumberIG;
    playerWon = resetPlayerWon;
    playerPlaying = resetPlayerPlaying;
}


void startGame() {
    pickRandomWord();
    makeArrayListOfChosenWord();
    makeArrayListUnderScore();
}

public static void main(String[] args) throws InterruptedException {
    HangmanTwo h = new HangmanTwo();
    h.printGameInstructions();
    h.askToStart();
    if (h.start == true) {
        h.startGame();
        h.playGame();
    }

}




}

And GUI

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HangmanGuiGui {
    TextFieldSouthHandler tfsHandler = new TextFieldSouthHandler();
    ButtonEnterHandler beHandler = new ButtonEnterHandler();
    JFrame frame = new JFrame("Hangman");
    JLabel label = new JLabel("Welcome to Hangman");
    public JTextArea textAreaCenter = new JTextArea();
    JTextField textFieldSouth = new JTextField();
    JScrollPane scrollPane = new JScrollPane();
    JPanel panelWest = new JPanel(new BorderLayout());
    JPanel subPanelWest = new JPanel();
    JTextArea textAreaWest = new JTextArea();
    JPanel panelSouth = new JPanel(new BorderLayout());
    JButton buttonEnter = new JButton("Enter");
    //Icon aba = new ImageIcon(getClass().getResource("hangman1.jpg"));
    //JLabel picLabel = new JLabel(aba);
    JPanel panelEast = new JPanel();

    void buildGui() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        textAreaCenter.setEditable(false);
        textFieldSouth.addKeyListener(tfsHandler);
        textAreaWest.setEditable(false);
        buttonEnter.addActionListener(beHandler);
        panelSouth.add(BorderLayout.CENTER, textFieldSouth);
        panelSouth.add(BorderLayout.EAST, buttonEnter);

        //subPanelWest.add(picLabel);
        JPanel panelwesteast = new JPanel();
        JPanel panelwestwest = new JPanel();
        JPanel panelwestsouth = new JPanel();
        panelWest.add(BorderLayout.SOUTH, panelwestsouth);
        panelWest.add(BorderLayout.EAST, panelwesteast);
        panelWest.add(BorderLayout.WEST, panelwestwest);
        panelWest.add(BorderLayout.NORTH, subPanelWest);
        panelWest.add(BorderLayout.CENTER, textAreaWest);

        scrollPane.getViewport().setView (textAreaCenter);
        frame.getContentPane().add(BorderLayout.NORTH, label);
        frame.getContentPane().add(BorderLayout.CENTER, scrollPane);
        frame.getContentPane().add(BorderLayout.SOUTH, panelSouth);
        frame.getContentPane().add(BorderLayout.WEST, panelWest);
        frame.getContentPane().add(BorderLayout.EAST, panelEast);
        frame.setSize(800, 600);
        frame.setVisible(true);
    }

private class TextFieldSouthHandler implements  KeyListener {

    public void keyPressed(KeyEvent event) {
            if (event.getKeyCode()==KeyEvent.VK_ENTER) {
                //boolean bee = javax.swing.SwingUtilities.isEventDispatchThread();
                javax.swing.SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        HangmanTwo.uInput1 = textFieldSouth.getText();
                        textFieldSouth.setText("");
                    }
                    });
            }
    }

    public void keyTyped(KeyEvent event) {
    }

    public void keyReleased(KeyEvent event) {
    }
}

private class ButtonEnterHandler implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                HangmanTwo.uInput1 = textFieldSouth.getText();
                textFieldSouth.setText("");
                textFieldSouth.requestFocus();
            }
            });
    }
} 

}
/*javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
}
}); */

Upvotes: 3

Views: 141

Answers (2)

Jorge_B
Jorge_B

Reputation: 9872

You have literally translated a main-loop structure, which is perfectly suitable for a command line application, to swing. Then you immerse yourself in infinite loops in your main thread looking for a change in shared variables. This conflicts with the way Swing manages things, the main thread of the application is paramount for Swing to manage its repainting and its event handling, and your code is competing against it for the processor. I think we can think of a better design for a Swing application.

You have 2 possibilites:

  • You can take your original command line program and just replace any reading you take from the keyboard for a modal dialog asking for a letter. This way you respect your sequential design and avoid multi-threading problems.
  • Or my favorite: I suggest that you consider completely changing your sequential design to a responsive one. In this case, you renounce to a main loop, just show a JPanel inside a JFrame with your UI, and then just write responsive event handlers to each button or change in your inputs. You should just store the state of your program into the main class and your event handlers interact with it.

By the way, whatever your decision, I strongly advise you to remove all that invokeLater(new Runnable() ...), being innecessary and maybe dangerous (you can be introducing race conditions between your event handlers by doing it)

Upvotes: 4

Flavio
Flavio

Reputation: 11977

In order to avoid major headaches, you should redesign the control of your application.

Without a GUI, you directly control the process: wait for input, process, show results, repeat.

With a GUI, you show a window and then do nothing. When the user inputs something, the GUI invokes one of your callback methods, and there you react according to your current state.

So: don't try to have a control thread, it's very easy to have a lot of threading problems. Set some variables which tell you what is the current game state (wait for "START" keyword, wait for guess, finished...), and update them when the user does something.

Upvotes: 4

Related Questions