/* Copyright (C) 2002 Paolo Boldi and Sebastiano Vigna This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. referee: A driver program for checking IOI-like game programs version: 1.0 This program takes as arguments two player programs and makes them play one against the other. We assume that each player is written so that it reads information about the game from standard input, and then writes its moves on standard output. When it is the opponent's turn to move, a player must read the opponent's moves from standard input. This is the setup used for game problems in IOI 2001. We build a number of (named) pipes that interconnect the players and a number of auxiliary processes in the following way: ____________ / | / v y[0]/ -----> (handler 0) ----- / | | / |fifo[0][0] |fifo[0][1] / | | / | v -- stdin --> (Y process) (player 0) (player 1) \ ^ | \ | | \ |fifo[1][0] |fifo[1][1] y[1]\ | | \ ------ (handler 1) <---- \ ^ \_____________| The Y process copies stdin in two unnamed pipes. The handler processes read exclusively from those pipes until they are empty; then, they continue reading from the named pipes that connect them with the player with the same index. Everything handler i reads is fed into the player 1-i, again via a named pipe. (More precisely, a handler reads from a player when there is no input available from the Y process.) As a result, you can start the two player and give the game configuration to referee on standard input: the data will be duplicated, fed to the players, and then the game will start. Note that for this to have any sense, the players must write on stderr at some point the game outcome. If the option -0 (-1) is specified, the moves of player 0 (1) are written on stdout. WARNING: all player output *MUST* be followed by a C fflush(stdout); statement (or analogous statements for other languages) !! */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int optind; char *dummy[2]; #define BUFSIZE 1024 #define max(a,b) ((a)>(b)?(a):(b)) int main(int argc,char *argv[]) { char opt; int output[2] = { 0, 0 }, fdin = fileno(stdin), i, j, s; char *player[2]; pid_t ychild, handlechild[2], playchild[2]; int status; /* This set of pipes connects the two players. The first index is the direction (player i to player 1-i). The second index is the section (0 for player-to-handler, 1 for handler-to-player). */ char fifo[2][2][L_tmpnam]; /* This set of pipes duplicates stdin for the two players. */ int y[2][2]; if (argc<2) { fprintf(stderr, "Usage: %s [options] player0 player1\n" "Options:\n" " -0\t\t\tPrint output of the first player\n" " -1\t\t\tPrint output of the second player\n", argv[0]); return 1; } while( ( opt = getopt(argc, argv, "01") ) != -1 ) { switch(opt) { case '0': case '1': output[opt-'0'] = 1; break; case ':': case '?': return 1; } } if (argc-optind != 2) { fprintf(stderr, "You must specify exactly two players\n"); return 1; } player[0] = argv[optind]; player[1] = argv[optind+1]; if (pipe(y[0]) || pipe(y[1])) { fprintf(stderr, "Can't create pipe\n"); return 1; } /* We set up temporary names for all named pipes. */ for(i=0; i<2; i++) for(j=0; j<2; j++) /* tmpnam() is bad, but it's POSIX */ if (mkfifo(tmpnam(fifo[i][j]), S_IRUSR | S_IWUSR)) fprintf(stderr, "Can't create name pipe\n"); /* We create the Y process: it reads from stdin characters and duplicates them into y[0] and y[1]. */ switch(ychild=fork()) { case -1: fprintf(stderr, "Unsuccessul Y process fork()\n"); return 1; case 0: { char c[BUFSIZE]; int l; while((l = read(fdin, c, BUFSIZE)) > 0) { if (write(y[0][1], c, l) < l) fprintf(stderr, "Error writing to Y pipe 0\n"); if (write(y[1][1], c, l) < l) fprintf(stderr, "Error writing to Y pipe 1\n"); } close( y[0][1] ); close( y[1][1] ); return 0; } } /* We now fork the handlers. */ for(i=0; i<2; i++) { switch(handlechild[i]=fork()) { case -1: fprintf(stderr, "Unsuccessul handler %d fork()\n", i); return 1; case 0: { char c[BUFSIZE+1]; int l, eofy = 0, eofp = 0, readfd, writefd, M; fd_set readfdset; if ((readfd = open(fifo[i][0], O_RDONLY)) == -1) fprintf(stderr, "Cannot open fifo from player %d to handler\n", i); if ((writefd = open(fifo[i][1], O_WRONLY)) == -1) fprintf(stderr, "Cannot open fifo from handler to player %d\n", i); M = max(y[i][0], readfd)+1; while(!eofy || !eofp) { FD_ZERO(&readfdset); if (!eofy) FD_SET(y[i][0], &readfdset); if (!eofp) FD_SET(readfd, &readfdset); /* We wait both on the Y process output and the opponent's output. */ s = pselect(M, &readfdset, NULL, NULL, NULL, NULL); if ( s < 0 ) { fprintf(stderr, "Handler %d received an error while waiting for input: %s\n", i, strerror(errno)); return 1; } if (FD_ISSET(y[i][0], &readfdset)) { /* We got Y process stuff */ l = read(y[i][0], c, BUFSIZE); if (l == 0) { eofy = 1; close(y[i][0]); } else if (l > 0) { if (write(writefd, c, l) < l) fprintf(stderr, "Error copying stdin to player %d stdin", i); } else { fprintf(stderr, "Error while reading pipe in handler %d\n", i); return 1; } } else if (FD_ISSET(readfd, &readfdset)) { /* Otherwise, we got (and we read) opponent's stuff */ l = read(readfd, c, BUFSIZE); if (l == 0) { eofp = 1; close(readfd); } else if (l > 0) { if (write(writefd, c, l) < l) fprintf(stderr, "Error copying player %d stdout to player %d stdin", 1-i, i); if (output[i]) { c[l] = 0; fputs(c, stdout); fflush(stdout); } } else { fprintf(stderr, "Error while reading pipe in handler %d\n", i); return 1; } } } close(writefd); return 0; } } } /* We now fork the players. */ for(i=0; i<2; i++) { switch(playchild[i]=fork()) { case -1: fprintf(stderr, "Unsuccessul player %d fork()\n", i); return 1; case 0: { if (freopen(fifo[i][0], "w", stdout) == NULL) fprintf(stderr, "Cannot reopen player %d stdout: %s\n", i, strerror(errno)); if (freopen(fifo[1-i][1], "r", stdin) == NULL) fprintf(stderr, "Cannot reopen player %d stdin: %s\n", i, strerror(errno)); execve(player[i], dummy, NULL); fprintf(stderr, "Error starting player %d\n", i); return 1; } } } /* Wait for players to end. */ for ( i = 0; i < 2; i++ ) { waitpid( playchild[i], &status, 0 ); if ( !WIFEXITED( status ) ) { fprintf( stderr, "Bad status returned by player %d (%d)\n", i, status ); return 1; } } /* Kill all other children. */ for ( i = 0; i < 2 ; i++ ) { kill( handlechild[i], SIGKILL ); waitpid( handlechild[i], &status, 0 ); } kill( ychild, SIGKILL ); waitpid( ychild, &status, 0 ); for(i=0; i<2; i++) for(j=0; j<2; j++) unlink(fifo[i][j]); return 0; }