Search code examples
javaswingjframeactionlistenerjtextfield

Integrating Swing in a simple text adventure game


I am fairly new to some intermediate concepts in Java. Recently I made a text adventure game called DazzleQuest that runs entirely in the developer console/terminal. It involves my friends as characters so I wanted to show it to them and gain experience with Java by transferring the functionality of the command line and output from the console to a simple Swing interface comprised of a JTextArea to show the game's output and a JTextField, with ActionListener, to handle commands from the user.

My main DazzleQuest class contains methods called run() and handleCommand(), which I figure I need to integrate with my Frame class and its subclass TextFieldHandler [which extends ActionListener]. I am wondering, in general, what is the best way to integrate these methods and classes. My current attempts have been confusing and though I have a basic grasp on how to make different classes and methods communicate, it is tentative.

Apologies for the wordiness and lack of specificity. I would share my code but am unsure of how much and exactly what to give. If you think you can answer my question but need an example of what I have, please say so. Even pseudocode as an answer would be appreciated. Thank you! Understanding this could be very helpful for my education as a programmer.

EDIT: Here are simplified examples of my code from the version of the game that runs entirely in the developer console. This is my main class DazzleQuest:

public class DazzleQuest {

public void run() {
    listCommands();
    for (;;) {
        StdOut.println(new StringBuilder("You are in ").append(currentRoom.getName()).append(".").toString());
        StdOut.println(new StringBuilder("You can go to: ").append(currentRoom.listExits()).toString());
        StdOut.print("> ");
        handleCommand(StdIn.readLine());
        StdOut.println();
    }

public void handleCommand(String line) {
        String[] words = line.split(" ");
        if (words[0].equals("look"))
            look();
        }
    }

}

And here is my current JFrame setup, in the Frame class:

import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.JTextArea;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class Frame extends JFrame {

    public static JTextField field;
    public JTextArea area;

    public Frame() {
        setLayout(new FlowLayout());

        area = new JTextArea(20, 40);
        area.setEditable(false);

        field = new JTextField(20);
        add(area);
        add(field);
        pack();
        setVisible(true);

         TextFieldHandler handler = new TextFieldHandler();

            field.addActionListener(handler);
        }

        public class TextFieldHandler implements ActionListener{

            public void actionPerformed(ActionEvent event)
            {
                String line = field.getText();
                area.append(line + "\n");
                field.setText("");
            }
        }


    public static void main(String args[]) {

        Frame test = new Frame();
        test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

I know how to print output to the JTextArea instead of to the console. My issue is how exactly to get handleCommand() to work with actionPerformed() in the Frame class, or whether I should set up an action listener in the main class or something like that. Any examples of implementation would be greatly appreciated.

Thanks again for anything you can tell me!


Solution

  • First, regarding including code, you should take a look at https://stackoverflow.com/help/mcve for how to help us help you.

    Now, given that you haven't provided any code (yet!) giving suggestions on how you should structure your program is a bit tricky. But I'll make an attempt anyways and might edit the answer when you've provided a MWE.

    In your case, JTextArea is really just a fancy System.out.print as you will only use it to display the text that your adventure game outputs (if I'm understanding you correctly). So basically, you can write a method that accepts a string, and that method will append that string to your JTextArea. Then just replace your current output-lines with this method.

    Then you have your JTextField which you want to replace your (I'm guessing) Scanner(System.in) with. I assume you know how to set up an action listener that responds to Enter or a submit-button. How to set these up is not all that strange, and lots of guides talk about how to do it. https://docs.oracle.com/javase/tutorial/uiswing/components/textfield.html gives a good example.

    Now, in general regarding the structure of your program. You do not want to be dependent on how the user enters the text or how you display it. What I mean with this is that you do not want input- or display-logic to meddle with your game (business) logic. Your game logic should just receive input, from where ever, and output it to where ever. How and what the calling functions then decide to do with the information is not for the game logic to care about.

    So, applied to the method names you posted. I assume run() is where you kick things off. As you have output that you always want to display, you might want to pass in an interface here that has the method print(string) (or the like) which should be a method that prints the text to whatever text-element is used to display the text, be it System.out or JTextArea. The run() method never needs to know. handleCommand() should also just accept a value (I assume a String) and handle it like it should, no matter who or what called it.

    I'm having trouble giving you more advice with no code. But my general recommendation is: Don't mix presentation logic with business logic. And give each method as little info as possible, because giving them more than they actually need usually leads to a less flexible flow and structure.


    EDIT now that some code has been added.

    I know how to print output to the JTextArea instead of to the console. My issue is how exactly to get handleCommand() to work with actionPerformed() in the Frame class

    Go with the actionPerformed() approach. I recommend this because you won't have to bother with as much if you decide to Thread your application sometime in the future (multiplayer?). https://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html gives some good info about how to write your listener. But I'll give you some suggestion code as well.

    ...
    area.setEditable(false);
    
    field = new JTextField(20);
    field.addActionListener(new SendText());    
    ....
    
    class SendText implements ActionListener{
        public void actionPerformed(ActionEvent ae){
            if(ae.getSource() == field){
                String str = field.getText();
                if(!str.equals("")){
                    commandHandler.handle(str);
                }
            }
        }
    }
    

    And then rewrite your run() method

    public void run(){
       ui.print(commandHandler.listCommands());
    
       while(true){
           if(commandHandler.continue()){
               ui.print(commandHandler.events());
           }
       }
    }
    

    So now you have a very small run() method that has no real tie-in to either the logic or the display. ui.print(str) is a method that your UI class (Frame in your case) that just appends what ever string is sent to it to it's drawing area, be it System.out or a JTextArea.

    commandHandler is new. This is where your game logic should be. It has a method handle(string) which is your old handleCommand(string). It has a continue() method that returns a boolean. The boolean should be set to true whenever you want the story to continue, eg when the user enters a command. And finally events() (which is a bad name) that returns what has happened since the last command was sent in and gives the users options on what to do next. I've not done an implementation of that, as I just want to give the concept to you. The UI doesn't do any logic processing. The run() method doesn't expect anything, it just keeps checking if continue() is true. commandHandler is where all the bizz is.

    Now, you do have a infinite loop running. So if you get any performance issues you could look into Thread. This model should support a move to it.