Я создал делегат для классов Process
и ProcessBuilder
, чтобы показать, как следует использовать остальную часть кода. Я называю эти классы GameEngineProcess
и GameEngineProcessBuilder
соответственно.
GameEngineProcess
создает ответы, которые являются простыми String
с и добавляются непосредственно в JTextArea
GUI игрока. На самом деле он расширяет Thread
, чтобы позволить ему работать асинхронно. Таким образом, реализация этого специфицированного c класса - это не то, о чем вы просите, а для имитации класса Process
. Я добавил некоторую задержку в ответах этого класса, чтобы имитировать время, необходимое движку для их генерации.
Затем есть пользовательский класс OnUserActionWorker
, который расширяет SwingWorker
и выполняет асинхронно то, что вы запрашиваете : он получает ответы от процесса двигателя и перенаправляет их в GUI, который обновляет его JTextArea
. Этот класс используется один раз для каждого запроса движка, ie мы создаем и выполняем новый экземпляр этого класса для каждого запроса, который пользователь создает при взаимодействии с GUI. Обратите внимание, что это не означает, что двигатель закрывается и вновь открывается для каждого запроса. GameEngineProcess
запускается один раз, а затем остается включенным в течение всего времени безотказной игры.
Я предполагаю, что у вас есть способ сообщить, завершены ли все ответы на один запрос к движку. Для простоты в этом коде, который я написал, существует сообщение (типа String
), которое пишется каждый раз в потоке процесса, чтобы указать конец ответов на запрос. Это константа END_OF_MESSAGES
. Таким образом, это позволяет OnUserActionWorker
знать, когда следует прекратить получение ответов, поэтому следующий экземпляр будет создаваться позднее для каждого нового запроса.
И, наконец, GUI, то есть JFrame
состоящий из JTextArea
и сетки кнопок, с которыми игрок может взаимодействовать и отправлять команду запроса в движок в зависимости от нажатой кнопки. Снова я использую String
s в качестве команд, но я предполагаю, что это, вероятно, то, что вам также понадобится в этом случае.
Следует код:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;
import java.util.Objects;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
public class Main {
//Just a simple 'flag' to indicate end of responses per engine request:
private static final String END_OF_MESSAGES = "\u0000\u0000\u0000\u0000";
//A class simulating the 'ProcessBuilder' class:
private static class GameEngineProcessBuilder {
private String executionCommand;
public GameEngineProcessBuilder(final String executionCommand) {
this.executionCommand = executionCommand;
}
public GameEngineProcessBuilder command(final String executionCommand) {
this.executionCommand = executionCommand;
return this;
}
public GameEngineProcess start() throws IOException {
final GameEngineProcess gep = new GameEngineProcess(executionCommand);
gep.setDaemon(true);
gep.start();
return gep;
}
}
//A class simulating the 'Process' class:
private static class GameEngineProcess extends Thread {
private final String executionCommand; //Actually not used.
private final PipedInputStream stdin, clientStdin;
private final PipedOutputStream stdout, clientStdout;
public GameEngineProcess(final String executionCommand) throws IOException {
this.executionCommand = Objects.toString(executionCommand); //Assuming nulls allowed.
//Client side streams:
clientStdout = new PipedOutputStream();
clientStdin = new PipedInputStream();
//Remote streams (of the engine):
stdin = new PipedInputStream(clientStdout);
stdout = new PipedOutputStream(clientStdin);
}
public OutputStream getOutputStream() {
return clientStdout;
}
public InputStream getInputStream() {
return clientStdin;
}
@Override
public void run() {
try {
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(stdout));
final BufferedReader br = new BufferedReader(new InputStreamReader(stdin));
String line = br.readLine();
while (line != null) {
for (int i = 0; i < 10; ++i) { //Simulate many responses per request.
Thread.sleep(333); //Simulate a delay in the responses.
bw.write(line + " (" + i + ')'); //Echo the line with the index.
bw.newLine();
bw.flush();
}
bw.write(END_OF_MESSAGES); //Indicate termination of this particular request.
bw.newLine();
bw.flush();
line = br.readLine();
}
System.out.println("Process gracefull shutdown.");
}
catch (final InterruptedException | IOException x) {
System.err.println("Process termination with error: " + x);
}
}
}
//This is the SwingWorker that handles the responses from the engine and updates the GUI.
private static class OnUserActionWorker extends SwingWorker<Void, String> {
private final GameFrame gui;
private final String commandToEngine;
private OnUserActionWorker(final GameFrame gui,
final String commandToEngine) {
this.gui = Objects.requireNonNull(gui);
this.commandToEngine = Objects.toString(commandToEngine); //Assuming nulls allowed.
}
//Not on the EDT...
@Override
protected Void doInBackground() throws Exception {
final BufferedWriter bw = gui.getEngineProcessWriter();
final BufferedReader br = gui.getEngineProcessReader();
//Send request:
bw.write(commandToEngine);
bw.newLine();
bw.flush();
//Receive responses:
String line = br.readLine();
while (line != null && !line.equals(END_OF_MESSAGES)) {
publish(line); //Use 'publish' to forward the text to the 'process' method.
line = br.readLine();
}
return null;
}
//On the EDT...
@Override
protected void done() {
gui.responseDone(); //Indicate end of responses at the GUI level.
}
//On the EDT...
@Override
protected void process(final List<String> chunks) {
chunks.forEach(chunk -> gui.responsePart(chunk)); //Sets the text of the the text area of the GUI.
}
}
//The main frame of the GUI of the user/player:
private static class GameFrame extends JFrame implements Runnable {
private final JButton[][] grid;
private final JTextArea output;
private BufferedReader procReader;
private BufferedWriter procWriter;
public GameFrame(final int rows,
final int cols) {
super("Chess with remote engine");
output = new JTextArea(rows, cols);
output.setEditable(false);
output.setFont(new Font(Font.MONOSPACED, Font.ITALIC, output.getFont().getSize()));
final JPanel gridPanel = new JPanel(new GridLayout(0, cols));
grid = new JButton[rows][cols];
for (int row = 0; row < rows; ++row)
for (int col = 0; col < cols; ++col) {
final JButton b = new JButton(String.format("Chessman %02d,%02d", row, col));
b.setPreferredSize(new Dimension(b.getPreferredSize().width, 50));
b.addActionListener(e -> sendCommandToEngine("Click \"" + b.getText() + "\"!"));
gridPanel.add(b);
grid[row][col] = b;
}
final JScrollPane outputScroll = new JScrollPane(output);
outputScroll.setPreferredSize(gridPanel.getPreferredSize());
final JPanel contents = new JPanel(new BorderLayout());
contents.add(gridPanel, BorderLayout.LINE_START);
contents.add(outputScroll, BorderLayout.CENTER);
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.getContentPane().add(contents);
super.pack();
}
//Utility method to enable/disable all the buttons of the grid at once:
private void gridSetEnabled(final boolean enabled) {
for (final JButton[] row: grid)
for (final JButton b: row)
b.setEnabled(enabled);
}
//This is the method that sends the next request to the engine:
private void sendCommandToEngine(final String commandToEngine) {
gridSetEnabled(false);
output.setText("> Command accepted.");
new OnUserActionWorker(this, commandToEngine).execute();
}
public BufferedReader getEngineProcessReader() {
return procReader;
}
public BufferedWriter getEngineProcessWriter() {
return procWriter;
}
//Called by 'SwingWorker.process':
public void responsePart(final String msg) {
output.append("\n" + msg);
}
//Called by 'SwingWorker.done':
public void responseDone() {
output.append("\n> Response finished.");
gridSetEnabled(true);
}
@Override
public void run() {
try {
//Here you build and start the process:
final GameEngineProcess proc = new GameEngineProcessBuilder("stockfish").start();
//Here you obtain the I/O streams:
procWriter = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream()));
procReader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
//Finally show the GUI:
setLocationRelativeTo(null);
setVisible(true);
}
catch (final IOException iox) {
JOptionPane.showMessageDialog(null, iox.toString());
}
}
}
public static void main(final String[] args) {
new GameFrame(3, 3).run(); //The main thread starts the game, which shows the GUI...
}
}
И, наконец, Другое важное предположение, которое я сделал, заключается в том, что когда пользователь взаимодействует с GUI, GUI блокирует ввод (но продолжает реагировать на другие события). Это препятствует тому, чтобы пользователь имел больше чем один активный запрос к двигателю одновременно. Под блокирующим вводом я просто подразумеваю, что при нажатии кнопки сначала все кнопки отключаются, а затем команда отправляется в движок. После этого все кнопки снова включаются, когда все ответы на последний сделанный запрос завершаются sh.
Если вам нужно одновременно выполнить несколько запросов к одному движку, вам, вероятно, потребуется синхронизируйте доступ к некоторым методам GUI, а также убедитесь, что каждый OnUserActionWorker
может отличать guish своих ответов от других. Так что это была бы другая история, но дайте мне знать, если вы этого хотите.
Чтобы проверить отзывчивость EDT во время получения ответов, вы можете, например, просто изменить размер окна с помощью мыши. пока (десять) ответов все еще принимаются, или просто обратите внимание, что ответы распечатываются в JTextArea
в режиме реального времени.
Надеюсь, это поможет.