Implement flipped search
[xboard.git] / backend.c
index 21562ab..7ffbd84 100644 (file)
--- a/backend.c
+++ b/backend.c
 #ifdef WIN32
 #include <windows.h>
 
-#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
-
 int flock(int f, int code);
 #define LOCK_EX 2
 #define SLASH '\\'
 
 #else
 
-#define DoSleep( n ) if( (n) >= 0) sleep(n)
+#include <sys/file.h>
 #define SLASH '/'
 
 #endif
@@ -233,7 +231,7 @@ void InitDrawingSizes(int x, int y);
 void NextMatchGame P((void));
 int NextTourneyGame P((int nr, int *swap));
 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
-FILE *WriteTourneyFile P((char *results));
+FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
 
 #ifdef WIN32
@@ -467,6 +465,7 @@ int have_sent_ICS_logon = 0;
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
+Boolean adjustedClock;
 long timeControl_2; /* [AS] Allow separate time controls */
 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
@@ -620,10 +619,10 @@ ChessSquare GothicArray[2][BOARD_FILES] = {
 
 #ifdef FALCON
 ChessSquare FalconArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
-        WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
-        BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
+        WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
+        BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
 };
 #else // !FALCON
 #define FalconArray CapablancaArray
@@ -881,16 +880,17 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 
 static char resetOptions[] = 
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+       "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
        "-firstOptions \"\" -firstNPS -1 -fn \"\"";
 
 void
 Load(ChessProgramState *cps, int i)
 {
     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
-    if(engineLine[0]) { // an engine was selected from the combo box
+    if(engineLine && 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(resetOptions); appData.fenOverride[0] = NULL;
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
        ParseArgsFromString(buf);
        SwapEngines(i);
        ReplaceEngine(cps, i);
@@ -906,8 +906,8 @@ Load(ChessProgramState *cps, int i)
        appData.directory[i] = strdup(engineName);
        p[-1] = SLASH;
     } else appData.directory[i] = ".";
-    if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
     if(params[0]) {
+       if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
        snprintf(command, MSG_SIZ, "%s %s", p, params);
        p = command;
     }
@@ -980,6 +980,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();
@@ -1435,7 +1436,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);
@@ -1663,6 +1664,18 @@ InitBackEnd3 P((void))
     }
 }
 
+void
+HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
+{
+    DisplayBook(current+1);
+
+    MoveHistorySet( movelist, first, last, current, pvInfoList );
+
+    EvalGraphSet( first, last, current, pvInfoList );
+
+    MakeEngineOutputTitle();
+}
+
 /*
  * Establish will establish a contact to a remote host.port.
  * Sets icsPR to a ProcRef for a process (or pseudo-process)
@@ -1855,7 +1868,7 @@ SendToICS(s)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
@@ -1874,7 +1887,7 @@ SendToICSDelayed(s,msdelay)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     if (appData.debugMode) {
@@ -3887,7 +3900,7 @@ read_from_ics(isr, closure, data, count, error)
 #if ZIPPY
                if (appData.zippyPlay && first.initDone) {
                    ZippyGameEnd(endtype, why);
-                   if (first.pr == NULL) {
+                   if (first.pr == NoProc) {
                      /* Start the next process early so that we'll
                         be ready for the next challenge */
                      StartChessProgram(&first);
@@ -4860,6 +4873,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);
     }
@@ -4900,6 +4918,7 @@ SendMoveToProgram(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
@@ -5075,6 +5094,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 {
@@ -5131,7 +5151,7 @@ int PromoScroll(int x, int y)
   int step = 0;
 
   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
-  if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
+  if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
   if(!step) return FALSE;
   lastX = x; lastY = y;
@@ -5406,7 +5426,7 @@ char *
 PvToSAN(char *pv)
 {
        static char buf[10*MSG_SIZ];
-       int i, k=0, savedEnd=endPV;
+       int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
        *buf = NULLCHAR;
        if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
        ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
@@ -5416,7 +5436,7 @@ PvToSAN(char *pv)
            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);
+       if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
        endPV = savedEnd;
        return buf;
 }
@@ -5459,11 +5479,11 @@ UnLoadPV()
 void
 MovePV(int x, int y, int h)
 { // step through PV based on mouse coordinates (called on mouse move)
-  int margin = h>>3, step = 0;
+  int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
 
   // we must somehow check if right button is still down (might be released off board!)
   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
-  if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
+  if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
   if(!step) return;
   lastX = x; lastY = y;
@@ -6223,7 +6243,6 @@ OKToStartUserMove(x, y)
       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6251,6 +6270,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"));
@@ -6294,6 +6315,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;
@@ -6385,7 +6407,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6411,6 +6432,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey ||!appData.variations) return; // [HGM] only variations
       case EditGame:
       case IcsExamining:
       case BeginningOfGame:
@@ -6526,6 +6549,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) {
@@ -6602,7 +6628,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;
@@ -6667,6 +6693,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;
@@ -6744,7 +6773,7 @@ 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;
@@ -7136,7 +7165,7 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
        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;
+       return 2; // grab
       case IcsObserving:
        if(!appData.icsEngineAnalyze) return -1;
       case IcsPlayingWhite:
@@ -7357,6 +7386,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
@@ -8228,7 +8279,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        if (StrStr(message, "analyze")) {
            cps->analysisSupport = FALSE;
            cps->analyzing = FALSE;
-           Reset(FALSE, TRUE);
+//         Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
+           EditGameEvent(); // [HGM] try to preserve loaded game
            snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
            DisplayError(buf2, 0);
            return;
@@ -8593,6 +8645,19 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
                if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
 
+               if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
+                       char buf[MSG_SIZ];
+                       FILE *f;
+                       snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
+                       buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
+                                            gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
+                       if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
+                       if(f = fopen(buf, "w")) { // export PV to applicable PV file
+                               fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
+                               fclose(f);
+                       } else DisplayError("failed writing PV", 0);
+               }
+
                tempStats.depth = plylev;
                tempStats.nodes = nodes;
                tempStats.time = time;
@@ -8878,6 +8943,7 @@ ParseGameHistory(game)
            break;
          case WhiteDrop:
          case BlackDrop:
+           if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
            fromX = moveType == WhiteDrop ?
              (int) CharToPiece(ToUpper(currentMoveString[0])) :
            (int) CharToPiece(ToLower(currentMoveString[0]));
@@ -9005,15 +9071,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
@@ -9298,6 +9368,11 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
 {
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    (void) CoordsToAlgebraic(boards[forwardMostMove],
+                            PosFlags(forwardMostMove),
+                            fromY, fromX, toY, toX, promoChar,
+                            parseList[forwardMostMove]);
+
     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
         int timeLeft; static int lastLoadFlag=0; int king, piece;
         piece = boards[forwardMostMove][fromY][fromX];
@@ -9305,10 +9380,14 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
         if(gameInfo.variant == VariantKnightmate)
             king += (int) WhiteUnicorn - (int) WhiteKing;
         if(forwardMostMove == 0) {
-            if(blackPlaysFirst)
+            if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
+                fprintf(serverMoves, "%s;", UserName());
+            else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
                 fprintf(serverMoves, "%s;", second.tidy);
             fprintf(serverMoves, "%s;", first.tidy);
-            if(!blackPlaysFirst)
+            if(gameMode == MachinePlaysWhite)
+                fprintf(serverMoves, "%s;", UserName());
+            else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
                 fprintf(serverMoves, "%s;", second.tidy);
         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
         lastLoadFlag = loadFlag;
@@ -9329,20 +9408,24 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
         // promotion suffix
         if(promoChar != NULLCHAR)
-                fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
+                fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
         if(!loadFlag) {
+               char buf[MOVE_LEN*2], *p; int len;
             fprintf(serverMoves, "/%d/%d",
                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
             else                      timeLeft = blackTimeRemaining/1000;
             fprintf(serverMoves, "/%d", timeLeft);
+               strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
+               if(p = strchr(buf, '=')) *p = NULLCHAR;
+               len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
+            fprintf(serverMoves, "/%s", buf);
         }
         fflush(serverMoves);
     }
 
-    if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
-      DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
-                       0, 1);
+    if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
+       GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
       return;
     }
     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
@@ -9356,6 +9439,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+    adjustedClock = FALSE;
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -9363,10 +9447,6 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     }
     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
                              moveList[forwardMostMove - 1]);
-    (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
-                            PosFlags(forwardMostMove - 1),
-                            fromY, fromX, toY, toX, promoChar,
-                            parseList[forwardMostMove - 1]);
     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
       case MT_NONE:
       case MT_STALEMATE:
@@ -9413,7 +9493,6 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
-    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9677,12 +9756,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);
@@ -9694,6 +9774,7 @@ WriteTourneyFile(char *results)
        fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
        fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
        fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
        if(searchTime > 0)
                fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
        else {
@@ -9706,10 +9787,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);
@@ -9727,7 +9871,7 @@ CreateTourney(char *name)
            }
            ASSIGN(appData.tourneyFile, name);
            if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
-           if((f = WriteTourneyFile("")) == NULL) return 0;
+           if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
        }
        fclose(f);
        appData.noChessProgram = FALSE;
@@ -9736,9 +9880,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;
@@ -9761,7 +9902,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
@@ -9795,7 +9936,8 @@ SetPlayer(int player)
     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); appData.fenOverride[0] = NULL;
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
+       appData.firstHasOwnBookUCI = !appData.defNoBook;
        ParseArgsFromString(buf);
     }
     free(engineName);
@@ -9898,7 +10040,7 @@ NextTourneyGame(int nr, int *swapColors)
        matchGame = 1; roundNr = nr / syncInterval + 1;
     }
 
-    if(first.pr != NoProc) return 1; // engines already loaded
+    if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
 
     // redefine engines, engine dir, etc.
     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
@@ -9916,16 +10058,18 @@ NextTourneyGame(int nr, int *swapColors)
 void
 NextMatchGame()
 {   // performs game initialization that does not invoke engines, and then tries to start the game
-    int firstWhite, swapColors = 0;
+    int res, firstWhite, swapColors = 0;
     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
     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
+    res = LoadGameOrPosition(matchGame); // setup game
+    appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
+    if(!res) return; // abort when bad game/pos file
     TwoMachinesEvent();
 }
 
@@ -10112,7 +10256,7 @@ GameEnds(result, resultDetails, whosays)
             if(result==WhiteWins) c = '+';
             if(result==BlackWins) c = '-';
             if(resultDetails != NULL)
-                fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+                fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
         }
        if (resultDetails != NULL) {
            gameInfo.result = result;
@@ -10306,6 +10450,7 @@ GameEnds(result, resultDetails, whosays)
 
        if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
        if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
+           if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
            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
@@ -10444,6 +10589,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;
@@ -10502,7 +10648,7 @@ Reset(redraw, init)
     timeRemaining[0][0] = whiteTimeRemaining;
     timeRemaining[1][0] = blackTimeRemaining;
 
-    if (first.pr == NULL) {
+    if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
     if (init) {
@@ -10551,8 +10697,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); */
@@ -10999,7 +11145,333 @@ 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;
+}
 
+#define Q_PROMO  4
+#define Q_EP     3
+#define Q_BCASTL 2
+#define Q_WCASTL 1
+
+int pieceList[256], quickBoard[256];
+ChessSquare pieceType[256] = { EmptySquare };
+Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
+int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
+Boolean epOK, flipSearch;
+
+typedef struct {
+    unsigned char piece, to;
+} Move;
+
+#define DATABASESIZE 10000000 /* good for 100k games */
+Move moveDatabase[DATABASESIZE];
+int movePtr;
+
+void MakePieceList(Board board, int *counts)
+{
+    int r, f, n=Q_PROMO;
+    for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+       int sq = f + (r<<4);
+        if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
+           quickBoard[sq] = ++n;
+           pieceList[n] = sq;
+           pieceType[n] = board[r][f];
+           counts[board[r][f]]++;
+           if(board[r][f] == WhiteKing) pieceList[1] = n; else
+           if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
+       }
+    }
+    epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
+}
+
+void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
+{
+    int sq = fromX + (fromY<<4);
+    int piece = quickBoard[sq];
+    quickBoard[sq] = 0;
+    moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
+    if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+       int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
+       moveDatabase[movePtr++].piece = Q_WCASTL;
+       quickBoard[sq] = piece;
+       piece = quickBoard[from]; quickBoard[from] = 0;
+       moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+    } else
+    if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+       int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
+       moveDatabase[movePtr++].piece = Q_BCASTL;
+       quickBoard[sq] = piece;
+       piece = quickBoard[from]; quickBoard[from] = 0;
+       moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+    } else
+    if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
+       quickBoard[(fromY<<4)+toX] = 0;
+       moveDatabase[movePtr].piece = Q_EP;
+       moveDatabase[movePtr++].to = (fromY<<4)+toX;
+       moveDatabase[movePtr].to = sq;
+    } else
+    if(promoPiece != pieceType[piece]) {
+       moveDatabase[movePtr++].piece = Q_PROMO;
+       moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
+    }
+    moveDatabase[movePtr].piece = piece;
+    quickBoard[sq] = piece;
+    movePtr++;
+}
+
+int PackGame(Board board)
+{
+    moveDatabase[movePtr].piece = 0; // terminate previous game
+    if(movePtr > DATABASESIZE - 500) return 0; // gamble on that game will not be more than 250 moves
+    movePtr++;
+    MakePieceList(board, counts);
+    return movePtr;
+}
+
+int QuickCompare(Board board, int *minCounts, int *maxCounts)
+{   // compare according to search mode
+    int r, f;
+    switch(appData.searchMode)
+    {
+      case 1: // exact position match
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       }
+       return TRUE;
+      case 2: // can have extra material on empty squares
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] == EmptySquare) continue;
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       }
+       return TRUE;
+      case 3: // material with exact Pawn structure
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
+           if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+       } // fall through to material comparison
+      case 4: // exact material
+       for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
+       return TRUE;
+      case 6: // material range with given imbalance
+       for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
+       // fall through to range comparison
+      case 5: // material range
+       for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
+       return TRUE;
+    }
+}
+
+int QuickScan(Board board, Move *move)
+{   // reconstruct game,and compare all positions in it
+    int cnt=0, stretch=0;
+    MakePieceList(board, counts);
+    do {
+       int piece = move->piece;
+       int to = move->to, from = pieceList[piece];
+       if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
+         if(!piece) return -1;
+         if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
+           piece = (++move)->piece;
+           from = pieceList[piece];
+           counts[pieceType[piece]]--;
+           pieceType[piece] = (ChessSquare) move->to;
+           counts[move->to]++;
+         } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
+           counts[pieceType[quickBoard[to]]]--;
+           quickBoard[to] = 0;
+           move++;
+           continue;
+         } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
+           piece = pieceList[piece]; // first two elements of pieceList contain King numbers
+           from  = pieceList[piece]; // so this must be King
+           quickBoard[from] = 0;
+           quickBoard[to] = piece;
+           pieceList[piece] = to;
+           move++;
+           continue;
+         }
+       }
+       if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
+       quickBoard[from] = 0;
+       quickBoard[to] = piece;
+       pieceList[piece] = to;
+       cnt++;
+       if(QuickCompare(soughtBoard, minSought, maxSought) ||
+          appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
+          flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
+                               appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
+         ) {
+           static int lastCounts[EmptySquare+1];
+           int i;
+           if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+           if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+       } else stretch = 0;
+       if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
+       move++;
+    } while(1);
+}
+
+InitSearch()
+{
+    int r, f;
+    flipSearch = FALSE;
+    CopyBoard(soughtBoard, boards[currentMove]);
+    MakePieceList(soughtBoard, maxSought);
+    CopyBoard(reverseBoard, boards[currentMove]);
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+       int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
+       if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
+       reverseBoard[r][f] = piece;
+    }
+    for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
+    if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
+                || (boards[currentMove][CASTLING][2] == NoRights || 
+                    boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
+                && (boards[currentMove][CASTLING][5] == NoRights || 
+                    boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
+      ) {
+       flipSearch = TRUE;
+       CopyBoard(flipBoard, soughtBoard);
+       CopyBoard(rotateBoard, reverseBoard);
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
+           rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
+       }
+    }
+    for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
+    if(appData.searchMode >= 5) {
+       for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
+       MakePieceList(soughtBoard, minSought);
+       for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
+    }
+}
+
+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;
+
+    // weed out games based on numerical tag comparison
+    if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
+    if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
+    if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
+    if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
+    if(!initDone) {
+       for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
+       initDone = TRUE;
+    }
+    if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
+    else CopyBoard(boards[scratch], initialPosition); // default start position
+    if(lg->moves) {
+       if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
+       if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
+    }
+    if(btm) plyNr++;
+    if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
+    fseek(f, lg->offset, 0);
+    yynewfile(f);
+    while(1) {
+       yyboardindex = scratch;
+       quickFlag = plyNr+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
+           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
+       plyNr++;
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
+       if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
+       if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
+       if(appData.findMirror) {
+           if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
+           if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
+       }
+    }
+}
 
 /* Load the nth game from open file f */
 int
@@ -11014,7 +11486,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 */
 
@@ -11040,6 +11512,7 @@ LoadGame(f, gameNumber, title, useList)
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
+           pos = lg->position;
            gn = 1;
        }
        else {
@@ -11439,10 +11912,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();
     }
@@ -11520,7 +11994,7 @@ LoadPosition(f, positionNumber, title)
     lastLoadPositionFP = f;
     lastLoadPositionNumber = positionNumber;
     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
-    if (first.pr == NoProc) {
+    if (first.pr == NoProc && !appData.noChessProgram) {
       StartChessProgram(&first);
       InitChessProgram(&first, FALSE);
     }
@@ -11592,7 +12066,6 @@ LoadPosition(f, positionNumber, title)
     }
     startedFromSetupPosition = TRUE;
 
-    SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
        currentMove = forwardMostMove = backwardMostMove = 1;
@@ -11605,7 +12078,10 @@ LoadPosition(f, positionNumber, title)
        DisplayMessage("", _("White to play"));
     }
     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
-    SendBoard(&first, forwardMostMove);
+    if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
+       SendToProgram("force\n", &first);
+       SendBoard(&first, forwardMostMove);
+    }
     if (appData.debugMode) {
 int i, j;
   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
@@ -11671,12 +12147,18 @@ SaveGameToFile(filename, append)
 {
     FILE *f;
     char buf[MSG_SIZ];
-    int result;
+    int result, i, t,tot=0;
 
     if (strcmp(filename, "-") == 0) {
        return SaveGame(stdout, 0, NULL);
     } else {
-       f = fopen(filename, append ? "a" : "w");
+       for(i=0; i<10; i++) { // upto 10 tries
+            f = fopen(filename, append ? "a" : "w");
+            if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
+            if(f || errno != 13) break;
+            DoSleep(t = 5 + random()%11); // wait 5-15 msec
+            tot += t;
+       }
        if (f == NULL) {
            snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
            DisplayError(buf, errno);
@@ -12629,6 +13111,7 @@ AnalyzeFileEvent()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
+    if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
 }
 
 void
@@ -12833,7 +13316,7 @@ int
 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
 {
     char buf[MSG_SIZ];
-    if (cps->pr == NULL) {
+    if (cps->pr == NoProc) {
        StartChessProgram(cps);
        if (cps->protocolVersion == 1) {
          retry();
@@ -13118,9 +13601,11 @@ EditGameEvent()
            SendToProgram("undo\n", &first);
            i--;
        }
+       if(!adjustedClock) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
        DisplayBothClocks();
+       }
        if (whiteFlag || blackFlag) {
            whiteFlag = blackFlag = 0;
        }
@@ -13558,7 +14043,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) {
@@ -13568,7 +14055,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) {
@@ -13759,7 +14248,6 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
-    DisplayBook(currentMove);
 }
 
 
@@ -13845,6 +14333,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--;
        }
@@ -13862,7 +14360,6 @@ BackwardInner(target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
-    DisplayBook(currentMove);
 }
 
 void
@@ -14355,6 +14852,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
     if (len == 0) return;
 
     if (commentList[index] != NULL) {
+      Boolean addClosingBrace = addBraces;
        old = commentList[index];
        oldlen = strlen(old);
        while(commentList[index][oldlen-1] ==  '\n')
@@ -14371,7 +14869,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
        if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
        else          strcat(commentList[index], "\n");
        strcat(commentList[index], text);
-       if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
+       if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
        else          strcat(commentList[index], "\n");
     } else {
        commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
@@ -14471,7 +14969,7 @@ char *GetInfoFromComment( int index, char * text )
             while( *++sep >= '0' && *sep <= '9'); // strip seconds
             if(deci >= 0)
             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
-            while(*sep == ' ') sep++;
+            while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
         }
 
         if( depth <= 0 ) {
@@ -14499,7 +14997,7 @@ SendToProgram(message, cps)
     int count, outCount, error;
     char buf[MSG_SIZ];
 
-    if (cps->pr == NULL) return;
+    if (cps->pr == NoProc) return;
     Attention(cps);
 
     if (appData.debugMode) {
@@ -15429,9 +15927,11 @@ NextTickLength(timeRemaining)
 void
 AdjustClock(Boolean which, int dir)
 {
+    if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
     if(which) blackTimeRemaining += 60000*dir;
     else      whiteTimeRemaining += 60000*dir;
     DisplayBothClocks();
+    adjustedClock = TRUE;
 }
 
 /* Stop clocks and reset to a fresh time control */
@@ -15455,6 +15955,7 @@ ResetClocks()
     }
     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
     DisplayBothClocks();
+    adjustedClock = FALSE;
 }
 
 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
@@ -16359,6 +16860,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)
@@ -16402,6 +16904,7 @@ PushTail(int firstMove, int lastMove)
 
        PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
+       if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
 }
 
 void
@@ -16410,8 +16913,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;
@@ -16459,8 +16962,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;
 }
 
@@ -16487,7 +16992,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 ]}