Fix type of shuffleOpenings
[xboard.git] / backend.c
index fa46773..6764bea 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -233,6 +233,7 @@ void InitDrawingSizes(int x, int y);
 void NextMatchGame P((void));
 int NextTourneyGame P((int nr, int *swap));
 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
+FILE *WriteTourneyFile P((char *results));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -417,7 +418,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
 
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
 
 /* premove variables */
 int premoveToX = 0;
@@ -492,7 +493,7 @@ int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
 int   initialRulePlies, FENrulePlies;
 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
 int loadFlag = 0;
-int shuffleOpenings;
+Boolean shuffleOpenings;
 int mute; // mute all sounds
 
 // [HGM] vari: next 12 to save and restore variations
@@ -796,7 +797,11 @@ InitEngine(ChessProgramState *cps, int n)
 
     /* [HGM] debug */
     cps->debug = FALSE;
+
     cps->supportsNPS = UNKNOWN;
+    cps->memSize = FALSE;
+    cps->maxCores = FALSE;
+    cps->egtFormats[0] = NULLCHAR;
 
     /* [HGM] options */
     cps->optionSettings  = appData.engOptions[n];
@@ -856,6 +861,7 @@ ReplaceEngine(ChessProgramState *cps, int n)
     appData.noChessProgram = FALSE;
     appData.clockMode = TRUE;
     InitEngine(cps, n);
+    UpdateLogos(TRUE);
     if(n) return; // only startup first engine immediately; second can wait
     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
     LoadEngine();
@@ -871,7 +877,7 @@ static char resetOptions[] =
 void
 Load(ChessProgramState *cps, int i)
 {
-    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
+    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
     if(engineLine[0]) { // an engine was selected from the combo box
        snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
        SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
@@ -891,6 +897,7 @@ Load(ChessProgramState *cps, int i)
        appData.directory[i] = strdup(engineName);
        p[-1] = SLASH;
     } else appData.directory[i] = ".";
+    if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
     if(params[0]) {
        snprintf(command, MSG_SIZ, "%s %s", p, params);
        p = command;
@@ -903,15 +910,18 @@ Load(ChessProgramState *cps, int i)
     if(useNick) ASSIGN(appData.pgnName[i], nickName);
     if(addToList) {
        int len;
+       char quote;
        q = firstChessProgramNames;
        if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
+       quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
+                       quote, p, quote, appData.directory[i], 
                        useNick ? " -fn \"" : "",
                        useNick ? nickName : "",
                        useNick ? "\"" : "",
                        v1 ? " -firstProtocolVersion 1" : "",
                        hasBook ? "" : " -fNoOwnBookUCI",
-                       isUCI ? " -fUCI" : "",
+                       isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
                        storeVariant ? " -variant " : "",
                        storeVariant ? VariantName(gameInfo.variant) : "");
        firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
@@ -1014,6 +1024,13 @@ InitBackEnd1()
     InitEngine(&second, 1);
     CommonEngineInit();
 
+    pairing.which = "pairing"; // pairing engine
+    pairing.pr = NoProc;
+    pairing.isr = NULL;
+    pairing.program = appData.pairingEngine;
+    pairing.host = "localhost";
+    pairing.dir = ".";
+
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
@@ -1364,7 +1381,7 @@ ReserveGame(int gameNr, char resChar)
     safeStrCpy(q, p, strlen(p) + 2);
     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
-    if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
+    if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
        if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
        q[nextGame] = '*';
     }
@@ -1376,7 +1393,7 @@ ReserveGame(int gameNr, char resChar)
     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
     DisplayMessage(buf, "");
     free(p); appData.results = q;
-    if(nextGame <= appData.matchGames && resChar != ' ' &&
+    if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
        UnloadEngine(&first);  // next game belongs to other pairing;
        UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
@@ -1389,8 +1406,7 @@ MatchEvent(int mode)
        int dummy;
        if(matchMode) { // already in match mode: switch it off
            abortMatch = TRUE;
-           appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
-           ModeHighlight(); // kludgey way to remove checkmark...
+           if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
            return;
        }
 //     if(gameMode != BeginningOfGame) {
@@ -1398,14 +1414,29 @@ MatchEvent(int mode)
 //         return;
 //     }
        abortMatch = FALSE;
-       appData.matchGames = appData.defaultMatchGames;
+       if(mode == 2) appData.matchGames = appData.defaultMatchGames;
        /* Set up machine vs. machine match */
        nextGame = 0;
-       NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
+       NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
        if(appData.tourneyFile[0]) {
            ReserveGame(-1, 0);
            if(nextGame > appData.matchGames) {
                char buf[MSG_SIZ];
+               if(strchr(appData.results, '*') == NULL) {
+                   FILE *f;
+                   appData.tourneyCycles++;
+                   if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+                       fclose(f);
+                       NextTourneyGame(-1, &dummy);
+                       ReserveGame(-1, 0);
+                       if(nextGame <= appData.matchGames) {
+                           DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+                           matchMode = mode;
+                           ScheduleDelayedEvent(NextMatchGame, 10000);
+                           return;
+                       }
+                   }
+               }
                snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
                DisplayError(buf, 0);
                appData.tourneyFile[0] = 0;
@@ -1520,6 +1551,8 @@ InitBackEnd3 P((void))
            if(f = fopen(appData.tourneyFile, "r")) {
                ParseArgsFromFile(f); // make sure tourney parmeters re known
                fclose(f);
+               appData.clockMode = TRUE;
+               SetGNUMode();
            } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
        }
        MatchEvent(TRUE);
@@ -5293,20 +5326,12 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     endPV++;
     CopyBoard(boards[endPV], boards[endPV-1]);
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
-    moveList[endPV-1][0] = fromX + AAA;
-    moveList[endPV-1][1] = fromY + ONE;
-    moveList[endPV-1][2] = toX + AAA;
-    moveList[endPV-1][3] = toY + ONE;
-    moveList[endPV-1][4] = promoChar;
-    moveList[endPV-1][5] = NULLCHAR;
+    CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
     strncat(moveList[endPV-1], "\n", MOVE_LEN);
-    if(storeComments)
-       CoordsToAlgebraic(boards[endPV - 1],
+    CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
                             fromY, fromX, toY, toX, promoChar,
                             parseList[endPV - 1]);
-    else
-       parseList[endPV-1][0] = NULLCHAR;
   } while(valid);
   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
@@ -5317,7 +5342,7 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
 
 int
 MultiPV(ChessProgramState *cps)
-{      // check if engine supports MultiPV, and if so, return the nmber of the option that sets it
+{      // check if engine supports MultiPV, and if so, return the number of the option that sets it
        int i;
        for(i=0; i<cps->nrOptions; i++)
            if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
@@ -5343,14 +5368,14 @@ LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
        buf[index] = 0;
        if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
                int n = first.option[multi].value;
-               if(origIndex < 10) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+               if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
                snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
                if(first.option[multi].value != n) SendToProgram(buf2, &first);
                first.option[multi].value = n;
                *start = *end = 0;
-               return TRUE;
+               return FALSE;
        }
-       ParsePV(buf+startPV, FALSE, !shiftKey);
+       ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
        return TRUE;
 }
@@ -5370,15 +5395,22 @@ UnLoadPV()
   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
   if(endPV < 0) return;
   endPV = -1;
-  if(shiftKey && gameMode == AnalyzeMode) {
-       if(pushed) storedGames--; // abandon shelved tail of original game
+  if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+       Boolean saveAnimate = appData.animate;
+       if(pushed) {
+           if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
+               if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
+           } else storedGames--; // abandon shelved tail of original game
+       }
        pushed = FALSE;
        forwardMostMove = currentMove;
        currentMove = oldFMM;
+       appData.animate = FALSE;
        ToNrEvent(forwardMostMove);
+       appData.animate = saveAnimate;
   }
   currentMove = forwardMostMove;
-  if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
+  if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
   ClearPremoveHighlights();
   DrawPosition(TRUE, boards[currentMove]);
 }
@@ -7104,6 +7136,15 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
     SetProgramStats( &stats );
 }
 
+void
+ClearEngineOutputPane(int which)
+{
+    static FrontEndProgramStats dummyStats;
+    dummyStats.which = which;
+    dummyStats.pv = "#";
+    SetProgramStats( &dummyStats );
+}
+
 #define MAXPLAYERS 500
 
 char *
@@ -7113,6 +7154,8 @@ TourneyStandings(int display)
     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
     char result, *p, *names[MAXPLAYERS];
 
+    if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
+       return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
     names[0] = p = strdup(appData.participants);
     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
 
@@ -7571,8 +7614,23 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
     if(bookHit) {
        // after a book hit we never send 'go', and the code after the call to this routine
        // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
-       char buf[MSG_SIZ];
-       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
+       char buf[MSG_SIZ], *move = bookHit;
+       if(cps->useSAN) {
+           int fromX, fromY, toX, toY;
+           char promoChar;
+           ChessMove moveType;
+           move = buf + 30;
+           if (ParseOneMove(bookHit, forwardMostMove, &moveType,
+                                &fromX, &fromY, &toX, &toY, &promoChar)) {
+               (void) CoordsToAlgebraic(boards[forwardMostMove],
+                                   PosFlags(forwardMostMove),
+                                   fromY, fromX, toY, toX, promoChar, move);
+           } else {
+               if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
+               bookHit = NULL;
+           }
+       }
+       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
        SendToProgram(buf, cps);
        if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
@@ -7596,6 +7654,8 @@ void DeferredBookMove(void)
        HandleMachineMove(savedMessage, savedState);
 }
 
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
 void
 HandleMachineMove(message, cps)
      char *message;
@@ -7610,6 +7670,17 @@ HandleMachineMove(message, cps)
     int machineWhite;
     char *bookHit;
 
+    if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+       // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+       if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
+           DisplayError(_("Invalid pairing from pairing engine"), 0);
+           return;
+       }
+       pairingReceived = 1;
+       NextMatchGame();
+       return; // Skim the pairing messages here.
+    }
+
     cps->userError = 0;
 
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
@@ -9429,6 +9500,7 @@ InitChessProgram(cps, setup)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
+    ClearEngineOutputPane(cps == &second);
 }
 
 
@@ -9459,9 +9531,14 @@ StartChessProgram(cps)
 
     if (err != 0) {
       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
-       DisplayFatalError(buf, err, 1);
-       cps->pr = NoProc;
-       cps->isr = NULL;
+       DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+       if(cps != &first) return;
+       appData.noChessProgram = TRUE;
+       ThawUI();
+       SetNCPMode();
+//     DisplayFatalError(buf, err, 1);
+//     cps->pr = NoProc;
+//     cps->isr = NULL;
        return;
     }
 
@@ -9495,39 +9572,95 @@ TwoMachinesEventIfReady P((void))
   TwoMachinesEvent();
 }
 
+char *
+MakeName(char *template)
+{
+    time_t clock;
+    struct tm *tm;
+    static char buf[MSG_SIZ];
+    char *p = buf;
+    int i;
+
+    clock = time((time_t *)NULL);
+    tm = localtime(&clock);
+
+    while(*p++ = *template++) if(p[-1] == '%') {
+       switch(*template++) {
+         case 0:   *p = 0; return buf;
+         case 'Y': i = tm->tm_year+1900; break;
+         case 'y': i = tm->tm_year-100; break;
+         case 'M': i = tm->tm_mon+1; break;
+         case 'd': i = tm->tm_mday; break;
+         case 'h': i = tm->tm_hour; break;
+         case 'm': i = tm->tm_min; break;
+         case 's': i = tm->tm_sec; break;
+         default:  i = 0;
+       }
+       snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
+    }
+    return buf;
+}
+
+int
+CountPlayers(char *p)
+{
+    int n = 0;
+    while(p = strchr(p, '\n')) p++, n++; // count participants
+    return n;
+}
+
+FILE *
+WriteTourneyFile(char *results)
+{   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+    FILE *f = fopen(appData.tourneyFile, "w");
+    if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
+       // create a file with tournament description
+       fprintf(f, "-participants {%s}\n", appData.participants);
+       fprintf(f, "-tourneyType %d\n", appData.tourneyType);
+       fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+       fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+       fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+       fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+       fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+       fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+       fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+       fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
+       fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
+       fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       if(searchTime > 0)
+               fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+       else {
+               fprintf(f, "-mps %d\n", appData.movesPerSession);
+               fprintf(f, "-tc %s\n", appData.timeControl);
+               fprintf(f, "-inc %.2f\n", appData.timeIncrement);
+       }
+       fprintf(f, "-results \"%s\"\n", results);
+    }
+    return f;
+}
+
 int
 CreateTourney(char *name)
 {
        FILE *f;
-       if(name[0] == NULLCHAR) return 0;
-       f = fopen(appData.tourneyFile, "r");
+       if(name[0] == NULLCHAR) {
+           if(appData.participants[0])
+               DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+           return 0;
+       }
+       f = fopen(name, "r");
        if(f) { // file exists
+           ASSIGN(appData.tourneyFile, name);
            ParseArgsFromFile(f); // parse it
        } else {
-           f = fopen(appData.tourneyFile, "w");
-           if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
-               // create a file with tournament description
-               fprintf(f, "-participants {%s}\n", appData.participants);
-               fprintf(f, "-tourneyType %d\n", appData.tourneyType);
-               fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
-               fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
-               fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
-               fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
-               fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
-               fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
-               fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
-               fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
-               fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
-               fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
-               if(searchTime > 0)
-                       fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
-               else {
-                       fprintf(f, "-mps %d\n", appData.movesPerSession);
-                       fprintf(f, "-tc %s\n", appData.timeControl);
-                       fprintf(f, "-inc %.2f\n", appData.timeIncrement);
-               }
-               fprintf(f, "-results \"\"\n");
+           if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+           if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+               DisplayError(_("Not enough participants"), 0);
+               return 0;
            }
+           ASSIGN(appData.tourneyFile, name);
+           if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
+           if((f = WriteTourneyFile("")) == NULL) return 0;
        }
        fclose(f);
        appData.noChessProgram = FALSE;
@@ -9603,7 +9736,7 @@ SetPlayer(int player)
 int
 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
 {   // determine players from game number
-    int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
+    int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
 
     if(appData.tourneyType == 0) {
        roundsPerCycle = (nPlayers - 1) | 1;
@@ -9631,9 +9764,9 @@ Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInter
            *whitePlayer = curRound;
            *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
        } else {
-           *whitePlayer = curRound - pairingsPerRound + curPairing;
+           *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
            if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
-           *blackPlayer = curRound + pairingsPerRound - curPairing;
+           *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
            if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
        }
     } else if(appData.tourneyType > 0) {
@@ -9650,7 +9783,7 @@ int
 NextTourneyGame(int nr, int *swapColors)
 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
     char *p, *q;
-    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
+    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
     FILE *tf;
     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
     tf = fopen(appData.tourneyFile, "r");
@@ -9658,9 +9791,9 @@ NextTourneyGame(int nr, int *swapColors)
     ParseArgsFromFile(tf); fclose(tf);
     InitTimeControls(); // TC might be altered from tourney file
 
-    p = appData.participants;
-    while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
-    *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+    nPlayers = CountPlayers(appData.participants); // count participants
+    if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
+    *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
 
     if(syncInterval) {
        p = q = appData.results;
@@ -9674,6 +9807,29 @@ NextTourneyGame(int nr, int *swapColors)
        waitingForGame = FALSE;
     }
 
+    if(appData.tourneyType < 0) {
+       if(nr>=0 && !pairingReceived) {
+           char buf[1<<16];
+           if(pairing.pr == NoProc) {
+               if(!appData.pairingEngine[0]) {
+                   DisplayFatalError(_("No pairing engine specified"), 0, 1);
+                   return 0;
+               }
+               StartChessProgram(&pairing); // starts the pairing engine
+           }
+           snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+           SendToProgram(buf, &pairing);
+           snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+           SendToProgram(buf, &pairing);
+           return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+       }
+       pairingReceived = 0;                              // ... so we continue here 
+       *swapColors = 0;
+       appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+       whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+       matchGame = 1; roundNr = nr / syncInterval + 1;
+    }
+
     if(first.pr != NoProc) return 1; // engines already loaded
 
     // redefine engines, engine dir, etc.
@@ -9685,6 +9841,7 @@ NextTourneyGame(int nr, int *swapColors)
     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
     InitEngine(&second, 1);
     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
+    UpdateLogos(FALSE);     // leave display to ModeHiglight()
     return 1;
 }
 
@@ -10079,9 +10236,10 @@ GameEnds(result, resultDetails, whosays)
        }
 
        if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
-       if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
+       if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
            ReserveGame(nextGame, resChar); // sets nextGame
            if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
+           else ranking = strdup("busy"); //suppress popup when aborted but not finished
        } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
 
        if (nextGame <= appData.matchGames && !abortMatch) {
@@ -10116,11 +10274,13 @@ GameEnds(result, resultDetails, whosays)
     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
        if(matchMode == TRUE) { // match through command line: exit with or without popup
            if(ranking) {
+               ToNrEvent(forwardMostMove);
                if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
                else ExitEvent(0);
            } else DisplayFatalError(buf, 0, 0);
        } else { // match through menu; just stop, with or without popup
            matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+           ModeHighlight();
            if(ranking){
                if(strcmp(ranking, "busy")) DisplayNote(ranking);
            } else DisplayNote(buf);
@@ -12249,6 +12409,9 @@ ExitEvent(status)
        RemoveInputSource(second.isr);
     }
 
+    if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+    if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
     ShutDownFrontEnd();
     exit(status);
 }
@@ -12560,6 +12723,12 @@ DisplayTwoMachinesTitle()
 {
     char buf[MSG_SIZ];
     if (appData.matchGames > 0) {
+        if(appData.tourneyFile[0]) {
+         snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
+                  gameInfo.white, gameInfo.black,
+                  nextGame+1, appData.matchGames+1,
+                  appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
+        } else 
         if (first.twoMachinesColor[0] == 'w') {
          snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
                   gameInfo.white, gameInfo.black,
@@ -12690,7 +12859,7 @@ TwoMachinesEvent P((void))
 
     gameMode = TwoMachinesPlay;
     pausing = FALSE;
-    ModeHighlight();
+    ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
     firstMove = TRUE;
@@ -14310,6 +14479,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (count <= 0) {
        if (count == 0) {
            RemoveInputSource(cps->isr);
+           if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
            snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
                    _(cps->which), cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */