Don't add PV moves on board clicking in AnalyzeMode
[xboard.git] / backend.c
index 0a8b682..61a9f1e 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -129,6 +129,7 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 # include "zippy.h"
 #endif
 #include "backendz.h"
+#include "evalgraph.h"
 #include "gettext.h"
 
 #ifdef ENABLE_NLS
@@ -152,7 +153,6 @@ void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
                         char *buf, int count, int error));
 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
                      char *buf, int count, int error));
-void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
@@ -223,6 +223,8 @@ int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
 FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
+void ToggleSecond P((void));
+void PauseEngine P((ChessProgramState *cps));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -270,6 +272,7 @@ char lastMsg[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
+int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -422,7 +425,7 @@ Boolean alarmSounded;
 /* end premove variables */
 
 char *ics_prefix = "$";
-int ics_type = ICS_GENERIC;
+enum ICS_TYPE ics_type = ICS_GENERIC;
 
 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
 int pauseExamForwardMostMove = 0;
@@ -461,7 +464,7 @@ long timeControl_2; /* [AS] Allow separate time controls */
 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0, nextGame = 0, roundNr = 0;
-Boolean waitingForGame = FALSE;
+Boolean waitingForGame = FALSE, startingEngine = FALSE;
 TimeMark programStartTime, pauseStart;
 char ics_handle[MSG_SIZ];
 int have_set_title = 0;
@@ -783,6 +786,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->analysisSupport = 2; /* detect */
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
+    cps->reload = FALSE;
 
     /* New features added by Tord: */
     cps->useFEN960 = FALSE;
@@ -836,6 +840,8 @@ InitEngine (ChessProgramState *cps, int n)
 
 ChessProgramState *savCps;
 
+GameMode oldMode;
+
 void
 LoadEngine ()
 {
@@ -845,21 +851,25 @@ LoadEngine ()
     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
+       oldMode = BeginningOfGame; // to prevent restoring old mode
     }
     InitChessProgram(savCps, FALSE);
-    SendToProgram("force\n", savCps);
+    if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
     DisplayMessage("", "");
     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
     ThawUI();
     SetGNUMode();
+    if(oldMode == AnalyzeMode) AnalyzeModeEvent();
 }
 
 void
 ReplaceEngine (ChessProgramState *cps, int n)
 {
-    EditGameEvent();
+    oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
+    keepInfo = 1;
+    if(oldMode != BeginningOfGame) EditGameEvent();
+    keepInfo = 0;
     UnloadEngine(cps);
     appData.noChessProgram = FALSE;
     appData.clockMode = TRUE;
@@ -873,7 +883,7 @@ ReplaceEngine (ChessProgramState *cps, int n)
 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 
-static char resetOptions[] = 
+static char resetOptions[] =
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
        "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
        "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
@@ -945,7 +955,7 @@ Load (ChessProgramState *cps, int i)
        if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
        quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
        snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
-                       quote, p, quote, appData.directory[i], 
+                       quote, p, quote, appData.directory[i],
                        useNick ? " -fn \"" : "",
                        useNick ? nickName : "",
                        useNick ? "\"" : "",
@@ -1253,7 +1263,7 @@ GetTimeQuota (int movenr, int lastUsed, char *tcString)
     long time, increment;
     char *s = tcString;
 
-    if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
+    if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
     do {
         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
         nextSession = s; suddenDeath = moves == 0 && increment == 0;
@@ -1282,16 +1292,16 @@ ParseTimeControl (char *tc, float ti, int mps)
 
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
-    else 
+    else
       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
   } else {
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
-    else 
+    else
       snprintf(buf, MSG_SIZ, ":%s", mytc);
   }
   fullTimeControlString = StrSave(buf); // this should now be in PGN format
-  
+
   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
     return FALSE;
   }
@@ -1843,6 +1853,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
 {
     int outError, outCount;
     static int gotEof = 0;
+    static FILE *ini;
 
     /* Pass data read from player on to ICS */
     if (count > 0) {
@@ -1851,6 +1862,17 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
        if (outCount < count) {
             DisplayFatalError(_("Error writing to ICS"), outError, 1);
        }
+       if(have_sent_ICS_logon == 2) {
+         if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
+           fprintf(ini, "%s", message);
+           have_sent_ICS_logon = 3;
+         } else
+           have_sent_ICS_logon = 1;
+       } else if(have_sent_ICS_logon == 3) {
+           fprintf(ini, "%s", message);
+           fclose(ini);
+         have_sent_ICS_logon = 1;
+       }
     } else if (count < 0) {
        RemoveInputSource(isr);
        DisplayFatalError(_("Error reading from keyboard"), error, 1);
@@ -2510,6 +2532,12 @@ PlotSeekAd (int i)
 }
 
 void
+PlotSingleSeekAd (int i)
+{
+       PlotSeekAd(i);
+}
+
+void
 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
 {
        char buf[MSG_SIZ], *ext = "";
@@ -2529,7 +2557,7 @@ AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, i
            seekNrList[nrOfSeekAds] = nr;
            zList[nrOfSeekAds] = 0;
            seekAdList[nrOfSeekAds++] = StrSave(buf);
-           if(plot) PlotSeekAd(nrOfSeekAds-1);
+           if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
        }
 }
 
@@ -2599,7 +2627,7 @@ DrawSeekGraph ()
     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
        int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
        yy = h-1-yy;
-       DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+       DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
        if(i%500 == 0) {
            char buf[MSG_SIZ];
            snprintf(buf, MSG_SIZ, "%d", i);
@@ -3430,9 +3458,15 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                continue;
            }
 
-           if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
-               ICSInitScript();
-               have_sent_ICS_logon = 1;
+           if (looking_at(buf, &i, "login:")) {
+             if (!have_sent_ICS_logon) {
+               if(ICSInitScript())
+                 have_sent_ICS_logon = 1;
+               else // no init script was found
+                 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
+             } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
+                 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
+             }
                continue;
            }
 
@@ -4152,9 +4186,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
 void
 ParseBoard12 (char *string)
 {
+#if ZIPPY
+    int i, takeback;
+    char *bookHit = NULL; // [HGM] book
+#endif
     GameMode newGameMode;
-    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
-    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
+    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
+    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
     char to_play, board_chars[200];
     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
@@ -4166,7 +4204,6 @@ ParseBoard12 (char *string)
     int fromX, fromY, toX, toY;
     char promoChar;
     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
-    char *bookHit = NULL; // [HGM] book
     Boolean weird = FALSE, reqFlag = FALSE;
 
     fromX = fromY = toX = toY = -1;
@@ -4898,6 +4935,13 @@ GetMoveListEvent ()
 }
 
 void
+SendToBoth (char *msg)
+{   // to make it easy to keep two engines in step in dual analysis
+    SendToProgram(msg, &first);
+    if(second.analyzing) SendToProgram(msg, &second);
+}
+
+void
 AnalysisPeriodicEvent (int force)
 {
     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
@@ -4905,7 +4949,7 @@ AnalysisPeriodicEvent (int force)
       return;
 
     /* Send . command to Crafty to collect stats */
-    SendToProgram(".\n", &first);
+    SendToBoth(".\n");
 
     /* Don't send another until we get a response (this makes
        us stop sending to old Crafty's which don't understand
@@ -5136,6 +5180,11 @@ UploadGameEvent ()
     for(i = backwardMostMove; i<last; i++) {
        char buf[20];
        snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
+       if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
+           int len = strlen(moveList[i]);
+           snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
+           if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
+       }
        SendToICS(buf);
     }
     SendToICS(ics_prefix);
@@ -5377,7 +5426,8 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
   Boolean valid;
   int nr = 0;
 
-  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+  lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
+  if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
     pushed = TRUE;
   }
@@ -5442,11 +5492,14 @@ MultiPV (ChessProgramState *cps)
        return -1;
 }
 
+Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+
 Boolean
-LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
+LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
 {
        int startPV, multi, lineStart, origIndex = index;
        char *p, buf2[MSG_SIZ];
+       ChessProgramState *cps = (pane ? &second : &first);
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
@@ -5458,12 +5511,12 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
        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;
-       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
-               int n = first.option[multi].value;
+       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
+               int n = cps->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;
+               if(cps->option[multi].value != n) SendToProgram(buf2, cps);
+               cps->option[multi].value = n;
                *start = *end = 0;
                return FALSE;
        } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
@@ -5472,6 +5525,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
        }
        ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
+       extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
        return TRUE;
 }
 
@@ -5481,7 +5535,7 @@ PvToSAN (char *pv)
        static char buf[10*MSG_SIZ];
        int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
        *buf = NULLCHAR;
-       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
        ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
        for(i = forwardMostMove; i<endPV; i++){
            if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
@@ -5489,6 +5543,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(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
        if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
        endPV = savedEnd;
        return buf;
@@ -5500,6 +5555,7 @@ LoadPV (int x, int y)
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
+  extendGame = FALSE;
   return TRUE;
 }
 
@@ -5510,7 +5566,7 @@ UnLoadPV ()
   if(endPV < 0) return;
   if(appData.autoCopyPV) CopyFENToClipboard();
   endPV = -1;
-  if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+  if(extendGame && currentMove > forwardMostMove) {
        Boolean saveAnimate = appData.animate;
        if(pushed) {
            if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
@@ -5867,6 +5923,7 @@ InitPosition (int redraw)
     case VariantSChess:
       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
       gameInfo.holdingsSize = 7;
+      for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
       break;
     case VariantJanus:
       pieces = JanusArray;
@@ -6039,7 +6096,7 @@ InitPosition (int redraw)
       initialPosition[2][0] = BlackAngel;
       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
-      initialPosition[1][1] = initialPosition[2][1] = 
+      initialPosition[1][1] = initialPosition[2][1] =
       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
      }
   if (appData.debugMode) {
@@ -6193,12 +6250,12 @@ UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, cha
 }
 
 static int
-ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
+ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
     char buf[MSG_SIZ];
     int j, k;
     ChessMove moveType;
-    if(promoChar == -1) { // kludge to indicate best move
+    if((signed char)promoChar == -1) { // kludge to indicate best move
        if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
            return 1; // if unparsable, abort
     }
@@ -6214,7 +6271,7 @@ ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, c
     // inform engine
     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
-    SendToProgram(buf, &first);
+    SendToBoth(buf);
     return (state == '+');
 }
 
@@ -6227,16 +6284,16 @@ ExcludeClick (int index)
        if(index < 13) { // none: include all
            WriteMap(0); // clear map
            for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
-           SendToProgram("include all\n", &first); // and inform engine
+           SendToBoth("include all\n"); // and inform engine
        } else if(index > 18) { // tail
            if(exclusionHeader[19] == '-') { // tail was excluded
-               SendToProgram("include all\n", &first);
+               SendToBoth("include all\n");
                WriteMap(0); // clear map completely
                // now re-exclude selected moves
                for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
                    ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
            } else { // tail was included or in mixed state
-               SendToProgram("exclude all\n", &first);
+               SendToBoth("exclude all\n");
                WriteMap(0xFF); // fill map completely
                // now re-include selected moves
                j = 0; // count them
@@ -6494,7 +6551,7 @@ OKToStartUserMove (int x, int y)
 }
 
 Boolean
-OnlyMove (int *x, int *y, Boolean captures) 
+OnlyMove (int *x, int *y, Boolean captures)
 {
     DisambiguateClosure cl;
     if (appData.zippyPlay || !appData.testLegality) return FALSE;
@@ -6707,12 +6764,12 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
          if( pup != EmptySquare ) return;
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
-          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
+          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
                moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
           // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
           if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
           fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
+          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
          fromY = DROP_RANK;
     }
 
@@ -6730,7 +6787,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
        }
     }
 
-    if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
+    if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
             ClearPremoveHighlights(); // was included
        else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
@@ -6865,14 +6922,21 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom
                           gameMode == MachinePlaysBlack)) {
       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
     }
-    if (gameMode != EditGame && gameMode != PlayFromGameFile) {
+    if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
         // [HGM] book: if program might be playing, let it use book
        bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
        first.maybeThinking = TRUE;
     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
        if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
        SendBoard(&first, currentMove+1);
-    } else SendMoveToProgram(forwardMostMove-1, &first);
+       if(second.analyzing) {
+           if(!second.useSetboard) SendToProgram("undo\n", &second);
+           SendBoard(&second, currentMove+1);
+       }
+    } else {
+       SendMoveToProgram(forwardMostMove-1, &first);
+       if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
+    }
     if (currentMove == cmailOldMove + 1) {
       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
     }
@@ -7080,6 +7144,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        return;
       }
       doubleClick = FALSE;
+      if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
+       doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
+      }
       fromX = x; fromY = y; toX = toY = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
@@ -7159,7 +7226,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            }
           }
           if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
-          second = FALSE; 
+          second = FALSE;
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
@@ -7202,9 +7269,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     if(!sweepSelecting) {
        toX = x;
        toY = y;
-       saveAnimate = appData.animate;
     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
 
+    saveAnimate = appData.animate;
     if (clickType == Press) {
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
@@ -7232,17 +7299,19 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
     } else {
+#if 0
+// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
        /* Finish drag move */
        if (appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
            ClearHighlights();
        }
+#endif
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
-    MarkTargetSquares(1);
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
@@ -7267,6 +7336,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        }
        ClearHighlights();
        fromX = fromY = -1;
+        MarkTargetSquares(1);
        DrawPosition(TRUE, boards[currentMove]);
        return;
     }
@@ -7278,6 +7348,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
+        MarkTargetSquares(1);
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
            // [HGM] super: promotion to captured piece selected from holdings
            ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
@@ -7300,6 +7371,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
           Explode(boards[currentMove-1], fromX, fromY, toX, toY))
            DrawPosition(TRUE, boards[currentMove]);
+        MarkTargetSquares(1);
        fromX = fromY = -1;
     }
     appData.animate = saveAnimate;
@@ -7556,6 +7628,9 @@ MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisCo
                        || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
                // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
 
+       } else if(v == VariantKnightmate) {
+               if(nMine == 1) return FALSE;
+               if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
        } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
                int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
 
@@ -7608,7 +7683,7 @@ Adjudicate (ChessProgramState *cps)
        //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
        //       Actually ending the game is now based on the additional internal condition canAdjudicate.
        //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
-       int k, count = 0; static int bare = 1;
+       int k, drop, count = 0; static int bare = 1;
        ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
        Boolean canAdjudicate = !appData.icsActive;
 
@@ -7692,13 +7767,21 @@ Adjudicate (ChessProgramState *cps)
                        boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
                                                   ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
                                                                        EP_CHECKMATE : EP_WINS);
-                   else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+                   else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
                        boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
                }
                break;
              case MT_CHECKMATE:
                reason = "Xboard adjudication: Checkmate";
                boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+               if(gameInfo.variant == VariantShogi) {
+                   if(forwardMostMove > backwardMostMove
+                      && moveList[forwardMostMove-1][1] == '@'
+                      && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
+                       reason = "XBoard adjudication: pawn-drop mate";
+                       boards[forwardMostMove][EP_STATUS] = EP_WINS;
+                   }
+               }
                break;
            }
 
@@ -7763,10 +7846,12 @@ Adjudicate (ChessProgramState *cps)
 
                 /* Check for rep-draws */
                 count = 0;
+                drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
+                                              && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
                 for(k = forwardMostMove-2;
-                    k>=backwardMostMove && k>=forwardMostMove-100 &&
+                    k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
-                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
                     k-=2)
                 {   int rights=0;
                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
@@ -7792,7 +7877,7 @@ Adjudicate (ChessProgramState *cps)
                              /* adjudicate after user-specified nr of repeats */
                             int result = GameIsDrawn;
                             char *details = "XBoard adjudication: repetition draw";
-                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+                            if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
                                // [HGM] xiangqi: check for forbidden perpetuals
                                int m, ourPerpetual = 1, hisPerpetual = 1;
                                for(m=forwardMostMove; m>k; m-=2) {
@@ -7810,6 +7895,12 @@ Adjudicate (ChessProgramState *cps)
                                if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
                                    break; // (or we would have caught him before). Abort repetition-checking loop.
                                } else
+                               if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
+                                   if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
+                                       result = BlackWins;
+                                       details = "Xboard adjudication: repetition";
+                                   }
+                               } else // it must be XQ
                                // Now check for perpetual chases
                                if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
                                    hisPerpetual = PerpetualChase(k, forwardMostMove);
@@ -7958,7 +8049,7 @@ LoadError (char *errmess, ChessProgramState *cps)
     if(cps->initDone) return FALSE;
     cps->isr = NULL; // this should suppress further error popups from breaking pipes
     DestroyChildProcess(cps->pr, 9 ); // just to be sure
-    cps->pr = NoProc; 
+    cps->pr = NoProc;
     if(cps == &first) {
        appData.noChessProgram = TRUE;
        gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
@@ -7983,6 +8074,8 @@ DeferredBookMove (void)
 }
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
@@ -8048,6 +8141,22 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
        (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
     {
+        if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
+           if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
+           safeStrCpy(stashedInputMove, message, MSG_SIZ);
+           stalledEngine = cps;
+           if(appData.ponderNextMove) { // bring opponent out of ponder
+               if(gameMode == TwoMachinesPlay) {
+                   if(cps->other->pause)
+                       PauseEngine(cps->other);
+                   else
+                       SendToProgram("easy\n", cps->other);
+               }
+           }
+           StopClocks();
+           return;
+       }
+
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
          if (gameMode == BeginningOfGame) {
@@ -8118,7 +8227,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            /* Machine move could not be parsed; ignore it. */
          snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
                    machineMove, _(cps->which));
-           DisplayError(buf1, 0);
+           DisplayMoveError(buf1);
             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
@@ -8315,7 +8424,7 @@ 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 || gameInfo.variant == VariantFairy) && 
+    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) return;
@@ -8383,7 +8492,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
          snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
          SendToICS(buf1);
        }
-      }
+      } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
       return;
     }
     if (!strncmp(message, "tellall ", 8)) {
@@ -9315,6 +9424,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
        }
 
+       if(gameInfo.variant == VariantSChess) { // update virginity
+          if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
+          if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
+          if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
+          if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
+       }
+
      if (fromX == toX && fromY == toY) return;
 
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
@@ -9364,11 +9480,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY >= BOARD_HEIGHT>>1)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -9425,11 +9541,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY < BOARD_HEIGHT>>1)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -9604,8 +9720,12 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
              && fromX != toX && fromY != toY)
                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
         // promotion suffix
-        if(promoChar != NULLCHAR)
-                fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
+        if(promoChar != NULLCHAR) {
+            if(fromY == 0 || fromY == BOARD_HEIGHT-1)
+                 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
+                                                ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
+            else 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",
@@ -9614,6 +9734,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
             else                      timeLeft = blackTimeRemaining/1000;
             fprintf(serverMoves, "/%d", timeLeft);
                strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
+               if(p = strchr(buf, '/')) *p = NULLCHAR; else
                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);
@@ -9674,9 +9795,6 @@ ShowMove (int fromX, int fromY, int toX, int toY)
                AnimateMove(boards[forwardMostMove - 1],
                            fromX, fromY, toX, toY);
            }
-           if (appData.highlightLastMove) {
-               SetHighlights(fromX, fromY, toX, toY);
-           }
        }
        currentMove = forwardMostMove;
     }
@@ -9684,6 +9802,11 @@ ShowMove (int fromX, int fromY, int toX, int toY)
     if (instant) return;
 
     DisplayMove(currentMove - 1);
+    if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+           if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
+               SetHighlights(fromX, fromY, toX, toY);
+           }
+    }
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
@@ -9846,6 +9969,33 @@ InitChessProgram (ChessProgramState *cps, int setup)
 
 
 void
+ResendOptions (ChessProgramState *cps)
+{ // send the stored value of the options
+  int i;
+  char buf[MSG_SIZ];
+  Option *opt = cps->option;
+  for(i=0; i<cps->nrOptions; i++, opt++) {
+      switch(opt->type) {
+        case Spin:
+        case Slider:
+        case CheckBox:
+           snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
+          break;
+        case ComboBox:
+          snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
+          break;
+        default:
+           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
+          break;
+        case Button:
+        case SaveButton:
+          continue;
+      }
+      SendToProgram(buf, cps);
+  }
+}
+
+void
 StartChessProgram (ChessProgramState *cps)
 {
     char buf[MSG_SIZ];
@@ -9885,9 +10035,12 @@ StartChessProgram (ChessProgramState *cps)
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
     if (cps->protocolVersion > 1) {
       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
-      cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
-      cps->comboCnt = 0;  //                and values of combo boxes
+      if(!cps->reload) { // do not clear options when reloading because of -xreuse
+        cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+        cps->comboCnt = 0;  //                and values of combo boxes
+      }
       SendToProgram(buf, cps);
+      if(cps->reload) ResendOptions(cps);
     } else {
       SendToProgram("xboard\n", cps);
     }
@@ -9908,7 +10061,6 @@ TwoMachinesEventIfReady P((void))
     return;
   }
   DisplayMessage("", ""); curMess = 0;
-  ThawUI();
   TwoMachinesEvent();
 }
 
@@ -9968,7 +10120,15 @@ 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, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
+       fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
+       fprintf(f, "-bookDepth %d\n", appData.bookDepth);
+       fprintf(f, "-bookVariation %d\n", appData.bookStrength);
        fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
+       fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
+       fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
+       fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
+       fprintf(f, "-smpCores %d\n", appData.smpCores);
        if(searchTime > 0)
                fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
        else {
@@ -10042,6 +10202,27 @@ Substitute (char *participants, int expunge)
 }
 
 int
+CheckPlayers (char *participants)
+{
+       int i;
+       char buf[MSG_SIZ], *p;
+       NamesToList(firstChessProgramNames, command, mnemonic, "all");
+       while(p = strchr(participants, '\n')) {
+           *p = NULLCHAR;
+           for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
+           if(!mnemonic[i]) {
+               snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
+               *p = '\n';
+               DisplayError(buf, 0);
+               return 1;
+           }
+           *p = '\n';
+           participants = p + 1;
+       }
+       return 0;
+}
+
+int
 CreateTourney (char *name)
 {
        FILE *f;
@@ -10063,6 +10244,7 @@ CreateTourney (char *name)
                DisplayError(_("Not enough participants"), 0);
                return 0;
            }
+           if(CheckPlayers(appData.participants)) return 0;
            ASSIGN(appData.tourneyFile, name);
            if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
            if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
@@ -10141,6 +10323,28 @@ SwapEngines (int n)
 }
 
 int
+GetEngineLine (char *s, int n)
+{
+    int i;
+    char buf[MSG_SIZ];
+    extern char *icsNames;
+    if(!s || !*s) return 0;
+    NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
+    for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
+    if(!mnemonic[i]) return 0;
+    if(n == 11) return 1; // just testing if there was a match
+    snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
+    if(n == 1) SwapEngines(n);
+    ParseArgsFromString(buf);
+    if(n == 1) SwapEngines(n);
+    if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
+       SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
+       ParseArgsFromString(buf);
+    }
+    return 1;
+}
+
+int
 SetPlayer (int player, char *p)
 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
     int i;
@@ -10153,6 +10357,13 @@ SetPlayer (int player, char *p)
        ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
        appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
        ParseArgsFromString(buf);
+    } else { // no engine with this nickname is installed!
+       snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
+       ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
+       matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+       ModeHighlight();
+       DisplayError(buf, 0);
+       return 0;
     }
     free(engineName);
     return i;
@@ -10218,7 +10429,7 @@ Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInte
        *blackPlayer = curRound + appData.tourneyType;
     }
 
-    // take care of white/black alternation per round. 
+    // 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;
 }
@@ -10227,7 +10438,7 @@ 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;
+    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
     FILE *tf;
     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
     tf = fopen(appData.tourneyFile, "r");
@@ -10267,7 +10478,7 @@ NextTourneyGame (int nr, int *swapColors)
            SendToProgram(buf, &pairing);
            return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
        }
-       pairingReceived = 0;                              // ... so we continue here 
+       pairingReceived = 0;                              // ... so we continue here
        *swapColors = 0;
        appData.matchGames = appData.tourneyCycles * syncInterval - 1;
        whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
@@ -10279,18 +10490,18 @@ NextTourneyGame (int nr, int *swapColors)
     // redefine engines, engine dir, etc.
     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
     if(first.pr == NoProc) {
-      SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
+      if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
     }
     if(second.pr == NoProc) {
       SwapEngines(1);
-      SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
+      if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
       SwapEngines(1);         // and make that valid for second engine by swapping
       InitEngine(&second, 1);
     }
     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
     UpdateLogos(FALSE);     // leave display to ModeHiglight()
-    return 1;
+    return OK;
 }
 
 void
@@ -10391,6 +10602,8 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
 
     fromX = fromY = -1; // [HGM] abort any move the user is entering.
 
+    if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
+
     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
           game is over, but the engine can offer to draw, claim
@@ -10483,6 +10696,10 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                       resultDetails = buf;
                 }
                 /* (Claiming a loss is accepted no questions asked!) */
+           } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
+               forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
+               result = GameUnfinished;
+               if(!*appData.tourneyFile) matchGame--; // replay even in plain match
            }
            /* [HGM] bare: don't allow bare King to win */
            if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
@@ -10527,6 +10744,9 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
                                                                ) {
                    if (*appData.saveGameFile != NULLCHAR) {
+                       if(result == GameUnfinished && matchMode && *appData.tourneyFile)
+                           AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
+                       else
                        SaveGameToFile(appData.saveGameFile, TRUE);
                    } else if (appData.autoSaveGames) {
                        AutoSaveGame();
@@ -10534,6 +10754,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    if (*appData.savePositionFile != NULLCHAR) {
                        SavePositionToFile(appData.savePositionFile);
                    }
+                   AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
                }
            }
 
@@ -10650,6 +10871,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            SendToProgram("quit\n", &first);
             DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(first.pr, first.useSigterm);
+           first.reload = TRUE;
        }
        first.pr = NoProc;
     }
@@ -10675,11 +10897,12 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            SendToProgram("quit\n", &second);
             DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(second.pr, second.useSigterm);
+           second.reload = TRUE;
        }
        second.pr = NoProc;
     }
 
-    if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+    if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
        char resChar = '=';
         switch (result) {
        case WhiteWins:
@@ -10704,7 +10927,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
          break;
        }
 
-       if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+       if(exiting) 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
@@ -10803,8 +11026,8 @@ ResurrectChessProgram ()
 
     if (appData.noChessProgram) return 1;
 
-    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(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, because we started engine
        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 {
@@ -10931,6 +11154,11 @@ AutoPlayGameLoop ()
     }
 }
 
+void
+AnalyzeNextGame()
+{
+    ReloadGame(1); // next game
+}
 
 int
 AutoPlayOneMove ()
@@ -10952,7 +11180,14 @@ AutoPlayOneMove ()
     }
 
     if (currentMove >= forwardMostMove) {
-      if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
+      if(gameMode == AnalyzeFile) {
+         if(appData.loadGameIndex == -1) {
+           GameEnds(EndOfFile, NULL, GE_FILE);
+          ScheduleDelayedEvent(AnalyzeNextGame, 10);
+         } else {
+          ExitAnalyzeMode(); SendToProgram("force\n", &first);
+        }
+      }
 //      gameMode = EndOfGame;
 //      ModeHighlight();
 
@@ -11627,12 +11862,12 @@ InitSearch ()
        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; 
+    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][2] == NoRights ||
                     boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
-                && (boards[currentMove][CASTLING][5] == NoRights || 
+                && (boards[currentMove][CASTLING][5] == NoRights ||
                     boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
       ) {
        flipSearch = TRUE;
@@ -11654,6 +11889,7 @@ InitSearch ()
 }
 
 GameInfo dummyInfo;
+static int creatingBook;
 
 int
 GameContainsPosition (FILE *f, ListGame *lg)
@@ -11796,6 +12032,9 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            gn = 1;
        }
        else {
+           if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
+             appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
+           else
            DisplayError(_("Game number out of range"), 0);
            return FALSE;
        }
@@ -12114,6 +12353,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        cm = (ChessMove) Myylex();
     }
 
+  if(!creatingBook) {
     if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
@@ -12126,6 +12366,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     }
        DisplayBothClocks();
     }
+  }
 
     /* [HGM] server: flag to write setup moves in broadcast file as one */
     loadFlag = appData.suppressLoadMoves;
@@ -12189,9 +12430,11 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
 
     if (oldGameMode == AnalyzeFile ||
        oldGameMode == AnalyzeMode) {
+      appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
       AnalyzeFileEvent();
     }
 
+    if(creatingBook) return TRUE;
     if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
     } else
@@ -13226,6 +13469,20 @@ ExitEvent (int status)
 }
 
 void
+PauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("pause\n", cps);
+    cps->pause = 2;
+}
+
+void
+UnPauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("resume\n", cps);
+    cps->pause = 1;
+}
+
+void
 PauseEvent ()
 {
     if (appData.debugMode)
@@ -13233,8 +13490,24 @@ PauseEvent ()
     if (pausing) {
        pausing = FALSE;
        ModeHighlight();
+       if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
+           StartClocks();
+           if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
+               if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
+               else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
+           }
+           if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
+           HandleMachineMove(stashedInputMove, stalledEngine);
+           stalledEngine = NULL;
+           return;
+       }
        if (gameMode == MachinePlaysWhite ||
-           gameMode == MachinePlaysBlack) {
+           gameMode == TwoMachinesPlay   ||
+           gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
+           if(first.pause)  UnPauseEngine(&first);
+           else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
+           if(second.pause) UnPauseEngine(&second);
+           else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
            StartClocks();
        } else {
            DisplayBothClocks();
@@ -13246,7 +13519,7 @@ PauseEvent ()
            Reset(FALSE, TRUE);
            SendToICS(ics_prefix);
            SendToICS("refresh\n");
-       } else if (currentMove < forwardMostMove) {
+       } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
            ForwardInner(forwardMostMove);
        }
        pauseExamInvalid = FALSE;
@@ -13277,12 +13550,30 @@ PauseEvent ()
          case TwoMachinesPlay:
            if (forwardMostMove == 0)
              return;           /* don't pause if no one has moved */
-           if ((gameMode == MachinePlaysWhite &&
-                !WhiteOnMove(forwardMostMove)) ||
-               (gameMode == MachinePlaysBlack &&
-                WhiteOnMove(forwardMostMove))) {
+           if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
+               ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
+               if(onMove->pause) {           // thinking engine can be paused
+                   PauseEngine(onMove);      // do it
+                   if(onMove->other->pause)  // pondering opponent can always be paused immediately
+                       PauseEngine(onMove->other);
+                   else
+                       SendToProgram("easy\n", onMove->other);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
+           } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+               if(first.pause) {
+                   PauseEngine(&first);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
+           } else { // human on move, pause pondering by either method
+               if(first.pause)
+                   PauseEngine(&first);
+               else if(appData.ponderNextMove)
+                   SendToProgram("easy\n", &first);
                StopClocks();
            }
+           // if no immediate pausing is possible, wait for engine to move, and stop clocks then
+         case AnalyzeMode:
            pausing = TRUE;
            ModeHighlight();
            break;
@@ -13317,16 +13608,74 @@ EditTagsEvent ()
 }
 
 void
+ToggleSecond ()
+{
+  if(second.analyzing) {
+    SendToProgram("exit\n", &second);
+    second.analyzing = FALSE;
+  } else {
+    if (second.pr == NoProc) StartChessProgram(&second);
+    InitChessProgram(&second, FALSE);
+    FeedMovesToProgram(&second, currentMove);
+
+    SendToProgram("analyze\n", &second);
+    second.analyzing = TRUE;
+  }
+}
+
+/* Toggle ShowThinking */
+void
+ToggleShowThinking()
+{
+  appData.showThinking = !appData.showThinking;
+  ShowThinkingEvent();
+}
+
+int
 AnalyzeModeEvent ()
 {
+    char buf[MSG_SIZ];
+
+    if (!first.analysisSupport) {
+      snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+      DisplayError(buf, 0);
+      return 0;
+    }
+    /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
+    if (appData.icsActive) {
+        if (gameMode != IcsObserving) {
+         snprintf(buf, MSG_SIZ, _("You are not observing a game"));
+            DisplayError(buf, 0);
+            /* secure check */
+            if (appData.icsEngineAnalyze) {
+                if (appData.debugMode)
+                    fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
+                ExitAnalyzeMode();
+                ModeHighlight();
+            }
+            return 0;
+        }
+        /* if enable, user wants to disable icsEngineAnalyze */
+        if (appData.icsEngineAnalyze) {
+                ExitAnalyzeMode();
+                ModeHighlight();
+                return 0;
+        }
+        appData.icsEngineAnalyze = TRUE;
+        if (appData.debugMode)
+            fprintf(debugFP, _("ICS engine analyze starting... \n"));
+    }
+
+    if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
     if (appData.noChessProgram || gameMode == AnalyzeMode)
-      return;
+      return 0;
 
     if (gameMode != AnalyzeFile) {
         if (!appData.icsEngineAnalyze) {
                EditGameEvent();
-               if (gameMode != EditGame) return;
+               if (gameMode != EditGame) return 0;
         }
+       if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -13342,6 +13691,7 @@ AnalyzeModeEvent ()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
+    return 1;
 }
 
 void
@@ -13350,9 +13700,19 @@ AnalyzeFileEvent ()
     if (appData.noChessProgram || gameMode == AnalyzeFile)
       return;
 
+    if (!first.analysisSupport) {
+      char buf[MSG_SIZ];
+      snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+      DisplayError(buf, 0);
+      return;
+    }
+
     if (gameMode != AnalyzeMode) {
+       keepInfo = 1; // mere annotating should not alter PGN tags
        EditGameEvent();
+       keepInfo = 0;
        if (gameMode != EditGame) return;
+       if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -13363,12 +13723,12 @@ AnalyzeFileEvent ()
     gameMode = AnalyzeFile;
     pausing = FALSE;
     ModeHighlight();
-    SetGameInfo();
 
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
+    AnalysisPeriodicEvent(1);
 }
 
 void
@@ -13538,7 +13898,7 @@ DisplayTwoMachinesTitle ()
                   gameInfo.white, _("vs."), gameInfo.black,
                   nextGame+1, appData.matchGames+1,
                   appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
-        } else 
+        } else
         if (first.twoMachinesColor[0] == 'w') {
          snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
                   gameInfo.white, _("vs."),  gameInfo.black,
@@ -13577,9 +13937,10 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
        StartChessProgram(cps);
        if (cps->protocolVersion == 1) {
          retry();
+         ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
        } else {
          /* kludge: allow timeout for initial "feature" command */
-         FreezeUI();
+         if(retry != TwoMachinesEventIfReady) FreezeUI();
          snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
          DisplayMessage("", buf);
          ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
@@ -13632,16 +13993,19 @@ TwoMachinesEvent P((void))
 
 //    forwardMostMove = currentMove;
     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
+    startingEngine = TRUE;
 
     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.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+    if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+       startingEngine = FALSE;
        DisplayError("second engine does not play this", 0);
        return;
     }
@@ -13675,7 +14039,7 @@ TwoMachinesEvent P((void))
     }
 
     gameMode = TwoMachinesPlay;
-    pausing = FALSE;
+    pausing = startingEngine = FALSE;
     ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
@@ -13916,8 +14280,8 @@ ExitAnalyzeMode ()
         DisplayMessage("",_("Close ICS engine analyze..."));
     }
     if (first.analysisSupport && first.analyzing) {
-      SendToProgram("exit\n", &first);
-      first.analyzing = FALSE;
+      SendToBoth("exit\n");
+      first.analyzing = second.analyzing = FALSE;
     }
     thinkOutput[0] = NULLCHAR;
 }
@@ -13932,14 +14296,22 @@ EditPositionDone (Boolean fakeRights)
     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
       boards[0][EP_STATUS] = EP_NONE;
       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
-    if(boards[0][0][BOARD_WIDTH>>1] == king) {
-       boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
+      if(boards[0][0][BOARD_WIDTH>>1] == king) {
+       boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
        boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
       } else boards[0][CASTLING][2] = NoRights;
-    if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
-       boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
+      if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+       boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
        boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
       } else boards[0][CASTLING][5] = NoRights;
+      if(gameInfo.variant == VariantSChess) {
+       int i;
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
+         boards[0][VIRGIN][i] = 0;
+         if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
+         if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+       }
+      }
     }
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
@@ -13955,6 +14327,7 @@ EditPositionDone (Boolean fakeRights)
         fprintf(debugFP, "EditPosDone\n");
     }
     DisplayTitle("");
+    DisplayMessage("", "");
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     gameMode = EditGame;
@@ -14493,6 +14866,7 @@ ForwardInner (int target)
        gameMode == Training || gameMode == PlayFromGameFile ||
        gameMode == AnalyzeFile) {
        while (currentMove < target) {
+           if(second.analyzing) SendMoveToProgram(currentMove, &second);
            SendMoveToProgram(currentMove++, &first);
        }
     } else {
@@ -14603,11 +14977,15 @@ BackwardInner (int target)
                for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
                    if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
                }
-               SendBoard(&first, i); 
-               for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
+               SendBoard(&first, i);
+             if(second.analyzing) SendBoard(&second, i);
+               for(currentMove=i; currentMove<target; currentMove++) {
+                   SendMoveToProgram(currentMove, &first);
+                   if(second.analyzing) SendMoveToProgram(currentMove, &second);
+               }
                break;
            }
-           SendToProgram("undo\n", &first);
+           SendToBoth("undo\n");
            currentMove--;
        }
     } else {
@@ -14823,6 +15201,40 @@ HintEvent ()
 }
 
 void
+CreateBookEvent ()
+{
+    ListGame * lg = (ListGame *) gameList.head;
+    FILE *f;
+    int nItem;
+    static int secondTime = FALSE;
+
+    if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+        DisplayError(_("Game list not loaded or empty"), 0);
+        return;
+    }
+
+    if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
+        fclose(f);
+       secondTime++;
+       DisplayNote(_("Book file exists! Try again for overwrite."));
+       return;
+    }
+
+    creatingBook = TRUE;
+    secondTime = FALSE;
+
+    /* Get list size */
+    for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+       LoadGame(f, nItem, "", TRUE);
+       AddGameToBook(TRUE);
+        lg = (ListGame *) lg->node.succ;
+    }
+
+    creatingBook = FALSE;
+    FlushBook();
+}
+
+void
 BookEvent ()
 {
     if (appData.noChessProgram) return;
@@ -14946,6 +15358,8 @@ SetGameInfo ()
     ChessMove r = GameUnfinished;
     char *p = NULL;
 
+    if(keepInfo) return;
+
     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
        r = gameInfo.result;
        p = gameInfo.resultDetails;
@@ -15043,7 +15457,7 @@ ReplaceComment (int index, char *text)
     char *p;
     float score;
 
-    if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
+    if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
        pvInfoList[index-1].depth == len &&
        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
@@ -15356,7 +15770,7 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou
                   sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
                   sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
                   sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
-                  sscanf(message, "hint: %c", &c)!=1 && 
+                  sscanf(message, "hint: %c", &c)!=1 &&
                   sscanf(message, "pong %c", &c)!=1   && start != '#') {
                    quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
                    print = (appData.engineComments >= 2);
@@ -15650,6 +16064,7 @@ FeatureDone (ChessProgramState *cps, int val)
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
   }
   cps->initDone = val;
+  if(val) cps->reload = FALSE;
 }
 
 /* Parse feature command from engine */
@@ -15694,7 +16109,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
-    if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
+    if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
     if (IntFeature(&p, "done", &val, cps)) {
       FeatureDone(cps, val);
       continue;
@@ -15712,6 +16127,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
     if (StringFeature(&p, "option", buf, cps)) {
+       if(cps->reload) continue; // we are reloading because of xreuse
        FREE(cps->option[cps->nrOptions].name);
        cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
        safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
@@ -15838,9 +16254,9 @@ AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
 void
 TypeInEvent (char firstChar)
 {
-    if ((gameMode == BeginningOfGame && !appData.icsActive) || 
+    if ((gameMode == BeginningOfGame && !appData.icsActive) ||
         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
-       gameMode == AnalyzeMode || gameMode == EditGame || 
+       gameMode == AnalyzeMode || gameMode == EditGame ||
        gameMode == EditPosition || gameMode == IcsExamining ||
        gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
        isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
@@ -15876,16 +16292,16 @@ TypeInDoneEvent (char *move)
            return;
         }
 
-      if (gameMode != EditGame && currentMove != forwardMostMove && 
+      if (gameMode != EditGame && currentMove != forwardMostMove &&
        gameMode != Training) {
        DisplayMoveError(_("Displayed move is not current"));
       } else {
-       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
          &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
        if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
-       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
          &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
-         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);
        } else {
          DisplayMoveError(_("Could not parse move"));
        }
@@ -16659,14 +17075,30 @@ PositionToFEN (int move, char *overrideCastling)
 
         /* [HGM] write true castling rights */
         if( nrCastlingRights == 6 ) {
+            int q, k=0;
             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
-               boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
-            if(boards[move][CASTLING][1] == BOARD_LEFT &&
-               boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
+               boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
+            q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+                 boards[move][CASTLING][2] != NoRights  );
+            if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
+               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
+                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+                    if((boards[move][0][i] != WhiteKing || k+q == 0) &&
+                        boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+            }
+           if(q) *p++ = 'Q';
+            k = 0;
             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
-               boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
-            if(boards[move][CASTLING][4] == BOARD_LEFT &&
-               boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
+               boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
+            q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+                 boards[move][CASTLING][5] != NoRights  );
+            if(gameInfo.variant == VariantSChess) {
+               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+                    if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
+                        boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+            }
+            if(q) *p++ = 'q';
         }
      }
      if (q == p) *p++ = '-'; /* No castling rights */
@@ -16732,7 +17164,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
 {
     int i, j;
     char *p, c;
-    int emptycount;
+    int emptycount, virgin[BOARD_FILES];
     ChessSquare piece;
 
     p = fen;
@@ -16861,17 +17293,18 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
 
     while(*p==' ') p++;
     if(nrCastlingRights) {
-      if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+      if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
+      if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
           /* castling indicator present, so default becomes no castlings */
           for(i=0; i<nrCastlingRights; i++ ) {
                  board[CASTLING][i] = NoRights;
           }
       }
       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
-             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
-        char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
+        int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
 
         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
@@ -16888,25 +17321,34 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
+             if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
+             if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
               break;
           case'Q':
               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
+             if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
+             if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
               break;
           case'k':
               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
+             if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
+             if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
               break;
           case'q':
               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
+             if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
+             if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
           case '-':
               break;
           default: /* FRC castlings */
               if(c >= 'a') { /* black rights */
+                 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
                   if(i == BOARD_RGHT) break;
@@ -16919,6 +17361,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
                   else
                       board[CASTLING][4] = c;
               } else { /* white rights */
+                 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[0][i] == WhiteKing) break;
                   if(i == BOARD_RGHT) break;
@@ -16934,6 +17377,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
       }
       for(i=0; i<nrCastlingRights; i++)
         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
+      if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
     if (appData.debugMode) {
         fprintf(debugFP, "FEN castling rights:");
         for(i=0; i<nrCastlingRights; i++)
@@ -17257,3 +17701,59 @@ LoadVariation (int index, char *text)
        ToNrEvent(currentMove+1);
 }
 
+void
+LoadTheme ()
+{
+    char *p, *q, buf[MSG_SIZ];
+    if(engineLine && engineLine[0]) { // a theme was selected from the listbox
+       snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
+       ParseArgsFromString(buf);
+       ActivateTheme(TRUE); // also redo colors
+       return;
+    }
+    p = nickName;
+    if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
+    {
+       int len;
+       q = appData.themeNames;
+       snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
+      if(appData.useBitmaps) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
+               appData.liteBackTextureFile, appData.darkBackTextureFile,
+               appData.liteBackTextureMode,
+               appData.darkBackTextureMode );
+      } else {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
+               Col2Text(2),   // lightSquareColor
+               Col2Text(3) ); // darkSquareColor
+      }
+      if(appData.useBorder) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
+               appData.border);
+      } else {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
+      }
+      if(appData.useFont) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
+               appData.renderPiecesWithFont,
+               appData.fontToPieceTable,
+               Col2Text(9),    // appData.fontBackColorWhite
+               Col2Text(10) ); // appData.fontForeColorBlack
+      } else {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
+               appData.pieceDirectory);
+       if(!appData.pieceDirectory[0])
+         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
+               Col2Text(0),   // whitePieceColor
+               Col2Text(1) ); // blackPieceColor
+      }
+      snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
+               Col2Text(4),   // highlightSquareColor
+               Col2Text(5) ); // premoveHighlightColor
+       appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
+       if(insert != q) insert[-1] = NULLCHAR;
+       snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
+       if(q)   free(q);
+    }
+    ActivateTheme(FALSE);
+}