Shuffle prototypes to correct header, or add them there
[xboard.git] / backend.c
index 9b0fb08..a8e342d 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -152,7 +152,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));
@@ -222,6 +221,7 @@ int NextTourneyGame P((int nr, int *swap));
 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
 FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
+static void ExcludeClick P((int index));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -449,15 +449,15 @@ int adjudicateLossPlies = 6;
 char white_holding[64], black_holding[64];
 TimeMark lastNodeCountTime;
 long lastNodeCount=0;
-int shiftKey; // [HGM] set by mouse handler
+int shiftKey, controlKey; // [HGM] set by mouse handler
 
 int have_sent_ICS_logon = 0;
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
 Boolean adjustedClock;
 long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
+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;
@@ -850,7 +850,7 @@ LoadEngine ()
     SendToProgram("force\n", savCps);
     DisplayMessage("", "");
     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
-    for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+    for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
     ThawUI();
     SetGNUMode();
 }
@@ -875,7 +875,8 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 static char resetOptions[] = 
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
        "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
-       "-firstOptions \"\" -firstNPS -1 -fn \"\"";
+       "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+       "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
 
 void
 FloatToFront(char **list, char *engineLine)
@@ -896,6 +897,8 @@ FloatToFront(char **list, char *engineLine)
     ASSIGN(*list, tidy+1);
 }
 
+char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
+
 void
 Load (ChessProgramState *cps, int i)
 {
@@ -903,7 +906,8 @@ Load (ChessProgramState *cps, int i)
     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
        snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
        SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
-       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
+       ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
+       FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
        appData.firstProtocolVersion = PROTOVER;
        ParseArgsFromString(buf);
        SwapEngines(i);
@@ -914,19 +918,20 @@ Load (ChessProgramState *cps, int i)
     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
+    if(engineDir[0] != NULLCHAR) {
+       ASSIGN(appData.directory[i], engineDir); p = engineName;
+    } else if(p != engineName) { // derive directory from engine path, when not given
        p[-1] = 0;
-       appData.directory[i] = strdup(engineName);
+       ASSIGN(appData.directory[i], engineName);
        p[-1] = SLASH;
-    } else appData.directory[i] = ".";
+       if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
+    } else { ASSIGN(appData.directory[i], "."); }
     if(params[0]) {
        if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
        snprintf(command, MSG_SIZ, "%s %s", p, params);
        p = command;
     }
-    appData.chessProgram[i] = strdup(p);
+    ASSIGN(appData.chessProgram[i], p);
     appData.isUCI[i] = isUCI;
     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
     appData.hasOwnBookUCI[i] = hasBook;
@@ -948,8 +953,10 @@ Load (ChessProgramState *cps, int i)
                        isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
                        storeVariant ? " -variant " : "",
                        storeVariant ? VariantName(gameInfo.variant) : "");
+       if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
        firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
-       snprintf(firstChessProgramNames, len, "%s%s", q, buf);
+       if(insert != q) insert[-1] = NULLCHAR;
+       snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
        if(q)   free(q);
        FloatToFront(&appData.recentEngineList, buf);
     }
@@ -1485,6 +1492,8 @@ MatchEvent (int mode)
        NextMatchGame();
 }
 
+char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
+
 void
 InitBackEnd3 P((void))
 {
@@ -1498,7 +1507,7 @@ InitBackEnd3 P((void))
        free(programVersion);
        programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
        sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
-       FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
+       FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
     }
 
     if (appData.icsActive) {
@@ -2436,6 +2445,7 @@ VariantSwitch (Board board, VariantClass newVariant)
           board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
             board[i][j];
      }
+     board[HOLDINGS_SET] = 0;
      gameInfo.boardWidth  = newWidth;
      gameInfo.boardHeight = newHeight;
      gameInfo.holdingsWidth = newHoldingsWidth;
@@ -2483,8 +2493,8 @@ PlotSeekAd (int i)
        xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
        if(r < minRating+100 && r >=0 ) r = minRating+100;
        if(r > maxRating) r = maxRating;
-       if(tc < 1.) tc = 1.;
-       if(tc > 95.) tc = 95.;
+       if(tc < 1.f) tc = 1.f;
+       if(tc > 95.f) tc = 95.f;
        x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
        y = ((double)r - minRating)/(maxRating - minRating)
            * (h-vMargin-squareSize/8-1) + vMargin;
@@ -2614,6 +2624,13 @@ SeekGraphClick (ClickType click, int x, int y, int moving)
 {
     static int lastDown = 0, displayed = 0, lastSecond;
     if(y < 0) return FALSE;
+    if(!(appData.seekGraph && appData.icsActive && loggedOn &&
+       (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
+       if(!seekGraphUp) return FALSE;
+       seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
+       DrawPosition(TRUE, NULL);
+       return TRUE;
+    }
     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
        if(click == Release || moving) return FALSE;
        nrOfSeekAds = 0;
@@ -3560,7 +3577,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
            if (looking_at(buf, &i, "% ") ||
                ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
                 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
-               if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
+               if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
                    soughtPending = FALSE;
                    seekGraphUp = TRUE;
                    DrawSeekGraph();
@@ -4208,6 +4225,7 @@ ParseBoard12 (char *string)
        newGameMode =
          ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
            IcsPlayingWhite : IcsPlayingBlack;
+       soughtPending =FALSE; // [HGM] seekgraph: solve race condition
        break;
       case RELATION_EXAMINING:
        newGameMode = IcsExamining;
@@ -4223,9 +4241,12 @@ ParseBoard12 (char *string)
        break;
     }
 
-    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
+       gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
         && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
+      int fac = strchr(elapsed_time, '.') ? 1 : 1000;
+      static int lastBgGame = -1;
       char *toSqr;
       for (k = 0; k < ranks; k++) {
         for (j = 0; j < files; j++)
@@ -4247,14 +4268,35 @@ ParseBoard12 (char *string)
       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
       if(partnerUp) DrawPosition(FALSE, partnerBoard);
-      if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
-      snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
-                (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
+      if(twoBoards) {
+         DisplayWhiteClock(white_time*fac, to_play == 'W');
+         DisplayBlackClock(black_time*fac, to_play != 'W');
+         activePartner = to_play;
+         if(gamenum != lastBgGame) {
+             char buf[MSG_SIZ];
+             snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
+             DisplayTitle(buf);
+         }
+         lastBgGame = gamenum;
+         activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
+                     partnerUp = 0; flipView = !flipView; } // [HGM] dual
+      snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
+                (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
       DisplayMessage(partnerStatus, "");
        partnerBoardValid = TRUE;
       return;
     }
 
+    if(appData.dualBoard && appData.bgObserve) {
+       if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
+           SendToICS(ics_prefix), SendToICS("pobserve\n");
+       else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
+           SendToICS(buf);
+       }
+    }
+
     /* Modify behavior for initial board display on move listing
        of wild games.
        */
@@ -4289,6 +4331,7 @@ ParseBoard12 (char *string)
     }
 
    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
+                                       move_str[1] == '@' && !gameInfo.holdingsWidth ||
                                        weird && (int)gameInfo.variant < (int)VariantShogi) {
      /* [HGM] We seem to have switched variant unexpectedly
       * Try to guess new variant from board size
@@ -4298,7 +4341,8 @@ ParseBoard12 (char *string)
          if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
          if(ranks == 8 && files == 12) newVariant = VariantCourier; else
          if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
-         if(!weird) newVariant = VariantNormal;
+         if(ranks == 10 && files == 10) newVariant = VariantGrand; else
+         if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
          /* Get a move list just to see the header, which
             will tell us whether this is really bug or zh */
@@ -4507,7 +4551,10 @@ ParseBoard12 (char *string)
         r = boards[moveNum][CASTLING][5] = initialRights[5];
     }
     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
-    boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+    boards[moveNum][EP_STATUS] = EP_NONE;
+    if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
+    if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
+    if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
 
 
     if (ics_getting_history == H_GOT_REQ_HEADER ||
@@ -5146,6 +5193,12 @@ 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));
+    if(toX >= 0) {
+       int victim = boards[currentMove][toY][toX];
+       boards[currentMove][toY][toX] = promoSweep;
+       DrawPosition(FALSE, boards[currentMove]);
+       boards[currentMove][toY][toX] = victim;
+    } else
     ChangeDragPiece(promoSweep);
 }
 
@@ -5412,6 +5465,9 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
                first.option[multi].value = n;
                *start = *end = 0;
                return FALSE;
+       } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
+               ExcludeClick(origIndex - lineStart);
+               return FALSE;
        }
        ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
@@ -6090,6 +6146,115 @@ SendBoard (ChessProgramState *cps, int moveNum)
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
+char exclusionHeader[MSG_SIZ];
+int exCnt, excludePtr;
+typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
+static Exclusion excluTab[200];
+static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
+
+static void
+WriteMap (int s)
+{
+    int j;
+    for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
+    exclusionHeader[19] = s ? '-' : '+'; // update tail state
+}
+
+static void
+ClearMap ()
+{
+    safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
+    excludePtr = 24; exCnt = 0;
+    WriteMap(0);
+}
+
+static void
+UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
+{   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
+    char buf[2*MOVE_LEN], *p;
+    Exclusion *e = excluTab;
+    int i;
+    for(i=0; i<exCnt; i++)
+       if(e[i].ff == fromX && e[i].fr == fromY &&
+          e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
+    if(i == exCnt) { // was not in exclude list; add it
+       CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
+       if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
+           if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
+           return; // abort
+       }
+       e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
+       excludePtr++; e[i].mark = excludePtr++;
+       for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
+       exCnt++;
+    }
+    exclusionHeader[e[i].mark] = state;
+}
+
+static int
+ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed 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(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
+           return 1; // if unparsable, abort
+    }
+    // update exclusion map (resolving toggle by consulting existing state)
+    k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
+    j = k%8; k >>= 3;
+    if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
+    if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
+        excludeMap[k] |=   1<<j;
+    else excludeMap[k] &= ~(1<<j);
+    // update header
+    UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
+    // inform engine
+    snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
+    CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
+    SendToProgram(buf, &first);
+    return (state == '+');
+}
+
+static void
+ExcludeClick (int index)
+{
+    int i, j;
+    Exclusion *e = excluTab;
+    if(index < 25) { // none, best or tail clicked
+       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
+       } else if(index > 18) { // tail
+           if(exclusionHeader[19] == '-') { // tail was excluded
+               SendToProgram("include all\n", &first);
+               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);
+               WriteMap(0xFF); // fill map completely
+               // now re-include selected moves
+               j = 0; // count them
+               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, '+'), j++;
+               if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
+           }
+       } else { // best
+           ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
+       }
+    } else {
+       for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
+           char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
+           ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
+           break;
+       }
+    }
+}
+
 ChessSquare
 DefaultPromoChoice (int white)
 {
@@ -6391,12 +6556,14 @@ int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
 int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = EndOfFile;
+int doubleClick;
 
 void
 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
 {
     ChessMove moveType;
-    ChessSquare pdown, pup;
+    ChessSquare pup;
+    int ff=fromX, rf=fromY, ft=toX, rt=toY;
 
     /* Check if the user is playing in turn.  This is complicated because we
        let the user "pick up" a piece before it is his turn.  So the piece he
@@ -6525,7 +6692,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                    if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
                }
            } else
-           boards[0][fromY][fromX] = EmptySquare;
+           boards[0][fromY][fromX] = gatingPiece;
            DrawPosition(FALSE, boards[currentMove]);
            return;
        }
@@ -6533,7 +6700,6 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     }
 
     if(toX < 0 || toY < 0) return;
-    pdown = boards[currentMove][fromY][fromX];
     pup = boards[currentMove][toY][toX];
 
     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
@@ -6563,6 +6729,13 @@ 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(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
+            ClearPremoveHighlights(); // was included
+       else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
+       return;
+    }
+
     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
@@ -6634,6 +6807,8 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom
     else forwardMostMove = currentMove;
   }
 
+  ClearMap();
+
   /* If we need the chess program but it's dead, restart it */
   ResurrectChessProgram();
 
@@ -6768,11 +6943,11 @@ void
 MarkTargetSquares (int clear)
 {
   int x, y;
+  if(clear) // no reason to ever suppress clearing
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
      !appData.testLegality || gameMode == EditPosition) return;
-  if(clear) {
-    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
-  } else {
+  if(!clear) {
     int capt = 0;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
@@ -6781,7 +6956,7 @@ MarkTargetSquares (int clear)
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
     }
   }
-  DrawPosition(TRUE, NULL);
+  DrawPosition(FALSE, NULL);
 }
 
 int
@@ -6820,15 +6995,14 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, clearFlag = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
+    static TimeMark lastClickTime, prevClickTime;
 
-    if(appData.seekGraph && appData.icsActive && loggedOn &&
-       (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
-       SeekGraphClick(clickType, xPix, yPix, 0);
-       return;
-    }
+    if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
+
+    prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
 
     if (clickType == Press) ErrorPopDown();
 
@@ -6845,7 +7019,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
        promoDefaultAltered = TRUE;
-       if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+       if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
     }
 
     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
@@ -6877,7 +7051,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
        return;
 
-    if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
+    if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
+       // could be static click on premove from-square: abort premove
+       gotPremove = 0;
+       ClearPremoveHighlights();
+    }
+
+    if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
        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
@@ -6898,6 +7078,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        }
        return;
       }
+      doubleClick = FALSE;
       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
@@ -6906,6 +7087,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
+               if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
@@ -6915,6 +7097,8 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                }
                if (appData.highlightDragging) {
                    SetHighlights(fromX, fromY, -1, -1);
+               } else {
+                   ClearHighlights();
                }
            } else fromX = fromY = -1;
            return;
@@ -6944,6 +7128,10 @@ LeftClick (ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+           if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+               second = FALSE; // first double-click rather than scond click
+               doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
+           }
            promoDefaultAltered = FALSE;
            MarkTargetSquares(1);
           if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
@@ -6957,7 +7145,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                  (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
                  gatingPiece = boards[currentMove][fromY][fromX];
-               else gatingPiece = EmptySquare;
+               else gatingPiece = doubleClick ? fromP : EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
@@ -6990,9 +7178,10 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
        }
-       if (second) {
+       if (second || sweepSelecting) {
            /* Second up/down in same square; just abort move */
-           second = 0;
+           if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
+           second = sweepSelecting = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
            ClearHighlights();
@@ -7009,10 +7198,12 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
-    toX = x;
-    toY = y;
+    if(!sweepSelecting) {
+       toX = x;
+       toY = y;
+    } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
+
     saveAnimate = appData.animate;
-    MarkTargetSquares(1);
     if (clickType == Press) {
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
@@ -7020,16 +7211,18 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
-       if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+       if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+         if(appData.sweepSelect) {
            ChessSquare piece = boards[currentMove][fromY][fromX];
-           DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
            promoSweep = defaultPromoChoice;
            if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
            selectFlag = 0; lastX = xPix; lastY = yPix;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
+           sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
-           DrawPosition(FALSE, boards[currentMove]);
-           return;
+           MarkTargetSquares(1);
+         }
+         return; // promo popup appears on up-click
        }
        /* Finish clickclick move */
        if (appData.animate || appData.highlightLastMove) {
@@ -7048,6 +7241,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        /* 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) {
@@ -7079,7 +7273,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     // off-board moves should not be highlighted
     if(x < 0 || y < 0) ClearHighlights();
 
-    if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
+    if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -7757,6 +7951,25 @@ SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
     return bookHit; // notify caller of hit, so it can take action to send move to opponent
 }
 
+int
+LoadError (char *errmess, ChessProgramState *cps)
+{   // unloads engine and switches back to -ncp mode if it was first
+    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; 
+    if(cps == &first) {
+       appData.noChessProgram = TRUE;
+       gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
+       gameMode = BeginningOfGame; ModeHighlight();
+       SetNCPMode();
+    }
+    if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
+    DisplayMessage("", ""); // erase waiting message
+    if(errmess) DisplayError(errmess, 0); // announce reason, if given
+    return TRUE;
+}
+
 char *savedMessage;
 ChessProgramState *savedState;
 void
@@ -7779,7 +7992,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
     ChessMove moveType;
     char promoChar;
     char *p, *pv=buf1;
-    int machineWhite;
+    int machineWhite, oldError;
     char *bookHit;
 
     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
@@ -7793,7 +8006,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
        return; // Skim the pairing messages here.
     }
 
-    cps->userError = 0;
+    oldError = cps->userError; cps->userError = 0;
 
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
     /*
@@ -8348,7 +8561,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
     /*
      * If chess program startup fails, exit with an error message.
-     * Attempts to recover here are futile.
+     * Attempts to recover here are futile. [HGM] Well, we try anyway
      */
     if ((StrStr(message, "unknown host") != NULL)
        || (StrStr(message, "No remote directory") != NULL)
@@ -8362,8 +8575,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                _(cps->which), cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
        if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
-           if(cps == &first) appData.noChessProgram = TRUE;
-           DisplayError(buf1, 0);
+           if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
+           if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
        }
        return;
     }
@@ -9322,10 +9535,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
     } else
     if(promoChar == '+') {
-        /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
+        /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
         board[toY][toX] = (ChessSquare) (PROMOTED piece);
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
-        board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
+        ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
+       if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
+          && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
+        board[toY][toX] = newPiece;
     }
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
                && promoChar != NULLCHAR && gameInfo.holdingsSize) {
@@ -9862,6 +10078,7 @@ NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
 {
     char buf[MSG_SIZ], *p, *q;
     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
+    insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
     skip = !all && group[0]; // if group requested, we start in skip mode
     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
        p = names; q = buf; header = 0;
@@ -9869,7 +10086,7 @@ NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
        *q = 0;
        if(*p == '\n') p++;
        if(buf[0] == '#') {
-           if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
+           if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
            depth++; // we must be entering a new group
            if(all) continue; // suppress printing group headers when complete list requested
            header = 1;
@@ -9878,7 +10095,7 @@ NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
        if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
        if(engineList[i]) free(engineList[i]);
        engineList[i] = strdup(buf);
-       if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
+       if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
        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, " (");
@@ -9913,6 +10130,13 @@ SwapEngines (int n)
     SWAP(pgnName, p)
     SWAP(pvSAN, h)
     SWAP(engOptions, p)
+    SWAP(engInitString, p)
+    SWAP(computerString, p)
+    SWAP(features, p)
+    SWAP(fenOverride, p)
+    SWAP(NPS, h)
+    SWAP(accumulateTC, h)
+    SWAP(host, p)
 }
 
 int
@@ -10688,6 +10912,7 @@ Reset (int redraw, int init)
     DisplayMessage("", "");
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
+    ClearMap();        // [HGM] exclude: invalidate map
 }
 
 void
@@ -10700,7 +10925,7 @@ AutoPlayGameLoop ()
          continue;
        if (appData.timeDelay < 0)
          return;
-       StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+       StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
        break;
     }
 }
@@ -11356,15 +11581,18 @@ QuickScan (Board board, Move *move)
            piece = pieceList[piece]; // first two elements of pieceList contain King numbers
            from  = pieceList[piece]; // so this must be King
            quickBoard[from] = 0;
-           quickBoard[to] = piece;
            pieceList[piece] = to;
-           move++;
-           continue;
+           from = pieceList[(++move)->piece]; // for FRC this has to be done here
+           quickBoard[from] = 0; // rook
+           quickBoard[to] = piece;
+           to = move->to; piece = move->piece;
+           goto aftercastle;
          }
        }
        if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
        if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
        quickBoard[from] = 0;
+      aftercastle:
        quickBoard[to] = piece;
        pieceList[piece] = to;
        cnt++; turn ^= 3;
@@ -11963,7 +12191,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
       AnalyzeFileEvent();
     }
 
-    if (!matchMode && pos >= 0) {
+    if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
     } else
     if (matchMode || appData.timeDelay == 0) {
@@ -12314,7 +12542,6 @@ int
 SaveGamePGN (FILE *f)
 {
     int i, offset, linelen, newblock;
-    time_t tm;
 //    char *movetext;
     char numtext[32];
     int movelen, numlen, blank;
@@ -12322,8 +12549,6 @@ SaveGamePGN (FILE *f)
 
     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
 
-    tm = time((time_t *) NULL);
-
     PrintPGNTags(f, &gameInfo);
 
     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
@@ -13142,7 +13367,7 @@ AnalyzeFileEvent ()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
-    if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+    if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
 }
 
 void
@@ -13354,7 +13579,7 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
        } else {
          /* kludge: allow timeout for initial "feature" command */
          FreezeUI();
-         snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
+         snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
          DisplayMessage("", buf);
          ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
        }
@@ -13414,6 +13639,12 @@ TwoMachinesEvent P((void))
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+
+    if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+       DisplayError("second engine does not play this", 0);
+       return;
+    }
+
     if(!stalling) {
       InitChessProgram(&second, FALSE); // unbalances ping of second engine
       SendToProgram("force\n", &second);
@@ -13671,6 +13902,7 @@ EditPositionEvent ()
     currentMove = forwardMostMove = backwardMostMove = 0;
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
     DisplayMove(-1);
+    if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
 }
 
 void
@@ -14277,6 +14509,7 @@ ForwardInner (int target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
+    ClearMap(); // [HGM] exclude: invalidate map
 }
 
 
@@ -14390,6 +14623,7 @@ BackwardInner (int target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
+    ClearMap(); // [HGM] exclude: invalidate map
 }
 
 void
@@ -15069,10 +15303,10 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou
     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(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
+           if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     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; }
@@ -15354,8 +15588,8 @@ ParseOption (Option *opt, ChessProgramState *cps)
            if(sscanf(p, " -check %d", &def) < 1) return FALSE;
            opt->value = (def != 0);
            opt->type = CheckBox;
-       } else if(p = strstr(opt->name, " -combo ")) {
-           opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
+       } else if(p = strstr(opt->name, " -combo ")) {
+           opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
            cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
            if(*q == '*') cps->comboList[cps->comboCnt-1]++;
            opt->value = n = 0;
@@ -15456,6 +15690,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
+    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 */
@@ -15475,7 +15710,10 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
-    if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
+    if (StringFeature(&p, "option", buf, cps)) {
+       FREE(cps->option[cps->nrOptions].name);
+       cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
+       safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
        if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
          snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
            SendToProgram(buf, cps);
@@ -16007,6 +16245,16 @@ DecrementClocks ()
     }
     if (CheckFlags()) return;
 
+    if(twoBoards) { // count down secondary board's clocks as well
+       activePartnerTime -= lastTickLength;
+       partnerUp = 1;
+       if(activePartner == 'W')
+           DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
+       else
+           DisplayBlackClock(activePartnerTime, TRUE);
+       partnerUp = 0;
+    }
+
     tickStartTM = now;
     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);