I'm making a simple text-based turn-based combat game in Java (as a learning exercise, so I'm trying to understand everything I do) and I want to simulate the opponent taking a turn. This essentially involves printing out a message (e.g. "Enemy's turn"), processing the enemy's turn, pausing (with Thread.sleep
), and then printing out another message ("e.g. "Your turn").
My GUI is a JApplet
class with Swing elements, though this is because it was simple to make and understand rather than because I have any attachment to using Swing. I have no experience in multithreading, but have read that SwingWorker
is useful in this sort of situation (updating a Swing GUI while a lengthy background process is running). Here is a version with no actual game code and all included in the same class for simplicity:
public class SimpleGame extends JApplet implements ActionListener {
private Game game;
private String inputCommand;
private JTextArea textOutput;
private JTextField inputField;
public void init() {
textOutput = new JTextArea();
DefaultCaret caret = (DefaultCaret) textOutput.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
textOutput.setEditable(false);
Container window = getContentPane();
window.setLayout(new BorderLayout());
window.add(textOutput, BorderLayout.CENTER);
JButton submitButton = new JButton("Submit");
submitButton.addActionListener(this);
inputField = new JTextField(20);
inputField.addActionListener(this);
JPanel inputPanel = new JPanel();
inputPanel.add(inputField);
inputPanel.add(submitButton);
window.add(inputPanel, BorderLayout.SOUTH);
inputField.requestFocusInWindow();
game = new Game();
}
public class GuiUpdater extends SwingWorker<Boolean, String> {
private String command;
public GuiUpdater(String inputCommand) {
super();
command = inputCommand;
}
public void publish(String chunk) {
super.publish(chunk);
}
protected Boolean doInBackground() throws Exception {
Output.setWorker(this);
boolean successfulCommand = game.performCommand(command);
return successfulCommand;
}
protected void process(List<String> chunks) {
for (String chunk : chunks) {
textOutput.append(chunk);
}
}
}
public void actionPerformed(ActionEvent event) {
if (game.isItYourTurn()) {
inputCommand = inputField.getText();
if (inputCommand.length() != 0) {
GuiUpdater worker = new GuiUpdater(inputCommand);
worker.execute();
boolean successfulCommand;
try {
successfulCommand = worker.get();
} catch (InterruptedException | ExecutionException e) {
successfulCommand = false;
}
if (!successfulCommand) {
textOutput.append("Invalid command.\n");
}
inputField.setText("");
}
} else {
textOutput.append("\nWait for your turn!\n");
}
}
private static class Output {
private static GuiUpdater worker;
static void setWorker(GuiUpdater newWorker) {
worker = newWorker;
}
static void update(String updateText) {
worker.publish(updateText);
}
}
private class Game {
private boolean yourTurn;
public Game() {
yourTurn = true;
}
public boolean isItYourTurn() {
return yourTurn;
}
public boolean performCommand(String inputString) throws InterruptedException {
yourTurn = false;
// perform player action
Output.update("\nEnemy turn");
Thread.sleep(1000);
// perform enemy action
Output.update("\nYour turn");
yourTurn = true;
return true;
}
}
}
Unfortunately instead of outputting one line, waiting a second, then outputting the second line, all lines are outputted to the Swing GUI after all sleeps have finished. If I replace my Output.update("text")
with System.out.println("text")
then the timing works as expected, but obviously I don't want to just rely on the console output for my game.
If anyone has either an explanation as to what I'm doing wrong or an alternative method of pausing between outputs it would be greatly appreciated. Cheers!
Your call to worker.get() is blocking the swing-applet thread until the worker completes. You need to move the handling of the result into the SwingWorker done() method, which won't be executed until the doInBackground() method has completed. That part of the code should look like this:
public class GuiUpdater extends SwingWorker<Boolean, String> {
private String command;
public GuiUpdater(String inputCommand) {
super();
command = inputCommand;
}
public void publish(String chunk) {
super.publish(chunk);
}
@Override
protected Boolean doInBackground() throws Exception {
Output.setWorker(this);
boolean successfulCommand = game.performCommand(command);
return successfulCommand;
}
@Override
protected void process(List<String> chunks) {
for (String chunk : chunks) {
textOutput.append(chunk);
}
}
@Override
protected void done() {
boolean successfulCommand;
try {
successfulCommand = get();
} catch (InterruptedException | ExecutionException e) {
successfulCommand = false;
}
if (!successfulCommand) {
textOutput.append("Invalid command.\n");
}
inputField.setText("");
}
}
@Override
public void actionPerformed(ActionEvent event) {
if (game.isItYourTurn()) {
inputCommand = inputField.getText();
if (inputCommand.length() != 0) {
GuiUpdater worker = new GuiUpdater(inputCommand);
worker.execute();
}
} else {
textOutput.append("\nWait for your turn!\n");
}
}