Calculus5000
Calculus5000

Reputation: 427

How to put user input from JTextField into another method

Today I wrote an implementation of Hangman (code below) that was originally written to be used on the command-line. I'm in the process of getting it to work on some simple GUI, and I've almost got it to work on a GUI, however I'm having difficulty in determining how to get the user input from the JTextField into the appropriate parts of the code (like the userGuess() method invoked in the play() method).

I understand that the action listener linked to the JTextField will run on the EDT & so isn't that a separate thread from the thread of that play() is in? Do I need to send the info from the EDT thread to the main thread? I know I can get the text using .getText() but I'd like for the code to wait for the user to make a guess (i.e. block), like how it waits in the command-line when Scanner(System.in) is used.

So basically, I'm asking how do I get the user input from the JTextField & transport it to another method that's running? Thanks for your help.

EDIT: Ok so I've separated code for the GUI from the game code

HangmanGame Class

    public class HangmanGame {
        public static void main(String[] args){
            HangmanModel hangmanModel = new HangmanModel();
            hangmanModel.readFile("words.txt");
            hangmanModel.play();
        }
    }

HangmanModel Class

    import java.io.*;
    import java.util.*;
    import java.util.List;


    public class HangmanModel {
        String[] wordList;
        Set<Character> alphabet;


        Scanner k = new Scanner(System.in);

        boolean keepGoing;
        boolean keepPlaying;    // allows the player to play another game of hangman
        int GuessesRemaining;
        HangmanView hangmanView = new HangmanView();

        // checkIfWon - sees if the user has won the game
        private boolean checkIfWon(){
            for(boolean tof : hangmanView.getLettersRevealed()){
                if(!tof)
                    return false;
            }
            return true;
        }

        // chooseSecretWord - selects a word
        private String chooseSecretWord(String[] wordList){
            return wordList[(int)(Math.random() * wordList.length)];
        }

        // createAlphabetSet - Creates the alphabet set that's used to ensure that the user's guess not a number nor a special character
        private void createAlphabetSet(){
            alphabet = new HashSet<Character>(26);
            for(Character c = 'a'; c<='z'; c++){
                alphabet.add(c);
            }
        }

        // play - Initiates a game of hangman
        public void play(){
            keepPlaying = true;
            createAlphabetSet();

            while(keepPlaying) {
                setUpGame();
                hangmanView.appendMessage("Welcome to hangman! You have 5 guesses to guess the secret word.\n");
                System.out.println("Welcome to hangman! You have 5 guesses to guess the secret word.");

                // user's guess
                String guess;
                while (GuessesRemaining > 0 && keepGoing) {
                    hangmanView.drawSecretWord();

                    // No letters have been guessed by the user at the beginning.
                    if(hangmanView.getLettersGuessed().size() != 0) {
                        hangmanView.drawLettersGuessed();
                    }

                    guess = userGuess("Guess a letter:");
                    updateSecretWord(guess);

                    hangmanView.appendMessage("\n");
                    System.out.println();

                    if (checkIfWon()) {
                        keepGoing = false;
                        hangmanView.appendMessage("Well done! You guessed " + hangmanView.getSecretWord() + " with " + GuessesRemaining + " guesses left!\n");
                        System.out.println("Well done! You guessed " + hangmanView.getSecretWord() + " with " + GuessesRemaining + " guesses left!");
                    }
                }

                // If player runs out of guesses
                if (GuessesRemaining == 0) {
                    hangmanView.appendMessage("Tough luck. The secret word was " + hangmanView.getSecretWord() + "\n");
                    System.out.println("Tough luck. The secret word was " + hangmanView.getSecretWord());
                }

                playAgain("Would you like to play another game of hangman? (type yes or no)");
            }
        }

        // playAgain - Allows the user to play another game of hangman
        private void playAgain(String message){
            hangmanView.appendMessage(message + "\n");
            System.out.println(message);
            String ans = k.next().toLowerCase();

            if(ans.equals("yes")) {
                hangmanView.appendMessage("Okay! Let's play another game of hangman!\n\n\n");
                System.out.println("Okay! Let's play another game of hangman!\n\n");
            }else if(ans.equals("no")){
                keepPlaying = false;
                hangmanView.appendMessage("Fair enough. Bye!\n");
                System.out.println("Fair enough. Bye!");
            }else{
                playAgain("Invalid input. Please type in yes or no: ");
            }
        }

        // readFile - read in wordList
        String[] readFile(String loc){
            try {
                File f = new File(loc);
                assert f.exists() : "File doesn't exist";

                BufferedReader input = new BufferedReader(new FileReader(f));

                // read in the stuff into an arrayList here
                wordList = input.readLine().split(" ");

                // close the input stream
                input.close();
            }catch(IOException ioException){
                ioException.printStackTrace();
            }
            return wordList;
        }

        // setUpGame - sets up the variables appropriately
        private void setUpGame(){
            keepGoing = true;
            GuessesRemaining = 5;
            hangmanView.setSecretWord(chooseSecretWord(wordList));
            hangmanView.setLettersRevealed(new boolean[hangmanView.getSecretWord().length()]);
            hangmanView.setLettersGuessed(new HashSet<Character>(26));     // 26 letters in alphabet
        }

        // updateSecretWord - updates which letters of the secret word have been revealed
        private void updateSecretWord(String l){
            List<Integer> changeBool = new ArrayList<Integer>();

            if(hangmanView.getSecretWord().contains(l)){
                // Searches through secretWord & notes down all letters that equal the user's guess
                for(int i=0; i<hangmanView.getSecretWord().length(); i++){
                    if(hangmanView.getSecretWord().charAt(i) == l.charAt(0))
                        changeBool.add(i);
                }
                hangmanView.newLetterRevealed(changeBool);

            }else{
                GuessesRemaining--;
                hangmanView.appendMessage("Letter not found. You have " + GuessesRemaining + " guesses remaining.\n");
                System.out.println("Letter not found. You have " + GuessesRemaining + " guesses remaining.");
            }
        }

        // get input from the user
        private String userGuess(String message){
            hangmanView.appendMessage(message + "\n");
            System.out.println(message);
            String l = k.next().toLowerCase();

            if(l.length() == 0) {
                l = userGuess("Nothing has been typed. Please guess a letter:");
            }else if(l.length() > 1) {
                l = userGuess("More than one letter was typed. Please select only one letter:");
            }else if(hangmanView.getLettersGuessed().contains(l.charAt(0))){
                l = userGuess("Letter already guessed. Please choose another letter:");
            }else if(!alphabet.contains(l.charAt(0))){
                l = userGuess("Not a letter. Please choose a letter:");
            }

            hangmanView.addLetterGuessed(l.charAt(0));
            return l;
        }
    }

HangmanView Class

    import javax.swing.*;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.Document;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.*;
    import java.util.List;


    public class HangmanView {
        private String secretWord;
        private Set<Character> lettersGuessed;    // letters the user has guessed
        private boolean[] lettersRevealed;       // determines if the letter should be revealed or not

        // GUI
        private Document doc;
        private JTextField textField;

        public HangmanView(){
            buildGUI();
        }

        // addLetterGuessed - adds the new letter that was guessed by the user to lettersGuessed
        void addLetterGuessed(char letter){
            lettersGuessed.add(letter);
        }

        // appendMessage - appends text to the text area
        void appendMessage(final String message){
            SwingUtilities.invokeLater(
                    new Runnable(){
                        @Override
                        public void run(){
                            try{
                                doc.insertString(doc.getLength(), message, null);
                            }catch(BadLocationException badLocationException){
                                badLocationException.printStackTrace();
                            }
                        }
                    }
            );
        }

        // buildGUI - builds the GUI
        void buildGUI(){
            JFrame f = new JFrame("Hangman");

            // Text area
            JTextArea textArea = new JTextArea();
            doc = textArea.getDocument();
            textArea.setEditable(false);
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);

            JScrollPane jsp = new JScrollPane(textArea);

            // Text field for user to type letters in
            textField = new JTextField(10);      // TODO fix the textField's size
            JButton guessButton = new JButton("Guess");

            // Add listeners to textField & guessButton
            TextListener textListener = new TextListener();
            guessButton.addActionListener(textListener);
            textField.addActionListener(textListener);

            // Add everything to frame
            JPanel panel = new JPanel();
            panel.add(BorderLayout.WEST, textField);
            panel.add(BorderLayout.EAST, guessButton);
            f.add(BorderLayout.CENTER, jsp);
            f.add(BorderLayout.SOUTH, panel);

            f.setSize(300, 300);
            f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            f.setVisible(true);
        }

        // drawLettersGuessed - Outputs the letters guessed
        void drawLettersGuessed(){
            appendMessage("Letters already guessed: ");
            System.out.print("Letters already guessed: ");
            for (Character el : lettersGuessed) {
                appendMessage(el + " ");
                System.out.print(el + " ");
            }
            appendMessage("\n");
            System.out.println();
        }

        // drawSecretWord - draws the secret word with dashes & etc for user to use to guess the word with
        void drawSecretWord(){
            StringBuilder word = new StringBuilder();
            for(int i=0; i<lettersRevealed.length; i++){

                if(lettersRevealed[i]){
                    String s = secretWord.charAt(i) + " ";
                    word.append(s);
                }else{
                    word.append("_ ");
                }
            }
            appendMessage(word + "\n");
            System.out.println(word);
        }

        // newLetterRevealed - updates lettersRevealed with the new letter that has been guessed by the user
        void newLetterRevealed(List<Integer> change){
            // Changes the boolean value for those letters @ their corresponding indexes
            for(Integer idx : change)
                lettersRevealed[idx] = true;
        }


        // GETTERS
        String getText(){
            return textField.getText();
        }

        String getSecretWord(){
            return secretWord;
        }

        Set<Character> getLettersGuessed(){
            return lettersGuessed;
        }

        boolean[] getLettersRevealed(){
            return lettersRevealed;
        }

        // SETTERS
        void setLettersGuessed(HashSet<Character> s){
            lettersGuessed = s;
        }

        void setLettersRevealed(boolean[] a){
            lettersRevealed = a;
            Arrays.fill(lettersRevealed, false);
        }

        void setSecretWord(String word){
            secretWord = word;
        }

        void setText(final String t){
            SwingUtilities.invokeLater(
                    new Runnable(){
                        @Override
                        public void run(){
                            textField.setText(t);
                        }
                    }
            );
        }

        // ActionListener       -- TODO need the input from the the textField to go into userGuess method somehow
        private class TextListener implements ActionListener{
            @Override
            public void actionPerformed(ActionEvent ev){
                appendMessage(getText());
                setText(null);
            }
        }
    }

Upvotes: 0

Views: 1380

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347184

GUI's are non-linear in operation/event driven. Something happens, you respond to that change, unlike command line programs which tend to be very linear.

You need to set up callbacks to events you are interested and respond to those events accordingly, updating the state of the UI and data accordingly...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class HangMan {

    public static void main(String[] args) {
        new HangMan();
    }

    public HangMan() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<JLabel> listSecrets;
        private String secretWord = "rabbit";
        private Set<Character> guesses;
        private JTextField guessField;

        private JLabel numberOfGuessesLabel;
        private int numberOfGuesses = 0;

        public TestPane() {
            guesses = new HashSet<>(25);
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            guessField = new JTextField(2);
            JButton applyGuess = new JButton("Apply");

            JPanel guessPane = new JPanel();
            guessPane.add(new JLabel("Make a guesss:"));
            guessPane.add(guessField);
            guessPane.add(applyGuess);

            add(guessPane, gbc);

            listSecrets = new ArrayList<>(25);

            JPanel guessesPanel = new JPanel();
            for (char c : secretWord.toCharArray()) {
                JLabel label = new JLabel("_");
                listSecrets.add(label);
                guessesPanel.add(label);
            }

            add(guessesPanel, gbc);

            numberOfGuessesLabel = new JLabel("0");
            add(numberOfGuessesLabel, gbc);

            GuessHandler handler = new GuessHandler();
            guessField.addActionListener(handler);
            applyGuess.addActionListener(handler);
        }

        public class GuessHandler implements ActionListener {

            @Override
            public void actionPerformed(ActionEvent e) {
                String text = guessField.getText();
                // This section should be part of your model layer, but for brevity
                // I've implemented directly....
                if (text.trim().length() > 0) {
                    if (!guesses.contains(text.charAt(0))) {
                        guesses.add(text.charAt(0));

                        int index = -1;
                        int searchFrom = 0;
                        while ((index = secretWord.indexOf(text, searchFrom)) != -1) {
                            JLabel label = listSecrets.get(index);
                            label.setText(Character.toString(text.charAt(0)).toUpperCase());
                            searchFrom = index + 1;
                        }

                        numberOfGuesses++;
                        numberOfGuessesLabel.setText(String.valueOf(numberOfGuesses));
                    }
                }
                guessField.setText(null);
                guessField.requestFocusInWindow();
            }

        }

    }

}

Normally, I'd prefer to separate the game logic out of the UI, allowing the UI to make modifications to the data/model and update the UI in response to those changes, for for brevity, I've maintained within the UI.

This is commonly known as Model-View-Controller. You can also see more details in Creating a GUI With JFC/Swing

Upvotes: 2

anezch
anezch

Reputation: 11

It will be easier if the GUI and the Game class are separated. The Game class deals with the game logic, while the GUI manages the user interface as a bridge between user and the game logic.

So to send the textfield event to the game is to listen on the textfield's key press event and send the character to the, say, guess(char c) method of the Game class.

Each call to the guess() method may trigger an event like wrong guess, game solved, and run out of guess. GUI class should listen to these events and present the appropriate feedback to the user.

Upvotes: 1

Related Questions