Let XBoard propose name of tourney file
[xboard.git] / backend.c
index c0d36aa..c73d6e7 100644 (file)
--- a/backend.c
+++ b/backend.c
 
 #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)
+#define SLASH '/'
 
 #endif
 
@@ -188,7 +193,7 @@ void SendTimeControl P((ChessProgramState *cps,
 char *TimeControlTagValue P((void));
 void Attention P((ChessProgramState *cps));
 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
-void ResurrectChessProgram P((void));
+int ResurrectChessProgram P((void));
 void DisplayComment P((int moveNumber, char *text));
 void DisplayMove P((int moveNumber));
 
@@ -225,6 +230,10 @@ void OutputKibitz(int window, char *text);
 int PerpetualChase(int first, int last);
 int EngineOutputIsUp();
 void InitDrawingSizes(int x, int y);
+void NextMatchGame P((void));
+int NextTourneyGame P((int nr, int *swap));
+int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
+FILE *WriteTourneyFile P((char *results));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -241,6 +250,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H
 void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
+Boolean abortMatch;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -267,8 +277,9 @@ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
 extern int chatCount;
 int chattingPartner;
 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
+char lastMsg[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
-ChessSquare promoSweep = EmptySquare, defaultPromoChoice, savePiece = EmptySquare;
+ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
 
 /* States for ics_getting_history */
@@ -325,7 +336,7 @@ safeStrCpy( char *dst, const char *src, size_t count )
     {
       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
       if(appData.debugMode)
-      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
+      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
     }
 
   return dst;
@@ -407,7 +418,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
 
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
 
 /* premove variables */
 int premoveToX = 0;
@@ -457,8 +468,9 @@ long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhi
 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];
-int matchGame = 0;
-TimeMark programStartTime;
+int matchGame = 0, nextGame = 0, roundNr = 0;
+Boolean waitingForGame = FALSE;
+TimeMark programStartTime, pauseStart;
 char ics_handle[MSG_SIZ];
 int have_set_title = 0;
 
@@ -496,6 +508,8 @@ ChessMove savedResult[MAX_VARIATIONS];
 
 void PushTail P((int firstMove, int lastMove));
 Boolean PopTail P((Boolean annotate));
+void PushInner P((int firstMove, int lastMove));
+void PopInner P((Boolean annotate));
 void CleanupTail P((void));
 
 ChessSquare  FIDEArray[2][BOARD_FILES] = {
@@ -659,15 +673,300 @@ ClearProgramStats()
 }
 
 void
-InitBackEnd1()
+CommonEngineInit()
+{   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
+    if (appData.firstPlaysBlack) {
+       first.twoMachinesColor = "black\n";
+       second.twoMachinesColor = "white\n";
+    } else {
+       first.twoMachinesColor = "white\n";
+       second.twoMachinesColor = "black\n";
+    }
+
+    first.other = &second;
+    second.other = &first;
+
+    { float norm = 1;
+        if(appData.timeOddsMode) {
+            norm = appData.timeOdds[0];
+            if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
+        }
+        first.timeOdds  = appData.timeOdds[0]/norm;
+        second.timeOdds = appData.timeOdds[1]/norm;
+    }
+
+    if(programVersion) free(programVersion);
+    if (appData.noChessProgram) {
+       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+       sprintf(programVersion, "%s", PACKAGE_STRING);
+    } else {
+      /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+      programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+      sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+    }
+}
+
+void
+UnloadEngine(ChessProgramState *cps)
+{
+       /* Kill off first chess program */
+       if (cps->isr != NULL)
+         RemoveInputSource(cps->isr);
+       cps->isr = NULL;
+
+       if (cps->pr != NoProc) {
+           ExitAnalyzeMode();
+            DoSleep( appData.delayBeforeQuit );
+           SendToProgram("quit\n", cps);
+            DoSleep( appData.delayAfterQuit );
+           DestroyChildProcess(cps->pr, cps->useSigterm);
+       }
+       cps->pr = NoProc;
+       if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
+}
+
+void
+ClearOptions(ChessProgramState *cps)
+{
+    int i;
+    cps->nrOptions = cps->comboCnt = 0;
+    for(i=0; i<MAX_OPTIONS; i++) {
+       cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
+       cps->option[i].textValue = 0;
+    }
+}
+
+char *engineNames[] = {
+"first",
+"second"
+};
+
+void
+InitEngine(ChessProgramState *cps, int n)
+{   // [HGM] all engine initialiation put in a function that does one engine
+
+    ClearOptions(cps);
+
+    cps->which = engineNames[n];
+    cps->maybeThinking = FALSE;
+    cps->pr = NoProc;
+    cps->isr = NULL;
+    cps->sendTime = 2;
+    cps->sendDrawOffers = 1;
+
+    cps->program = appData.chessProgram[n];
+    cps->host = appData.host[n];
+    cps->dir = appData.directory[n];
+    cps->initString = appData.engInitString[n];
+    cps->computerString = appData.computerString[n];
+    cps->useSigint  = TRUE;
+    cps->useSigterm = TRUE;
+    cps->reuse = appData.reuse[n];
+    cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
+    cps->useSetboard = FALSE;
+    cps->useSAN = FALSE;
+    cps->usePing = FALSE;
+    cps->lastPing = 0;
+    cps->lastPong = 0;
+    cps->usePlayother = FALSE;
+    cps->useColors = TRUE;
+    cps->useUsermove = FALSE;
+    cps->sendICS = FALSE;
+    cps->sendName = appData.icsActive;
+    cps->sdKludge = FALSE;
+    cps->stKludge = FALSE;
+    TidyProgramName(cps->program, cps->host, cps->tidy);
+    cps->matchWins = 0;
+    safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+    cps->analysisSupport = 2; /* detect */
+    cps->analyzing = FALSE;
+    cps->initDone = FALSE;
+
+    /* New features added by Tord: */
+    cps->useFEN960 = FALSE;
+    cps->useOOCastle = TRUE;
+    /* End of new features added by Tord. */
+    cps->fenOverride  = appData.fenOverride[n];
+
+    /* [HGM] time odds: set factor for each machine */
+    cps->timeOdds  = appData.timeOdds[n];
+
+    /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
+    cps->accumulateTC = appData.accumulateTC[n];
+    cps->maxNrOfSessions = 1;
+
+    /* [HGM] debug */
+    cps->debug = FALSE;
+
+    cps->supportsNPS = UNKNOWN;
+    cps->memSize = FALSE;
+    cps->maxCores = FALSE;
+    cps->egtFormats[0] = NULLCHAR;
+
+    /* [HGM] options */
+    cps->optionSettings  = appData.engOptions[n];
+
+    cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
+    cps->isUCI = appData.isUCI[n]; /* [AS] */
+    cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
+
+    if (appData.protocolVersion[n] > PROTOVER
+       || appData.protocolVersion[n] < 1)
+      {
+       char buf[MSG_SIZ];
+       int len;
+
+       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
+                      appData.protocolVersion[n]);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
+       DisplayFatalError(buf, 0, 2);
+      }
+    else
+      {
+       cps->protocolVersion = appData.protocolVersion[n];
+      }
+
+    InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
+}
+
+ChessProgramState *savCps;
+
+void
+LoadEngine()
+{
+    int i;
+    if(WaitForEngine(savCps, LoadEngine)) return;
+    CommonEngineInit(); // recalculate time odds
+    if(gameInfo.variant != StringToVariant(appData.variant)) {
+       // we changed variant when loading the engine; this forces us to reset
+       Reset(TRUE, savCps != &first);
+       EditGameEvent(); // for consistency with other path, as Reset changes mode
+    }
+    InitChessProgram(savCps, FALSE);
+    SendToProgram("force\n", savCps);
+    DisplayMessage("", "");
+    if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
+    for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+    ThawUI();
+    SetGNUMode();
+}
+
+void
+ReplaceEngine(ChessProgramState *cps, int n)
+{
+    EditGameEvent();
+    UnloadEngine(cps);
+    appData.noChessProgram = FALSE;
+    appData.clockMode = TRUE;
+    InitEngine(cps, n);
+    if(n) return; // only startup first engine immediately; second can wait
+    savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
+    LoadEngine();
+}
+
+extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
+extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
+
+static char resetOptions[] = 
+       "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+       "-firstOptions \"\" -firstNPS -1 -fn \"\"";
+
+void
+Load(ChessProgramState *cps, int i)
+{
+    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
+    if(engineLine[0]) { // an engine was selected from the combo box
+       snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
+       SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
+       ParseArgsFromString(buf);
+       SwapEngines(i);
+       ReplaceEngine(cps, i);
+       return;
+    }
+    p = engineName;
+    while(q = strchr(p, SLASH)) p = q+1;
+    if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
+    if(engineDir[0] != NULLCHAR)
+       appData.directory[i] = engineDir;
+    else if(p != engineName) { // derive directory from engine path, when not given
+       p[-1] = 0;
+       appData.directory[i] = strdup(engineName);
+       p[-1] = SLASH;
+    } else appData.directory[i] = ".";
+    if(params[0]) {
+       snprintf(command, MSG_SIZ, "%s %s", p, params);
+       p = command;
+    }
+    appData.chessProgram[i] = strdup(p);
+    appData.isUCI[i] = isUCI;
+    appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
+    appData.hasOwnBookUCI[i] = hasBook;
+    if(!nickName[0]) useNick = FALSE;
+    if(useNick) ASSIGN(appData.pgnName[i], nickName);
+    if(addToList) {
+       int len;
+       q = firstChessProgramNames;
+       if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
+                       useNick ? " -fn \"" : "",
+                       useNick ? nickName : "",
+                       useNick ? "\"" : "",
+                       v1 ? " -firstProtocolVersion 1" : "",
+                       hasBook ? "" : " -fNoOwnBookUCI",
+                       isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
+                       storeVariant ? " -variant " : "",
+                       storeVariant ? VariantName(gameInfo.variant) : "");
+       firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
+       snprintf(firstChessProgramNames, len, "%s%s", q, buf);
+       if(q)   free(q);
+    }
+    ReplaceEngine(cps, i);
+}
+
+void
+InitTimeControls()
 {
     int matched, min, sec;
+    /*
+     * Parse timeControl resource
+     */
+    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+                         appData.movesPerSession)) {
+       char buf[MSG_SIZ];
+       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+       DisplayFatalError(buf, 0, 2);
+    }
+
+    /*
+     * Parse searchTime resource
+     */
+    if (*appData.searchTime != NULLCHAR) {
+       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+       if (matched == 1) {
+           searchTime = min * 60;
+       } else if (matched == 2) {
+           searchTime = min * 60 + sec;
+       } else {
+           char buf[MSG_SIZ];
+           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+           DisplayFatalError(buf, 0, 2);
+       }
+    }
+}
+
+void
+InitBackEnd1()
+{
 
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
 
     GetTimeMark(&programStartTime);
     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+    pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
 
     ClearProgramStats();
     programStats.ok_to_send = 1;
@@ -711,166 +1010,24 @@ InitBackEnd1()
         }
     }
 
-    /*
-     * Parse timeControl resource
-     */
-    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
-                         appData.movesPerSession)) {
-       char buf[MSG_SIZ];
-       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
-       DisplayFatalError(buf, 0, 2);
-    }
-
-    /*
-     * Parse searchTime resource
-     */
-    if (*appData.searchTime != NULLCHAR) {
-       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
-       if (matched == 1) {
-           searchTime = min * 60;
-       } else if (matched == 2) {
-           searchTime = min * 60 + sec;
-       } else {
-           char buf[MSG_SIZ];
-           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
-           DisplayFatalError(buf, 0, 2);
-       }
-    }
+    InitTimeControls();
 
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
 
-    first.which = "first";
-    second.which = "second";
-    first.maybeThinking = second.maybeThinking = FALSE;
-    first.pr = second.pr = NoProc;
-    first.isr = second.isr = NULL;
-    first.sendTime = second.sendTime = 2;
-    first.sendDrawOffers = 1;
-    if (appData.firstPlaysBlack) {
-       first.twoMachinesColor = "black\n";
-       second.twoMachinesColor = "white\n";
-    } else {
-       first.twoMachinesColor = "white\n";
-       second.twoMachinesColor = "black\n";
-    }
-    first.program = appData.firstChessProgram;
-    second.program = appData.secondChessProgram;
-    first.host = appData.firstHost;
-    second.host = appData.secondHost;
-    first.dir = appData.firstDirectory;
-    second.dir = appData.secondDirectory;
-    first.other = &second;
-    second.other = &first;
-    first.initString = appData.initString;
-    second.initString = appData.secondInitString;
-    first.computerString = appData.firstComputerString;
-    second.computerString = appData.secondComputerString;
-    first.useSigint = second.useSigint = TRUE;
-    first.useSigterm = second.useSigterm = TRUE;
-    first.reuse = appData.reuseFirst;
-    second.reuse = appData.reuseSecond;
-    first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
-    second.nps = appData.secondNPS;
-    first.useSetboard = second.useSetboard = FALSE;
-    first.useSAN = second.useSAN = FALSE;
-    first.usePing = second.usePing = FALSE;
-    first.lastPing = second.lastPing = 0;
-    first.lastPong = second.lastPong = 0;
-    first.usePlayother = second.usePlayother = FALSE;
-    first.useColors = second.useColors = TRUE;
-    first.useUsermove = second.useUsermove = FALSE;
-    first.sendICS = second.sendICS = FALSE;
-    first.sendName = second.sendName = appData.icsActive;
-    first.sdKludge = second.sdKludge = FALSE;
-    first.stKludge = second.stKludge = FALSE;
-    TidyProgramName(first.program, first.host, first.tidy);
-    TidyProgramName(second.program, second.host, second.tidy);
-    first.matchWins = second.matchWins = 0;
-    safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
-    safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
-    first.analysisSupport = second.analysisSupport = 2; /* detect */
-    first.analyzing = second.analyzing = FALSE;
-    first.initDone = second.initDone = FALSE;
-
-    /* New features added by Tord: */
-    first.useFEN960 = FALSE; second.useFEN960 = FALSE;
-    first.useOOCastle = TRUE; second.useOOCastle = TRUE;
-    /* End of new features added by Tord. */
-    first.fenOverride  = appData.fenOverride1;
-    second.fenOverride = appData.fenOverride2;
-
-    /* [HGM] time odds: set factor for each machine */
-    first.timeOdds  = appData.firstTimeOdds;
-    second.timeOdds = appData.secondTimeOdds;
-    { float norm = 1;
-        if(appData.timeOddsMode) {
-            norm = first.timeOdds;
-            if(norm > second.timeOdds) norm = second.timeOdds;
-        }
-        first.timeOdds /= norm;
-        second.timeOdds /= norm;
-    }
-
-    /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
-    first.accumulateTC = appData.firstAccumulateTC;
-    second.accumulateTC = appData.secondAccumulateTC;
-    first.maxNrOfSessions = second.maxNrOfSessions = 1;
-
-    /* [HGM] debug */
-    first.debug = second.debug = FALSE;
-    first.supportsNPS = second.supportsNPS = UNKNOWN;
-
-    /* [HGM] options */
-    first.optionSettings  = appData.firstOptions;
-    second.optionSettings = appData.secondOptions;
-
-    first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
-    second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
-    first.isUCI = appData.firstIsUCI; /* [AS] */
-    second.isUCI = appData.secondIsUCI; /* [AS] */
-    first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
-    second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
-
-    if (appData.firstProtocolVersion > PROTOVER
-       || appData.firstProtocolVersion < 1)
-      {
-       char buf[MSG_SIZ];
-       int len;
-
-       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
-                      appData.firstProtocolVersion);
-       if( (len > MSG_SIZ) && appData.debugMode )
-         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
-
-       DisplayFatalError(buf, 0, 2);
-      }
-    else
-      {
-       first.protocolVersion = appData.firstProtocolVersion;
-      }
-
-    if (appData.secondProtocolVersion > PROTOVER
-       || appData.secondProtocolVersion < 1)
-      {
-       char buf[MSG_SIZ];
-       int len;
-
-       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
-                      appData.secondProtocolVersion);
-       if( (len > MSG_SIZ) && appData.debugMode )
-         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+    InitEngine(&first, 0);
+    InitEngine(&second, 1);
+    CommonEngineInit();
 
-       DisplayFatalError(buf, 0, 2);
-      }
-    else
-      {
-       second.protocolVersion = appData.secondProtocolVersion;
-      }
+    pairing.which = "pairing"; // pairing engine
+    pairing.pr = NoProc;
+    pairing.isr = NULL;
+    pairing.program = appData.pairingEngine;
+    pairing.host = "localhost";
+    pairing.dir = ".";
 
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
-//    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
        appData.clockMode = FALSE;
        first.sendTime = second.sendTime = 0;
@@ -886,15 +1043,6 @@ InitBackEnd1()
     }
 #endif
 
-    if (appData.noChessProgram) {
-       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
-       sprintf(programVersion, "%s", PACKAGE_STRING);
-    } else {
-      /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
-      programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
-      sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
-    }
-
     if (!appData.icsActive) {
       char buf[MSG_SIZ];
       int len;
@@ -970,8 +1118,6 @@ InitBackEnd1()
       }
     }
 
-    InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
-    InitEngineUCI( installDir, &second );
 }
 
 int NextIntegerFromString( char ** str, long * value )
@@ -1175,6 +1321,135 @@ InitBackEnd2()
     }
 }
 
+int
+CalculateIndex(int index, int gameNr)
+{   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
+    int res;
+    if(index > 0) return index; // fixed nmber
+    if(index == 0) return 1;
+    res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
+    if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
+    return res;
+}
+
+int
+LoadGameOrPosition(int gameNr)
+{   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
+    if (*appData.loadGameFile != NULLCHAR) {
+       if (!LoadGameFromFile(appData.loadGameFile,
+               CalculateIndex(appData.loadGameIndex, gameNr),
+                             appData.loadGameFile, FALSE)) {
+           DisplayFatalError(_("Bad game file"), 0, 1);
+           return 0;
+       }
+    } else if (*appData.loadPositionFile != NULLCHAR) {
+       if (!LoadPositionFromFile(appData.loadPositionFile,
+               CalculateIndex(appData.loadPositionIndex, gameNr),
+                                 appData.loadPositionFile)) {
+           DisplayFatalError(_("Bad position file"), 0, 1);
+           return 0;
+       }
+    }
+    return 1;
+}
+
+void
+ReserveGame(int gameNr, char resChar)
+{
+    FILE *tf = fopen(appData.tourneyFile, "r+");
+    char *p, *q, c, buf[MSG_SIZ];
+    if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
+    safeStrCpy(buf, lastMsg, MSG_SIZ);
+    DisplayMessage(_("Pick new game"), "");
+    flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
+    ParseArgsFromFile(tf);
+    p = q = appData.results;
+    if(appData.debugMode) {
+      char *r = appData.participants;
+      fprintf(debugFP, "results = '%s'\n", p);
+      while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
+      fprintf(debugFP, "\n");
+    }
+    while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
+    nextGame = q - p;
+    q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
+    safeStrCpy(q, p, strlen(p) + 2);
+    if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
+    if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
+    if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
+       if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
+       q[nextGame] = '*';
+    }
+    fseek(tf, -(strlen(p)+4), SEEK_END);
+    c = fgetc(tf);
+    if(c != '"') // depending on DOS or Unix line endings we can be one off
+        fseek(tf, -(strlen(p)+2), SEEK_END);
+    else fseek(tf, -(strlen(p)+3), SEEK_END);
+    fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
+    DisplayMessage(buf, "");
+    free(p); appData.results = q;
+    if(nextGame <= appData.matchGames && resChar != ' ' &&
+       (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
+       UnloadEngine(&first);  // next game belongs to other pairing;
+       UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
+    }
+}
+
+void
+MatchEvent(int mode)
+{      // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
+       int dummy;
+       if(matchMode) { // already in match mode: switch it off
+           abortMatch = TRUE;
+           appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
+           ModeHighlight(); // kludgey way to remove checkmark...
+           return;
+       }
+//     if(gameMode != BeginningOfGame) {
+//         DisplayError(_("You can only start a match from the initial position."), 0);
+//         return;
+//     }
+       abortMatch = FALSE;
+       if(mode == 2) appData.matchGames = appData.defaultMatchGames;
+       /* Set up machine vs. machine match */
+       nextGame = 0;
+       NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
+       if(appData.tourneyFile[0]) {
+           ReserveGame(-1, 0);
+           if(nextGame > appData.matchGames) {
+               char buf[MSG_SIZ];
+               if(strchr(appData.results, '*') == NULL) {
+                   FILE *f;
+                   appData.tourneyCycles++;
+                   if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+                       fclose(f);
+                       NextTourneyGame(-1, &dummy);
+                       ReserveGame(-1, 0);
+                       if(nextGame <= appData.matchGames) {
+                           DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+                           matchMode = mode;
+                           ScheduleDelayedEvent(NextMatchGame, 10000);
+                           return;
+                       }
+                   }
+               }
+               snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
+               DisplayError(buf, 0);
+               appData.tourneyFile[0] = 0;
+               return;
+           }
+       } else
+       if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
+           DisplayFatalError(_("Can't have a match with no chess programs"),
+                             0, 2);
+           return;
+       }
+       matchMode = mode;
+       matchGame = roundNr = 1;
+       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
+       NextMatchGame();
+}
+
 void
 InitBackEnd3 P((void))
 {
@@ -1235,6 +1510,12 @@ InitBackEnd3 P((void))
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
       initialMode = BeginningOfGame;
+      if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
+        gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
+        ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
+        gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
+        ModeHighlight();
+      }
     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
       initialMode = TwoMachinesPlay;
     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
@@ -1261,34 +1542,14 @@ InitBackEnd3 P((void))
     }
 
     if (appData.matchMode) {
-       /* Set up machine vs. machine match */
-       if (appData.noChessProgram) {
-           DisplayFatalError(_("Can't have a match with no chess programs"),
-                             0, 2);
-           return;
-       }
-       matchMode = TRUE;
-       matchGame = 1;
-       if (*appData.loadGameFile != NULLCHAR) {
-           int index = appData.loadGameIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadGameFromFile(appData.loadGameFile,
-                                 index,
-                                 appData.loadGameFile, FALSE)) {
-               DisplayFatalError(_("Bad game file"), 0, 1);
-               return;
-           }
-       } else if (*appData.loadPositionFile != NULLCHAR) {
-           int index = appData.loadPositionIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadPositionFromFile(appData.loadPositionFile,
-                                     index,
-                                     appData.loadPositionFile)) {
-               DisplayFatalError(_("Bad position file"), 0, 1);
-               return;
-           }
-       }
-       TwoMachinesEvent();
+       if(appData.tourneyFile[0]) { // start tourney from command line
+           FILE *f;
+           if(f = fopen(appData.tourneyFile, "r")) {
+               ParseArgsFromFile(f); // make sure tourney parmeters re known
+               fclose(f);
+           } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
+       }
+       MatchEvent(TRUE);
     } else if (*appData.cmailGameName != NULLCHAR) {
        /* Set up cmail mode */
        ReloadCmailMsgEvent(TRUE);
@@ -2919,9 +3180,9 @@ read_from_ics(isr, closure, data, count, error)
                }
            } // [HGM] chat: end of patch
 
+          backup = i;
            if (appData.zippyTalk || appData.zippyPlay) {
                 /* [DM] Backup address for color zippy lines */
-                backup = i;
 #if ZIPPY
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
@@ -3086,6 +3347,8 @@ read_from_ics(isr, closure, data, count, error)
                    continue;
            }
 
+          if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
+
            if (looking_at(buf, &i, "\\   ")) {
                if (prevColor != ColorNormal) {
                    if (oldi > next_out) {
@@ -4814,9 +5077,7 @@ ProcessICSInitScript(f)
 }
 
 
-static int lastX, lastY, selectFlag, dragging, sweepX, sweepY;
-static ChessSquare substitute = EmptySquare;
-static char defaultPromoChar;
+static int lastX, lastY, selectFlag, dragging;
 
 void
 Sweep(int step)
@@ -4826,7 +5087,7 @@ Sweep(int step)
     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
-    if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare;
+    if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
     do {
        promoSweep -= step;
        if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
@@ -4837,30 +5098,22 @@ Sweep(int step)
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
            appData.testLegality && (promoSweep == king ||
            gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
-    boards[currentMove][sweepY][sweepX] = promoSweep;
-    DrawPosition(FALSE, boards[currentMove]);
+    ChangeDragPiece(promoSweep);
 }
 
 int PromoScroll(int x, int y)
 {
   int step = 0;
+
   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
-  if(!selectFlag) {
-       if(y - lastY < 4 && lastY - y < 4) return FALSE; // assume dragging until significant distance
-       if(substitute != EmptySquare && ((promoSweep >= BlackPawn) == flipView ? y <= lastY : y >= lastY)) { // we started dragging
-           defaultPromoChar = ToLower(PieceToChar(promoSweep));   // fix choice
-           promoSweep = EmptySquare;
-           return FALSE;
-       }
-       DragPieceEnd(x, y); dragging = 0;
-       selectFlag = 1; // we committed to sweep-selecting
-  }
-  if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return TRUE;
+  if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
-  if(!step) return TRUE;
+  if(!step) return FALSE;
   lastX = x; lastY = y;
-  Sweep(step);
-  return TRUE;
+  if((promoSweep < BlackPawn) == flipView) step = -step;
+  if(step > 0) selectFlag = 1;
+  if(!selectFlag) Sweep(step);
+  return FALSE;
 }
 
 void
@@ -5015,15 +5268,20 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
+Boolean pushed = FALSE;
 
 void
-ParsePV(char *pv, Boolean storeComments)
+ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
   int fromX, fromY, toX, toY; char promoChar;
   ChessMove moveType;
   Boolean valid;
   int nr = 0;
 
+  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+    PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
+    pushed = TRUE;
+  }
   endPV = forwardMostMove;
   do {
     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
@@ -5062,45 +5320,56 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     endPV++;
     CopyBoard(boards[endPV], boards[endPV-1]);
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
-    moveList[endPV-1][0] = fromX + AAA;
-    moveList[endPV-1][1] = fromY + ONE;
-    moveList[endPV-1][2] = toX + AAA;
-    moveList[endPV-1][3] = toY + ONE;
-    moveList[endPV-1][4] = promoChar;
-    moveList[endPV-1][5] = NULLCHAR;
+    CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
     strncat(moveList[endPV-1], "\n", MOVE_LEN);
-    if(storeComments)
-       CoordsToAlgebraic(boards[endPV - 1],
+    CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
                             fromY, fromX, toY, toX, promoChar,
                             parseList[endPV - 1]);
-    else
-       parseList[endPV-1][0] = NULLCHAR;
   } while(valid);
-  currentMove = endPV;
+  currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
   DrawPosition(TRUE, boards[currentMove]);
 }
 
+int
+MultiPV(ChessProgramState *cps)
+{      // check if engine supports MultiPV, and if so, return the number of the option that sets it
+       int i;
+       for(i=0; i<cps->nrOptions; i++)
+           if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
+               return i;
+       return -1;
+}
+
 Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
-       int startPV;
-       char *p;
+       int startPV, multi, lineStart, origIndex = index;
+       char *p, buf2[MSG_SIZ];
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
        while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
-       startPV = index;
+       lineStart = startPV = index;
        while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
        if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
        index = startPV;
        do{ while(buf[index] && buf[index] != '\n') index++;
        } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       ParsePV(buf+startPV, FALSE);
+       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
+               int n = first.option[multi].value;
+               if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+               snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
+               if(first.option[multi].value != n) SendToProgram(buf2, &first);
+               first.option[multi].value = n;
+               *start = *end = 0;
+               return FALSE;
+       }
+       ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
        return TRUE;
 }
@@ -5110,16 +5379,32 @@ LoadPV(int x, int y)
 { // called on right mouse click to load PV
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
-  ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+  ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
   return TRUE;
 }
 
 void
 UnLoadPV()
 {
+  int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
   if(endPV < 0) return;
   endPV = -1;
+  if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+       Boolean saveAnimate = appData.animate;
+       if(pushed) {
+           if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
+               if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
+           } else storedGames--; // abandon shelved tail of original game
+       }
+       pushed = FALSE;
+       forwardMostMove = currentMove;
+       currentMove = oldFMM;
+       appData.animate = FALSE;
+       ToNrEvent(forwardMostMove);
+       appData.animate = saveAnimate;
+  }
   currentMove = forwardMostMove;
+  if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
   ClearPremoveHighlights();
   DrawPosition(TRUE, boards[currentMove]);
 }
@@ -5127,9 +5412,10 @@ 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, dist;
+  int margin = h>>3, step = 0;
 
   // 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( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
   if(!step) return;
@@ -5813,14 +6099,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
        *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
        return FALSE;
     }
-    // with sweep-selection we take the selected default
-    if(appData.sweepSelect) {
-       *promoChoice = defaultPromoChar;
-       return FALSE;
-    }
     // give caller the default choice even if we will not make it
     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
+    if(appData.sweepSelect && gameInfo.variant != VariantGreat
+                          && gameInfo.variant != VariantShogi
+                          && gameInfo.variant != VariantSuper) return FALSE;
     if(autoQueen) return FALSE; // predetermined
 
     // suppress promotion popup on illegal moves that are not premoves
@@ -6433,18 +6717,23 @@ ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
 
 int CanPromote(ChessSquare piece, int y)
 {
+       if(gameMode == EditPosition) return FALSE; // no promotions when editing position
+       // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
+       if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
+          gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
+          gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+                                                 gameInfo.variant == VariantMakruk) return FALSE;
        return (piece == BlackPawn && y == 1 ||
                piece == WhitePawn && y == BOARD_HEIGHT-2 ||
-               gameInfo.variant != VariantSuper && 
-                       (piece == BlackLance && y == 1 ||
-                        piece == WhiteLance && y == BOARD_HEIGHT-2) );
+               piece == BlackLance && y == 1 ||
+               piece == WhiteLance && y == BOARD_HEIGHT-2 );
 }
 
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
-    int x, y, canPromote;
+    int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
 
@@ -6467,21 +6756,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 
     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
-       defaultPromoChar = ToLower(PieceToChar(defaultPromoChoice = promoSweep));
-       if(gameInfo.variant == VariantShogi) defaultPromoChar = (promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+');
+       defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
        promoDefaultAltered = TRUE;
-       if(savePiece != EmptySquare) {
-           boards[currentMove][sweepY][sweepX] = savePiece; savePiece = EmptySquare;
-           clickType = Press; x = toX; y = toY; // fake up-click on to-square to finish one-click move
-       } else x = fromX, y = fromY;             // and fake up-click on same square otherwise
-    }
-
-    if(substitute != EmptySquare) {
-       boards[currentMove][fromY][fromX] = substitute;
-       substitute = EmptySquare;
-       DragPieceEnd(xPix, yPix); dragging = 0;
-       DrawPosition(FALSE, boards[currentMove]);
+       if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
     }
 
     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
@@ -6513,17 +6791,18 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        return;
 
     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
-       fromX = fromY = -1; // second click on piece after altering default treated as first click
+       fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
 
-   if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
+    if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
        int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
                    gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
        defaultPromoChoice = DefaultPromoChoice(side);
-   }
+    }
 
     autoQueen = appData.alwaysPromoteToQueen;
 
     if (fromX == -1) {
+      int originalY = y;
       gatingPiece = EmptySquare;
       if (clickType != Press) {
        if(dragging) { // [HGM] from-square must have been reset due to game end since last press
@@ -6532,31 +6811,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        }
        return;
       }
-      if(appData.oneClick && OnlyMove(&x, &y, FALSE)) {
-           if(appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY)) {
-               promoSweep = defaultPromoChoice;
-               savePiece = boards[currentMove][sweepY = toY = y][sweepX = toX = x];
-               selectFlag = 0; lastX = xPix; lastY = yPix;
-               Sweep(0); // Pawn that is going to promote: preview promotion piece
-               return;
-           }
-      } else {
+      fromX = x; fromY = y;
+      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
+        // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
+        appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
            /* First square */
-           if (OKToStartUserMove(x, y)) {
-               sweepX = fromX = x;
-               sweepY = fromY = y;
+           if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
-               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
+               DragPieceBegin(xPix, yPix); dragging = 1;
+               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
-                   substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
+                   DisplayMessage("", _("Pull pawn backwards to under-promote"));
                }
-               DragPieceBegin(xPix, yPix); dragging = 1;
                if (appData.highlightDragging) {
-                   SetHighlights(x, y, -1, -1);
+                   SetHighlights(fromX, fromY, -1, -1);
                }
-           }
+           } else fromX = fromY = -1;
            return;
        }
     }
@@ -6597,15 +6870,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
                  gatingPiece = boards[currentMove][fromY][fromX];
                else gatingPiece = EmptySquare;
-               sweepX = fromX = x;
-               sweepY = fromY = y; dragging = 1;
+               fromX = x;
+               fromY = y; dragging = 1;
                MarkTargetSquares(0);
+               DragPieceBegin(xPix, yPix);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
                    promoSweep = defaultPromoChoice;
-                   substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
                }
-               DragPieceBegin(xPix, yPix);
            }
           }
           if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
@@ -6617,6 +6890,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 
     if (clickType == Release && x == fromX && y == fromY) {
        DragPieceEnd(xPix, yPix); dragging = 0;
+       if(clearFlag) {
+           // a deferred attempt to click-click move an empty square on top of a piece
+           boards[currentMove][y][x] = EmptySquare;
+           ClearHighlights();
+           DrawPosition(FALSE, boards[currentMove]);
+           fromX = fromY = -1; clearFlag = 0;
+           return;
+       }
        if (appData.animateDragging) {
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
@@ -6636,12 +6917,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        return;
     }
 
+    clearFlag = 0;
+
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     toX = x;
     toY = y;
     saveAnimate = appData.animate;
     if (clickType == Press) {
+       if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
+           // must be Edit Position mode with empty-square selected
+           fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
+           if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
+           return;
+       }
        /* Finish clickclick move */
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -6707,14 +6996,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            DisplayMessage("Click in holdings to choose piece", "");
            return;
        }
-       if(appData.sweepSelect && clickType == Press) {
-            lastX = xPix; lastY = yPix;
-            ChessSquare piece = boards[currentMove][fromY][fromX];
-            promoSweep = defaultPromoChoice;
-            if(gameInfo.variant == VariantShogi) promoSweep = PROMOTED piece;
-            sweepX = toX; sweepY = toY;
-            selectFlag = 1; Sweep(0);
-       } else PromotionPopUp();
+       PromotionPopUp();
     } else {
        int oldMove = currentMove;
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
@@ -6848,6 +7130,52 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
     SetProgramStats( &stats );
 }
 
+#define MAXPLAYERS 500
+
+char *
+TourneyStandings(int display)
+{
+    int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
+    int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
+    char result, *p, *names[MAXPLAYERS];
+
+    if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
+
+    names[0] = p = strdup(appData.participants);
+    while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
+
+    for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
+
+    while(result = appData.results[nr]) {
+       color = Pairing(nr, nPlayers, &w, &b, &dummy);
+       if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
+       wScore = bScore = 0;
+       switch(result) {
+         case '+': wScore = 2; break;
+         case '-': bScore = 2; break;
+         case '=': wScore = bScore = 1; break;
+         case ' ':
+         case '*': return strdup("busy"); // tourney not finished
+       }
+       score[w] += wScore;
+       score[b] += bScore;
+       games[w]++;
+       games[b]++;
+       nr++;
+    }
+    if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
+    for(w=0; w<nPlayers; w++) {
+       bScore = -1;
+       for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
+       ranking[w] = b; points[w] = bScore; score[b] = -2;
+    }
+    p = malloc(nPlayers*34+1);
+    for(w=0; w<nPlayers && w<display; w++)
+       sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
+    free(names[0]);
+    return p;
+}
+
 void
 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
 {      // count all piece types
@@ -7271,8 +7599,23 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
     if(bookHit) {
        // after a book hit we never send 'go', and the code after the call to this routine
        // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
-       char buf[MSG_SIZ];
-       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
+       char buf[MSG_SIZ], *move = bookHit;
+       if(cps->useSAN) {
+           int fromX, fromY, toX, toY;
+           char promoChar;
+           ChessMove moveType;
+           move = buf + 30;
+           if (ParseOneMove(bookHit, forwardMostMove, &moveType,
+                                &fromX, &fromY, &toX, &toY, &promoChar)) {
+               (void) CoordsToAlgebraic(boards[forwardMostMove],
+                                   PosFlags(forwardMostMove),
+                                   fromY, fromX, toY, toX, promoChar, move);
+           } else {
+               if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
+               bookHit = NULL;
+           }
+       }
+       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
        SendToProgram(buf, cps);
        if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
@@ -7296,6 +7639,8 @@ void DeferredBookMove(void)
        HandleMachineMove(savedMessage, savedState);
 }
 
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
 void
 HandleMachineMove(message, cps)
      char *message;
@@ -7310,6 +7655,14 @@ HandleMachineMove(message, cps)
     int machineWhite;
     char *bookHit;
 
+    if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+       // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+       if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
+       pairingReceived = 1;
+       NextMatchGame();
+       return; // Skim the pairing messages here.
+    }
+
     cps->userError = 0;
 
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
@@ -7882,7 +8235,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
                _(cps->which), cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
-       DisplayFatalError(buf1, 0, 1);
+       if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
+           if(cps == &first) appData.noChessProgram = TRUE;
+           DisplayError(buf1, 0);
+       }
        return;
     }
 
@@ -8974,6 +9330,7 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9155,9 +9512,14 @@ StartChessProgram(cps)
 
     if (err != 0) {
       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
-       DisplayFatalError(buf, err, 1);
-       cps->pr = NoProc;
-       cps->isr = NULL;
+       DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+       if(cps != &first) return;
+       appData.noChessProgram = TRUE;
+       ThawUI();
+       SetNCPMode();
+//     DisplayFatalError(buf, err, 1);
+//     cps->pr = NoProc;
+//     cps->isr = NULL;
        return;
     }
 
@@ -9172,49 +9534,302 @@ StartChessProgram(cps)
     }
 }
 
-
 void
 TwoMachinesEventIfReady P((void))
 {
+  static int curMess = 0;
   if (first.lastPing != first.lastPong) {
-    DisplayMessage("", _("Waiting for first chess program"));
+    if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
     return;
   }
   if (second.lastPing != second.lastPong) {
-    DisplayMessage("", _("Waiting for second chess program"));
+    if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
     return;
   }
+  DisplayMessage("", ""); curMess = 0;
   ThawUI();
   TwoMachinesEvent();
 }
 
-void
-NextMatchGame P((void))
+char *
+MakeName(char *template)
 {
-    int index; /* [HGM] autoinc: step load index during match */
-    Reset(FALSE, TRUE);
-    if (*appData.loadGameFile != NULLCHAR) {
-       index = appData.loadGameIndex;
-       if(index < 0) { // [HGM] autoinc
-           lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
-           if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
-       }
-       LoadGameFromFile(appData.loadGameFile,
-                        index,
-                        appData.loadGameFile, FALSE);
-    } else if (*appData.loadPositionFile != NULLCHAR) {
-       index = appData.loadPositionIndex;
-       if(index < 0) { // [HGM] autoinc
-           lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
-           if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+    time_t clock;
+    struct tm *tm;
+    static char buf[MSG_SIZ];
+    char *p = buf;
+    int i;
+
+    clock = time((time_t *)NULL);
+    tm = localtime(&clock);
+
+    while(*p++ = *template++) if(p[-1] == '%') {
+       switch(*template++) {
+         case 0:   *p = 0; return buf;
+         case 'Y': i = tm->tm_year+1900; break;
+         case 'y': i = tm->tm_year-100; break;
+         case 'M': i = tm->tm_mon+1; break;
+         case 'd': i = tm->tm_mday; break;
+         case 'h': i = tm->tm_hour; break;
+         case 'm': i = tm->tm_min; break;
+         case 's': i = tm->tm_sec; break;
+         default:  i = 0;
        }
-       LoadPositionFromFile(appData.loadPositionFile,
-                            index,
-                            appData.loadPositionFile);
+       snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
     }
-    TwoMachinesEventIfReady();
+    return buf;
+}
+
+int
+CountPlayers(char *p)
+{
+    int n = 0;
+    while(p = strchr(p, '\n')) p++, n++; // count participants
+    return n;
+}
+
+FILE *
+WriteTourneyFile(char *results)
+{   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+    FILE *f = fopen(appData.tourneyFile, "w");
+    if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
+       // create a file with tournament description
+       fprintf(f, "-participants {%s}\n", appData.participants);
+       fprintf(f, "-tourneyType %d\n", appData.tourneyType);
+       fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+       fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+       fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+       fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+       fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+       fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+       fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+       fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
+       fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
+       fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       if(searchTime > 0)
+               fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+       else {
+               fprintf(f, "-mps %d\n", appData.movesPerSession);
+               fprintf(f, "-tc %s\n", appData.timeControl);
+               fprintf(f, "-inc %.2f\n", appData.timeIncrement);
+       }
+       fprintf(f, "-results \"%s\"\n", results);
+    }
+    return f;
+}
+
+int
+CreateTourney(char *name)
+{
+       FILE *f;
+       if(name[0] == NULLCHAR) {
+           if(appData.participants[0])
+               DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+           return 0;
+       }
+       f = fopen(name, "r");
+       if(f) { // file exists
+           ASSIGN(appData.tourneyFile, name);
+           ParseArgsFromFile(f); // parse it
+       } else {
+           if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+           if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+               DisplayError(_("Not enough participants"), 0);
+               return 0;
+           }
+           ASSIGN(appData.tourneyFile, name);
+           if((f = WriteTourneyFile("")) == NULL) return 0;
+       }
+       fclose(f);
+       appData.noChessProgram = FALSE;
+       appData.clockMode = TRUE;
+       SetGNUMode();
+       return 1;
+}
+
+#define MAXENGINES 1000
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
+void NamesToList(char *names, char **engineList, char **engineMnemonic)
+{
+    char buf[MSG_SIZ], *p, *q;
+    int i=1;
+    while(*names) {
+       p = names; q = buf;
+       while(*p && *p != '\n') *q++ = *p++;
+       *q = 0;
+       if(engineList[i]) free(engineList[i]);
+       engineList[i] = strdup(buf);
+       if(*p == '\n') p++;
+       TidyProgramName(engineList[i], "localhost", buf);
+       if(engineMnemonic[i]) free(engineMnemonic[i]);
+       if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
+           strcat(buf, " (");
+           sscanf(q + 8, "%s", buf + strlen(buf));
+           strcat(buf, ")");
+       }
+       engineMnemonic[i] = strdup(buf);
+       names = p; i++;
+      if(i > MAXENGINES - 2) break;
+    }
+    engineList[i] = NULL;
+}
+
+// following implemented as macro to avoid type limitations
+#define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
+
+void SwapEngines(int n)
+{   // swap settings for first engine and other engine (so far only some selected options)
+    int h;
+    char *p;
+    if(n == 0) return;
+    SWAP(directory, p)
+    SWAP(chessProgram, p)
+    SWAP(isUCI, h)
+    SWAP(hasOwnBookUCI, h)
+    SWAP(protocolVersion, h)
+    SWAP(reuse, h)
+    SWAP(scoreIsAbsolute, h)
+    SWAP(timeOdds, h)
+    SWAP(logo, p)
+    SWAP(pgnName, p)
+}
+
+void
+SetPlayer(int player)
+{   // [HGM] find the engine line of the partcipant given by number, and parse its options.
+    int i;
+    char buf[MSG_SIZ], *engineName, *p = appData.participants;
+    for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
+    engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
+    for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
+    if(mnemonic[i]) {
+       snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
+       ParseArgsFromString(buf);
+    }
+    free(engineName);
+}
+
+int
+Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
+{   // determine players from game number
+    int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
+
+    if(appData.tourneyType == 0) {
+       roundsPerCycle = (nPlayers - 1) | 1;
+       pairingsPerRound = nPlayers / 2;
+    } else if(appData.tourneyType > 0) {
+       roundsPerCycle = nPlayers - appData.tourneyType;
+       pairingsPerRound = appData.tourneyType;
+    }
+    gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
+    gamesPerCycle = gamesPerRound * roundsPerCycle;
+    appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
+    curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
+    curRound = nr / gamesPerRound; nr %= gamesPerRound;
+    curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
+    matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
+    roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
+
+    if(appData.cycleSync) *syncInterval = gamesPerCycle;
+    if(appData.roundSync) *syncInterval = gamesPerRound;
+
+    if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
+
+    if(appData.tourneyType == 0) {
+       if(curPairing == (nPlayers-1)/2 ) {
+           *whitePlayer = curRound;
+           *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
+       } else {
+           *whitePlayer = curRound - pairingsPerRound + curPairing;
+           if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
+           *blackPlayer = curRound + pairingsPerRound - curPairing;
+           if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
+       }
+    } else if(appData.tourneyType > 0) {
+       *whitePlayer = curPairing;
+       *blackPlayer = curRound + appData.tourneyType;
+    }
+
+    // take care of white/black alternation per round. 
+    // For cycles and games this is already taken care of by default, derived from matchGame!
+    return curRound & 1;
+}
+
+int
+NextTourneyGame(int nr, int *swapColors)
+{   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
+    char *p, *q;
+    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
+    FILE *tf;
+    if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
+    tf = fopen(appData.tourneyFile, "r");
+    if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
+    ParseArgsFromFile(tf); fclose(tf);
+    InitTimeControls(); // TC might be altered from tourney file
+
+    nPlayers = CountPlayers(appData.participants); // count participants
+    if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
+       if(nr>=0 && !pairingReceived) {
+           char buf[1<<16];
+           if(pairing.pr == NoProc) StartChessProgram(&pairing);
+           snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+           SendToProgram(buf, &pairing);
+           snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+           SendToProgram(buf, &pairing);
+           return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+       }
+       pairingReceived = 0;                              // ... so we continue here 
+       syncInterval = nPlayers/2; *swapColors = 0;
+       appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+       whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+       matchGame = 1; roundNr = nr / syncInterval + 1;
+    } else
+    *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+
+    if(syncInterval) {
+       p = q = appData.results;
+       while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
+       if(firstBusy/syncInterval < (nextGame/syncInterval)) {
+           DisplayMessage(_("Waiting for other game(s)"),"");
+           waitingForGame = TRUE;
+           ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
+           return 0;
+       }
+       waitingForGame = FALSE;
+    }
+
+    if(first.pr != NoProc) return 1; // engines already loaded
+
+    // redefine engines, engine dir, etc.
+    NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
+    SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
+    SwapEngines(1);
+    SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
+    SwapEngines(1);         // and make that valid for second engine by swapping
+    InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
+    InitEngine(&second, 1);
+    CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
+    return 1;
+}
+
+void
+NextMatchGame()
+{   // performs game initialization that does not invoke engines, and then tries to start the game
+    int 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
+    Reset(FALSE, first.pr != NoProc);
+    appData.noChessProgram = FALSE;
+    if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
+    TwoMachinesEvent();
 }
 
 void UserAdjudicationEvent( int result )
@@ -9264,7 +9879,7 @@ GameEnds(result, resultDetails, whosays)
 {
     GameMode nextGameMode;
     int isIcsGame;
-    char buf[MSG_SIZ], popupRequested = 0;
+    char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
 
     if(endingGame) return; /* [HGM] crash: forbid recursion */
     endingGame = 1;
@@ -9566,9 +10181,11 @@ GameEnds(result, resultDetails, whosays)
        second.pr = NoProc;
     }
 
-    if (matchMode && gameMode == TwoMachinesPlay) {
+    if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+       char resChar = '=';
         switch (result) {
        case WhiteWins:
+         resChar = '+';
          if (first.twoMachinesColor[0] == 'w') {
            first.matchWins++;
          } else {
@@ -9576,27 +10193,30 @@ GameEnds(result, resultDetails, whosays)
          }
          break;
        case BlackWins:
+         resChar = '-';
          if (first.twoMachinesColor[0] == 'b') {
            first.matchWins++;
          } else {
            second.matchWins++;
          }
          break;
+       case GameUnfinished:
+         resChar = ' ';
        default:
          break;
        }
-       if (matchGame < appData.matchGames) {
-           char *tmp;
-           if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
-               tmp = first.twoMachinesColor;
-               first.twoMachinesColor = second.twoMachinesColor;
-               second.twoMachinesColor = tmp;
-           }
+
+       if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+       if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
+           ReserveGame(nextGame, resChar); // sets nextGame
+           if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
+       } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
+
+       if (nextGame <= appData.matchGames && !abortMatch) {
            gameMode = nextGameMode;
-           matchGame++;
-            if(appData.matchPause>10000 || appData.matchPause<10)
-                appData.matchPause = 10000; /* [HGM] make pause adjustable */
-            ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
+           matchGame = nextGame; // this will be overruled in tourney mode!
+           GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
+            ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
            endingGame = 0; /* [HGM] crash */
            return;
        } else {
@@ -9622,10 +10242,18 @@ GameEnds(result, resultDetails, whosays)
     ModeHighlight();
     endingGame = 0;  /* [HGM] crash */
     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
-      if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
-       matchMode = FALSE; appData.matchGames = matchGame = 0;
-       DisplayNote(buf);
+       if(matchMode == TRUE) { // match through command line: exit with or without popup
+           if(ranking) {
+               if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
+               else ExitEvent(0);
+           } else DisplayFatalError(buf, 0, 0);
+       } else { // match through menu; just stop, with or without popup
+           matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+           if(ranking){
+               if(strcmp(ranking, "busy")) DisplayNote(ranking);
+           } else DisplayNote(buf);
       }
+      if(ranking) free(ranking);
     }
 }
 
@@ -9664,15 +10292,23 @@ FeedMovesToProgram(cps, upto)
 }
 
 
-void
+int
 ResurrectChessProgram()
 {
      /* The chess program may have exited.
        If so, restart it and feed it all the moves made so far. */
+    static int doInit = 0;
 
-    if (appData.noChessProgram || first.pr != NoProc) return;
+    if (appData.noChessProgram) return 1;
 
-    StartChessProgram(&first);
+    if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
+       if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
+       if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
+       doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
+    } else {
+       if (first.pr != NoProc) return 1;
+       StartChessProgram(&first);
+    }
     InitChessProgram(&first, FALSE);
     FeedMovesToProgram(&first, currentMove);
 
@@ -9689,6 +10325,7 @@ ResurrectChessProgram()
       SendToProgram("analyze\n", &first);
       first.analyzing = TRUE;
     }
+    return 1;
 }
 
 /*
@@ -9762,6 +10399,7 @@ Reset(redraw, init)
     ResetClocks();
     timeRemaining[0][0] = whiteTimeRemaining;
     timeRemaining[1][0] = blackTimeRemaining;
+
     if (first.pr == NULL) {
        StartChessProgram(&first);
     }
@@ -10931,6 +11569,7 @@ SaveGameToFile(filename, append)
 {
     FILE *f;
     char buf[MSG_SIZ];
+    int result;
 
     if (strcmp(filename, "-") == 0) {
        return SaveGame(stdout, 0, NULL);
@@ -10941,7 +11580,14 @@ SaveGameToFile(filename, append)
            DisplayError(buf, errno);
            return FALSE;
        } else {
-           return SaveGame(f, 0, NULL);
+           safeStrCpy(buf, lastMsg, MSG_SIZ);
+           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...
+           result = SaveGame(f, 0, NULL);
+           DisplayMessage(buf, "");
+           return result;
        }
     }
 }
@@ -11303,7 +11949,13 @@ SavePositionToFile(filename)
            DisplayError(buf, errno);
            return FALSE;
        } else {
+           safeStrCpy(buf, lastMsg, MSG_SIZ);
+           DisplayMessage(_("Waiting for access to save file"), "");
+           flock(fileno(f), LOCK_EX); // [HGM] lock
+           DisplayMessage(_("Saving position"), "");
+           lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
            SavePosition(f, 0, NULL);
+           DisplayMessage(buf, "");
            return TRUE;
        }
     }
@@ -11725,6 +12377,9 @@ ExitEvent(status)
        RemoveInputSource(second.isr);
     }
 
+    if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+    if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
     ShutDownFrontEnd();
     exit(status);
 }
@@ -11815,6 +12470,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
+    bookUp = FALSE;
     EditTagsPopUp(tags, NULL);
     free(tags);
 }
@@ -12066,16 +12722,18 @@ SettingsMenuIfReady()
 }
 
 int
-WaitForSecond(DelayedEventCallback retry)
+WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
 {
-    if (second.pr == NULL) {
-       StartChessProgram(&second);
-       if (second.protocolVersion == 1) {
+    char buf[MSG_SIZ];
+    if (cps->pr == NULL) {
+       StartChessProgram(cps);
+       if (cps->protocolVersion == 1) {
          retry();
        } else {
          /* kludge: allow timeout for initial "feature" command */
          FreezeUI();
-         DisplayMessage("", _("Starting second chess program"));
+         snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
+         DisplayMessage("", buf);
          ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
        }
        return 1;
@@ -12090,6 +12748,9 @@ TwoMachinesEvent P((void))
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
     char *bookHit = NULL;
+    static int stalling = 0;
+    TimeMark now;
+    long wait;
 
     if (appData.noChessProgram) return;
 
@@ -12123,16 +12784,31 @@ TwoMachinesEvent P((void))
 
 //    forwardMostMove = currentMove;
     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
-    ResurrectChessProgram();   /* in case first program isn't running */
 
-    if(WaitForSecond(TwoMachinesEventIfReady)) return;
-    DisplayMessage("", "");
-    InitChessProgram(&second, FALSE);
-    SendToProgram("force\n", &second);
+    if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
+
+    if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
-      ScheduleDelayedEvent(TwoMachinesEvent, 10);
+      ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
+      return;
+    }
+    if(!stalling) {
+      InitChessProgram(&second, FALSE); // unbalances ping of second engine
+      SendToProgram("force\n", &second);
+      stalling = 1;
+      ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+    GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
+    if(appData.matchPause>10000 || appData.matchPause<10)
+                appData.matchPause = 10000; /* [HGM] make pause adjustable */
+    wait = SubtractTimeMarks(&now, &pauseStart);
+    if(wait < appData.matchPause) {
+       ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
+       return;
+    }
+    stalling = 0;
+    DisplayMessage("", "");
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
     if (appData.debugMode) {
@@ -12154,7 +12830,7 @@ TwoMachinesEvent P((void))
     } else {
        onmove = &second;
     }
-
+    if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
     SendToProgram(first.computerString, &first);
     if (first.sendName) {
       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
@@ -12975,6 +13651,7 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
+    DisplayBook(currentMove);
 }
 
 
@@ -13077,6 +13754,7 @@ BackwardInner(target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
+    DisplayBook(currentMove);
 }
 
 void
@@ -13434,19 +14112,19 @@ SetGameInfo()
        gameInfo.event = StrSave( appData.pgnEventHeader );
        gameInfo.site = StrSave(HostName());
        gameInfo.date = PGNDate();
-       if (matchGame > 0) {
+       if (roundNr > 0) {
            char buf[MSG_SIZ];
-           snprintf(buf, MSG_SIZ, "%d", matchGame);
+           snprintf(buf, MSG_SIZ, "%d", roundNr);
            gameInfo.round = StrSave(buf);
        } else {
            gameInfo.round = StrSave("-");
        }
        if (first.twoMachinesColor[0] == 'w') {
-           gameInfo.white = StrSave(first.tidy);
-           gameInfo.black = StrSave(second.tidy);
+           gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
+           gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
        } else {
-           gameInfo.white = StrSave(second.tidy);
-           gameInfo.black = StrSave(first.tidy);
+           gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
+           gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
        }
        gameInfo.timeControl = TimeControlTagValue();
        break;
@@ -13728,16 +14406,21 @@ SendToProgram(message, cps)
     outCount = OutputToProcess(cps->pr, message, count, &error);
     if (outCount < count && !exiting
                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
+      if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
-                gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+               if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
+                gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
             } else {
-                gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+                ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+               if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+                gameInfo.result = res;
             }
             gameInfo.resultDetails = StrSave(buf);
         }
+       if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
     }
 }
@@ -13757,18 +14440,23 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (isr != cps->isr) return; /* Killed intentionally */
     if (count <= 0) {
        if (count == 0) {
+           RemoveInputSource(cps->isr);
+           if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
            snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
                    _(cps->which), cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
-                    gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+                   if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
+                    gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
                 } else {
-                    gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+                    ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+                   if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+                    gameInfo.result = res;
                 }
                 gameInfo.resultDetails = StrSave(buf);
             }
-           RemoveInputSource(cps->isr);
+           if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
            snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
@@ -14109,7 +14797,8 @@ FeatureDone(cps, val)
   DelayedEventCallback cb = GetDelayedEvent();
   if ((cb == InitBackEnd3 && cps == &first) ||
       (cb == SettingsMenuIfReady && cps == &second) ||
-      (cb == TwoMachinesEventIfReady && cps == &second)) {
+      (cb == LoadEngine) ||
+      (cb == TwoMachinesEventIfReady)) {
     CancelDelayedEvent();
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
   }
@@ -14302,6 +14991,55 @@ AskQuestionEvent(title, question, replyPrefix, which)
 }
 
 void
+TypeInEvent(char firstChar)
+{
+    if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
+        gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
+       gameMode == AnalyzeMode || gameMode == EditGame || \r
+       gameMode == EditPosition || gameMode == IcsExamining ||\r
+       gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
+       isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
+               ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
+                 gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
+       gameMode == Training) PopUpMoveDialog(firstChar);
+}
+
+void
+TypeInDoneEvent(char *move)
+{
+       Board board;
+       int n, fromX, fromY, toX, toY;
+       char promoChar;
+       ChessMove moveType;\r
+
+       // [HGM] FENedit\r
+       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
+               EditPositionPasteFEN(move);\r
+               return;\r
+       }\r
+       // [HGM] movenum: allow move number to be typed in any mode\r
+       if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
+         ToNrEvent(2*n-1);\r
+         return;\r
+       }\r
+
+      if (gameMode != EditGame && currentMove != forwardMostMove && \r
+       gameMode != Training) {\r
+       DisplayMoveError(_("Displayed move is not current"));\r
+      } else {\r
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
+       if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
+       } else {\r
+         DisplayMoveError(_("Could not parse move"));\r
+       }
+      }\r
+}\r
+
+void
 DisplayMove(moveNumber)
      int moveNumber;
 {
@@ -15524,16 +16262,10 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 // [HGM] vari: routines for shelving variations
 
 void
-PushTail(int firstMove, int lastMove)
+PushInner(int firstMove, int lastMove)
 {
        int i, j, nrMoves = lastMove - firstMove;
 
-       if(appData.icsActive) { // only in local mode
-               forwardMostMove = currentMove; // mimic old ICS behavior
-               return;
-       }
-       if(storedGames >= MAX_VARIATIONS-1) return;
-
        // push current tail of game on stack
        savedResult[storedGames] = gameInfo.result;
        savedDetails[storedGames] = gameInfo.resultDetails;
@@ -15558,19 +16290,27 @@ PushTail(int firstMove, int lastMove)
 
        storedGames++;
        forwardMostMove = firstMove; // truncate game so we can start variation
+}
+
+void
+PushTail(int firstMove, int lastMove)
+{
+       if(appData.icsActive) { // only in local mode
+               forwardMostMove = currentMove; // mimic old ICS behavior
+               return;
+       }
+       if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
+
+       PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
-Boolean
-PopTail(Boolean annotate)
+void
+PopInner(Boolean annotate)
 {
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       if(appData.icsActive) return FALSE; // only in local mode
-       if(!storedGames) return FALSE; // sanity
-       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
-
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
        nrMoves = savedLast[storedGames] - currentMove;
@@ -15610,6 +16350,17 @@ PopTail(Boolean annotate)
       }
        gameInfo.resultDetails = savedDetails[storedGames];
        forwardMostMove = currentMove + nrMoves;
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+       if(appData.icsActive) return FALSE; // only in local mode
+       if(!storedGames) return FALSE; // sanity
+       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+       PopInner(annotate);
+
        if(storedGames == 0) GreyRevert(TRUE);
        return TRUE;
 }
@@ -15656,7 +16407,7 @@ LoadVariation(int index, char *text)
        PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
        // kludge: use ParsePV() to append variation to game
        move = currentMove;
-       ParsePV(start, TRUE);
+       ParsePV(start, TRUE, TRUE);
        forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
        ClearPremoveHighlights();
        CommentPopDown();