0

I'm trying to write a little chess program - actually more of a chess GUI. The chess GUI should use the stockfish chess engine in the background when the player plays against the computer.

I got stockfish installed and can run it in the terminal and communicate with it via STDIN and STDOUT, for example I can type 'isready' and stockfish responds with 'readyok'.

Now I'm trying to be able to communicate from the chess GUI to stockfish continuously via some IPC method on linux. Looked first into pipes, but discarded that because pipes communicate unidirectional. Then I read of FIFOs and of Bash redirections, and trying that now. It sort of works, because I can read one line of output from stockfish. But that just works for the first line. When I then send 'isready' to stockfish via a FIFO, and try to read the next output from stockfish, there is no response. I use Bash redirections to redirect STDIN and STDOUT of stockfish to FIFOs.

I run this script to start stockfish in one terminal:

#!/bin/bash rm /tmp/to_stockchess -f mkfifo /tmp/to_stockchess rm /tmp/from_stockchess -f mkfifo /tmp/from_stockchess stockfish < /tmp/to_stockchess > /tmp/from_stockchess 

I call this script with ./stockfish.sh

And I have this c program for example(I'm new to C)

#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fpi; fpi = fopen("/tmp/to_stockchess", "w"); FILE *fpo; fpo = fopen ("/tmp/from_stockchess", "r"); char * line = NULL; size_t len = 0; ssize_t read; read = getline(&line, &len, fpo); printf("Retrieved line of length %zu:\n", read); printf("%s", line); fprintf(fpi, "isready\n"); read = getline(&line, &len, fpo); printf("Retrieved line of length %zu:\n", read); printf("%s", line); fclose (fpi); fclose (fpo); return 0; } 

Output from the program in the terminal(but the program doesn't halt, it waits):

Retrieved line of length 74: Stockfish 11 64 POPCNT by T. Romstad, M. Costalba, J. Kiiski, G. Linscott 

Alas that doesn't work continuously(I have no loop now, just trying to read two or more times without a loop), for example the stockfish script terminates in one terminal(instead of running continuously), after one line is read from the stockfish output FIFO. Or I can just read one line of output from the stockfish output FIFO. If there is an easier way to IPC with stockfish via STDIN and STDOUT, I can also try that. Thank you.

1 Answer 1

1

Since you're already working with C, then I'd suggest you manage stockchess in C as well. There is a library function, popen() that will give you a unidirectional pipe to a process -- that doesn't suite your use case. You can, however, set it up yourself.

Consider the following example program:

#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> /** * Creates two pipes, forks, and runns the given command. One pipe is * connected between the given *out and the standard input stream of the child; * the other pipe is connected between the given *in and the standard output * stream of the child. * * Returns the pid of the child on success, -1 otherwise. On error, errno * will be set accordingly. */ int bi_popen(const char* const command, FILE** const in, FILE** const out) { const int READ_END = 0; const int WRITE_END = 1; const int INVALID_FD = -1; int to_child[2] = { INVALID_FD, INVALID_FD }; int to_parent[2] = { INVALID_FD, INVALID_FD }; *in = NULL; *out = NULL; if (command == NULL || in == NULL || out == NULL) { errno = EINVAL; goto bail; } if (pipe(to_child) < 0) { goto bail; } if (pipe(to_parent) < 0) { goto bail; } const pid_t pid = fork(); if (pid < 0) { goto bail; } if (pid == 0) { // Child if (dup2(to_child[READ_END], STDIN_FILENO) < 0) { perror("dup2"); exit(1); } close(to_child[READ_END]); close(to_child[WRITE_END]); if (dup2(to_parent[WRITE_END], STDOUT_FILENO) < 0) { perror("dup2"); exit(1); } close(to_parent[READ_END]); close(to_parent[WRITE_END]); execlp(command, command, NULL); perror("execlp"); exit(1); } // Parent close(to_child[READ_END]); to_child[READ_END] = INVALID_FD; close(to_parent[WRITE_END]); to_parent[WRITE_END] = INVALID_FD; *in = fdopen(to_parent[READ_END], "r"); if (*in == NULL) { goto bail; } to_parent[READ_END] = INVALID_FD; *out = fdopen(to_child[WRITE_END], "w"); if (*out == NULL) { goto bail; } to_child[WRITE_END] = INVALID_FD; setvbuf(*out, NULL, _IONBF, BUFSIZ); return pid; bail: ; // Goto label must be a statement, this is an empty statement const int old_errno = errno; if (*in != NULL) { fclose(*in); } if (*out != NULL) { fclose(*out); } for (int i = 0; i < 2; ++i) { if (to_child[i] != INVALID_FD) { close(to_child[i]); } if (to_parent[i] != INVALID_FD) { close(to_parent[i]); } } errno = old_errno; return -1; } int main(void) { FILE* in = NULL; FILE* out = NULL; char* line = NULL; size_t size = 0; const int pid = bi_popen("/bin/bash", &in, &out); if (pid < 0) { perror("bi_popen"); return 1; } fprintf(out, "ls -l a.out\n"); getline(&line, &size, in); printf("-> %s", line); fprintf(out, "pwd\n"); getline(&line, &size, in); printf("-> %s", line); fprintf(out, "date\n"); getline(&line, &size, in); printf("-> %s", line); // Since in this case we can tell the child to terminate, we'll do so // and wait for it to terminate before we close down. fprintf(out, "exit\n"); waitpid(pid, NULL, 0); fclose(in); fclose(out); return 0; } 

In the program, I have defined a function bi_popen. The function take as input the path of a program to run as well as two FILE*: in for input from the command and out for output to the command.

bi_popen sets up two pipes, one for communicating from the parent process to the child process, and another for communicating from the child process to the parent process.

Next, bi_popen forks, creating a new process. The child process connects its standard output to the write-end of the pipe to the parent, and connects its standard input to the read-end of the pipe from the parent. It then cleans up the old pipe file descriptors and uses execlp to replace the running process with the given command. That new program inherits the standard input/output configuration with the pipes. On success, execlp never returns.

In the case of the parent --- when fork returns a non-zero value ---, the parent process closes the unnecessary ends of the pipe, and uses fdopen to create FILE* associated with the relevant pipe file descriptors. It updates the in and out output parameters with those values. Finally, it uses execlp on the output FILE* to make it unbuffered (so that you don't have to explicitly flush content you send to the child process).

The main function is an example of how to use the bi_popen function. It calls bi_popen with the command as /bin/bash. Anything written to the out stream is sent to bash to execute. Anything bash prints to standard output is available for reading from in.

Here's an example run of the program:

$ ./a.out -> -rwxr-xr-x 1 user group 20400 Aug 29 17:09 a.out -> /home/user/src/bidirecitonal_popen -> Sat Aug 29 05:10:52 PM EDT 2020 

Note that main writes a series of commands to the child process (here, bash), and the command responded with the expected output.

In your case, you could replace "/bin/bash" with "stockfish", then use out to write commands to stockfish and in to read the responses.

2
  • Thank you for the suggestions and nice explanations! I spent the last evenings trying to find a way to communicate with stockfish. I will implement it with bi_open() now. Commented Aug 29, 2020 at 5:54
  • Thank you! I tested it with the chess engine stockfish and it works like a charm. I'm so glad that it works now and can continue with the chess GUI. I'm also happy that I now have a general way to communicate with programs via STDIN and STDOUT. Commented Aug 29, 2020 at 18:43

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.