Emulating user input for java.util.Scanner

2019-08-07 04:47发布

问题:

I'm writing a game in Java, and I want the user to be able to provide input from both the command line and my GUI. Currently, I use this method to get input:

    static String getInput(){
        System.out.println("Your move:");
        Scanner sc = new Scanner(System.in);
        return sc.nextLine();
    }

I want to keep using this, but let a mousePressed event emulate the user actually typing in their input as well. It's not that efficient of a solution, but it makes sense in my application. So the question is: how do I simulate a user typing to System.in from the code side?

回答1:

This is possible - the easiest substitution for System.in would be a PipedInputStream. This must be hooked up to a PipedOutputStream that writes from another thread (in this case, the Swing thread).

public class GameInput {

    private Scanner scanner;

    /**CLI constructor*/
    public GameInput() {
        scanner = new Scanner(System.in);
    }

    /**GUI constructor*/
    public GameInput(PipedOutputStream out) throws IOException {
        InputStream in = new PipedInputStream(out);
        scanner = new Scanner(in);
    }

    public String getInput() {
        return scanner.nextLine();
    }

    public static void main(String[] args) throws IOException {
        GameInput gameInput;

        PipedOutputStream output = new PipedOutputStream();
        final PrintWriter writer = new PrintWriter(output);
        gameInput = new GameInput(output);

        final JTextField textField = new JTextField(30);
        final JButton button = new JButton("OK");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String data = textField.getText();
                writer.println(data);
                writer.flush();
            }
        });

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new FlowLayout());
        frame.getContentPane().add(textField);
        frame.getContentPane().add(button);
        frame.pack();
        frame.setVisible(true);

        String data = gameInput.getInput();
        System.out.println("Input=" + data);
        System.exit(0);
    }

}

However, it might be better to rethink your game logic to cut out the streams altogether in GUI mode.



回答2:

To be honest, after re-reading your question I'm not exactly sure what you want.

Anyway, perhaps you need to check out the method java.lang.System.setIn(InputStream in). This will allow you to change what reader is used to read input from the terminal (i.e. changing it from the actual terminal to what ever you like)



回答3:

Assuming you have many operations like the given example, you might consider the interface approach described by Richie_W but make one routine per operation rather than generic "in/out" methods.

For example:

public interface IGameInteraction
{
    public String askForMove( String prompt );
    public boolean askAreYouSure( String prompt );
}

Your command-line implementation is clear; now your GUI implementation could use an appropriate dialog for each logical operation rather than just be a text area that's really just the command-line version.

Furthermore this is easier to write unit tests against because in your tests you can stub out these routines in any manner.



回答4:

I made an application once that could run via the command line or using a GUI.
The way I did this was to define an Interface (named IODevice) which defined the following methods:

  • public String getInput();
  • public void showOutput(String output);

  • I then had two classes which implemented this interface - One used the host computer's terminal (as you are doing now) and one used a JTextArea (output) / JOptionPane (input).

    Perhaps you could do something similar - To change the input device used, simply change the instance of the IODevice.


    Hope this is of some use.