プログラミング - チェスエンジンstockfish/FIFO/Bashリダイレクトと通信

プログラミング - チェスエンジンstockfish/FIFO/Bashリダイレクトと通信

私は小さなチェスプログラムを書こうとしています。実際にはチェスのGUIに近いです。チェスGUIは、プレーヤーがコンピュータと対戦している間、内部的にStockfishチェスエンジンを使用する必要があります。

私はstockfishをインストールし、それを端末で実行し、STDINとSTDOUTを介して通信できます。たとえば、「isready」と入力すると、stockfishは「readyok」として応答します。

今私は、LinuxのいくつかのIPCメソッドを介してチェスGUIでstockfishと継続的に通信できるように努めています。最初はパイプを見ましたが、パイプ通信が一方向だったのであきらめました。それからFIFOとBashのリダイレクトについて読んで、今やってみましょう。 stockfishの出力の1行を読むことができるので、ある種の効果があります。しかし、これは最初の行でのみ機能します。その後、FIFOを介してstockfishに「isready」を送信し、stockfishの次の出力を読み取ろうとすると、応答はありません。 Bashリダイレクトを使用して、stockfishのSTDINとSTDOUTをFIFOにリダイレクトしました。

端末でstockfishを起動するためにこのスクリプトを実行します。

#!/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

./stockfish.sh を使用してこのスクリプトを呼び出します。

たとえば、私は次のCプログラムを持っています。 (私は初めて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;
}

端末へのプログラムの出力(ただし、プログラムは停止せずに待機する):

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

ああ、これは継続的に動作しません(今はループがありません。ループなしで2回以上読み取ろうとしました)。たとえば、stockfishスクリプトは1つの端末から1行を読み取り、1つの端末で終了します(連続して実行する代わりに)。実行)コード出力FIFO。あるいは、stockfish出力FIFOから1行の出力を読み取ることもできます。 Stockfishを使用してSTDINとSTDOUTを介してIPCを実行するより簡単な方法がある場合は、私も試してみてください。ありがとうございます。

ベストアンサー1

すでにCを使用しているので、stockchess管理にもCを使用することをお勧めします。popen()以下を提供するライブラリ機能があります。一方向プロセスへのパイプ - ユースケースには適していません。ただし、直接設定することもできます。

次のプログラム例を考えてみましょう。

#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;
}

プログラムで関数を定義しますbi_popen。この関数は、実行するプログラムのパスと入力の2つのパスを入力として使用しますFILE*in~からコマンドとout出力到着注文する。

bi_popen2つのパイプを設定します。 1 つは親プロセスから子プロセスへの通信用で、もう 1 つは子プロセスから親プロセスへの通信用です。

次に、bi_popen fork新しいプロセスを作成します。子プロセスは、標準出力を親パイプの書き込み端に接続し、標準入力を親パイプの読み出し端に接続します。次に、古いパイプファイル記述子をクリーンアップし、execlp実行中のプロセスを指定されたコマンドに置き換えます。新しいプログラムはパイプの標準入力/出力構成を継承します。一度成功すれば、execlp再び戻りません。

親プロセスの場合、forkゼロ以外の値が返されると、親プロセスはパイプの不要な端を閉じて、fdopen生成するFILE*関連パイプに関連付けられたファイル記述子を使用します。inこの値でパラメータを更新して出力します。最後にバッファリングされないように出力をout使用します。したがって、子プロセスに転送される内容を明示的にフラッシュする必要はありません。execlpFILE*

このmain機能は使用方法の例ですbi_popenbi_popenasコマンドを使用して呼び出されます/bin/bash。ストリームに書き込まれたoutすべてのコンテンツは、実行のためにbashに送信されます。 bashが標準出力に印刷するすべての内容を読むことができますin

以下は、このプログラムの仕組みの例です。

$ ./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

main一連のコマンドが子プロセス(ここ)に書き込まれ、そのbashコマンドは期待される出力で応答します。

あなたの場合は、「/bin/bash」を「stockfish」に置き換えてから、outコマンドを作成してstockfish応答inを読み取ることができます。

おすすめ記事