how to make a chess interface

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

tvrzsky
Posts: 128
Joined: Sat Sep 23, 2006 7:10 pm
Location: Prague

Re: how to make a chess interface

Post by tvrzsky »

ilari wrote: Yes, that helps. It's actually a relief that the crash always happened at the start of a match, because it means that the crash can probably be reproduced without waiting for hours. If it happens only every 20th time or so, it most likely means that there's a thread synchronization problem when the game threads are created and launched. This bug would probably be very easy to locate and fix if I could see a debugger backtrace (of a debug version of cutechess-cli) of the crash.
Yes, my impression was that there could be some race condition when starting threads. However it was strange that nobody else reported that.
Filip
User avatar
hgm
Posts: 27787
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: how to make a chess interface

Post by hgm »

tvrzsky wrote:BTW how copes WinBoard with very short time controls in range of tens milliseconds per move in those circumstances? Do I understand well that WinBoard is running locally and only engines run on remote host so all the communication goes through the network?
If you run with the -noGUI flag to suppress display update during games, WinBoard has no problem with 10msec/move. Engine-to-engine delays were typically about 1.5msec on my machine, only a bit slower than cutechess-cli. (Even for UCI engines, where using UCI2WB was a bit faster than using Polyglot.)

In the setup I had in mind, WinBoard would be running locally, and (most of) the engines would be running remote. This is the only way if you do not want to run all engines on the same machine (e.g. because you have a quad and two duals, and want to play 8 games). But I understand now that this is not the task you want to address.

Btw, in theory it would be possible to have instances of WinBoard run on different machines (e.g. four on the quad, two on each dual) playing on the same tourney, (all using engines on their own machine), provided they have across-network access to the same tournament and PGN file on one of the machines. This seems to be tricky, though, because it seems that the file locking, which should protect PGN and tourney file from corruption when two WB instances want to write it at the same time, might not work for access over a network. (I have never tried this myself.)
tvrzsky
Posts: 128
Joined: Sat Sep 23, 2006 7:10 pm
Location: Prague

Re: how to make a chess interface

Post by tvrzsky »

ilari wrote: Yes, that helps. It's actually a relief that the crash always happened at the start of a match, because it means that the crash can probably be reproduced without waiting for hours. If it happens only every 20th time or so, it most likely means that there's a thread synchronization problem when the game threads are created and launched. This bug would probably be very easy to locate and fix if I could see a debugger backtrace (of a debug version of cutechess-cli) of the crash.
I should add that at the time of the crash all the instances of starting engines were already running, hence the "annoying killing in the Taskmanager" :)
tvrzsky
Posts: 128
Joined: Sat Sep 23, 2006 7:10 pm
Location: Prague

Re: how to make a chess interface

Post by tvrzsky »

hgm wrote:
tvrzsky wrote:BTW how copes WinBoard with very short time controls in range of tens milliseconds per move in those circumstances? Do I understand well that WinBoard is running locally and only engines run on remote host so all the communication goes through the network?
If you run with the -noGUI flag to suppress display update during games, WinBoard has no problem with 10msec/move. Engine-to-engine delays were typically about 1.5msec on my machine, only a bit slower than cutechess-cli. (Even for UCI engines, where using UCI2WB was a bit faster than using Polyglot.)
What does it mean delay exactly here? Is it time between sending move a2a4 from the first engine and receiving a2a4 by the second one, i. e. is it comprised of the communication delay E1=>WB, WB=>E2 and time for WB to parse output, check legality etc. and send the command?
Is it 1.5msec running all locally via pipes? And are there any additional delays when running through the network (I mean some LAN on Ethernet or WiFi typically)?
In the setup I had in mind, WinBoard would be running locally, and (most of) the engines would be running remote. This is the only way if you do not want to run all engines on the same machine (e.g. because you have a quad and two duals
:shock: How did you got it right? I am really puzzled ... :D
, and want to play 8 games). But I understand now that this is not the task you want to address.
Yes, 8 games is too little, I want more definitely. :D
No, seriously, I am afraid of running those hyperbullet games on more machines because of consistency of results. These games are so sensitive to timing issues. Another thing is that both of my duals are almost identical hardware wise and software wise yet test result were often very different on them, I mean my engine for example does better on first one than on another one.
Btw, in theory it would be possible to have instances of WinBoard run on different machines (e.g. four on the quad, two on each dual) playing on the same tourney, (all using engines on their own machine), provided they have across-network access to the same tournament and PGN file on one of the machines. This seems to be tricky, though, because it seems that the file locking, which should protect PGN and tourney file from corruption when two WB instances want to write it at the same time, might not work for access over a network. (I have never tried this myself.)
Yes, it seems to be too complicated, in this case having one server on each machine should be simpler. And separate PGNs should be merged afterwards.
Chan Rasjid
Posts: 588
Joined: Thu Mar 09, 2006 4:47 pm
Location: Singapore

Re: how to make a chess interface

Post by Chan Rasjid »

Hello,

This is a simple C program that launches two uci engines to play a simple game. I tested that it compiles and runs under linux.

Incidentally, my xboard cannot work anymore (arch linux +kde; something about... Failed to start 1st chess engine... ) so my chess program has to stop for a while; there is so far no good-enough replacement linux chess interface for uci engines meant for programmers.

Rasjid

Code: Select all

#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/timeb.h>
#include <sys/time.h>
#include <assert.h>

/* This program  launches two uci engines and have them play a simple game. */

#define WHITE 0
#define BLACK 1

static FILE * filein&#91;2&#93;, * fileout&#91;2&#93;;

void sendmsg&#40;const char *msg, const int bw&#41; &#123;
    fprintf&#40;fileout&#91;bw&#93;, "%s", msg&#41;;
&#125;

void myfatal&#40;const char *msg&#41; &#123;
    char buf&#91;1024&#93;;
    sprintf&#40;buf, "Program aborting, %s", msg&#41;;
    perror&#40;buf&#41;;
    printf&#40;"%s", fgets&#40;buf, 1023, stderr&#41;);
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    exit&#40;EXIT_FAILURE&#41;;
&#125;

void getmsg&#40;char * line, int nbyte, const int bw&#41; &#123;
    if &#40;fgets&#40;line, nbyte, filein&#91;bw&#93;) == NULL&#41; &#123;
        char buf&#91;256&#93;;
        sprintf&#40;buf, "side %d getmsg - fgets failed", bw&#41;;
        myfatal&#40;buf&#41;;
    &#125;
&#125;

struct timeval tv_start;

unsigned int msecfrom&#40;struct timeval *start&#41; &#123;
    struct timeval now;
    if (!gettimeofday&#40;&now, NULL&#41;);
    else &#123;
        myfatal&#40;"gettimeofday failed");
    &#125;
    return &#40;now.tv_sec - start->tv_sec&#41; * 1000
            + &#40;now.tv_usec - start->tv_usec&#41; / 1000;
&#125;

int main&#40;int argc, char *argv&#91;&#93;) &#123;
    char line&#91;65536&#93;;
    char command&#91;1024&#93;;
    pid_t pid&#91;2&#93;;
    int pipe_i&#91;2&#93;&#91;2&#93;;
    int pipe_o&#91;2&#93;&#91;2&#93;;

    /* Create the white pipes, fork white child process */
    if &#40;pipe&#40;pipe_i&#91;WHITE&#93;)) &#123;
        myfatal&#40;"create pipe_i failed");
    &#125;

    if &#40;pipe&#40;pipe_o&#91;WHITE&#93;)) &#123;
        myfatal&#40;"create pipe_o failed");
    &#125;

    /* Create white child process. */
    pid&#91;WHITE&#93; = fork&#40;);
    if &#40;pid&#91;WHITE&#93; < &#40;pid_t&#41; 0&#41; &#123;
        myfatal&#40;"fork failed");
    &#125;

    if &#40;pid&#91;WHITE&#93; == &#40;pid_t&#41; 0&#41; &#123;
        /* This is the child process. Close other end first. */
        // replace standard input with input part of pipe
        printf&#40;"white child process\n");

        if &#40;dup2&#40;pipe_i&#91;WHITE&#93;&#91;0&#93;, 0&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_i&#91;WHITE&#93;&#91;0&#93; failed");
        &#125;
        close&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;);
        // replace standard output with output part of pipe
        if &#40;dup2&#40;pipe_o&#91;WHITE&#93;&#91;1&#93;, 1&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_o&#91;1&#93; failed");
        &#125;
        close&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;);
        setvbuf&#40;stdin, NULL, _IONBF, 0&#41;;
        setvbuf&#40;stdout, NULL, _IONBF, 0&#41;;
        execlp&#40;"/home/rasjid/chess/fruit/fruit_22", "fruit_22", NULL&#41;;
        //execlp&#40;"/home/rasjid/chess/texel/texel-101-64-ja", "texel", NULL&#41;;

        //execl returns only on error
        exit&#40;EXIT_FAILURE&#41;;
    &#125; else &#123;
        //parent process
        printf&#40;"parent process\n");
        close&#40;pipe_i&#91;WHITE&#93;&#91;0&#93;);
        close&#40;pipe_o&#91;WHITE&#93;&#91;1&#93;);

        if (&#40;filein&#91;WHITE&#93; = fdopen&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;, "r")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_o&#91;0&#93; failed");
        &#125;
        if (&#40;fileout&#91;WHITE&#93; = fdopen&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;, "w")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_i&#91;1&#93; failed");
        &#125;

        setvbuf&#40;filein&#91;WHITE&#93;, NULL, _IONBF, 0&#41;;
        setvbuf&#40;fileout&#91;WHITE&#93;, NULL, _IONBF, 0&#41;;
    &#125;

    /* Create the black pipes, fork black child process */
    if &#40;pipe&#40;pipe_i&#91;BLACK&#93;)) &#123;
        myfatal&#40;"create pipe_i failed");
    &#125;

    if &#40;pipe&#40;pipe_o&#91;BLACK&#93;)) &#123;
        myfatal&#40;"create pipe_o failed");
    &#125;

    /* Create black child process. */
    pid&#91;BLACK&#93; = fork&#40;);
    if &#40;pid&#91;BLACK&#93; < &#40;pid_t&#41; 0&#41; &#123;
        myfatal&#40;"fork failed");
    &#125;

    if &#40;pid&#91;BLACK&#93; == &#40;pid_t&#41; 0&#41; &#123;
        /* This is the child process. Close other end first. */
        // replace standard input with input part of pipe
        printf&#40;"black child process\n");
        //white pipes are also inherited, child end must be closed
        close&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;);
        close&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;);

        if &#40;dup2&#40;pipe_i&#91;BLACK&#93;&#91;0&#93;, 0&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_i&#91;BLACK&#93;&#91;0&#93; failed");
        &#125;
        close&#40;pipe_i&#91;BLACK&#93;&#91;1&#93;);
        // replace standard output with output part of pipe
        if &#40;dup2&#40;pipe_o&#91;BLACK&#93;&#91;1&#93;, 1&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_o&#91;1&#93; failed");
        &#125;
        close&#40;pipe_o&#91;BLACK&#93;&#91;0&#93;);
        setvbuf&#40;stdin, NULL, _IONBF, 0&#41;;
        setvbuf&#40;stdout, NULL, _IONBF, 0&#41;;
        //execlp&#40;"/home/rasjid/chess/fruit/fruit_22", "fruit_22", NULL&#41;;
        execlp&#40;"/home/rasjid/chess/texel/texel-101-64-ja", "texel", NULL&#41;;

        //execl returns only on error
        exit&#40;EXIT_FAILURE&#41;;
    &#125; else &#123;
        //parent process
        printf&#40;"parent process\n");
        close&#40;pipe_i&#91;BLACK&#93;&#91;0&#93;);
        close&#40;pipe_o&#91;BLACK&#93;&#91;1&#93;);

        if (&#40;filein&#91;BLACK&#93; = fdopen&#40;pipe_o&#91;BLACK&#93;&#91;0&#93;, "r")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_o&#91;0&#93; failed");
        &#125;
        if (&#40;fileout&#91;BLACK&#93; = fdopen&#40;pipe_i&#91;BLACK&#93;&#91;1&#93;, "w")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_i&#91;1&#93; failed");
        &#125;

        setvbuf&#40;filein&#91;BLACK&#93;, NULL, _IONBF, 0&#41;;
        setvbuf&#40;fileout&#91;BLACK&#93;, NULL, _IONBF, 0&#41;;
    &#125;

    sendmsg&#40;"uci\n", WHITE&#41;;
    while &#40;1&#41; &#123;
        getmsg&#40;line, 65536, WHITE&#41;;
        sscanf&#40;line, "%s", command&#41;;
        if (!strcmp&#40;command, "uciok")) &#123;
            sendmsg&#40;"setoption name Hash value 64", WHITE&#41;;
            break;
        &#125;
    &#125;

    printf&#40;"white uci ok\n");

    sendmsg&#40;"uci\n", BLACK&#41;;
    while &#40;1&#41; &#123;
        getmsg&#40;line, 65536, BLACK&#41;;
        sscanf&#40;line, "%s", command&#41;;
        if (!strcmp&#40;command, "uciok")) &#123;
            sendmsg&#40;"setoption name Hash value 64", BLACK&#41;;
            break;
        &#125;
    &#125;
    printf&#40;"black uci ok\n");

    //postion command
    //sendmsg&#40;"isready\n", WHITE&#41;;
    int side, nummove;
    unsigned int ms;
    int clockms&#91;2&#93; = &#123;60000, 60000&#125;;
    char init_position&#91;1024&#93;; // = "position startpos";
    char moves&#91;1024&#93;&#91;8&#93;;
    char buf&#91;2048 * 8&#93;, *pmove;

    moves&#91;0&#93;&#91;0&#93; = 0;
    sprintf&#40;init_position, "position startpos");
    side = WHITE;

    while &#40;1&#41; &#123;
        sprintf&#40;buf, "%s", init_position&#41;;
        nummove = 0;
        pmove = &moves&#91;0&#93;&#91;0&#93;;
        while (*pmove != 0&#41; &#123;
            if &#40;nummove == 0&#41; &#123;
                strcat&#40;buf, " moves ");
            &#125; else &#123;
                strcat&#40;buf, " ");
            &#125;
            strcat&#40;buf, pmove&#41;;
            pmove += 8;
            ++nummove;
        &#125;
        strcat&#40;buf, "\n");
        //send position command
        printf&#40;"sent %s\n", buf&#41;;
        sendmsg&#40;buf, side&#41;;
        sendmsg&#40;"isready\n", side&#41;;
        while &#40;1&#41; &#123;
            getmsg&#40;line, 65536, side&#41;;
            sscanf&#40;line, "%s", command&#41;;
            if (!strcmp&#40;command, "readyok")) &#123;
                sprintf&#40;buf, "go wtime %d btime %d\n", clockms&#91;0&#93;, clockms&#91;1&#93;);
                //send go command
                sendmsg&#40;buf, side&#41;;
                if (!gettimeofday&#40;&tv_start, NULL&#41;);
                else &#123;
                    myfatal&#40;"W gettimeofday error");
                &#125;

                printf&#40;"sent %s\n", buf&#41;;
                // wait for move
                while &#40;1&#41; &#123;
                    getmsg&#40;line, 65536, side&#41;;
                    ms = msecfrom&#40;&tv_start&#41;;
                    sscanf&#40;line, "%s", command&#41;;
                    if (!strcmp&#40;command, "bestmove")) &#123;
                        if &#40;sscanf&#40;line, "%*s %s", command&#41; == 1&#41; &#123;
                            // bestmove
                            strcpy&#40;pmove, command&#41;;
                            *&#40;pmove + 8&#41; = 0;
                            clockms&#91;side&#93; -= ms;
                            if (!strcmp&#40;pmove, "0000")) &#123;
                                //side mated
                                goto GAME_OVER;
                            &#125;
                            if &#40;clockms&#91;side&#93; < 0&#41; &#123;
                                goto LOST_ON_TIME;
                            &#125;
                            printf&#40;"%4d %s\n", nummove + 1, command&#41;;
                            side ^= 1;
                            break;
                        &#125; else &#123;
                            myfatal&#40;"sscanf bestmove error 1");
                        &#125;
                    &#125;
                &#125;
                break;
            &#125;
        &#125;

        assert&#40;side == BLACK&#41;;

        sprintf&#40;buf, "%s", init_position&#41;;
        nummove = 0;
        pmove = &moves&#91;0&#93;&#91;0&#93;;
        while (*pmove != 0&#41; &#123;
            if &#40;nummove == 0&#41; &#123;
                strcat&#40;buf, " moves ");
            &#125; else &#123;
                strcat&#40;buf, " ");
            &#125;
            strcat&#40;buf, pmove&#41;;
            pmove += 8;
            ++nummove;
        &#125;
        strcat&#40;buf, "\n");
        //send position command
        printf&#40;"sent %s\n", buf&#41;;
        sendmsg&#40;buf, side&#41;;
        sendmsg&#40;"isready\n", side&#41;;
        while &#40;1&#41; &#123;
            getmsg&#40;line, 65536, side&#41;;
            sscanf&#40;line, "%s", command&#41;;
            if (!strcmp&#40;command, "readyok")) &#123;
                sprintf&#40;buf, "go wtime %d btime %d\n", clockms&#91;0&#93;, clockms&#91;1&#93;);
                //send go command
                sendmsg&#40;buf, side&#41;;
                if (!gettimeofday&#40;&tv_start, NULL&#41;);
                else &#123;
                    myfatal&#40;"B gettimeofday error");
                &#125;
                printf&#40;"sent %s\n", buf&#41;;
                // wait for move
                while &#40;1&#41; &#123;
                    getmsg&#40;line, 65536, side&#41;;
                    ms = msecfrom&#40;&tv_start&#41;;
                    sscanf&#40;line, "%s", command&#41;;
                    if (!strcmp&#40;command, "bestmove")) &#123;
                        if &#40;sscanf&#40;line, "%*s %s", command&#41; == 1&#41; &#123;
                            strcpy&#40;pmove, command&#41;;
                            // bestmove
                            *&#40;pmove + 8&#41; = 0;
                            clockms&#91;side&#93; -= ms;
                            if (!strcmp&#40;pmove, "0000")) &#123;
                                //side mated
                                goto GAME_OVER;
                            &#125;
                            if &#40;clockms&#91;side&#93; < 0&#41; &#123;
                                goto LOST_ON_TIME;
                            &#125;

                            printf&#40;"%4d %s\n", nummove + 1, command&#41;;
                            side ^= 1;
                            break;
                        &#125; else &#123;
                            myfatal&#40;"sscanf bestmove error 1");
                        &#125;
                    &#125;
                &#125;
                break;
            &#125;
        &#125;
    &#125;

LOST_ON_TIME&#58;
    ;
    printf&#40;"%s lost on time\n", side ? "Black" &#58; "White");
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    return EXIT_SUCCESS;

GAME_OVER&#58;
    ;
    printf&#40;"%s mated\n", side ? "Black" &#58; "White");
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    return EXIT_SUCCESS;
&#125;

User avatar
hgm
Posts: 27787
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: how to make a chess interface

Post by hgm »

tvrzsky wrote:What does it mean delay exactly here? Is it time between sending move a2a4 from the first engine and receiving a2a4 by the second one, i. e. is it comprised of the communication delay E1=>WB, WB=>E2 and time for WB to parse output, check legality etc. and send the command?
Is it 1.5msec running all locally via pipes? And are there any additional delays when running through the network (I mean some LAN on Ethernet or WiFi typically)?

Indeed, this was running locally through pipes. What I did was make modified versions of Fairy-Max (WB) and Fruit (UCI) which print sub-millisecond time stamps when receiving and sending a move. By running these modified versions the time stamps end up in the debug log of the GUI, and I made a simple program to extract statistics (averages, histograms) on the delay fro such a debug log.

I published the results for WinBoard/XBoard and cutechess-cli on my laptop under Linux and Windows some time ago here, and also posted a package with the modified engines and analysis program on my website, so people can use it to time their favorite GUI on their own system.
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: how to make a chess interface

Post by lucasart »

tvrzsky wrote:forking (frankly this idea is really strange for me)
Congratulations for coding your tournament manager. That's certainly a bigger task than it seems.

As for forking, it is indeed very strange. But so beautiful !
http://www.youtube.com/watch?v=xHu7qI1gDPA
User avatar
Don
Posts: 5106
Joined: Tue Apr 29, 2008 4:27 pm

Re: how to make a chess interface

Post by Don »

lucasart wrote:
kbhearn wrote:Setting up a child process differs between operating systems. You can use a library to get a common interface to the differing underlying function calls, i know QT provides a child process object, i'd guess the other major gui libraries might as well. Once you get the process setup with its stdin and stdout mapped to pipes you (or your library) have defined, you write to those pipes with standard io functions.

As for receiving from the programs, ways to do nonblocking reads/check for input are again, operating system specific. A seperate thread in an infinite read loop may just be simpler.
OK. For spawning a process asynchronously, it seems that GTKmm gives me a very nice and portable wrapper:
http://developer.gnome.org/glibmm/unsta ... Spawn.html

I'm still wondering (just for my understanding) if it's not possible to write this in standard C++ without using Qt or GTKmm, in a portable way. Basically I'm looking for a version of the exec() system call that is asynchronous, and gives me file handles for stdin/stdout.

Now, for reading responses from the program. I know one way (a loop that looks into the stdout without flushing it, I've got the code for POSIX and Windows in my chess engine). I'll have a look at the GTKmm documentation for a way to read a pipe without flushing it, as it will allow me to have portable code for that. But should I do a loop ? Or is there a way to connect a function to stuff appearing in a pipe, in the same way as you connect a function to a button being clicked for example in a GUI ?

Problem with a loop, is that I'll have to run it in a separate thread as you mentionned (otherwise the GUI will be irresponsive), and that I find it somewhat inelegant.
There are some libraries available for C or C++ that let you set up event loops in elegant ways. You do not need threads to do a good job with this. Also, this is probably already built in to most GUI libraries.
Capital punishment would be more effective as a preventive measure if it were administered prior to the crime.
User avatar
Don
Posts: 5106
Joined: Tue Apr 29, 2008 4:27 pm

Re: how to make a chess interface

Post by Don »

tvrzsky wrote:Sorry for my spamming here but another idea which I have in mind is to write some little server for remote controlling of tournaments. I am only in the phase of initial trials so far beacuse networking and using of sockets is totally new concept for me. However laziness is big driving force of progress and my dream is to by just one keyboard hit start it ALL - compiling, testing etc. and then after a few hours only have one look at some table and see - OK, this was worth of x Elo or this does not work :D
This is all simple stuff. I have a distributed tester that isn't quite the same thing but uses sockets to connect to a common server to schedule games. Several volunteers are helping me test Komodo with this.

It's easy to write a basic server (but a full featured one with all the bells and whistles could take weeks or months.) You could write a Swiss Tournament Server in a couple of days using any high level language. Add a day to this if you do not have a good move generator for the language the server is written in.
Capital punishment would be more effective as a preventive measure if it were administered prior to the crime.
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: how to make a chess interface

Post by lucasart »

Thank you so much! Your code is an invaluable ressource for me. It's also very clean and self documented.

Now, I need to play around with it, figure out how it works, and rewrite it in a way that suits my needs.
Chan Rasjid wrote:Hello,

This is a simple C program that launches two uci engines to play a simple game. I tested that it compiles and runs under linux.

Incidentally, my xboard cannot work anymore (arch linux +kde; something about... Failed to start 1st chess engine... ) so my chess program has to stop for a while; there is so far no good-enough replacement linux chess interface for uci engines meant for programmers.

Rasjid

Code: Select all

#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/timeb.h>
#include <sys/time.h>
#include <assert.h>

/* This program  launches two uci engines and have them play a simple game. */

#define WHITE 0
#define BLACK 1

static FILE * filein&#91;2&#93;, * fileout&#91;2&#93;;

void sendmsg&#40;const char *msg, const int bw&#41; &#123;
    fprintf&#40;fileout&#91;bw&#93;, "%s", msg&#41;;
&#125;

void myfatal&#40;const char *msg&#41; &#123;
    char buf&#91;1024&#93;;
    sprintf&#40;buf, "Program aborting, %s", msg&#41;;
    perror&#40;buf&#41;;
    printf&#40;"%s", fgets&#40;buf, 1023, stderr&#41;);
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    exit&#40;EXIT_FAILURE&#41;;
&#125;

void getmsg&#40;char * line, int nbyte, const int bw&#41; &#123;
    if &#40;fgets&#40;line, nbyte, filein&#91;bw&#93;) == NULL&#41; &#123;
        char buf&#91;256&#93;;
        sprintf&#40;buf, "side %d getmsg - fgets failed", bw&#41;;
        myfatal&#40;buf&#41;;
    &#125;
&#125;

struct timeval tv_start;

unsigned int msecfrom&#40;struct timeval *start&#41; &#123;
    struct timeval now;
    if (!gettimeofday&#40;&now, NULL&#41;);
    else &#123;
        myfatal&#40;"gettimeofday failed");
    &#125;
    return &#40;now.tv_sec - start->tv_sec&#41; * 1000
            + &#40;now.tv_usec - start->tv_usec&#41; / 1000;
&#125;

int main&#40;int argc, char *argv&#91;&#93;) &#123;
    char line&#91;65536&#93;;
    char command&#91;1024&#93;;
    pid_t pid&#91;2&#93;;
    int pipe_i&#91;2&#93;&#91;2&#93;;
    int pipe_o&#91;2&#93;&#91;2&#93;;

    /* Create the white pipes, fork white child process */
    if &#40;pipe&#40;pipe_i&#91;WHITE&#93;)) &#123;
        myfatal&#40;"create pipe_i failed");
    &#125;

    if &#40;pipe&#40;pipe_o&#91;WHITE&#93;)) &#123;
        myfatal&#40;"create pipe_o failed");
    &#125;

    /* Create white child process. */
    pid&#91;WHITE&#93; = fork&#40;);
    if &#40;pid&#91;WHITE&#93; < &#40;pid_t&#41; 0&#41; &#123;
        myfatal&#40;"fork failed");
    &#125;

    if &#40;pid&#91;WHITE&#93; == &#40;pid_t&#41; 0&#41; &#123;
        /* This is the child process. Close other end first. */
        // replace standard input with input part of pipe
        printf&#40;"white child process\n");

        if &#40;dup2&#40;pipe_i&#91;WHITE&#93;&#91;0&#93;, 0&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_i&#91;WHITE&#93;&#91;0&#93; failed");
        &#125;
        close&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;);
        // replace standard output with output part of pipe
        if &#40;dup2&#40;pipe_o&#91;WHITE&#93;&#91;1&#93;, 1&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_o&#91;1&#93; failed");
        &#125;
        close&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;);
        setvbuf&#40;stdin, NULL, _IONBF, 0&#41;;
        setvbuf&#40;stdout, NULL, _IONBF, 0&#41;;
        execlp&#40;"/home/rasjid/chess/fruit/fruit_22", "fruit_22", NULL&#41;;
        //execlp&#40;"/home/rasjid/chess/texel/texel-101-64-ja", "texel", NULL&#41;;

        //execl returns only on error
        exit&#40;EXIT_FAILURE&#41;;
    &#125; else &#123;
        //parent process
        printf&#40;"parent process\n");
        close&#40;pipe_i&#91;WHITE&#93;&#91;0&#93;);
        close&#40;pipe_o&#91;WHITE&#93;&#91;1&#93;);

        if (&#40;filein&#91;WHITE&#93; = fdopen&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;, "r")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_o&#91;0&#93; failed");
        &#125;
        if (&#40;fileout&#91;WHITE&#93; = fdopen&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;, "w")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_i&#91;1&#93; failed");
        &#125;

        setvbuf&#40;filein&#91;WHITE&#93;, NULL, _IONBF, 0&#41;;
        setvbuf&#40;fileout&#91;WHITE&#93;, NULL, _IONBF, 0&#41;;
    &#125;

    /* Create the black pipes, fork black child process */
    if &#40;pipe&#40;pipe_i&#91;BLACK&#93;)) &#123;
        myfatal&#40;"create pipe_i failed");
    &#125;

    if &#40;pipe&#40;pipe_o&#91;BLACK&#93;)) &#123;
        myfatal&#40;"create pipe_o failed");
    &#125;

    /* Create black child process. */
    pid&#91;BLACK&#93; = fork&#40;);
    if &#40;pid&#91;BLACK&#93; < &#40;pid_t&#41; 0&#41; &#123;
        myfatal&#40;"fork failed");
    &#125;

    if &#40;pid&#91;BLACK&#93; == &#40;pid_t&#41; 0&#41; &#123;
        /* This is the child process. Close other end first. */
        // replace standard input with input part of pipe
        printf&#40;"black child process\n");
        //white pipes are also inherited, child end must be closed
        close&#40;pipe_i&#91;WHITE&#93;&#91;1&#93;);
        close&#40;pipe_o&#91;WHITE&#93;&#91;0&#93;);

        if &#40;dup2&#40;pipe_i&#91;BLACK&#93;&#91;0&#93;, 0&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_i&#91;BLACK&#93;&#91;0&#93; failed");
        &#125;
        close&#40;pipe_i&#91;BLACK&#93;&#91;1&#93;);
        // replace standard output with output part of pipe
        if &#40;dup2&#40;pipe_o&#91;BLACK&#93;&#91;1&#93;, 1&#41; != -1&#41;;
        else &#123;
            myfatal&#40;"dup2 pipe_o&#91;1&#93; failed");
        &#125;
        close&#40;pipe_o&#91;BLACK&#93;&#91;0&#93;);
        setvbuf&#40;stdin, NULL, _IONBF, 0&#41;;
        setvbuf&#40;stdout, NULL, _IONBF, 0&#41;;
        //execlp&#40;"/home/rasjid/chess/fruit/fruit_22", "fruit_22", NULL&#41;;
        execlp&#40;"/home/rasjid/chess/texel/texel-101-64-ja", "texel", NULL&#41;;

        //execl returns only on error
        exit&#40;EXIT_FAILURE&#41;;
    &#125; else &#123;
        //parent process
        printf&#40;"parent process\n");
        close&#40;pipe_i&#91;BLACK&#93;&#91;0&#93;);
        close&#40;pipe_o&#91;BLACK&#93;&#91;1&#93;);

        if (&#40;filein&#91;BLACK&#93; = fdopen&#40;pipe_o&#91;BLACK&#93;&#91;0&#93;, "r")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_o&#91;0&#93; failed");
        &#125;
        if (&#40;fileout&#91;BLACK&#93; = fdopen&#40;pipe_i&#91;BLACK&#93;&#91;1&#93;, "w")) == NULL&#41; &#123;
            myfatal&#40;"parent open pipe_i&#91;1&#93; failed");
        &#125;

        setvbuf&#40;filein&#91;BLACK&#93;, NULL, _IONBF, 0&#41;;
        setvbuf&#40;fileout&#91;BLACK&#93;, NULL, _IONBF, 0&#41;;
    &#125;

    sendmsg&#40;"uci\n", WHITE&#41;;
    while &#40;1&#41; &#123;
        getmsg&#40;line, 65536, WHITE&#41;;
        sscanf&#40;line, "%s", command&#41;;
        if (!strcmp&#40;command, "uciok")) &#123;
            sendmsg&#40;"setoption name Hash value 64", WHITE&#41;;
            break;
        &#125;
    &#125;

    printf&#40;"white uci ok\n");

    sendmsg&#40;"uci\n", BLACK&#41;;
    while &#40;1&#41; &#123;
        getmsg&#40;line, 65536, BLACK&#41;;
        sscanf&#40;line, "%s", command&#41;;
        if (!strcmp&#40;command, "uciok")) &#123;
            sendmsg&#40;"setoption name Hash value 64", BLACK&#41;;
            break;
        &#125;
    &#125;
    printf&#40;"black uci ok\n");

    //postion command
    //sendmsg&#40;"isready\n", WHITE&#41;;
    int side, nummove;
    unsigned int ms;
    int clockms&#91;2&#93; = &#123;60000, 60000&#125;;
    char init_position&#91;1024&#93;; // = "position startpos";
    char moves&#91;1024&#93;&#91;8&#93;;
    char buf&#91;2048 * 8&#93;, *pmove;

    moves&#91;0&#93;&#91;0&#93; = 0;
    sprintf&#40;init_position, "position startpos");
    side = WHITE;

    while &#40;1&#41; &#123;
        sprintf&#40;buf, "%s", init_position&#41;;
        nummove = 0;
        pmove = &moves&#91;0&#93;&#91;0&#93;;
        while (*pmove != 0&#41; &#123;
            if &#40;nummove == 0&#41; &#123;
                strcat&#40;buf, " moves ");
            &#125; else &#123;
                strcat&#40;buf, " ");
            &#125;
            strcat&#40;buf, pmove&#41;;
            pmove += 8;
            ++nummove;
        &#125;
        strcat&#40;buf, "\n");
        //send position command
        printf&#40;"sent %s\n", buf&#41;;
        sendmsg&#40;buf, side&#41;;
        sendmsg&#40;"isready\n", side&#41;;
        while &#40;1&#41; &#123;
            getmsg&#40;line, 65536, side&#41;;
            sscanf&#40;line, "%s", command&#41;;
            if (!strcmp&#40;command, "readyok")) &#123;
                sprintf&#40;buf, "go wtime %d btime %d\n", clockms&#91;0&#93;, clockms&#91;1&#93;);
                //send go command
                sendmsg&#40;buf, side&#41;;
                if (!gettimeofday&#40;&tv_start, NULL&#41;);
                else &#123;
                    myfatal&#40;"W gettimeofday error");
                &#125;

                printf&#40;"sent %s\n", buf&#41;;
                // wait for move
                while &#40;1&#41; &#123;
                    getmsg&#40;line, 65536, side&#41;;
                    ms = msecfrom&#40;&tv_start&#41;;
                    sscanf&#40;line, "%s", command&#41;;
                    if (!strcmp&#40;command, "bestmove")) &#123;
                        if &#40;sscanf&#40;line, "%*s %s", command&#41; == 1&#41; &#123;
                            // bestmove
                            strcpy&#40;pmove, command&#41;;
                            *&#40;pmove + 8&#41; = 0;
                            clockms&#91;side&#93; -= ms;
                            if (!strcmp&#40;pmove, "0000")) &#123;
                                //side mated
                                goto GAME_OVER;
                            &#125;
                            if &#40;clockms&#91;side&#93; < 0&#41; &#123;
                                goto LOST_ON_TIME;
                            &#125;
                            printf&#40;"%4d %s\n", nummove + 1, command&#41;;
                            side ^= 1;
                            break;
                        &#125; else &#123;
                            myfatal&#40;"sscanf bestmove error 1");
                        &#125;
                    &#125;
                &#125;
                break;
            &#125;
        &#125;

        assert&#40;side == BLACK&#41;;

        sprintf&#40;buf, "%s", init_position&#41;;
        nummove = 0;
        pmove = &moves&#91;0&#93;&#91;0&#93;;
        while (*pmove != 0&#41; &#123;
            if &#40;nummove == 0&#41; &#123;
                strcat&#40;buf, " moves ");
            &#125; else &#123;
                strcat&#40;buf, " ");
            &#125;
            strcat&#40;buf, pmove&#41;;
            pmove += 8;
            ++nummove;
        &#125;
        strcat&#40;buf, "\n");
        //send position command
        printf&#40;"sent %s\n", buf&#41;;
        sendmsg&#40;buf, side&#41;;
        sendmsg&#40;"isready\n", side&#41;;
        while &#40;1&#41; &#123;
            getmsg&#40;line, 65536, side&#41;;
            sscanf&#40;line, "%s", command&#41;;
            if (!strcmp&#40;command, "readyok")) &#123;
                sprintf&#40;buf, "go wtime %d btime %d\n", clockms&#91;0&#93;, clockms&#91;1&#93;);
                //send go command
                sendmsg&#40;buf, side&#41;;
                if (!gettimeofday&#40;&tv_start, NULL&#41;);
                else &#123;
                    myfatal&#40;"B gettimeofday error");
                &#125;
                printf&#40;"sent %s\n", buf&#41;;
                // wait for move
                while &#40;1&#41; &#123;
                    getmsg&#40;line, 65536, side&#41;;
                    ms = msecfrom&#40;&tv_start&#41;;
                    sscanf&#40;line, "%s", command&#41;;
                    if (!strcmp&#40;command, "bestmove")) &#123;
                        if &#40;sscanf&#40;line, "%*s %s", command&#41; == 1&#41; &#123;
                            strcpy&#40;pmove, command&#41;;
                            // bestmove
                            *&#40;pmove + 8&#41; = 0;
                            clockms&#91;side&#93; -= ms;
                            if (!strcmp&#40;pmove, "0000")) &#123;
                                //side mated
                                goto GAME_OVER;
                            &#125;
                            if &#40;clockms&#91;side&#93; < 0&#41; &#123;
                                goto LOST_ON_TIME;
                            &#125;

                            printf&#40;"%4d %s\n", nummove + 1, command&#41;;
                            side ^= 1;
                            break;
                        &#125; else &#123;
                            myfatal&#40;"sscanf bestmove error 1");
                        &#125;
                    &#125;
                &#125;
                break;
            &#125;
        &#125;
    &#125;

LOST_ON_TIME&#58;
    ;
    printf&#40;"%s lost on time\n", side ? "Black" &#58; "White");
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    return EXIT_SUCCESS;

GAME_OVER&#58;
    ;
    printf&#40;"%s mated\n", side ? "Black" &#58; "White");
    sendmsg&#40;"quit\n", WHITE&#41;;
    sendmsg&#40;"quit\n", BLACK&#41;;
    return EXIT_SUCCESS;
&#125;