Allow two-games-per-opening to work with book
[xboard.git] / backend.c
index 6238eeb..26ebe3c 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -63,6 +63,7 @@ int flock(int f, int code);
 
 #else
 
+#include <sys/file.h>
 #define DoSleep( n ) if( (n) >= 0) sleep(n)
 #define SLASH '/'
 
@@ -233,7 +234,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));
+FILE *WriteTourneyFile P((char *results, FILE *f));
+void DisplayTwoMachinesTitle P(());
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -395,6 +397,7 @@ PosFlags(index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
   default:
@@ -493,7 +496,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
@@ -598,6 +601,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,
@@ -861,6 +871,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();
@@ -970,6 +981,7 @@ InitBackEnd1()
 
     GetTimeMark(&programStartTime);
     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+    appData.seedBase = random() + (random()<<15);
     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
 
     ClearProgramStats();
@@ -1110,13 +1122,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;
       }
@@ -1424,7 +1437,7 @@ MatchEvent(int mode)
                if(strchr(appData.results, '*') == NULL) {
                    FILE *f;
                    appData.tourneyCycles++;
-                   if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+                   if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
                        fclose(f);
                        NextTourneyGame(-1, &dummy);
                        ReserveGame(-1, 0);
@@ -1449,7 +1462,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();
 }
 
@@ -4639,8 +4652,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
@@ -4849,6 +4862,11 @@ SendMoveToProgram(moveNum, cps)
 {
     char buf[MSG_SIZ];
 
+    if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
+       // null move in variant where engine does not understand it (for analysis purposes)
+       SendBoard(cps, moveNum + 1); // send position after move in stead.
+       return;
+    }
     if (cps->useUsermove) {
       SendToProgram("usermove ", cps);
     }
@@ -4886,6 +4904,16 @@ 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
+       if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
+         if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
+         snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
+                                             moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
+       } else
+         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 */
@@ -5055,6 +5083,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
      char move[7];
 {
     if (rf == DROP_RANK) {
+      if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
       sprintf(move, "%c@%c%c\n",
                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
     } else {
@@ -5274,6 +5303,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 }
 
 Boolean pushed = FALSE;
+char *lastParseAttempt;
 
 void
 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
@@ -5291,6 +5321,7 @@ ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
   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);
@@ -5332,6 +5363,7 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
                             fromY, fromX, toY, toX, promoChar,
                             parseList[endPV - 1]);
   } while(valid);
+  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,
@@ -5379,6 +5411,25 @@ LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
        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
@@ -5722,6 +5773,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:
@@ -5838,7 +5897,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)
@@ -5852,7 +5911,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) {
@@ -5865,7 +5924,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 ) {
 
@@ -6024,7 +6089,7 @@ DefaultPromoChoice(int white)
 static int autoQueen; // [HGM] oneclick
 
 int
-HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
+HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
 {
     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
     /* [HGM] add Shogi promotions */
@@ -6044,7 +6109,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;
     }
 
@@ -6106,9 +6171,9 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     }
     // give caller the default choice even if we will not make it
     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
-    if(gameInfo.variant == VariantShogi) *promoChoice = '+';
-    if(appData.sweepSelect && gameInfo.variant != VariantGreat
-                          && gameInfo.variant != VariantShogi
+    if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
+    if(        sweepSelect && gameInfo.variant != VariantGreat
+                          && gameInfo.variant != VariantGrand
                           && gameInfo.variant != VariantSuper) return FALSE;
     if(autoQueen) return FALSE; // predetermined
 
@@ -6167,7 +6232,6 @@ OKToStartUserMove(x, y)
       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6195,6 +6259,8 @@ OKToStartUserMove(x, y)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
@@ -6238,6 +6304,7 @@ OKToStartUserMove(x, y)
     }
     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
        && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
+       && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
        && gameMode != AnalyzeFile && gameMode != Training) {
        DisplayMoveError(_("Displayed position is not current"));
        return FALSE;
@@ -6248,7 +6315,7 @@ OKToStartUserMove(x, y)
 Boolean
 OnlyMove(int *x, int *y, Boolean captures) {
     DisambiguateClosure cl;
-    if (appData.zippyPlay) return FALSE;
+    if (appData.zippyPlay || !appData.testLegality) return FALSE;
     switch(gameMode) {
       case MachinePlaysBlack:
       case IcsPlayingWhite:
@@ -6329,7 +6396,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6355,6 +6421,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey ||!appData.variations) return; // [HGM] only variations
       case EditGame:
       case IcsExamining:
       case BeginningOfGame:
@@ -6470,6 +6538,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
     /* [HGM] always test for legality, to get promotion info */
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
+
+    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
@@ -6490,8 +6561,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;
@@ -6546,7 +6617,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if ((gameMode == AnalyzeMode || gameMode == EditGame)
+  if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
                                && currentMove < forwardMostMove) {
     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
     else forwardMostMove = currentMove;
@@ -6611,6 +6682,9 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
         // [HGM] book: if program might be playing, let it use book
        bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
        first.maybeThinking = TRUE;
+    } else if(fromY == DROP_RANK && fromX == EmptySquare) {
+       if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
+       SendBoard(&first, currentMove+1);
     } else SendMoveToProgram(forwardMostMove-1, &first);
     if (currentMove == cmailOldMove + 1) {
       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
@@ -6688,13 +6762,13 @@ void
 MarkTargetSquares(int clear)
 {
   int x, y;
-  if(!appData.markers || !appData.highlightDragging ||
+  if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
      !appData.testLegality || gameMode == EditPosition) return;
   if(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)
@@ -6749,7 +6823,6 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 
     if (clickType == Press) ErrorPopDown();
-    MarkTargetSquares(1);
 
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
@@ -6764,7 +6837,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
        promoDefaultAltered = TRUE;
-       if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+       if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
     }
 
     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
@@ -6773,13 +6846,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;
            }
@@ -6816,7 +6890,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        }
        return;
       }
-      fromX = x; fromY = y;
+      fromX = x; fromY = y; toX = toY = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
         appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
@@ -6824,7 +6898,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
-               DragPieceBegin(xPix, yPix); dragging = 1;
+               DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
                    selectFlag = 0; lastX = xPix; lastY = yPix;
@@ -6863,6 +6937,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
            promoDefaultAltered = FALSE;
+           MarkTargetSquares(1);
           if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -6878,7 +6953,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                fromX = x;
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
-               DragPieceBegin(xPix, yPix);
+               DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
                    promoSweep = defaultPromoChoice;
                    selectFlag = 0; lastX = xPix; lastY = yPix;
@@ -6929,13 +7004,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     toX = x;
     toY = y;
     saveAnimate = appData.animate;
+    MarkTargetSquares(1);
     if (clickType == Press) {
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
-           fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
+           fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
+       if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+           ChessSquare piece = boards[currentMove][fromY][fromX];
+           DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
+           promoSweep = defaultPromoChoice;
+           if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
+           selectFlag = 0; lastX = xPix; lastY = yPix;
+           Sweep(0); // Pawn that is going to promote: preview promotion piece
+           DisplayMessage("", _("Pull pawn backwards to under-promote"));
+           DrawPosition(FALSE, boards[currentMove]);
+           return;
+       }
        /* Finish clickclick move */
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -6986,9 +7073,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 
     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
 
-    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        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;
@@ -7058,16 +7145,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; // grab
       case IcsObserving:
        if(!appData.icsEngineAnalyze) return -1;
       case IcsPlayingWhite:
@@ -7135,6 +7222,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 *
@@ -7144,6 +7240,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
 
@@ -7166,7 +7264,6 @@ TourneyStandings(int display)
        games[b]++;
        nr++;
     }
-    if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
     for(w=0; w<nPlayers; w++) {
        bScore = -1;
@@ -7278,6 +7375,28 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol
 }
 
 int
+CompareWithRights(Board b1, Board b2)
+{
+    int rights = 0;
+    if(!CompareBoards(b1, b2)) return FALSE;
+    if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
+    /* compare castling rights */
+    if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
+           rights++; /* King lost rights, while rook still had them */
+    if( b1[CASTLING][2] != NoRights ) { /* king has rights */
+        if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
+           rights++; /* but at least one rook lost them */
+    }
+    if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
+           rights++;
+    if( b1[CASTLING][5] != NoRights ) {
+        if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
+           rights++;
+    }
+    return rights == 0;
+}
+
+int
 Adjudicate(ChessProgramState *cps)
 {      // [HGM] some adjudications useful with buggy engines
        // [HGM] adjudicate: made into separate routine, which now can be called after every move
@@ -7655,7 +7774,7 @@ HandleMachineMove(message, cps)
     int fromX, fromY, toX, toY;
     ChessMove moveType;
     char promoChar;
-    char *p;
+    char *p, *pv=buf1;
     int machineWhite;
     char *bookHit;
 
@@ -8026,6 +8145,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;
     }
@@ -8033,6 +8153,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;
     }
@@ -8510,6 +8631,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;
@@ -8531,15 +8653,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");
                }
@@ -8576,14 +8698,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
@@ -8914,7 +9036,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 */
@@ -8923,15 +9045,19 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       oldEP = (signed char)board[EP_STATUS];
       board[EP_STATUS] = EP_NONE;
 
-      if( board[toY][toX] != EmptySquare )
-           board[EP_STATUS] = EP_CAPTURE;
-
   if (fromY == DROP_RANK) {
        /* must be first */
+        if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
+           board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
+           return;
+       }
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
       int i;
 
+      if( board[toY][toX] != EmptySquare )
+           board[EP_STATUS] = EP_CAPTURE;
+
       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
@@ -9012,18 +9138,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
@@ -9076,18 +9199,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
@@ -9148,7 +9268,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;
        }
@@ -9199,8 +9319,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
@@ -9428,6 +9548,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,
@@ -9489,6 +9611,7 @@ InitChessProgram(cps, setup)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
+    ClearEngineOutputPane(cps == &second);
 }
 
 
@@ -9598,12 +9721,13 @@ CountPlayers(char *p)
 }
 
 FILE *
-WriteTourneyFile(char *results)
+WriteTourneyFile(char *results, FILE *f)
 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
-    FILE *f = fopen(appData.tourneyFile, "w");
+    if(f == NULL) 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, "-seedBase %d\n", appData.seedBase);
        fprintf(f, "-tourneyType %d\n", appData.tourneyType);
        fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
        fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
@@ -9616,7 +9740,7 @@ WriteTourneyFile(char *results)
        fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
        fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
        if(searchTime > 0)
-               fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+               fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
        else {
                fprintf(f, "-mps %d\n", appData.movesPerSession);
                fprintf(f, "-tc %s\n", appData.timeControl);
@@ -9627,10 +9751,73 @@ WriteTourneyFile(char *results)
     return f;
 }
 
+#define MAXENGINES 1000
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
+void Substitute(char *participants, int expunge)
+{
+    int i, changed, changes=0, nPlayers=0;
+    char *p, *q, *r, buf[MSG_SIZ];
+    if(participants == NULL) return;
+    if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
+    r = p = participants; q = appData.participants;
+    while(*p && *p == *q) {
+       if(*p == '\n') r = p+1, nPlayers++;
+       p++; q++;
+    }
+    if(*p) { // difference
+       while(*p && *p++ != '\n');
+       while(*q && *q++ != '\n');
+      changed = nPlayers;
+       changes = 1 + (strcmp(p, q) != 0);
+    }
+    if(changes == 1) { // a single engine mnemonic was changed
+       q = r; while(*q) nPlayers += (*q++ == '\n');
+       p = buf; while(*r && (*p = *r++) != '\n') p++;
+       *p = NULLCHAR;
+       NamesToList(firstChessProgramNames, command, mnemonic);
+       for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
+       if(mnemonic[i]) { // The substitute is valid
+           FILE *f;
+           if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
+               flock(fileno(f), LOCK_EX);
+               ParseArgsFromFile(f);
+               fseek(f, 0, SEEK_SET);
+               FREE(appData.participants); appData.participants = participants;
+               if(expunge) { // erase results of replaced engine
+                   int len = strlen(appData.results), w, b, dummy;
+                   for(i=0; i<len; i++) {
+                       Pairing(i, nPlayers, &w, &b, &dummy);
+                       if((w == changed || b == changed) && appData.results[i] == '*') {
+                           DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
+                           fclose(f);
+                           return;
+                       }
+                   }
+                   for(i=0; i<len; i++) {
+                       Pairing(i, nPlayers, &w, &b, &dummy);
+                       if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
+                   }
+               }
+               WriteTourneyFile(appData.results, f);
+               fclose(f); // release lock
+               return;
+           }
+       } else DisplayError(_("No engine with the name you gave is installed"), 0);
+    }
+    if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
+    if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
+    free(participants);
+    return;
+}
+
 int
 CreateTourney(char *name)
 {
        FILE *f;
+       if(matchMode && strcmp(name, appData.tourneyFile)) {
+            ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
+       }
        if(name[0] == NULLCHAR) {
            if(appData.participants[0])
                DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
@@ -9647,7 +9834,8 @@ CreateTourney(char *name)
                return 0;
            }
            ASSIGN(appData.tourneyFile, name);
-           if((f = WriteTourneyFile("")) == NULL) return 0;
+           if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
+           if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
        }
        fclose(f);
        appData.noChessProgram = FALSE;
@@ -9656,9 +9844,6 @@ CreateTourney(char *name)
        return 1;
 }
 
-#define MAXENGINES 1000
-char *command[MAXENGINES], *mnemonic[MAXENGINES];
-
 void NamesToList(char *names, char **engineList, char **engineMnemonic)
 {
     char buf[MSG_SIZ], *p, *q;
@@ -9681,7 +9866,7 @@ void NamesToList(char *names, char **engineList, char **engineMnemonic)
        names = p; i++;
       if(i > MAXENGINES - 2) break;
     }
-    engineList[i] = NULL;
+    engineList[i] = engineMnemonic[i] = NULL;
 }
 
 // following implemented as macro to avoid type limitations
@@ -9702,6 +9887,7 @@ void SwapEngines(int n)
     SWAP(timeOdds, h)
     SWAP(logo, p)
     SWAP(pgnName, p)
+    SWAP(pvSAN, h)
 }
 
 void
@@ -9779,22 +9965,7 @@ NextTourneyGame(int nr, int *swapColors)
     InitTimeControls(); // TC might be altered from tourney file
 
     nPlayers = CountPlayers(appData.participants); // count participants
-    if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
-       if(nr>=0 && !pairingReceived) {
-           char buf[1<<16];
-           if(pairing.pr == NoProc) StartChessProgram(&pairing);
-           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 
-       syncInterval = nPlayers/2; *swapColors = 0;
-       appData.matchGames = appData.tourneyCycles * syncInterval - 1;
-       whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
-       matchGame = 1; roundNr = nr / syncInterval + 1;
-    } else
+    if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
 
     if(syncInterval) {
@@ -9809,6 +9980,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.
@@ -9820,6 +10014,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;
 }
 
@@ -9833,6 +10028,7 @@ NextMatchGame()
     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
+    if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
     Reset(FALSE, first.pr != NoProc);
     appData.noChessProgram = FALSE;
     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
@@ -9969,7 +10165,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) {
@@ -9995,7 +10191,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)
@@ -10233,6 +10430,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";
@@ -10252,11 +10450,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);
@@ -10350,6 +10550,7 @@ Reset(redraw, init)
                redraw, init, gameMode);
     }
     CleanupTail(); // [HGM] vari: delete any stored variations
+    CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -10457,8 +10658,8 @@ AutoPlayOneMove()
 
     if (currentMove >= forwardMostMove) {
       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
-      gameMode = EditGame;
-      ModeHighlight();
+//      gameMode = EndOfGame;
+//      ModeHighlight();
 
       /* [AS] Clear current move marker at the end of a game */
       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
@@ -10905,7 +11106,149 @@ ReloadGame(offset)
     }
 }
 
+int keys[EmptySquare+1];
+
+int
+PositionMatches(Board b1, Board b2)
+{
+    int r, f, sum=0;
+    switch(appData.searchMode) {
+       case 1: return CompareWithRights(b1, b2);
+       case 2:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
+           }
+           return TRUE;
+       case 3:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+             if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
+               sum += keys[b1[r][f]] - keys[b2[r][f]];
+           }
+           return sum==0;
+       case 4:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               sum += keys[b1[r][f]] - keys[b2[r][f]];
+           }
+           return sum==0;
+    }
+    return TRUE;
+}
+
+GameInfo dummyInfo;
+
+int GameContainsPosition(FILE *f, ListGame *lg)
+{
+    int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
+    int fromX, fromY, toX, toY;
+    char promoChar;
+    static int initDone=FALSE;
+
+    if(!initDone) {
+       for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
+       initDone = TRUE;
+    }
+    dummyInfo.variant = VariantNormal;
+    FREE(dummyInfo.fen); dummyInfo.fen = NULL;
+    dummyInfo.whiteRating = 0;
+    dummyInfo.blackRating = 0;
+    FREE(dummyInfo.date); dummyInfo.date = NULL;
+    fseek(f, lg->offset, 0);
+    yynewfile(f);
+    CopyBoard(boards[scratch], initialPosition); // default start position
+    while(1) {
+       yyboardindex = scratch + (plyNr&1);
+      quickFlag = 1;
+       next = Myylex();
+      quickFlag = 0;
+       switch(next) {
+           case PGNTag:
+               if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
+#if 0
+               ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
+               if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
+#else
+               // do it ourselves avoiding malloc
+               { char *p = yy_text+1, *q;
+                 while(!isdigit(*p) && !isalpha(*p)) p++;
+                 q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
+                 *p = NULLCHAR;
+                 if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
+                 if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
+                 if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
+                 if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
+                 if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
+                 if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
+                 if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
+               }
+#endif
+           default:
+               continue;
+
+           case XBoardGame:
+           case GNUChessGame:
+               if(plyNr) return -1; // after we have seen moves, this is for new game
+             continue;
 
+           case AmbiguousMove: // we cannot reconstruct the game beyond these two
+           case ImpossibleMove:
+           case WhiteWins: // game ends here with these four
+           case BlackWins:
+           case GameIsDrawn:
+           case GameUnfinished:
+               return -1;
+
+           case IllegalMove:
+               if(appData.testLegality) return -1;
+           case WhiteCapturesEnPassant:
+           case BlackCapturesEnPassant:
+           case WhitePromotion:
+           case BlackPromotion:
+           case WhiteNonPromotion:
+           case BlackNonPromotion:
+           case NormalMove:
+           case WhiteKingSideCastle:
+           case WhiteQueenSideCastle:
+           case BlackKingSideCastle:
+           case BlackQueenSideCastle:
+           case WhiteKingSideCastleWild:
+           case WhiteQueenSideCastleWild:
+           case BlackKingSideCastleWild:
+           case BlackQueenSideCastleWild:
+           case WhiteHSideCastleFR:
+           case WhiteASideCastleFR:
+           case BlackHSideCastleFR:
+           case BlackASideCastleFR:
+               fromX = currentMoveString[0] - AAA;
+               fromY = currentMoveString[1] - ONE;
+               toX = currentMoveString[2] - AAA;
+               toY = currentMoveString[3] - ONE;
+               promoChar = currentMoveString[4];
+               break;
+           case WhiteDrop:
+           case BlackDrop:
+               fromX = next == WhiteDrop ?
+                 (int) CharToPiece(ToUpper(currentMoveString[0])) :
+                 (int) CharToPiece(ToLower(currentMoveString[0]));
+               fromY = DROP_RANK;
+               toX = currentMoveString[2] - AAA;
+               toY = currentMoveString[3] - ONE;
+               break;
+       }
+       // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
+       if(plyNr == 0) { // but first figure out variant and initial position
+           if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
+           if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
+           if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
+           if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
+           if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
+           if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
+       }
+       CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
+       plyNr++;
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
+       if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
+    }
+}
 
 /* Load the nth game from open file f */
 int
@@ -10920,7 +11263,7 @@ LoadGame(f, gameNumber, title, useList)
     int gn = gameNumber;
     ListGame *lg = NULL;
     int numPGNTags = 0;
-    int err;
+    int err, pos = -1;
     GameMode oldGameMode;
     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 
@@ -10946,6 +11289,7 @@ LoadGame(f, gameNumber, title, useList)
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
+           pos = lg->position;
            gn = 1;
        }
        else {
@@ -11345,10 +11689,11 @@ LoadGame(f, gameNumber, title, useList)
       AnalyzeFileEvent();
     }
 
+    if (!matchMode && pos >= 0) {
+       ToNrEvent(pos); // [HGM] no autoplay if selected on position
+    } else
     if (matchMode || appData.timeDelay == 0) {
       ToEndEvent();
-      gameMode = EditGame;
-      ModeHighlight();
     } else if (appData.timeDelay > 0) {
       AutoPlayGameLoop();
     }
@@ -12835,7 +13180,7 @@ TwoMachinesEvent P((void))
 
     gameMode = TwoMachinesPlay;
     pausing = FALSE;
-    ModeHighlight();
+    ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
     firstMove = TRUE;
@@ -13464,7 +13809,9 @@ ClockClick(int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetBlackToPlayEvent();
-         } else if (gameMode == EditGame || shiftKey) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+          UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
+         } else if (shiftKey) {
            AdjustClock(which, -1);
          } else if (gameMode == IcsPlayingWhite ||
                     gameMode == MachinePlaysBlack) {
@@ -13474,7 +13821,9 @@ ClockClick(int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetWhiteToPlayEvent();
-         } else if (gameMode == EditGame || shiftKey) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+          UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
+         } else if (shiftKey) {
            AdjustClock(which, -1);
          } else if (gameMode == IcsPlayingBlack ||
                   gameMode == MachinePlaysWhite) {
@@ -13751,6 +14100,16 @@ BackwardInner(target)
     if (gameMode == EditGame || gameMode==AnalyzeMode ||
        gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
        while (currentMove > target) {
+           if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
+               // null move cannot be undone. Reload program with move history before it.
+               int i;
+               for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
+                   if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
+               }
+               SendBoard(&first, i); 
+               for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
+               break;
+           }
            SendToProgram("undo\n", &first);
            currentMove--;
        }
@@ -15007,14 +15366,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);
 }
 
@@ -15024,34 +15383,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)
@@ -15117,8 +15476,6 @@ DisplayComment(moveNumber, text)
      char *text;
 {
     char title[MSG_SIZ];
-    char buf[8000]; // comment can be long!
-    int score, depth;
 
     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
@@ -15127,14 +15484,6 @@ DisplayComment(moveNumber, text)
              WhiteOnMove(moveNumber) ? " " : ".. ",
              parseList[moveNumber]);
     }
-    // [HGM] PV info: display PV info together with (or as) comment
-    if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
-      if(text == NULL) text = "";
-      score = pvInfoList[moveNumber].score;
-      snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
-             depth, (pvInfoList[moveNumber].time+50)/100, text);
-      text = buf;
-    }
     if (text != NULL && (appData.autoDisplayComment || commentUp))
         CommentPopUp(title, text);
 }
@@ -15739,7 +16088,7 @@ PositionToFEN(move, overrideCastling)
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
-    char buf[128];
+    char buf[MSG_SIZ];
     char *p, *q;
     int emptycount;
     ChessSquare piece;
@@ -15750,6 +16099,7 @@ PositionToFEN(move, overrideCastling)
 
     /* Piece placement data */
     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+       if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
        emptycount = 0;
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            if (boards[move][i][j] == EmptySquare) {
@@ -16274,6 +16624,7 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 }
 
 // [HGM] vari: routines for shelving variations
+Boolean modeRestore = FALSE;
 
 void
 PushInner(int firstMove, int lastMove)
@@ -16317,6 +16668,7 @@ PushTail(int firstMove, int lastMove)
 
        PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
+       if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
 }
 
 void
@@ -16325,8 +16677,8 @@ PopInner(Boolean annotate)
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       storedGames--;
-       ToNrEvent(savedFirst[storedGames]); // sets currentMove
+       ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
+       storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
        nrMoves = savedLast[storedGames] - currentMove;
        if(annotate) {
                int cnt = 10;
@@ -16374,8 +16726,10 @@ PopTail(Boolean annotate)
        CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
 
        PopInner(annotate);
+       if(currentMove < forwardMostMove) ForwardEvent(); else
+       HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
 
-       if(storedGames == 0) GreyRevert(TRUE);
+       if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
        return TRUE;
 }
 
@@ -16402,7 +16756,7 @@ LoadVariation(int index, char *text)
        char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
        int level = 0, move;
 
-       if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+       if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
        // first find outermost bracketing variation
        while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
            if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}