Swing线程问题?

3

我写了一个用Java编写的游戏,可以在命令行中完美运行。然而,我一直在构建GUI并一直在更改以使其在GUI上运行,但是遇到了问题。这是一个猜词游戏,玩家可以猜测字母来猜测单词。如果玩家猜对了,游戏会显示特定的消息,如果玩家猜错了,游戏会显示不同的消息。但是,在GUI版本中进行两次猜测后,游戏就停止工作了...我已经尝试修复了几天,但没有任何运气...我尝试调用 javax.swing.SwingUtilities.invokeLater,但它仍然给我带来了问题。 希望能得到帮助,这里是代码(p.s.我仍在将东西从命令行移动到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();
    }

}




}

以及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() {
}
}); */

1
你尝试过使用调试器或纸张和铅笔来逐步检查吗?程序在哪个点的执行与您的期望不同? - Kevin Workman
这里不是关键invokeLater,逻辑部分应该是SwingWorker或其他线程的一部分,它将等待用户输入,并随着输入的到来,在EDT-Event Dispatcher Thread上更新GUI。对我来说,逻辑正在阻塞EDT。此外,请考虑使用JComponent.requestFocusInWindow(...)而不是requestFocus() - nIcE cOw
看一下这个Hangman Swing GUI文章,看看它是否能帮助你找到你所缺少的东西。 - Gilbert Le Blanc
是的,我已经尝试了逐步调试,但是我完全不懂调试,所以我不太确定GUI发生了什么。 - Abdul Ahmad
1
谢谢大家的回复,我以前从未使用过SwingWorker,但我会研究一下。如果有人发现其他问题,请告诉我。很好的cow,你能进一步解释一下逻辑是如何阻塞EDT的吗? - Abdul Ahmad
2个回答

4
为避免一些麻烦,你应该重新设计你的应用程序控制。
没有图形用户界面(GUI),你直接控制进程:等待输入、处理、显示结果、重复。
使用GUI,你展示一个窗口,然后什么也不做。当用户输入时,GUI调用你的一个回调方法,在那里根据当前状态做出反应。
所以:不要尝试有一个控制线程,这很容易导致许多线程问题。设置一些变量来告诉你当前游戏状态(等待"START"关键字、等待猜测、完成... ),并在用户做出某些操作时更新它们。

1
谢谢您的帮助,如果您能再详细解释一下,并且给我一个例子,那就太好了,这会非常有帮助。 - Abdul Ahmad

4
你把一个适用于命令行应用的主循环结构直接翻译到了Swing中。然后你在主线程里无限循环寻找共享变量的变化,这与Swing的管理方式冲突,因为应用程序的主线程对于Swing来说至关重要,它负责Swing的重绘和事件处理,而你的代码正在与它争夺处理器。我认为我们可以为Swing应用程序设计更好的解决方案。
你有两个选择:
1. 你可以采用原始命令行程序的方式,只是将从键盘输入改为使用模态对话框请求输入字母。这样你就尊重了顺序设计,并避免了多线程问题。
2. 或者我最喜欢的方式:我建议你完全改变你的顺序设计,采用响应式设计。在这种情况下,你放弃了主循环,在JFrame中展示一个JPanel,然后为每个按钮或输入更改编写响应式事件处理程序。你只需要将程序状态存储到主类中,让事件处理程序与之交互即可。
顺便说一下,无论你做出什么决定,我强烈建议你删除所有的invokeLater(new Runnable()...),因为它可能是不必要的,甚至是危险的(通过这样做,你可能会引入事件处理程序之间的竞争条件)。

1
谢谢您的解释,现在我有点明白了... 我会尝试实施您的建议。 - Abdul Ahmad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接