Add -afterTourney option
[xboard.git] / backend.c
index 26ebe3c..5e346e5 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -55,8 +55,6 @@
 #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 '\\'
@@ -64,7 +62,6 @@ int flock(int f, int code);
 #else
 
 #include <sys/file.h>
-#define DoSleep( n ) if( (n) >= 0) sleep(n)
 #define SLASH '/'
 
 #endif
@@ -150,12 +147,6 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 #endif
 
 
-/* A point in time */
-typedef struct {
-    long sec;  /* Assuming this is >= 32 bits */
-    int ms;    /* Assuming this is >= 16 bits */
-} TimeMark;
-
 int establish P((void));
 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
                         char *buf, int count, int error));
@@ -171,8 +162,6 @@ int LoadGameOneMove P((ChessMove readAhead));
 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
 int LoadPositionFromFile P((char *filename, int n, char *title));
 int SavePositionToFile P((char *filename));
-void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
-                                                                               Board board));
 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
 void ShowMove P((int fromX, int fromY, int toX, int toY));
 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
@@ -207,7 +196,6 @@ void StopClocks P((void));
 void ResetClocks P((void));
 char *PGNDate P((void));
 void SetGameInfo P((void));
-Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
 int RegisterMove P((void));
 void MakeRegisteredMove P((void));
 void TruncateGame P((void));
@@ -216,8 +204,6 @@ void CopyPlayerNameIntoFileName P((char **, char *));
 char *SavePart P((char *));
 int SaveGameOldStyle P((FILE *));
 int SaveGamePGN P((FILE *));
-void GetTimeMark P((TimeMark *));
-long SubtractTimeMarks P((TimeMark *, TimeMark *));
 int CheckFlags P((void));
 long NextTickLength P((long));
 void CheckTimeControl P((void));
@@ -468,6 +454,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];
@@ -621,10 +608,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
@@ -828,7 +815,7 @@ InitEngine(ChessProgramState *cps, int n)
 
        len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
                       appData.protocolVersion[n]);
-       if( (len > MSG_SIZ) && appData.debugMode )
+       if( (len >= MSG_SIZ) && appData.debugMode )
          fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
 
        DisplayFatalError(buf, 0, 2);
@@ -839,6 +826,7 @@ InitEngine(ChessProgramState *cps, int n)
       }
 
     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
+    ParseFeatures(appData.featureDefaults, cps);
 }
 
 ChessProgramState *savCps;
@@ -882,16 +870,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);
@@ -907,8 +896,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;
     }
@@ -1073,7 +1062,7 @@ InitBackEnd1()
       case VariantKriegspiel:   /* need to hide pieces and move details */
        /* case VariantFischeRandom: (Fabien: moved below) */
        len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
-       if( (len > MSG_SIZ) && appData.debugMode )
+       if( (len >= MSG_SIZ) && appData.debugMode )
          fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
 
        DisplayFatalError(buf, 0, 2);
@@ -1091,7 +1080,7 @@ InitBackEnd1()
       case Variant36:
       default:
        len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
-       if( (len > MSG_SIZ) && appData.debugMode )
+       if( (len >= MSG_SIZ) && appData.debugMode )
          fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
 
        DisplayFatalError(buf, 0, 2);
@@ -1496,7 +1485,7 @@ InitBackEnd3 P((void))
              len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
                        appData.icsHost, appData.icsPort);
 
-           if( (len > MSG_SIZ) && appData.debugMode )
+           if( (len >= MSG_SIZ) && appData.debugMode )
              fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
 
            DisplayFatalError(buf, err, 1);
@@ -1550,7 +1539,7 @@ InitBackEnd3 P((void))
       initialMode = Training;
     } else {
       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
-      if( (len > MSG_SIZ) && appData.debugMode )
+      if( (len >= MSG_SIZ) && appData.debugMode )
        fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
 
       DisplayFatalError(buf, 0, 2);
@@ -1665,6 +1654,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)
@@ -1857,7 +1858,7 @@ SendToICS(s)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
@@ -1876,7 +1877,7 @@ SendToICSDelayed(s,msdelay)
 {
     int count, outCount, outError;
 
-    if (icsPR == NULL) return;
+    if (icsPR == NoProc) return;
 
     count = strlen(s);
     if (appData.debugMode) {
@@ -2137,7 +2138,7 @@ StringToVariant(e)
          break;
        default:
          len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
-         if( (len > MSG_SIZ) && appData.debugMode )
+         if( (len >= MSG_SIZ) && appData.debugMode )
            fprintf(debugFP, "StringToVariant: buffer truncated.\n");
 
          DisplayError(buf, 0);
@@ -3627,7 +3628,7 @@ read_from_ics(isr, closure, data, count, error)
                        flipView = appData.flipView;
                        DrawPosition(TRUE, boards[currentMove]);
                        DisplayBothClocks();
-                       snprintf(str, MSG_SIZ, "%s vs. %s",
+                       snprintf(str, MSG_SIZ, _("%s vs. %s"),
                                gameInfo.white, gameInfo.black);
                        DisplayTitle(str);
                        gameMode = IcsIdle;
@@ -3854,6 +3855,7 @@ read_from_ics(isr, closure, data, count, error)
                    strncmp(why, "Continuing ", 11) == 0) {
                    gs_gamenum = gamenum;
                    safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
+                   if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
                    VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
 #if ZIPPY
                    if (appData.zippyPlay) {
@@ -3889,7 +3891,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);
@@ -4041,10 +4043,10 @@ read_from_ics(isr, closure, data, count, error)
                            char wh[16], bh[16];
                            PackHolding(wh, white_holding);
                            PackHolding(bh, black_holding);
-                           snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
+                           snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
                                    gameInfo.white, gameInfo.black);
                        } else {
-                         snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
+                         snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
                                    gameInfo.white, white_holding,
                                    gameInfo.black, black_holding);
                        }
@@ -4768,11 +4770,11 @@ ParseBoard12(string)
                    basetime, increment, (int) gameInfo.variant);
        } else {
            if(gameInfo.variant == VariantNormal)
-             snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
+             snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment);
            else
-             snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
+             snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment, VariantName(gameInfo.variant));
        }
@@ -4942,6 +4944,12 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
      char promoChar;
 {
     char user_move[MSG_SIZ];
+    char suffix[4];
+
+    if(gameInfo.variant == VariantSChess && promoChar) {
+       snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
+       if(toX == BOARD_WIDTH>>1) moveType = WhitePromotion; // kludge to do gating at Rook
+    } else suffix[0] = NULLCHAR;
 
     switch (moveType) {
       default:
@@ -4957,7 +4965,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
       case WhiteHSideCastleFR:
       case BlackHSideCastleFR:
       /* POP Fabien */
-       snprintf(user_move, MSG_SIZ, "o-o\n");
+       snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
        break;
       case WhiteQueenSideCastle:
       case BlackQueenSideCastle:
@@ -4967,7 +4975,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
       case WhiteASideCastleFR:
       case BlackASideCastleFR:
       /* POP Fabien */
-       snprintf(user_move, MSG_SIZ, "o-o-o\n");
+       snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
        break;
       case WhiteNonPromotion:
       case BlackNonPromotion:
@@ -5015,8 +5023,8 @@ UploadGameEvent()
     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
-       DisplayError("You cannot do this while you are playing or observing", 0);
-       return;
+      DisplayError(_("You cannot do this while you are playing or observing"), 0);
+      return;
     }
     if(gameMode != IcsExamining) { // is this ever not the case?
        char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
@@ -5128,7 +5136,7 @@ Sweep(int step)
        else if((int)promoSweep == -1) promoSweep = WhiteKing;
        else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
        else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
-       if(!step) step = 1;
+       if(!step) step = -1;
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
            appData.testLegality && (promoSweep == king ||
            gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
@@ -5140,7 +5148,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;
@@ -5415,7 +5423,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
@@ -5425,7 +5433,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;
 }
@@ -5468,11 +5476,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;
@@ -6648,7 +6656,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       gameMode = MachinePlaysBlack;
       StartClocks();
       SetGameInfo();
-      snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+      snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
       DisplayTitle(buf);
       if (first.sendName) {
        snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
@@ -7618,8 +7626,10 @@ Adjudicate(ChessProgramState *cps)
                                    hisPerpetual = PerpetualChase(k, forwardMostMove);
                                    ourPerpetual = PerpetualChase(k+1, forwardMostMove);
                                    if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+                                       static char resdet[MSG_SIZ];
                                        result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
-                                       details = "Xboard adjudication: perpetual chasing";
+                                       details = resdet;
+                                       snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
                                    } else
                                    if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
                                        break; // Abort repetition-checking loop.
@@ -8108,10 +8118,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
     }
 
-    if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+    if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
+                                       !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
       int dummy, s=6; char buf[MSG_SIZ];
-      if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
+      if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+      if(startedFromSetupPosition) return;
       ParseFEN(boards[0], &dummy, message+s);
       DrawPosition(TRUE, boards[0]);
       startedFromSetupPosition = TRUE;
@@ -8268,7 +8280,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;
@@ -8633,6 +8646,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;
@@ -8918,6 +8944,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]));
@@ -9342,6 +9369,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];
@@ -9349,10 +9381,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;
@@ -9373,20 +9409,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
@@ -9400,6 +9440,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);
@@ -9407,10 +9448,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:
@@ -9457,7 +9494,6 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
-    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9503,6 +9539,7 @@ InitChessProgram(cps, setup)
     hintRequested = FALSE;
     bookRequested = FALSE;
 
+    ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
     if(cps->memSize) { /* [HGM] memory */
@@ -9739,6 +9776,7 @@ WriteTourneyFile(char *results, FILE *f)
        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 {
@@ -9888,6 +9926,7 @@ void SwapEngines(int n)
     SWAP(logo, p)
     SWAP(pgnName, p)
     SWAP(pvSAN, h)
+    SWAP(engOptions, p)
 }
 
 void
@@ -9900,7 +9939,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);
@@ -10003,7 +10043,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
@@ -10021,7 +10061,7 @@ 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
@@ -10030,8 +10070,9 @@ NextMatchGame()
     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();
 }
 
@@ -10218,7 +10259,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;
@@ -10412,6 +10453,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
@@ -10431,6 +10473,7 @@ GameEnds(result, resultDetails, whosays)
                     first.matchWins, second.matchWins,
                     appData.matchGames - (first.matchWins + second.matchWins));
            if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
+           if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
            popupRequested++; // [HGM] crash: postpone to after resetting endingGame
            if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
                first.twoMachinesColor = "black\n";
@@ -10609,7 +10652,7 @@ Reset(redraw, init)
     timeRemaining[0][0] = whiteTimeRemaining;
     timeRemaining[1][0] = blackTimeRemaining;
 
-    if (first.pr == NULL) {
+    if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
     if (init) {
@@ -11134,6 +11177,231 @@ PositionMatches(Board b1, Board b2)
     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];
+int soughtTotal, turn;
+Boolean epOK, flipSearch;
+
+typedef struct {
+    unsigned char piece, to;
+} Move;
+
+#define DSIZE (250000)
+
+Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
+Move *moveDatabase = initialSpace;
+unsigned int movePtr, dataSize = DSIZE;
+
+int MakePieceList(Board board, int *counts)
+{
+    int r, f, n=Q_PROMO, total=0;
+    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
+           total++;
+       }
+    }
+    epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
+    return total;
+}
+
+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)
+{
+    Move *newSpace = NULL;
+    moveDatabase[movePtr].piece = 0; // terminate previous game
+    if(movePtr > dataSize) {
+       if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
+       dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
+       if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
+       if(newSpace) {
+           int i;
+           Move *p = moveDatabase, *q = newSpace;
+           for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
+           if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
+           moveDatabase = newSpace;
+       } else { // calloc failed, we must be out of memory. Too bad...
+           dataSize = 0; // prevent calloc events for all subsequent games
+           return 0;     // and signal this one isn't cached
+       }
+    }
+    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
+       if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
+       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;
+       }
+       break;
+      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;
+       }
+       break;
+      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;
+       break;
+      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, total = 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; total--;
+           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
+       if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
+       quickBoard[from] = 0;
+       quickBoard[to] = piece;
+       pieceList[piece] = to;
+       cnt++; turn ^= 3;
+       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);
+}
+
+void InitSearch()
+{
+    int r, f;
+    flipSearch = FALSE;
+    CopyBoard(soughtBoard, boards[currentMove]);
+    soughtTotal = MakePieceList(soughtBoard, maxSought);
+    soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
+    if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
+    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;
+    }
+    reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
+    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];
+    }
+    if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
+       soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
+}
+
 GameInfo dummyInfo;
 
 int GameContainsPosition(FILE *f, ListGame *lg)
@@ -11143,44 +11411,34 @@ int GameContainsPosition(FILE *f, ListGame *lg)
     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;
     }
-    dummyInfo.variant = VariantNormal;
-    FREE(dummyInfo.fen); dummyInfo.fen = NULL;
-    dummyInfo.whiteRating = 0;
-    dummyInfo.blackRating = 0;
-    FREE(dummyInfo.date); dummyInfo.date = NULL;
+    if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
+    else CopyBoard(boards[scratch], initialPosition); // default start position
+    if(lg->moves) {
+       turn = btm + 1;
+       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);
-    CopyBoard(boards[scratch], initialPosition); // default start position
     while(1) {
-       yyboardindex = scratch + (plyNr&1);
-      quickFlag = 1;
+       yyboardindex = scratch;
+       quickFlag = plyNr+1;
        next = Myylex();
-      quickFlag = 0;
+       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;
 
@@ -11232,21 +11490,18 @@ int GameContainsPosition(FILE *f, ListGame *lg)
                fromY = DROP_RANK;
                toX = currentMoveString[2] - AAA;
                toY = currentMoveString[3] - ONE;
+               promoChar = 0;
                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;
+       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;
+       }
     }
 }
 
@@ -11317,7 +11572,7 @@ LoadGame(f, gameNumber, title, useList)
     yynewfile(f);
 
     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
-      snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
+      snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
                lg->gameInfo.black);
            DisplayTitle(buf);
     } else if (*title != NULLCHAR) {
@@ -11771,7 +12026,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);
     }
@@ -11843,7 +12098,6 @@ LoadPosition(f, positionNumber, title)
     }
     startedFromSetupPosition = TRUE;
 
-    SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
        currentMove = forwardMostMove = backwardMostMove = 1;
@@ -11856,7 +12110,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");}
@@ -11922,12 +12179,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);
@@ -11937,7 +12200,7 @@ SaveGameToFile(filename, append)
            DisplayMessage(_("Waiting for access to save file"), "");
            flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
            DisplayMessage(_("Saving game"), "");
-           if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
+           if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
            result = SaveGame(f, 0, NULL);
            DisplayMessage(buf, "");
            return result;
@@ -12880,6 +13143,7 @@ AnalyzeFileEvent()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
+    if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
 }
 
 void
@@ -12923,7 +13187,7 @@ MachineWhiteEvent()
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
-    snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+    snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
     DisplayTitle(buf);
     if (first.sendName) {
       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
@@ -13000,7 +13264,7 @@ MachineBlackEvent()
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
-    snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+    snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
     DisplayTitle(buf);
     if (first.sendName) {
       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
@@ -13045,24 +13309,24 @@ DisplayTwoMachinesTitle()
     char buf[MSG_SIZ];
     if (appData.matchGames > 0) {
         if(appData.tourneyFile[0]) {
-         snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
+         snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
                   gameInfo.white, gameInfo.black,
                   nextGame+1, appData.matchGames+1,
                   appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
         } else 
         if (first.twoMachinesColor[0] == 'w') {
-         snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
+         snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
                   gameInfo.white, gameInfo.black,
                   first.matchWins, second.matchWins,
                   matchGame - 1 - (first.matchWins + second.matchWins));
        } else {
-         snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
+         snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
                   gameInfo.white, gameInfo.black,
                   second.matchWins, first.matchWins,
                   matchGame - 1 - (first.matchWins + second.matchWins));
        }
     } else {
-      snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+      snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
     }
     DisplayTitle(buf);
 }
@@ -13084,7 +13348,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();
@@ -13369,9 +13633,11 @@ EditGameEvent()
            SendToProgram("undo\n", &first);
            i--;
        }
+       if(!adjustedClock) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
        DisplayBothClocks();
+       }
        if (whiteFlag || blackFlag) {
            whiteFlag = blackFlag = 0;
        }
@@ -13964,6 +14230,8 @@ ForwardInner(target)
     if (gameMode == EditPosition)
       return;
 
+    MarkTargetSquares(1);
+
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
 
@@ -14014,7 +14282,6 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
-    DisplayBook(currentMove);
 }
 
 
@@ -14070,6 +14337,7 @@ BackwardInner(target)
                target, currentMove, forwardMostMove);
 
     if (gameMode == EditPosition) return;
+    MarkTargetSquares(1);
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
        DrawPosition(full_redraw, boards[currentMove]);
@@ -14127,7 +14395,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
@@ -14620,6 +14887,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')
@@ -14636,7 +14904,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...
@@ -14736,7 +15004,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 ) {
@@ -14764,7 +15032,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) {
@@ -15694,9 +15962,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 */
@@ -15720,6 +15990,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 */