1

I am currently trying to run simple tests with the Stockfish chess engine in C++ to get the best possible move. I am starting Stockfish as a child process and want to use the input and output streams to pass on the desired UCI commands, which I am exchanging with the parent process via a pipe. However, when I run the code, Stockfish just starts and nothing else happens. Typing in the UCI commands in the CLI then works, but that's obviously not the goal. What am I doing wrong?

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

void sig_handler(int sig) {}

int main() {
    int fd1[2];
    if (pipe(fd1) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }    

    int pid = fork();
    if (pid == 0) {
        execl("./stockfish", "./stockfish", nullptr);
        close(fd1[1]);
        char position[60];
        read(fd1[0], position, 60);
        close(fd1[0]);
        cout << "position " << position << endl;
        cout << "go" << endl;
        sleep(5000);
        cout << "stop" << endl;
        string move;
        cin >> move;
        exit(0);
    }

    else {
        close(fd1[0]);
        cout << "Parent process" << endl;
        string line = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        write(fd1[1], line.c_str(), strlen(line.c_str()) + 1);
        close(fd1[1]);

        signal(SIGINT, &sig_handler);
        wait(NULL);
        cout << "Parent done" << endl;
    }
    return 0;
}
Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
ImanityDev
  • 73
  • 6
  • 1
    I suggest using [Boost.Process](https://www.boost.org/doc/libs/release/doc/html/boost_process/tutorial.html). It makes "talking" to other processes pretty simple. There are both synchronous and asynchronous I/O examples there that you could follow. – Ted Lyngmo Jan 29 '23 at 18:23
  • You have a fundamental misunderstanding of what `execl()` does, I believe. Read the manpage and pay particular attention to the returnvalue (which you ignore), you'll be surprised. – Ulrich Eckhardt Jan 29 '23 at 19:06
  • @UlrichEckhardt `The exec() functions only return if an error has occurred. The return value is -1, and errno is set to indicate the error.` And what should this tell me? It does not throw an error, otherwise the Stockfish process wouldn't even start. – ImanityDev Jan 29 '23 at 19:24
  • 2
    You should ask yourself "will the code after `execl` be executed?" (spoiler: it will not) – teapot418 Jan 29 '23 at 20:59
  • Great how friendly some people here are :) – ImanityDev Jan 29 '23 at 22:28
  • Working pipe/fork/dup2/exec examples are abundant. I suggest you at least take a look at one. This one may be useful: https://stackoverflow.com/a/42422311/21105992 – teapot418 Jan 29 '23 at 23:06
  • Well, some people tend to be a bit ironic or even sarcastic, but don't take that as unfriendlyness ;) Point is: You placed some code after execl that doesn't seem to be pure error handling – this code won't be executed, though, if `execl` is successful, and if not, you might not even need error handling as the parent process should notice its child dying prematurely and should handle this case anyway (stockfisch itself might crash as well). – Aconcagua Jan 30 '23 at 09:33
  • The code you added at the child process looks pretty much like the IO-handling for your application, by the way – if so, this needs to be done at the *parent*! Any pipe ends you want to close for the child you need to close *before* calling execl, otherwise they will remain open in the child process. – Aconcagua Jan 30 '23 at 09:37
  • Not sure how stockfish communicates – but *if* via standard in and out, you need to re-map your pipe file descriptors to `stdin_fileno` and `stdout_fileno` (again before calling `execl`). There should be questions here on SO for how to do so... – Aconcagua Jan 30 '23 at 09:40
  • 2
    I already found the solution thanks to @TedLyngmo. I will post the code as answer to the question. – ImanityDev Jan 30 '23 at 09:42

1 Answers1

2

Thanks to @TedLyngmo and some research about the Boost library, it was very easy to implement:

#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>

using namespace std;
namespace bp = boost::process;

int main() {
    bp::ipstream is;
    bp::opstream os;

    bp::child c("./stockfish", bp::std_in < os, bp::std_out > is);
    os << "uci" << endl;
    os << "isready" << endl;
    os << "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" << endl;
    os << "go depth 30" << endl;

    string line;
    string move_string;
    while (getline(is, line)) {
        if (!line.compare(0, 8, "bestmove")) {
            move_string = line;
            break;
        }
    }
    // Delete the "bestmove" part of the string and get rid of any trailing characters divided by space
    move_string = move_string.substr(9, move_string.size()-9);
    vector<string> mv;
    boost::split(mv, move_string, boost::is_any_of(" "));
    cout << "Stockfish move: " << mv.at(0) << endl;

    return 0;
}
ImanityDev
  • 73
  • 6