Implement Grand Chess
[xboard.git] / backend.c
index f2c0c98..b914fe4 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -233,6 +233,8 @@ 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));
+void DisplayTwoMachinesTitle P(());
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -249,6 +251,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H
 void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
+Boolean abortMatch;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -393,6 +396,7 @@ PosFlags(index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
   default:
@@ -416,7 +420,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;
@@ -491,7 +495,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
@@ -506,6 +510,8 @@ ChessMove savedResult[MAX_VARIATIONS];
 
 void PushTail P((int firstMove, int lastMove));
 Boolean PopTail P((Boolean annotate));
+void PushInner P((int firstMove, int lastMove));
+void PopInner P((Boolean annotate));
 void CleanupTail P((void));
 
 ChessSquare  FIDEArray[2][BOARD_FILES] = {
@@ -594,6 +600,13 @@ ChessSquare JanusArray[2][BOARD_FILES] = {
         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
 };
 
+ChessSquare GrandArray[2][BOARD_FILES] = {
+    { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
+        WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
+    { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
+        BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
+};
+
 #ifdef GOTHIC
 ChessSquare GothicArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
@@ -793,7 +806,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];
@@ -853,21 +870,27 @@ 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();
 }
 
-extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
-extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
+extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
+extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
+
+static char resetOptions[] = 
+       "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+       "-firstOptions \"\" -firstNPS -1 -fn \"\"";
 
-void Load(ChessProgramState *cps, int i)
+void
+Load(ChessProgramState *cps, int i)
 {
-    char *p, *q, buf[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*
-       ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
        ParseArgsFromString(buf);
        SwapEngines(i);
        ReplaceEngine(cps, i);
@@ -875,29 +898,41 @@ void Load(ChessProgramState *cps, int i)
     }
     p = engineName;
     while(q = strchr(p, SLASH)) p = q+1;
-    if(*p== NULLCHAR) return;
-    appData.chessProgram[i] = strdup(p);
+    if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
     if(engineDir[0] != NULLCHAR)
        appData.directory[i] = engineDir;
     else if(p != engineName) { // derive directory from engine path, when not given
        p[-1] = 0;
        appData.directory[i] = strdup(engineName);
-       p[-1] = '/';
+       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;
+    }
+    appData.chessProgram[i] = strdup(p);
     appData.isUCI[i] = isUCI;
     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
     appData.hasOwnBookUCI[i] = hasBook;
+    if(!nickName[0]) useNick = FALSE;
+    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\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) : "");
-fprintf(debugFP, "new line: %s", buf);
        firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
        snprintf(firstChessProgramNames, len, "%s%s", q, buf);
        if(q)   free(q);
@@ -906,9 +941,39 @@ fprintf(debugFP, "new line: %s", buf);
 }
 
 void
-InitBackEnd1()
+InitTimeControls()
 {
     int matched, min, sec;
+    /*
+     * Parse timeControl resource
+     */
+    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+                         appData.movesPerSession)) {
+       char buf[MSG_SIZ];
+       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+       DisplayFatalError(buf, 0, 2);
+    }
+
+    /*
+     * Parse searchTime resource
+     */
+    if (*appData.searchTime != NULLCHAR) {
+       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+       if (matched == 1) {
+           searchTime = min * 60;
+       } else if (matched == 2) {
+           searchTime = min * 60 + sec;
+       } else {
+           char buf[MSG_SIZ];
+           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+           DisplayFatalError(buf, 0, 2);
+       }
+    }
+}
+
+void
+InitBackEnd1()
+{
 
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
@@ -959,31 +1024,7 @@ InitBackEnd1()
         }
     }
 
-    /*
-     * Parse timeControl resource
-     */
-    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
-                         appData.movesPerSession)) {
-       char buf[MSG_SIZ];
-       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
-       DisplayFatalError(buf, 0, 2);
-    }
-
-    /*
-     * Parse searchTime resource
-     */
-    if (*appData.searchTime != NULLCHAR) {
-       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
-       if (matched == 1) {
-           searchTime = min * 60;
-       } else if (matched == 2) {
-           searchTime = min * 60 + sec;
-       } else {
-           char buf[MSG_SIZ];
-           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
-           DisplayFatalError(buf, 0, 2);
-       }
-    }
+    InitTimeControls();
 
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
@@ -992,6 +1033,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
@@ -1072,13 +1120,14 @@ InitBackEnd1()
       case VariantAtomic:     /* should work except for win condition */
       case Variant3Check:     /* should work except for win condition */
       case VariantShatranj:   /* should work except for all win conditions */
-      case VariantMakruk:     /* should work except for daw countdown */
+      case VariantMakruk:     /* should work except for draw countdown */
       case VariantBerolina:   /* might work if TestLegality is off */
       case VariantCapaRandom: /* should work */
       case VariantJanus:      /* should work */
       case VariantSuper:      /* experimental */
       case VariantGreat:      /* experimental, requires legality testing to be off */
       case VariantSChess:     /* S-Chess, should work */
+      case VariantGrand:      /* should work */
       case VariantSpartan:    /* should work */
        break;
       }
@@ -1342,7 +1391,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] = '*';
     }
@@ -1354,7 +1403,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.
@@ -1365,13 +1414,39 @@ void
 MatchEvent(int mode)
 {      // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
        int dummy;
+       if(matchMode) { // already in match mode: switch it off
+           abortMatch = TRUE;
+           if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
+           return;
+       }
+//     if(gameMode != BeginningOfGame) {
+//         DisplayError(_("You can only start a match from the initial position."), 0);
+//         return;
+//     }
+       abortMatch = FALSE;
+       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;
@@ -1385,7 +1460,7 @@ MatchEvent(int mode)
        }
        matchMode = mode;
        matchGame = roundNr = 1;
-       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
+       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
        NextMatchGame();
 }
 
@@ -1486,6 +1561,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);
@@ -4573,8 +4650,8 @@ ParseBoard12(string)
            safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
            strcat(moveList[moveNum - 1], "\n");
 
-            if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
-                                 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
+            if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
+                                 && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
                 ChessSquare old, new = boards[moveNum][k][j];
                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
@@ -4820,6 +4897,11 @@ SendMoveToProgram(moveNum, cps)
          else SendToProgram("O-O-O\n", cps);
        }
        else SendToProgram(moveList[moveNum], cps);
+      } else
+      if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
+       snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
+                                            moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
+       SendToProgram(buf, cps);
       }
       else SendToProgram(moveList[moveNum], cps);
       /* End of additions by Tord */
@@ -5207,19 +5289,26 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
+Boolean pushed = FALSE;
+char *lastParseAttempt;
 
 void
-ParsePV(char *pv, Boolean storeComments)
+ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
   int fromX, fromY, toX, toY; char promoChar;
   ChessMove moveType;
   Boolean valid;
   int nr = 0;
 
+  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+    PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
+    pushed = TRUE;
+  }
   endPV = forwardMostMove;
   do {
     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
+    lastParseAttempt = pv;
     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
 if(appData.debugMode){
 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
@@ -5254,64 +5343,111 @@ 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 = endPV;
+  if(atEnd == 2) return; // used hidden, for PV conversion
+  currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
   DrawPosition(TRUE, boards[currentMove]);
 }
 
+int
+MultiPV(ChessProgramState *cps)
+{      // 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)
+               return i;
+       return -1;
+}
+
 Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
-       int startPV;
-       char *p;
+       int startPV, multi, lineStart, origIndex = index;
+       char *p, buf2[MSG_SIZ];
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
        while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
-       startPV = index;
+       lineStart = startPV = index;
        while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
        if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
        index = startPV;
        do{ while(buf[index] && buf[index] != '\n') index++;
        } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       ParsePV(buf+startPV, FALSE);
+       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
+               int n = first.option[multi].value;
+               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 FALSE;
+       }
+       ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
        return TRUE;
 }
 
+char *
+PvToSAN(char *pv)
+{
+       static char buf[10*MSG_SIZ];
+       int i, k=0, savedEnd=endPV;
+       *buf = NULLCHAR;
+       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+       ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
+       for(i = forwardMostMove; i<endPV; i++){
+           if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
+           else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
+           k += strlen(buf+k);
+       }
+       snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+       if(forwardMostMove < savedEnd) PopInner(0);
+       endPV = savedEnd;
+       return buf;
+}
+
 Boolean
 LoadPV(int x, int y)
 { // called on right mouse click to load PV
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
-  ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+  ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
   return TRUE;
 }
 
 void
 UnLoadPV()
 {
+  int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
   if(endPV < 0) return;
   endPV = -1;
+  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 continuation
   ClearPremoveHighlights();
   DrawPosition(TRUE, boards[currentMove]);
 }
@@ -5624,6 +5760,14 @@ InitPosition(redraw)
     case VariantTwoKings:
       pieces = twoKingsArray;
       break;
+    case VariantGrand:
+      pieces = GrandArray;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+      gameInfo.boardWidth = 10;
+      gameInfo.boardHeight = 10;
+      gameInfo.holdingsSize = 7;
+      break;
     case VariantCapaRandom:
       shuffleOpenings = TRUE;
     case VariantCapablanca:
@@ -5740,7 +5884,7 @@ InitPosition(redraw)
 
     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
     if(pawnRow < 1) pawnRow = 1;
-    if(gameInfo.variant == VariantMakruk) pawnRow = 2;
+    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
@@ -5754,7 +5898,7 @@ InitPosition(redraw)
             initialPosition[i][j] = s;
 
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
-        initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+        initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
@@ -5767,7 +5911,13 @@ InitPosition(redraw)
                 }
             }
         }
-        initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
+        if(gameInfo.variant == VariantGrand) {
+            if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
+               initialPosition[0][j] = WhiteRook;
+               initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
+            }
+        }
+        initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
     }
     if( (gameInfo.variant == VariantShogi) && !overrule ) {
 
@@ -5946,7 +6096,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3;
         highestPromotingPiece = (int)WhiteFerz;
-    } else if(gameInfo.variant == VariantMakruk) {
+    } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
         promotionZoneSize = 3;
     }
 
@@ -6392,8 +6542,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 {
     char *bookHit = 0;
 
-    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
-       // [HGM] superchess: suppress promotions to non-available piece
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
+       // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
        if(WhiteOnMove(currentMove)) {
            if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
@@ -6596,7 +6746,7 @@ MarkTargetSquares(int clear)
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
   } else {
     int capt = 0;
-    GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
+    GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
       if(capt)
@@ -6675,13 +6825,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
        if(gameInfo.holdingsWidth &&
                (WhiteOnMove(currentMove)
-                       ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
-                       : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
+                       ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
+                       : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
            // click in right holdings, for determining promotion piece
            ChessSquare p = boards[currentMove][y][x];
            if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
-           if(p != EmptySquare) {
-               FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
+           if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
+           if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
+               FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
                fromX = fromY = -1;
                return;
            }
@@ -6890,7 +7041,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
        SetHighlights(fromX, fromY, toX, toY);
-       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
            // [HGM] super: promotion to captured piece selected from holdings
            ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
            promotionChoice = TRUE;
@@ -6960,16 +7111,16 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
     if (action != Press) return -2; // return code to be ignored
     switch (gameMode) {
       case IcsExamining:
-       if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
+       if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
       case EditPosition:
-       if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
+       if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
        if (xSqr < 0 || ySqr < 0) return -1;
        if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
        pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
        toX = xSqr; toY = ySqr; lastX = x, lastY = y;
        if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
        NextPiece(0);
-       return -2;\r
+       return -2;
       case IcsObserving:
        if(!appData.icsEngineAnalyze) return -1;
       case IcsPlayingWhite:
@@ -7037,6 +7188,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 *
@@ -7046,6 +7206,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
 
@@ -7060,7 +7222,7 @@ TourneyStandings(int display)
          case '-': bScore = 2; break;
          case '=': wScore = bScore = 1; break;
          case ' ':
-         case '*': return NULL; // tourney not finished
+         case '*': return strdup("busy"); // tourney not finished
        }
        score[w] += wScore;
        score[b] += bScore;
@@ -7504,8 +7666,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
@@ -7529,6 +7706,8 @@ void DeferredBookMove(void)
        HandleMachineMove(savedMessage, savedState);
 }
 
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
 void
 HandleMachineMove(message, cps)
      char *message;
@@ -7539,10 +7718,21 @@ HandleMachineMove(message, cps)
     int fromX, fromY, toX, toY;
     ChessMove moveType;
     char promoChar;
-    char *p;
+    char *p, *pv=buf1;
     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
@@ -7899,6 +8089,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     if (!strncmp(message, "telluser ", 9)) {
        if(message[9] == '\\' && message[10] == '\\')
            EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
+       PlayTellSound();
        DisplayNote(message + 9);
        return;
     }
@@ -7906,6 +8097,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        cps->userError = 1;
        if(message[14] == '\\' && message[15] == '\\')
            EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
+       PlayTellSound();
        DisplayError(message + 14, 0);
        return;
     }
@@ -8383,6 +8575,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                     curscore = -curscore;
                 }
 
+               if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
 
                tempStats.depth = plylev;
                tempStats.nodes = nodes;
@@ -8404,15 +8597,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                }
 
                /* Buffer overflow protection */
-               if (buf1[0] != NULLCHAR) {
-                   if (strlen(buf1) >= sizeof(tempStats.movelist)
+               if (pv[0] != NULLCHAR) {
+                   if (strlen(pv) >= sizeof(tempStats.movelist)
                        && appData.debugMode) {
                        fprintf(debugFP,
                                "PV is too long; using the first %u bytes.\n",
                                (unsigned) sizeof(tempStats.movelist) - 1);
                    }
 
-                    safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
+                    safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
                } else {
                    sprintf(tempStats.movelist, " no PV\n");
                }
@@ -8449,14 +8642,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 if( buf1[0] != NULLCHAR ) {
                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
 
-                    if( strlen(buf1) > max_len ) {
+                    if( strlen(pv) > max_len ) {
                        if( appData.debugMode) {
                            fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
                         }
-                        buf1[max_len+1] = '\0';
+                        pv[max_len+1] = '\0';
                     }
 
-                    strcat( thinkOutput, buf1 );
+                    strcat( thinkOutput, pv);
                 }
 
                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
@@ -8787,7 +8980,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
      Board board;
 {
   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
-  int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
+  int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
@@ -8885,18 +9078,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         board[fromY][BOARD_LEFT] = EmptySquare;
     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
-               && toY >= BOARD_HEIGHT-promoRank
+               && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if (board[toY][toX] == EmptySquare) {
-            board[toY][toX] = WhiteQueen;
-       }
         if(gameInfo.variant==VariantBughouse ||
            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
-    } else if ((fromY == BOARD_HEIGHT-4)
+    } else if ((fromY >= BOARD_HEIGHT>>1)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -8949,18 +9139,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][2] = BlackRook;
     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
-              && toY < promoRank
+              && toY < promoRank && promoChar
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-       if (board[toY][toX] == EmptySquare) {
-           board[toY][toX] = BlackQueen;
-       }
         if(gameInfo.variant==VariantBughouse ||
            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
-    } else if ((fromY == 3)
+    } else if ((fromY < BOARD_HEIGHT>>1)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -9021,7 +9208,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
         /* [HGM] holdings: Add to holdings, if holdings exist */
-       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
                // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
                captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
        }
@@ -9072,8 +9259,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
     }
-    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
-               && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
+               && promoChar != NULLCHAR && gameInfo.holdingsSize) {
        // [HGM] superchess: take promotion piece out of holdings
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
        if((int)piece < (int)BlackPawn) { // determine stm from piece color
@@ -9210,6 +9397,7 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9300,6 +9488,8 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
       if( gameInfo.variant == VariantSChess )
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
+      if( gameInfo.variant == VariantGrand )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
 
       if(overruled) {
        snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
@@ -9361,6 +9551,7 @@ InitChessProgram(cps, setup)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
+    ClearEngineOutputPane(cps == &second);
 }
 
 
@@ -9391,9 +9582,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;
     }
 
@@ -9427,32 +9623,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);
-               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;
@@ -9506,6 +9765,8 @@ void SwapEngines(int n)
     SWAP(scoreIsAbsolute, h)
     SWAP(timeOdds, h)
     SWAP(logo, p)
+    SWAP(pgnName, p)
+    SWAP(pvSAN, h)
 }
 
 void
@@ -9513,14 +9774,12 @@ SetPlayer(int player)
 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
     int i;
     char buf[MSG_SIZ], *engineName, *p = appData.participants;
-    static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
-                                "-firstNeedsNoncompliantFEN false -firstNPS -1";
     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
     if(mnemonic[i]) {
        snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
-       ParseArgsFromString(resetOptions);
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
        ParseArgsFromString(buf);
     }
     free(engineName);
@@ -9529,7 +9788,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;
@@ -9557,9 +9816,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) {
@@ -9576,16 +9835,17 @@ 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");
     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
     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;
@@ -9599,6 +9859,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.
@@ -9610,6 +9893,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;
 }
 
@@ -9759,7 +10043,7 @@ GameEnds(result, resultDetails, whosays)
 
                // now verify win claims, but not in drop games, as we don't understand those yet
                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
-                                                || gameInfo.variant == VariantGreat) &&
+                                                || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
                     (result == WhiteWins && claimer == 'w' ||
                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
                      if (appData.debugMode) {
@@ -9785,7 +10069,8 @@ GameEnds(result, resultDetails, whosays)
                 /* (Claiming a loss is accepted no questions asked!) */
            }
            /* [HGM] bare: don't allow bare King to win */
-           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
+           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
+                                           || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
               && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
               && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
               && result != GameIsDrawn)
@@ -10007,9 +10292,10 @@ GameEnds(result, resultDetails, whosays)
        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) {
+       if (nextGame <= appData.matchGames && !abortMatch) {
            gameMode = nextGameMode;
            matchGame = nextGame; // this will be overruled in tourney mode!
            GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
@@ -10022,6 +10308,7 @@ GameEnds(result, resultDetails, whosays)
                     first.tidy, second.tidy,
                     first.matchWins, second.matchWins,
                     appData.matchGames - (first.matchWins + second.matchWins));
+           if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
            popupRequested++; // [HGM] crash: postpone to after resetting endingGame
            if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
                first.twoMachinesColor = "black\n";
@@ -10039,9 +10326,18 @@ GameEnds(result, resultDetails, whosays)
     ModeHighlight();
     endingGame = 0;  /* [HGM] crash */
     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
-      if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
-       matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
-       DisplayNote(ranking ? ranking : buf);
+       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);
       }
       if(ranking) free(ranking);
     }
@@ -12167,6 +12463,9 @@ ExitEvent(status)
        RemoveInputSource(second.isr);
     }
 
+    if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+    if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
     ShutDownFrontEnd();
     exit(status);
 }
@@ -12257,6 +12556,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
+    bookUp = FALSE;
     EditTagsPopUp(tags, NULL);
     free(tags);
 }
@@ -12477,6 +12777,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,
@@ -12607,7 +12913,7 @@ TwoMachinesEvent P((void))
 
     gameMode = TwoMachinesPlay;
     pausing = FALSE;
-    ModeHighlight();
+    ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
     firstMove = TRUE;
@@ -13437,6 +13743,7 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
+    DisplayBook(currentMove);
 }
 
 
@@ -13539,6 +13846,7 @@ BackwardInner(target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
+    DisplayBook(currentMove);
 }
 
 void
@@ -13904,11 +14212,11 @@ SetGameInfo()
            gameInfo.round = StrSave("-");
        }
        if (first.twoMachinesColor[0] == 'w') {
-           gameInfo.white = StrSave(first.tidy);
-           gameInfo.black = StrSave(second.tidy);
+           gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
+           gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
        } else {
-           gameInfo.white = StrSave(second.tidy);
-           gameInfo.black = StrSave(first.tidy);
+           gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
+           gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
        }
        gameInfo.timeControl = TimeControlTagValue();
        break;
@@ -14225,6 +14533,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 */
@@ -14776,14 +15085,14 @@ AskQuestionEvent(title, question, replyPrefix, which)
 void
 TypeInEvent(char firstChar)
 {
-    if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
-        gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
-       gameMode == AnalyzeMode || gameMode == EditGame || \r
-       gameMode == EditPosition || gameMode == IcsExamining ||\r
-       gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
-       isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
-               ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
-                 gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
+    if ((gameMode == BeginningOfGame && !appData.icsActive) || 
+        gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+       gameMode == AnalyzeMode || gameMode == EditGame || 
+       gameMode == EditPosition || gameMode == IcsExamining ||
+       gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
+       isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
+               ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
+                 gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
        gameMode == Training) PopUpMoveDialog(firstChar);
 }
 
@@ -14793,34 +15102,34 @@ TypeInDoneEvent(char *move)
        Board board;
        int n, fromX, fromY, toX, toY;
        char promoChar;
-       ChessMove moveType;\r
-
-       // [HGM] FENedit\r
-       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
-               EditPositionPasteFEN(move);\r
-               return;\r
-       }\r
-       // [HGM] movenum: allow move number to be typed in any mode\r
-       if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
-         ToNrEvent(2*n-1);\r
-         return;\r
-       }\r
-
-      if (gameMode != EditGame && currentMove != forwardMostMove && \r
-       gameMode != Training) {\r
-       DisplayMoveError(_("Displayed move is not current"));\r
-      } else {\r
-       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
-         &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
-       if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
-       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
-         &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
-         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
-       } else {\r
-         DisplayMoveError(_("Could not parse move"));\r
-       }
-      }\r
-}\r
+       ChessMove moveType;
+
+       // [HGM] FENedit
+       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
+               EditPositionPasteFEN(move);
+               return;
+       }
+       // [HGM] movenum: allow move number to be typed in any mode
+       if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
+         ToNrEvent(2*n-1);
+         return;
+       }
+
+      if (gameMode != EditGame && currentMove != forwardMostMove && 
+       gameMode != Training) {
+       DisplayMoveError(_("Displayed move is not current"));
+      } else {
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
+       if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
+       } else {
+         DisplayMoveError(_("Could not parse move"));
+       }
+      }
+}
 
 void
 DisplayMove(moveNumber)
@@ -16045,16 +16354,10 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 // [HGM] vari: routines for shelving variations
 
 void
-PushTail(int firstMove, int lastMove)
+PushInner(int firstMove, int lastMove)
 {
        int i, j, nrMoves = lastMove - firstMove;
 
-       if(appData.icsActive) { // only in local mode
-               forwardMostMove = currentMove; // mimic old ICS behavior
-               return;
-       }
-       if(storedGames >= MAX_VARIATIONS-1) return;
-
        // push current tail of game on stack
        savedResult[storedGames] = gameInfo.result;
        savedDetails[storedGames] = gameInfo.resultDetails;
@@ -16079,19 +16382,27 @@ PushTail(int firstMove, int lastMove)
 
        storedGames++;
        forwardMostMove = firstMove; // truncate game so we can start variation
+}
+
+void
+PushTail(int firstMove, int lastMove)
+{
+       if(appData.icsActive) { // only in local mode
+               forwardMostMove = currentMove; // mimic old ICS behavior
+               return;
+       }
+       if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
+
+       PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
-Boolean
-PopTail(Boolean annotate)
+void
+PopInner(Boolean annotate)
 {
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       if(appData.icsActive) return FALSE; // only in local mode
-       if(!storedGames) return FALSE; // sanity
-       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
-
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
        nrMoves = savedLast[storedGames] - currentMove;
@@ -16131,6 +16442,17 @@ PopTail(Boolean annotate)
       }
        gameInfo.resultDetails = savedDetails[storedGames];
        forwardMostMove = currentMove + nrMoves;
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+       if(appData.icsActive) return FALSE; // only in local mode
+       if(!storedGames) return FALSE; // sanity
+       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+       PopInner(annotate);
+
        if(storedGames == 0) GreyRevert(TRUE);
        return TRUE;
 }
@@ -16177,7 +16499,7 @@ LoadVariation(int index, char *text)
        PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
        // kludge: use ParsePV() to append variation to game
        move = currentMove;
-       ParsePV(start, TRUE);
+       ParsePV(start, TRUE, TRUE);
        forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
        ClearPremoveHighlights();
        CommentPopDown();